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,179 @@
1
+ """
2
+ Cache interface for breaking circular dependencies.
3
+
4
+ This module provides abstract interfaces for cache operations to prevent
5
+ circular dependencies between analysis_cache and language processing modules.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from collections.abc import Callable
12
+ from pathlib import Path
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ if TYPE_CHECKING:
16
+ # TODO: service-specific import removed
17
+ # from ..processing.languages.base import ParseResult
18
+ pass
19
+
20
+
21
+ class IAnalysisCache(ABC):
22
+ """Abstract interface for analysis cache operations."""
23
+
24
+ @abstractmethod
25
+ async def get_cached_result(self, file_path: Path, content: str) -> Any | None:
26
+ """Get cached analysis result if available and valid.
27
+
28
+ Args:
29
+ file_path: Path to the source file
30
+ content: Current file content
31
+
32
+ Returns:
33
+ Cached ParseResult if available, None otherwise
34
+ """
35
+ pass
36
+
37
+ @abstractmethod
38
+ async def cache_result(self, file_path: Path, content: str, parse_result: Any) -> None:
39
+ """Cache an analysis result.
40
+
41
+ Args:
42
+ file_path: Path to the source file
43
+ content: File content that was analyzed
44
+ parse_result: Analysis result to cache
45
+ """
46
+ pass
47
+
48
+
49
+ class ICacheManager(ABC):
50
+ """Abstract interface for Redis-based cache manager operations."""
51
+
52
+ @abstractmethod
53
+ async def get(self, key: str) -> Any | None:
54
+ """Get a value from cache.
55
+
56
+ Args:
57
+ key: Cache key
58
+
59
+ Returns:
60
+ Cached value or None if not found
61
+ """
62
+ ...
63
+
64
+ @abstractmethod
65
+ async def set(self, key: str, value: Any, ttl: int | None = None) -> bool:
66
+ """Set a value in cache.
67
+
68
+ Args:
69
+ key: Cache key
70
+ value: Value to cache
71
+ ttl: Time-to-live in seconds
72
+
73
+ Returns:
74
+ True if successful, False otherwise
75
+ """
76
+ ...
77
+
78
+ @abstractmethod
79
+ async def delete(
80
+ self, key: str, record_eviction: bool = True, eviction_reason: str = "manual"
81
+ ) -> bool:
82
+ """Delete a value from cache.
83
+
84
+ Args:
85
+ key: Cache key
86
+ record_eviction: Whether to record this deletion as an eviction
87
+ eviction_reason: Reason for eviction
88
+
89
+ Returns:
90
+ True if successful, False otherwise
91
+ """
92
+ ...
93
+
94
+ @abstractmethod
95
+ async def delete_pattern(self, pattern: str) -> int:
96
+ """Delete keys matching a pattern.
97
+
98
+ Args:
99
+ pattern: Key pattern (e.g., "container:*")
100
+
101
+ Returns:
102
+ Number of keys deleted
103
+ """
104
+ ...
105
+
106
+ @abstractmethod
107
+ async def get_or_compute(
108
+ self, key: str, compute_func: Callable[[], Any], ttl: int | None = None
109
+ ) -> Any:
110
+ """Get from cache or compute and cache.
111
+
112
+ Args:
113
+ key: Cache key
114
+ compute_func: Function to compute value if not cached
115
+ ttl: Time-to-live in seconds
116
+
117
+ Returns:
118
+ Cached or computed value
119
+ """
120
+ ...
121
+
122
+ @abstractmethod
123
+ def get_stats(self) -> dict[str, int]:
124
+ """Get cache statistics.
125
+
126
+ Returns:
127
+ Dictionary with hits, misses, errors counts
128
+ """
129
+ ...
130
+
131
+ @abstractmethod
132
+ async def clear_stats(self) -> None:
133
+ """Reset cache statistics."""
134
+ ...
135
+
136
+ @abstractmethod
137
+ async def get_container_stats(self, container_name: str) -> dict[str, Any]:
138
+ """Get cache statistics for a specific container.
139
+
140
+ Args:
141
+ container_name: Name of the container to get stats for
142
+
143
+ Returns:
144
+ Dictionary with hit_count, miss_count, size, entry_count
145
+ """
146
+ ...
147
+
148
+ @abstractmethod
149
+ async def clear_container_cache(self, container_name: str) -> int:
150
+ """Clear all cache entries for a specific container.
151
+
152
+ Args:
153
+ container_name: Name of the container whose cache should be cleared
154
+
155
+ Returns:
156
+ Number of cache entries deleted
157
+ """
158
+ ...
159
+
160
+ @abstractmethod
161
+ async def get_container_keys(
162
+ self, container_name: str, limit: int = 100, offset: int = 0
163
+ ) -> list[str]:
164
+ """Get paginated list of cache keys for a specific container.
165
+
166
+ Args:
167
+ container_name: Name of the container to get keys for
168
+ limit: Maximum number of keys to return
169
+ offset: Number of keys to skip
170
+
171
+ Returns:
172
+ List of cache key strings
173
+ """
174
+ ...
175
+
176
+ @abstractmethod
177
+ async def close(self) -> None:
178
+ """Close the cache manager and clean up resources."""
179
+ ...
@@ -0,0 +1,49 @@
1
+ """
2
+ Container interface for breaking circular dependencies.
3
+
4
+ This module provides abstract interfaces for container operations to prevent
5
+ circular dependencies between container_manager and graph_service modules.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import Protocol
12
+
13
+
14
+ class ContainerInfo(Protocol):
15
+ """Protocol for container information objects."""
16
+
17
+ container_name: str
18
+ code_graph_id: str
19
+ status: str
20
+ api_endpoint: str | None
21
+
22
+
23
+ class IContainerManager(ABC):
24
+ """Abstract interface for container management operations."""
25
+
26
+ @abstractmethod
27
+ def get_container_for_graph(self, code_graph_id: str) -> ContainerInfo | None:
28
+ """Get container information for a specific code graph ID.
29
+
30
+ Args:
31
+ code_graph_id: The code graph identifier
32
+
33
+ Returns:
34
+ ContainerInfo if found, None otherwise
35
+ """
36
+ pass
37
+
38
+ @abstractmethod
39
+ def list_containers(self) -> list[ContainerInfo]:
40
+ """List all containers.
41
+
42
+ Returns:
43
+ List of ContainerInfo objects
44
+ """
45
+ pass
46
+
47
+
48
+ # Type alias for container manager instances
49
+ ContainerManagerType = IContainerManager
@@ -0,0 +1,283 @@
1
+ """
2
+ Docker Service interface for breaking circular dependencies.
3
+
4
+ This module provides abstract interfaces for Docker container operations to prevent
5
+ circular dependencies and enable dependency injection.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ if TYPE_CHECKING:
14
+ from models.container import ContainerInfo, GitHubRepoRequest
15
+
16
+
17
+ class IDockerService(ABC):
18
+ """Abstract interface for Docker container operations."""
19
+
20
+ @abstractmethod
21
+ def is_docker_available(self) -> bool:
22
+ """Check if Docker is available and accessible.
23
+
24
+ Returns:
25
+ True if Docker is available, False otherwise
26
+ """
27
+ ...
28
+
29
+ @abstractmethod
30
+ def retry_docker_connection(self) -> bool:
31
+ """Attempt to reconnect to Docker if it wasn't available initially.
32
+
33
+ Returns:
34
+ True if reconnection succeeded, False otherwise
35
+ """
36
+ ...
37
+
38
+ @abstractmethod
39
+ async def create_container(
40
+ self, request: GitHubRepoRequest, env_vars: dict[str, str] | None = None
41
+ ) -> ContainerInfo:
42
+ """Create and start a Docker container for GitHub repository.
43
+
44
+ Args:
45
+ request: GitHub repository provisioning request
46
+ env_vars: Optional environment variables from secret store
47
+
48
+ Returns:
49
+ ContainerInfo with container details
50
+ """
51
+ ...
52
+
53
+ @abstractmethod
54
+ async def remove_container(self, container: ContainerInfo) -> bool:
55
+ """Remove existing container and cleanup.
56
+
57
+ Args:
58
+ container: Container info to remove
59
+
60
+ Returns:
61
+ True if removal succeeded, False otherwise
62
+ """
63
+ ...
64
+
65
+ @abstractmethod
66
+ async def remove_container_by_name(self, container_name: str) -> bool:
67
+ """Remove a container by its name and clean up resources.
68
+
69
+ Args:
70
+ container_name: Name of the container to remove
71
+
72
+ Returns:
73
+ True if removal succeeded, False otherwise
74
+ """
75
+ ...
76
+
77
+ @abstractmethod
78
+ async def remove_container_by_id(self, docker_id: str, force: bool = True) -> None:
79
+ """Remove a container by ID.
80
+
81
+ Args:
82
+ docker_id: Docker container ID
83
+ force: Force removal even if running
84
+ """
85
+ ...
86
+
87
+ @abstractmethod
88
+ async def start_container(self, docker_id: str) -> None:
89
+ """Start a container by ID.
90
+
91
+ Args:
92
+ docker_id: Docker container ID
93
+ """
94
+ ...
95
+
96
+ @abstractmethod
97
+ async def stop_container(self, docker_id: str, timeout: int = 10) -> None:
98
+ """Stop a container by ID.
99
+
100
+ Args:
101
+ docker_id: Docker container ID
102
+ timeout: Timeout in seconds before force kill
103
+ """
104
+ ...
105
+
106
+ @abstractmethod
107
+ async def list_containers(self) -> list[ContainerInfo]:
108
+ """List all managed containers with real-time status verification.
109
+
110
+ Returns:
111
+ List of ContainerInfo objects
112
+ """
113
+ ...
114
+
115
+ @abstractmethod
116
+ async def find_container_by_name(self, container_name: str) -> ContainerInfo | None:
117
+ """Find container by container_name.
118
+
119
+ Args:
120
+ container_name: Name of the container to find
121
+
122
+ Returns:
123
+ ContainerInfo if found, None otherwise
124
+ """
125
+ ...
126
+
127
+ @abstractmethod
128
+ async def find_existing_container(self, repo_url: str) -> ContainerInfo | None:
129
+ """Find existing container by repository URL.
130
+
131
+ Args:
132
+ repo_url: Repository URL to search for
133
+
134
+ Returns:
135
+ ContainerInfo if found, None otherwise
136
+ """
137
+ ...
138
+
139
+ @abstractmethod
140
+ async def initialize_code_graph(
141
+ self,
142
+ container: ContainerInfo,
143
+ analysis_settings: dict[str, Any] | None = None,
144
+ ) -> ContainerInfo:
145
+ """Manually initialize the code graph by calling the container's initialization endpoint.
146
+
147
+ Args:
148
+ container: Container info to initialize graph for
149
+ analysis_settings: Optional analysis configuration
150
+
151
+ Returns:
152
+ Updated ContainerInfo
153
+ """
154
+ ...
155
+
156
+ @abstractmethod
157
+ async def read_container_file(
158
+ self,
159
+ container_name: str,
160
+ file_path: str,
161
+ offset: int = 0,
162
+ length: int | None = None,
163
+ ) -> dict[str, Any]:
164
+ """Read file from container using docker exec.
165
+
166
+ Args:
167
+ container_name: Name of the container
168
+ file_path: Path to the file inside the container
169
+ offset: Starting offset for reading
170
+ length: Number of bytes to read
171
+
172
+ Returns:
173
+ Dictionary with file content and metadata
174
+ """
175
+ ...
176
+
177
+ @abstractmethod
178
+ async def list_container_directory(
179
+ self, container_name: str, path: str = "", recursive: bool = False
180
+ ) -> list[dict[str, Any]]:
181
+ """List directory contents in container.
182
+
183
+ Args:
184
+ container_name: Name of the container
185
+ path: Directory path to list
186
+ recursive: Whether to list recursively
187
+
188
+ Returns:
189
+ List of directory entries
190
+ """
191
+ ...
192
+
193
+ @abstractmethod
194
+ async def exec_in_container(
195
+ self,
196
+ docker_id: str,
197
+ command: list[str],
198
+ timeout: int = 30,
199
+ environment: dict[str, str] | None = None,
200
+ user: str | None = None,
201
+ ) -> dict[str, Any]:
202
+ """Execute command in container and return structured result.
203
+
204
+ Args:
205
+ docker_id: Docker container ID or name
206
+ command: Command to execute as list of strings
207
+ timeout: Execution timeout in seconds
208
+ environment: Optional environment variables
209
+ user: Optional user to run command as
210
+
211
+ Returns:
212
+ Dictionary with exit_code, stdout, stderr, execution_time, timeout
213
+ """
214
+ ...
215
+
216
+ @abstractmethod
217
+ async def create_file_in_container(
218
+ self, docker_id: str, file_path: str, content: str, mode: str = "w"
219
+ ) -> bool:
220
+ """Create file with content in container.
221
+
222
+ Args:
223
+ docker_id: Docker container ID or name
224
+ file_path: Path to create file at
225
+ content: Content to write
226
+ mode: Write mode ('w' for write, 'a' for append)
227
+
228
+ Returns:
229
+ True if successful, False otherwise
230
+ """
231
+ ...
232
+
233
+ @abstractmethod
234
+ async def get_container_stats(self, docker_id: str) -> dict[str, Any]:
235
+ """Get resource usage statistics for container.
236
+
237
+ Args:
238
+ docker_id: Docker container ID or name
239
+
240
+ Returns:
241
+ Dictionary with resource statistics
242
+ """
243
+ ...
244
+
245
+ @abstractmethod
246
+ def get_language_container_image(self, language: str) -> str:
247
+ """Get appropriate container image for language.
248
+
249
+ Args:
250
+ language: Programming language name
251
+
252
+ Returns:
253
+ Docker image name for the language
254
+ """
255
+ ...
256
+
257
+ @abstractmethod
258
+ async def create_test_container(self, config: dict[str, Any]) -> str:
259
+ """Create container specifically for test execution.
260
+
261
+ Args:
262
+ config: Container configuration
263
+
264
+ Returns:
265
+ Container ID
266
+ """
267
+ ...
268
+
269
+ @abstractmethod
270
+ async def install_tools_in_container(
271
+ self, docker_id: str, tools: list[str], language: str = "python"
272
+ ) -> bool:
273
+ """Install testing/quality tools in container.
274
+
275
+ Args:
276
+ docker_id: Docker container ID or name
277
+ tools: List of tool names to install
278
+ language: Programming language for tool installation
279
+
280
+ Returns:
281
+ True if successful, False otherwise
282
+ """
283
+ ...
@@ -0,0 +1,28 @@
1
+ """
2
+ Graph interface for breaking circular dependencies.
3
+
4
+ This module provides abstract interfaces for graph operations to prevent
5
+ circular dependencies between graph_service and container_manager modules.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import Any
12
+
13
+
14
+ class IGraphService(ABC):
15
+ """Abstract interface for graph service operations."""
16
+
17
+ @abstractmethod
18
+ async def delete_graph(self, graph_id: str, force: bool = False) -> dict[str, Any]:
19
+ """Delete a graph and all associated data.
20
+
21
+ Args:
22
+ graph_id: Unique identifier of the graph to delete
23
+ force: If True, force deletion even for containerized graphs
24
+
25
+ Returns:
26
+ Dict containing deletion results
27
+ """
28
+ pass
@@ -0,0 +1,72 @@
1
+ """
2
+ Security Service interface for breaking circular dependencies.
3
+
4
+ This module provides abstract interfaces for security validation operations to prevent
5
+ circular dependencies and enable dependency injection.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ if TYPE_CHECKING:
14
+ from models.security import (
15
+ SecurityValidationRequest,
16
+ SecurityValidationResult,
17
+ )
18
+
19
+
20
+ class ISecurityService(ABC):
21
+ """Abstract interface for security validation operations."""
22
+
23
+ @abstractmethod
24
+ async def validate_code_security(
25
+ self, request: SecurityValidationRequest
26
+ ) -> SecurityValidationResult:
27
+ """Main security validation method with comprehensive analysis.
28
+
29
+ Args:
30
+ request: Security validation request containing code and configuration
31
+
32
+ Returns:
33
+ SecurityValidationResult with safety status, violations, and risk score
34
+ """
35
+ ...
36
+
37
+ @abstractmethod
38
+ async def generate_sandbox_config(self, language: str, security_level: str) -> dict[str, Any]:
39
+ """Generate sandbox configuration based on language and security level.
40
+
41
+ Args:
42
+ language: Programming language for sandbox configuration
43
+ security_level: Security level (low, medium, high, critical)
44
+
45
+ Returns:
46
+ Dictionary containing sandbox configuration
47
+ """
48
+ ...
49
+
50
+ @abstractmethod
51
+ def get_dangerous_patterns(self, language: str) -> list[dict[str, Any]]:
52
+ """Get dangerous code patterns for a specific language.
53
+
54
+ Args:
55
+ language: Programming language name
56
+
57
+ Returns:
58
+ List of pattern dictionaries with pattern, severity, description
59
+ """
60
+ ...
61
+
62
+ @abstractmethod
63
+ def get_sandbox_config(self, language: str) -> dict[str, Any] | None:
64
+ """Get default sandbox configuration for a language.
65
+
66
+ Args:
67
+ language: Programming language name
68
+
69
+ Returns:
70
+ Sandbox configuration dictionary or None if not configured
71
+ """
72
+ ...
@@ -0,0 +1,67 @@
1
+ """
2
+ Webhook interface for breaking circular dependencies.
3
+
4
+ This module provides abstract interfaces for webhook event handling to prevent
5
+ circular dependencies between webhook handler and endpoint modules.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import Any
12
+
13
+
14
+ class IWebhookEventHandler(ABC):
15
+ """Abstract interface for webhook event handlers."""
16
+
17
+ @abstractmethod
18
+ async def analyze_pull_request_changes(
19
+ self, repo_name: str, pr_number: int, trigger: str
20
+ ) -> None:
21
+ """Analyze changes in a pull request and provide feedback.
22
+
23
+ Args:
24
+ repo_name: Repository name (owner/repo)
25
+ pr_number: Pull request number
26
+ trigger: What triggered the analysis
27
+ """
28
+ pass
29
+
30
+ @abstractmethod
31
+ async def analyze_repository_changes(
32
+ self, repo_name: str, branch: str, commits: list
33
+ ) -> dict[str, Any]:
34
+ """Analyze changes from push events.
35
+
36
+ Args:
37
+ repo_name: Repository name (owner/repo)
38
+ branch: Branch name
39
+ commits: List of commits
40
+
41
+ Returns:
42
+ Analysis results dictionary
43
+ """
44
+ pass
45
+
46
+
47
+ class WebhookEventHandlerRegistry:
48
+ """Registry for webhook event handlers to enable dependency injection."""
49
+
50
+ _handlers: dict[str, IWebhookEventHandler] = {}
51
+
52
+ @classmethod
53
+ def register(cls, name: str, handler: IWebhookEventHandler) -> None:
54
+ """Register a webhook event handler."""
55
+ cls._handlers[name] = handler
56
+
57
+ @classmethod
58
+ def get_handler(cls, name: str) -> IWebhookEventHandler | None:
59
+ """Get a registered webhook event handler."""
60
+ return cls._handlers.get(name)
61
+
62
+ @classmethod
63
+ def get(cls, name: str) -> IWebhookEventHandler:
64
+ """Get a registered webhook event handler (with error if not found)."""
65
+ if name not in cls._handlers:
66
+ raise ValueError(f"No handler registered with name: {name}")
67
+ return cls._handlers[name]