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,128 @@
|
|
|
1
|
+
"""Bug Hunter data models for error analysis and code remediation.
|
|
2
|
+
|
|
3
|
+
This module provides data structures for the Bug Hunter system, which analyzes
|
|
4
|
+
error logs, identifies bugs in code, and generates remediation suggestions. The models
|
|
5
|
+
support the full workflow from parsing errors to generating validated code modifications.
|
|
6
|
+
|
|
7
|
+
The Bug Hunter system uses these models to:
|
|
8
|
+
- Parse and categorize errors from various sources (logs, static analysis, runtime)
|
|
9
|
+
- Generate targeted code modifications to address identified issues
|
|
10
|
+
- Validate proposed changes for safety and correctness
|
|
11
|
+
- Provide comprehensive analysis results with confidence scoring
|
|
12
|
+
|
|
13
|
+
Classes:
|
|
14
|
+
ErrorType: Enumeration of detectable error types across languages
|
|
15
|
+
ParsedError: Structured representation of parsed errors with context
|
|
16
|
+
CodeFix: Proposed code modification with explanation
|
|
17
|
+
BugAnalysisResult: Complete analysis including errors and remediation
|
|
18
|
+
FixValidationResult: Safety validation for proposed modifications
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
```python
|
|
22
|
+
from bug_models import ErrorType, ParsedError, CodeFix, BugAnalysisResult
|
|
23
|
+
|
|
24
|
+
# Parse an error
|
|
25
|
+
error = ParsedError(
|
|
26
|
+
error_type=ErrorType.IMPORT_ERROR,
|
|
27
|
+
message="No module named 'requests'",
|
|
28
|
+
file_path="app/main.py",
|
|
29
|
+
line_number=5,
|
|
30
|
+
function_name="fetch_data",
|
|
31
|
+
stack_trace=[],
|
|
32
|
+
confidence=0.95
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Generate a remediation
|
|
36
|
+
fix = CodeFix(
|
|
37
|
+
file_path="requirements.txt",
|
|
38
|
+
line_number=None,
|
|
39
|
+
original_code="",
|
|
40
|
+
fixed_code="requests>=2.28.0\\n",
|
|
41
|
+
explanation="Add missing 'requests' dependency",
|
|
42
|
+
confidence=0.9,
|
|
43
|
+
test_suggestion="Run 'pip install -r requirements.txt' to verify"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Complete analysis result
|
|
47
|
+
result = BugAnalysisResult(
|
|
48
|
+
errors=[error],
|
|
49
|
+
resolves=[fix],
|
|
50
|
+
root_cause_analysis="Missing dependency declaration",
|
|
51
|
+
fix_summary="Add requests to requirements.txt",
|
|
52
|
+
estimated_effort="Low - single dependency addition",
|
|
53
|
+
risk_assessment="Minimal risk - standard library addition"
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
from __future__ import annotations
|
|
59
|
+
|
|
60
|
+
from enum import Enum
|
|
61
|
+
from typing import Any
|
|
62
|
+
|
|
63
|
+
from pydantic.dataclasses import dataclass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ErrorType(Enum):
|
|
67
|
+
"""Enumeration of error types that can be detected and resolved."""
|
|
68
|
+
|
|
69
|
+
SYNTAX_ERROR = "syntax_error"
|
|
70
|
+
IMPORT_ERROR = "import_error"
|
|
71
|
+
NAME_ERROR = "name_error"
|
|
72
|
+
TYPE_ERROR = "type_error"
|
|
73
|
+
ATTRIBUTE_ERROR = "attribute_error"
|
|
74
|
+
INDEX_ERROR = "index_error"
|
|
75
|
+
KEY_ERROR = "key_error"
|
|
76
|
+
VALUE_ERROR = "value_error"
|
|
77
|
+
RUNTIME_ERROR = "runtime_error"
|
|
78
|
+
LOGIC_ERROR = "logic_error"
|
|
79
|
+
PERFORMANCE_ERROR = "performance_error"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class ParsedError:
|
|
84
|
+
"""Structured representation of a parsed error from logs or code analysis."""
|
|
85
|
+
|
|
86
|
+
error_type: ErrorType
|
|
87
|
+
message: str
|
|
88
|
+
file_path: str | None
|
|
89
|
+
line_number: int | None
|
|
90
|
+
function_name: str | None
|
|
91
|
+
stack_trace: list[dict[str, Any]]
|
|
92
|
+
confidence: float
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class CodeFix:
|
|
97
|
+
"""Represents a proposed resolve for a code issue."""
|
|
98
|
+
|
|
99
|
+
file_path: str
|
|
100
|
+
line_number: int | None
|
|
101
|
+
original_code: str
|
|
102
|
+
fixed_code: str
|
|
103
|
+
explanation: str
|
|
104
|
+
confidence: float
|
|
105
|
+
test_suggestion: str | None = None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class BugAnalysisResult:
|
|
110
|
+
"""Complete result of bug analysis including errors, resolves, and assessments."""
|
|
111
|
+
|
|
112
|
+
errors: list[ParsedError]
|
|
113
|
+
resolves: list[CodeFix]
|
|
114
|
+
root_cause_analysis: str
|
|
115
|
+
fix_summary: str
|
|
116
|
+
estimated_effort: str
|
|
117
|
+
risk_assessment: str
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass
|
|
121
|
+
class BugFixValidationResult:
|
|
122
|
+
"""Result of validating a proposed resolve for safety and correctness."""
|
|
123
|
+
|
|
124
|
+
is_safe: bool
|
|
125
|
+
confidence: float
|
|
126
|
+
warnings: list[str]
|
|
127
|
+
suggestions: list[str]
|
|
128
|
+
risk_level: str
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""Pydantic models for code graph representations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
8
|
+
|
|
9
|
+
# Request Models
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CodebaseInitRequest(BaseModel):
|
|
13
|
+
"""Request model for initializing a new Code Graph for a codebase."""
|
|
14
|
+
|
|
15
|
+
container_name: str = Field(
|
|
16
|
+
...,
|
|
17
|
+
min_length=1,
|
|
18
|
+
description="Container name from which to initialize the graph",
|
|
19
|
+
pattern=r"^[a-zA-Z][a-zA-Z0-9\-_]{0,62}$",
|
|
20
|
+
json_schema_extra={"example": "codegraph-my-project"},
|
|
21
|
+
)
|
|
22
|
+
codebase_path: str | None = Field(
|
|
23
|
+
None,
|
|
24
|
+
min_length=1,
|
|
25
|
+
description="Path to the codebase directory (internal to the container). If not provided, will be automatically resolved from the container configuration.",
|
|
26
|
+
pattern=r"^/[a-zA-Z0-9/_\-\.]+$",
|
|
27
|
+
json_schema_extra={"example": "/app/repositories/my-project"},
|
|
28
|
+
)
|
|
29
|
+
include_tests: bool | None = Field(
|
|
30
|
+
None,
|
|
31
|
+
description="Whether to include test files in the graph analysis. If not specified, uses the default system setting.",
|
|
32
|
+
)
|
|
33
|
+
exclude_patterns: list[str] | None = Field(
|
|
34
|
+
None,
|
|
35
|
+
description="Additional patterns to exclude from analysis (e.g., ['**/legacy/**', 'temp_*']). These are added to the default ignore patterns.",
|
|
36
|
+
json_schema_extra={"example": ["**/node_modules/**", "**/dist/**"]},
|
|
37
|
+
)
|
|
38
|
+
include_patterns: list[str] | None = Field(
|
|
39
|
+
None,
|
|
40
|
+
description="Specific patterns to include even if they would normally be excluded (e.g., ['important_test_*.py']). These override exclude patterns.",
|
|
41
|
+
json_schema_extra={"example": ["**/important_*.py"]},
|
|
42
|
+
)
|
|
43
|
+
force_rebuild: bool | None = Field(
|
|
44
|
+
False,
|
|
45
|
+
description="Force rebuild of the graph even if it already exists. This will delete the existing database and create a new one.",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@field_validator("codebase_path")
|
|
49
|
+
@classmethod
|
|
50
|
+
def validate_codebase_path(cls, v: str | None) -> str | None:
|
|
51
|
+
"""Validate codebase path is safe and not a system directory."""
|
|
52
|
+
if v is None:
|
|
53
|
+
return v
|
|
54
|
+
|
|
55
|
+
if not v or not v.strip():
|
|
56
|
+
raise ValueError("Codebase path cannot be empty")
|
|
57
|
+
|
|
58
|
+
v = v.strip()
|
|
59
|
+
|
|
60
|
+
# Prevent dangerous paths
|
|
61
|
+
dangerous_paths = [
|
|
62
|
+
"/",
|
|
63
|
+
"/etc",
|
|
64
|
+
"/usr",
|
|
65
|
+
"/bin",
|
|
66
|
+
"/sbin",
|
|
67
|
+
"/var",
|
|
68
|
+
"/sys",
|
|
69
|
+
"/proc",
|
|
70
|
+
"/dev",
|
|
71
|
+
"/boot",
|
|
72
|
+
"/root",
|
|
73
|
+
]
|
|
74
|
+
if v in dangerous_paths or any(v.startswith(f"{p}/") for p in dangerous_paths[:5]):
|
|
75
|
+
raise ValueError(f"Invalid codebase path: {v}. Cannot analyze system directories.")
|
|
76
|
+
|
|
77
|
+
# Ensure path doesn't contain suspicious patterns
|
|
78
|
+
if ".." in v or "~" in v:
|
|
79
|
+
raise ValueError("Codebase path cannot contain '..' or '~'")
|
|
80
|
+
|
|
81
|
+
return v
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class CodeGraphAnalysisRequest(BaseModel):
|
|
85
|
+
"""Request model for triggering analysis on a Code Graph."""
|
|
86
|
+
|
|
87
|
+
analysis_depth: str = Field(
|
|
88
|
+
"full",
|
|
89
|
+
description="Depth of analysis to perform",
|
|
90
|
+
examples=["basic_structure", "python_ast", "full"],
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@field_validator("analysis_depth")
|
|
94
|
+
@classmethod
|
|
95
|
+
def validate_analysis_depth(cls, v: str) -> str:
|
|
96
|
+
"""Validate analysis depth value."""
|
|
97
|
+
valid_depths = ["basic_structure", "python_ast", "full"]
|
|
98
|
+
if v not in valid_depths:
|
|
99
|
+
raise ValueError(f"analysis_depth must be one of: {valid_depths}")
|
|
100
|
+
return v
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class PatternSearchRequest(BaseModel):
|
|
104
|
+
"""Request model for searching code patterns."""
|
|
105
|
+
|
|
106
|
+
pattern: str = Field(..., description="Regular expression or AST pattern to search for")
|
|
107
|
+
language: str | None = Field(None, description="Programming language for AST-based search")
|
|
108
|
+
file_paths: list[str] | None = Field(
|
|
109
|
+
None,
|
|
110
|
+
description="Specific files to search (if not provided, searches all files)",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class EntityQueryRequest(BaseModel):
|
|
115
|
+
"""Request model for querying entities in the Code Graph."""
|
|
116
|
+
|
|
117
|
+
entity_type: str = Field(..., description="Type of entity to query (e.g., 'function', 'class')")
|
|
118
|
+
name_pattern: str | None = Field(None, description="Optional pattern to match entity names")
|
|
119
|
+
limit: int = Field(100, description="Maximum number of results to return")
|
|
120
|
+
offset: int = Field(0, description="Offset for pagination")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class RelatedNodesRequest(BaseModel):
|
|
124
|
+
"""Request model for retrieving nodes related to a specific node."""
|
|
125
|
+
|
|
126
|
+
node_id: str = Field(..., description="ID of the node to find related nodes for")
|
|
127
|
+
relationship_type: str | None = Field(None, description="Optional filter by relationship type")
|
|
128
|
+
direction: str = Field("all", description="'outgoing', 'incoming', or 'all'")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class RawQueryRequest(BaseModel):
|
|
132
|
+
"""Request model for executing a raw SQL query against the Code Graph."""
|
|
133
|
+
|
|
134
|
+
sql_query: str = Field(..., description="SQL query to execute (use with caution)")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class NodeQuery(BaseModel):
|
|
138
|
+
"""Model for querying nodes in the knowledge graph."""
|
|
139
|
+
|
|
140
|
+
type: str | None = Field(None, description="Filter by node type")
|
|
141
|
+
properties: dict[str, Any] | None = Field(None, description="Filter by node properties")
|
|
142
|
+
text_search: str | None = Field(None, description="Full-text search in properties")
|
|
143
|
+
with_centrality: bool | None = Field(False, description="Include centrality metrics")
|
|
144
|
+
with_incoming: bool | None = Field(False, description="Include incoming edges count")
|
|
145
|
+
with_outgoing: bool | None = Field(False, description="Include outgoing edges count")
|
|
146
|
+
sort_by: str | None = Field(None, description="Property to sort by")
|
|
147
|
+
sort_direction: str | None = Field("desc", description="Sort direction")
|
|
148
|
+
limit: int | None = Field(100, description="Limit for pagination", gt=0, le=10000)
|
|
149
|
+
offset: int | None = Field(0, description="Offset for pagination", ge=0)
|
|
150
|
+
|
|
151
|
+
@field_validator("sort_direction")
|
|
152
|
+
@classmethod
|
|
153
|
+
def validate_sort_direction(cls, v: str | None) -> str:
|
|
154
|
+
"""Validate sort direction."""
|
|
155
|
+
if v and v.lower() not in ["asc", "desc"]:
|
|
156
|
+
raise ValueError("sort_direction must be 'asc' or 'desc'")
|
|
157
|
+
return v.lower() if v else "desc"
|
|
158
|
+
|
|
159
|
+
@field_validator("type")
|
|
160
|
+
@classmethod
|
|
161
|
+
def validate_type(cls, v: str | None) -> str | None:
|
|
162
|
+
"""Validate node type is not empty string."""
|
|
163
|
+
if v is not None and not v.strip():
|
|
164
|
+
raise ValueError("Node type cannot be empty string")
|
|
165
|
+
return v.strip() if v else None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# Response Models
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class NodeResponse(BaseModel):
|
|
172
|
+
"""Response model for a node in the Code Graph."""
|
|
173
|
+
|
|
174
|
+
id: str
|
|
175
|
+
type: str
|
|
176
|
+
properties: dict[str, Any]
|
|
177
|
+
centrality: dict[str, float] | None = None # Centrality metrics (e.g., pagerank, degree)
|
|
178
|
+
incoming_edges: int | None = None # Count of incoming edges
|
|
179
|
+
outgoing_edges: int | None = None # Count of outgoing edges
|
|
180
|
+
|
|
181
|
+
model_config = ConfigDict(
|
|
182
|
+
json_schema_extra={
|
|
183
|
+
"example": {
|
|
184
|
+
"id": "node-123",
|
|
185
|
+
"type": "function",
|
|
186
|
+
"properties": {"name": "process_data", "complexity": 5},
|
|
187
|
+
"centrality": {"pagerank": 0.23, "degree": 0.15},
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class EdgeResponse(BaseModel):
|
|
194
|
+
"""Response model for an edge in the Code Graph."""
|
|
195
|
+
|
|
196
|
+
source: str
|
|
197
|
+
target: str
|
|
198
|
+
relationship: str
|
|
199
|
+
properties: dict[str, Any]
|
|
200
|
+
direction: str | None = None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class InitResponse(BaseModel):
|
|
204
|
+
"""Response model for Code Graph initialization."""
|
|
205
|
+
|
|
206
|
+
message: str
|
|
207
|
+
container_name: str
|
|
208
|
+
graph_db_path: str
|
|
209
|
+
nodes_created: int
|
|
210
|
+
edges_created: int
|
|
211
|
+
status: str
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class CodeGraphAnalysisResponse(BaseModel):
|
|
215
|
+
"""Response model for Code Graph analysis triggering."""
|
|
216
|
+
|
|
217
|
+
code_graph_id: str
|
|
218
|
+
status: str
|
|
219
|
+
analysis_depth: str
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class PatternSearchResponse(BaseModel):
|
|
223
|
+
"""Response model for pattern search results."""
|
|
224
|
+
|
|
225
|
+
matches: list[dict[str, Any]]
|
|
226
|
+
count: int
|
|
227
|
+
search_time_ms: float | None = None
|
|
228
|
+
code_graph_id: str | None = None
|
|
229
|
+
error: str | None = None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class EntityQueryResponse(BaseModel):
|
|
233
|
+
"""Response model for entity query results."""
|
|
234
|
+
|
|
235
|
+
entities: list[NodeResponse]
|
|
236
|
+
total: int
|
|
237
|
+
limit: int
|
|
238
|
+
offset: int
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class RelatedNodesResponse(BaseModel):
|
|
242
|
+
"""Response model for related nodes query."""
|
|
243
|
+
|
|
244
|
+
nodes: list[NodeResponse]
|
|
245
|
+
edges: list[EdgeResponse]
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class RawQueryResponse(BaseModel):
|
|
249
|
+
"""Response model for raw SQL query results."""
|
|
250
|
+
|
|
251
|
+
results: list[dict[str, Any]]
|
|
252
|
+
row_count: int
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# Architecture Extension Models
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class ArchitectureNodeResponse(NodeResponse):
|
|
259
|
+
"""Extended node response with architecture data."""
|
|
260
|
+
|
|
261
|
+
architecture_data: dict[str, Any] | None = None
|
|
262
|
+
hotspot_score: float | None = None
|
|
263
|
+
quality_issues: list[dict[str, Any]] | None = None
|
|
264
|
+
dependencies: list[str] | None = None
|
|
265
|
+
component_id: str | None = None
|
|
266
|
+
layer: str | None = None
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class RuntimeEdgeResponse(EdgeResponse):
|
|
270
|
+
"""Extended edge response with runtime data."""
|
|
271
|
+
|
|
272
|
+
runtime_data: dict[str, Any] | None = None
|
|
273
|
+
call_count: int | None = None
|
|
274
|
+
avg_duration_ms: float | None = None
|
|
275
|
+
error_rate: float | None = None
|
|
276
|
+
confidence: float | None = None
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class ArchitectureCodeGraphResponse(BaseModel):
|
|
280
|
+
"""Response model for architecture code graph."""
|
|
281
|
+
|
|
282
|
+
nodes: list[ArchitectureNodeResponse]
|
|
283
|
+
edges: list[RuntimeEdgeResponse]
|
|
284
|
+
architecture_metadata: dict[str, Any] | None = None
|
|
285
|
+
quality_summary: dict[str, Any] | None = None
|
|
286
|
+
dependency_summary: dict[str, Any] | None = None
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class RuntimeGraphResponse(BaseModel):
|
|
290
|
+
"""Response model for runtime code graph."""
|
|
291
|
+
|
|
292
|
+
nodes: list[NodeResponse]
|
|
293
|
+
edges: list[RuntimeEdgeResponse]
|
|
294
|
+
runtime_metadata: dict[str, Any] | None = None
|
|
295
|
+
trace_summary: dict[str, Any] | None = None
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class CodeGraphIntegrationRequest(BaseModel):
|
|
299
|
+
"""Request model for integrating architecture data with code graph."""
|
|
300
|
+
|
|
301
|
+
include_architecture: bool = Field(True, description="Include architecture analysis data")
|
|
302
|
+
include_dependencies: bool = Field(True, description="Include dependency analysis data")
|
|
303
|
+
include_quality: bool = Field(True, description="Include quality analysis data")
|
|
304
|
+
include_runtime: bool = Field(False, description="Include runtime trace data")
|
|
305
|
+
quality_threshold: float | None = Field(None, description="Minimum quality score to include")
|
|
306
|
+
hotspot_threshold: float | None = Field(None, description="Minimum hotspot score to include")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# Rebuild all models to resolve forward references
|
|
310
|
+
CodebaseInitRequest.model_rebuild()
|
|
311
|
+
CodeGraphAnalysisRequest.model_rebuild()
|
|
312
|
+
PatternSearchRequest.model_rebuild()
|
|
313
|
+
EntityQueryRequest.model_rebuild()
|
|
314
|
+
RelatedNodesRequest.model_rebuild()
|
|
315
|
+
RawQueryRequest.model_rebuild()
|
|
316
|
+
CodeGraphIntegrationRequest.model_rebuild()
|
|
317
|
+
InitResponse.model_rebuild()
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Module for defining container models used in the application."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ContainerStatus(StrEnum):
|
|
13
|
+
"""Container status enumeration"""
|
|
14
|
+
|
|
15
|
+
CREATING = "creating"
|
|
16
|
+
RUNNING = "running"
|
|
17
|
+
STOPPED = "stopped"
|
|
18
|
+
ERROR = "error"
|
|
19
|
+
REMOVING = "removing"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AnalysisConfig(BaseModel):
|
|
23
|
+
"""Configuration for code analysis during provisioning."""
|
|
24
|
+
|
|
25
|
+
include_tests: bool | None = Field(
|
|
26
|
+
None,
|
|
27
|
+
description="Whether to include test files in code graph analysis",
|
|
28
|
+
)
|
|
29
|
+
language_hint: str | None = Field(
|
|
30
|
+
None,
|
|
31
|
+
description="Primary programming language hint for better analysis",
|
|
32
|
+
)
|
|
33
|
+
exclude_patterns: list[str] | None = Field(
|
|
34
|
+
None, description="Additional patterns to exclude from analysis"
|
|
35
|
+
)
|
|
36
|
+
include_patterns: list[str] | None = Field(
|
|
37
|
+
None, description="Patterns to include despite other exclusions"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class GitHubRepoRequest(BaseModel):
|
|
42
|
+
"""Request model for GitHub repository operations."""
|
|
43
|
+
|
|
44
|
+
model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True, extra="forbid")
|
|
45
|
+
repo_url: str = Field(
|
|
46
|
+
...,
|
|
47
|
+
min_length=1,
|
|
48
|
+
description="GitHub repository URL",
|
|
49
|
+
pattern=r"^https://github\.com/[A-Za-z0-9\-_.]+/[A-Za-z0-9\-_.]+(?:/pull/\d+)?(?:\.git)?/?$",
|
|
50
|
+
json_schema_extra={"example": "https://github.com/owner/repository"},
|
|
51
|
+
)
|
|
52
|
+
branch: str = Field(
|
|
53
|
+
"main",
|
|
54
|
+
min_length=1,
|
|
55
|
+
description="Git branch to clone",
|
|
56
|
+
json_schema_extra={"example": "main"},
|
|
57
|
+
)
|
|
58
|
+
container_name: str | None = Field(
|
|
59
|
+
None,
|
|
60
|
+
description="Optional container name",
|
|
61
|
+
pattern=r"^[a-zA-Z][a-zA-Z0-9\-_]{0,62}$",
|
|
62
|
+
json_schema_extra={"example": "my-analysis-container"},
|
|
63
|
+
)
|
|
64
|
+
description: str | None = Field(None, description="Optional description", max_length=500)
|
|
65
|
+
access_token: str | None = Field(
|
|
66
|
+
None,
|
|
67
|
+
description="GitHub Personal Access Token for private repositories",
|
|
68
|
+
json_schema_extra={"example": "<github-personal-access-token>"},
|
|
69
|
+
)
|
|
70
|
+
user_id: str | None = Field(
|
|
71
|
+
None,
|
|
72
|
+
description="User ID for multi-account workflow (defaults to anonymous if not provided)",
|
|
73
|
+
pattern=r"^[a-zA-Z0-9\-_\.@]+$",
|
|
74
|
+
max_length=128,
|
|
75
|
+
json_schema_extra={"example": "user-123"},
|
|
76
|
+
)
|
|
77
|
+
force_rebuild: bool | None = Field(
|
|
78
|
+
False, description="Force recreation if container already exists"
|
|
79
|
+
)
|
|
80
|
+
analysis_config: AnalysisConfig | None = Field(
|
|
81
|
+
None, description="Configuration for code analysis during provisioning"
|
|
82
|
+
)
|
|
83
|
+
pr_number: int | None = Field(
|
|
84
|
+
None, description="Pull request number to provision", ge=1, le=999999
|
|
85
|
+
)
|
|
86
|
+
provision_type: Literal["branch", "pull_request"] = Field(
|
|
87
|
+
"branch", description="Type of provisioning"
|
|
88
|
+
)
|
|
89
|
+
secret_config_id: str | None = Field(
|
|
90
|
+
None,
|
|
91
|
+
description="Client ID for retrieving environment variables from secrets store",
|
|
92
|
+
pattern=r"^[a-zA-Z0-9\-_\.]+$",
|
|
93
|
+
max_length=128,
|
|
94
|
+
json_schema_extra={"example": "repo-myorg-myproject"},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@field_validator("repo_url")
|
|
98
|
+
@classmethod
|
|
99
|
+
def validate_repo_url(cls, v):
|
|
100
|
+
"""Validate repo URL is not empty and follows GitHub URL format."""
|
|
101
|
+
if not v or not v.strip():
|
|
102
|
+
raise ValueError("Repository URL cannot be empty")
|
|
103
|
+
|
|
104
|
+
v = v.strip()
|
|
105
|
+
|
|
106
|
+
# Basic URL format validation for GitHub URLs
|
|
107
|
+
|
|
108
|
+
# Check if it's a PR URL
|
|
109
|
+
pr_url_pattern = re.compile(
|
|
110
|
+
r"^https://github\.com/[A-Za-z0-9\-_.]+/[A-Za-z0-9\-_.]+/pull/\d+(?:/.*)?$"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if pr_url_pattern.match(v):
|
|
114
|
+
# This is a PR URL - we'll handle parsing in the model validator
|
|
115
|
+
return v
|
|
116
|
+
|
|
117
|
+
# Standard repo URL pattern
|
|
118
|
+
github_url_pattern = re.compile(
|
|
119
|
+
r"^https://github\.com/[A-Za-z0-9\-_.]+/[A-Za-z0-9\-_.]+(?:\.git)?/?$"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Additional check for obviously invalid URLs like single characters
|
|
123
|
+
if len(v) < 10 or not v.startswith("https://"):
|
|
124
|
+
raise ValueError(
|
|
125
|
+
"Repository URL must be a valid GitHub URL (e.g., https://github.com/owner/repo)"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if not github_url_pattern.match(v):
|
|
129
|
+
raise ValueError(
|
|
130
|
+
"Repository URL must be a valid GitHub URL format (e.g., https://github.com/owner/repo)"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return v
|
|
134
|
+
|
|
135
|
+
@field_validator("branch")
|
|
136
|
+
@classmethod
|
|
137
|
+
def validate_branch(cls, v):
|
|
138
|
+
"""Validate branch is not empty."""
|
|
139
|
+
if not v or not v.strip():
|
|
140
|
+
raise ValueError("Branch name cannot be empty")
|
|
141
|
+
return v.strip()
|
|
142
|
+
|
|
143
|
+
@field_validator("pr_number")
|
|
144
|
+
@classmethod
|
|
145
|
+
def validate_pr_number(cls, v):
|
|
146
|
+
"""Validate PR number if provided."""
|
|
147
|
+
if v is not None and v <= 0:
|
|
148
|
+
raise ValueError("PR number must be positive if provided")
|
|
149
|
+
return v
|
|
150
|
+
|
|
151
|
+
@model_validator(mode="after")
|
|
152
|
+
def parse_pr_url(self):
|
|
153
|
+
"""Parse PR URL and extract repository URL and PR number if applicable."""
|
|
154
|
+
|
|
155
|
+
# Check if repo_url is a PR URL
|
|
156
|
+
pr_url_pattern = re.compile(
|
|
157
|
+
r"^https://github\.com/([A-Za-z0-9\-_.]+)/([A-Za-z0-9\-_.]+)/pull/(\d+)(?:/.*)?$"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
match = pr_url_pattern.match(self.repo_url)
|
|
161
|
+
if match:
|
|
162
|
+
# Extract components from PR URL
|
|
163
|
+
owner = match.group(1)
|
|
164
|
+
repo = match.group(2)
|
|
165
|
+
pr_number = int(match.group(3))
|
|
166
|
+
|
|
167
|
+
# Update fields
|
|
168
|
+
self.repo_url = f"https://github.com/{owner}/{repo}"
|
|
169
|
+
self.pr_number = pr_number
|
|
170
|
+
self.provision_type = "pull_request"
|
|
171
|
+
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class ContainerInfo(BaseModel):
|
|
176
|
+
"""Information model for container instances."""
|
|
177
|
+
|
|
178
|
+
container_name: (
|
|
179
|
+
str # Primary identifier - same as Docker container name, used to access code graphs
|
|
180
|
+
)
|
|
181
|
+
api_endpoint: str
|
|
182
|
+
code_graph_id: str = "" # Code graph identifier for this container
|
|
183
|
+
codebase_path: str
|
|
184
|
+
repo_url: str
|
|
185
|
+
branch: str
|
|
186
|
+
status: ContainerStatus
|
|
187
|
+
description: str | None = None
|
|
188
|
+
pr_number: int | None = None
|
|
189
|
+
provision_type: str = "branch"
|
|
190
|
+
github_access_token: str | None = Field(
|
|
191
|
+
None,
|
|
192
|
+
description="GitHub access token for this container's repository operations (deprecated - use user_token_id)",
|
|
193
|
+
)
|
|
194
|
+
user_token_id: str | None = Field(
|
|
195
|
+
None,
|
|
196
|
+
description="Reference to encrypted user GitHub token in SecureTokenManager",
|
|
197
|
+
)
|
|
198
|
+
user_id: str | None = Field(
|
|
199
|
+
None,
|
|
200
|
+
description="User ID who provisioned this container",
|
|
201
|
+
)
|
|
202
|
+
original_repo_owner: str | None = Field(
|
|
203
|
+
None,
|
|
204
|
+
description="Original repository owner (for creating upstream PRs)",
|
|
205
|
+
)
|
|
206
|
+
original_repo_name: str | None = Field(
|
|
207
|
+
None,
|
|
208
|
+
description="Original repository name (for creating upstream PRs)",
|
|
209
|
+
)
|
|
210
|
+
system_fork_owner: str | None = Field(
|
|
211
|
+
None,
|
|
212
|
+
description="System account owner where fork resides (e.g., 'nonagenticai')",
|
|
213
|
+
)
|
|
214
|
+
graph_initialized: bool = Field(
|
|
215
|
+
False,
|
|
216
|
+
description="Whether the code graph has been initialized for this container",
|
|
217
|
+
)
|
|
218
|
+
tenant_id: str | None = Field(
|
|
219
|
+
None,
|
|
220
|
+
description="Tenant identifier for multi-tenant data isolation",
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class ContainerSpec(BaseModel):
|
|
225
|
+
"""Specification model for container setup."""
|
|
226
|
+
|
|
227
|
+
name: str = Field(..., description="Container name")
|
|
228
|
+
image: str = Field(..., description="Docker image to use")
|
|
229
|
+
port: int = Field(..., description="Host port to bind to")
|
|
230
|
+
command: str | None = Field(None, description="Command to run in container")
|
|
231
|
+
working_dir: str | None = Field(None, description="Working directory")
|
|
232
|
+
environment: dict[str, Any] = Field(default_factory=dict, description="Environment variables")
|
|
233
|
+
volumes: dict[str, Any] = Field(default_factory=dict, description="Volume mounts")
|
|
234
|
+
|
|
235
|
+
@field_validator("name")
|
|
236
|
+
@classmethod
|
|
237
|
+
def validate_name(cls, v):
|
|
238
|
+
"""Validate container name is not empty."""
|
|
239
|
+
if not v or not v.strip():
|
|
240
|
+
raise ValueError("Container name cannot be empty")
|
|
241
|
+
return v.strip()
|