aegis-stack 0.1.0__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.

Potentially problematic release.


This version of aegis-stack might be problematic. Click here for more details.

Files changed (103) hide show
  1. aegis/__init__.py +5 -0
  2. aegis/__main__.py +374 -0
  3. aegis/core/CLAUDE.md +365 -0
  4. aegis/core/__init__.py +6 -0
  5. aegis/core/components.py +115 -0
  6. aegis/core/dependency_resolver.py +119 -0
  7. aegis/core/template_generator.py +163 -0
  8. aegis/templates/CLAUDE.md +306 -0
  9. aegis/templates/cookiecutter-aegis-project/cookiecutter.json +27 -0
  10. aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +172 -0
  11. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
  12. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +70 -0
  13. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +127 -0
  14. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
  15. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +211 -0
  16. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
  17. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
  18. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
  19. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py +321 -0
  20. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py +638 -0
  21. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py +41 -0
  22. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
  23. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
  24. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +134 -0
  25. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +247 -0
  26. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +14 -0
  27. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/tasks.py.j2 +596 -0
  28. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +133 -0
  29. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +16 -0
  30. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
  31. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
  32. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
  33. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
  34. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
  35. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +190 -0
  36. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +0 -0
  37. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/__init__.py +1 -0
  38. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/theme.py +46 -0
  39. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py +687 -0
  40. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
  41. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py +138 -0
  42. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md +213 -0
  43. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
  44. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
  45. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +78 -0
  46. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
  47. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +48 -0
  48. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +41 -0
  49. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +36 -0
  50. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
  51. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +119 -0
  52. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +526 -0
  53. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +32 -0
  54. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +279 -0
  55. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +119 -0
  56. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +60 -0
  57. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py +67 -0
  58. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +85 -0
  59. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
  60. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
  61. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
  62. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
  63. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +61 -0
  64. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
  65. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
  66. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +661 -0
  67. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +269 -0
  68. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
  69. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
  70. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
  71. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
  72. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1105 -0
  73. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +169 -0
  74. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
  75. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +195 -0
  76. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
  77. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +414 -0
  78. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
  79. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
  80. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
  81. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
  82. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
  83. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +156 -0
  84. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
  85. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +104 -0
  86. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
  87. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
  88. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +239 -0
  89. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +76 -0
  90. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +81 -0
  91. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
  92. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +376 -0
  93. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +633 -0
  94. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +665 -0
  95. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +602 -0
  96. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +96 -0
  97. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +224 -0
  98. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +50 -0
  99. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
  100. aegis_stack-0.1.0.dist-info/METADATA +114 -0
  101. aegis_stack-0.1.0.dist-info/RECORD +103 -0
  102. aegis_stack-0.1.0.dist-info/WHEEL +4 -0
  103. aegis_stack-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,134 @@
1
+ from typing import Any
2
+
3
+ from fastapi import APIRouter, HTTPException
4
+ from starlette import status
5
+
6
+ from app.services.system import (
7
+ DetailedHealthResponse,
8
+ HealthResponse,
9
+ get_system_status,
10
+ )
11
+
12
+ router = APIRouter()
13
+
14
+
15
+ @router.get("/", response_model=HealthResponse)
16
+ async def health_check() -> HealthResponse:
17
+ """
18
+ Quick health check endpoint.
19
+
20
+ Returns basic healthy/unhealthy status for load balancers and monitoring.
21
+ """
22
+ try:
23
+ system_status = await get_system_status()
24
+ return HealthResponse(
25
+ healthy=system_status.overall_healthy,
26
+ status="healthy" if system_status.overall_healthy else "unhealthy",
27
+ components=system_status.components,
28
+ timestamp=system_status.timestamp.isoformat(),
29
+ )
30
+ except Exception:
31
+ # If health checks fail completely, consider unhealthy
32
+ return HealthResponse(
33
+ healthy=False,
34
+ status="unhealthy",
35
+ components={},
36
+ timestamp="",
37
+ )
38
+
39
+
40
+ @router.get("/detailed", response_model=DetailedHealthResponse)
41
+ async def detailed_health() -> DetailedHealthResponse:
42
+ """
43
+ Detailed health check with component information.
44
+
45
+ Returns comprehensive system status including individual component health,
46
+ system metrics, and diagnostic information.
47
+ """
48
+ try:
49
+ system_status = await get_system_status()
50
+
51
+ # Always return 200 OK - service is available even if components are unhealthy
52
+ return DetailedHealthResponse(
53
+ healthy=system_status.overall_healthy,
54
+ status="healthy" if system_status.overall_healthy else "unhealthy",
55
+ service="{{ cookiecutter.project_name }}",
56
+ version="0.1.0",
57
+ components=system_status.components,
58
+ system_info=system_status.system_info,
59
+ timestamp=system_status.timestamp.isoformat(),
60
+ healthy_components=system_status.healthy_components,
61
+ unhealthy_components=system_status.unhealthy_components,
62
+ health_percentage=system_status.health_percentage,
63
+ )
64
+
65
+ except HTTPException:
66
+ # Re-raise HTTP exceptions from unexpected errors
67
+ raise
68
+ except Exception as e:
69
+ # Handle unexpected errors
70
+ raise HTTPException(
71
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
72
+ detail={
73
+ "message": "Health check failed",
74
+ "error": str(e),
75
+ "status": "unhealthy",
76
+ },
77
+ )
78
+
79
+
80
+ @router.get("/dashboard")
81
+ async def system_dashboard() -> dict[str, Any]:
82
+ """
83
+ System dashboard endpoint optimized for frontend consumption.
84
+
85
+ Returns system status with additional dashboard metadata like
86
+ alert counts, trend data, and formatted display information.
87
+ """
88
+ try:
89
+ system_status = await get_system_status()
90
+
91
+ # TODO: Implement alert tracking when alert management is enhanced
92
+ recent_alerts = 0
93
+
94
+ return {
95
+ "status": "healthy" if system_status.overall_healthy else "unhealthy",
96
+ "service": "{{ cookiecutter.project_name }}",
97
+ "version": "0.1.0",
98
+ "dashboard_data": {
99
+ "overall_status": {
100
+ "healthy": system_status.overall_healthy,
101
+ "percentage": system_status.health_percentage,
102
+ "status_text": (
103
+ "System Healthy"
104
+ if system_status.overall_healthy
105
+ else "Issues Detected"
106
+ ),
107
+ },
108
+ "components": {
109
+ name: {
110
+ "name": name,
111
+ "healthy": component.healthy,
112
+ "message": component.message,
113
+ "response_time_ms": component.response_time_ms,
114
+ "metadata": component.metadata,
115
+ }
116
+ for name, component in system_status.components.items()
117
+ },
118
+ "summary": {
119
+ "total_components": len(system_status.components),
120
+ "healthy_components": len(system_status.healthy_components),
121
+ "unhealthy_components": len(system_status.unhealthy_components),
122
+ "recent_alerts": recent_alerts,
123
+ },
124
+ "system_info": system_status.system_info,
125
+ "timestamp": system_status.timestamp.isoformat(),
126
+ "last_updated": system_status.timestamp.strftime("%H:%M:%S"),
127
+ },
128
+ }
129
+
130
+ except Exception as e:
131
+ raise HTTPException(
132
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
133
+ detail={"message": "Dashboard data unavailable", "error": str(e)},
134
+ )
@@ -0,0 +1,247 @@
1
+ {%- if cookiecutter.include_worker == "yes" %}
2
+ """
3
+ API models for background tasks and responses.
4
+
5
+ This module contains all Pydantic models used by the API layer for
6
+ task management, status tracking, and response formatting.
7
+ """
8
+
9
+ from datetime import datetime
10
+ from typing import Dict, Any, Optional
11
+
12
+ from pydantic import BaseModel, Field
13
+
14
+ from app.components.worker.constants import LoadTestTypes
15
+
16
+
17
+ class TaskRequest(BaseModel):
18
+ """Request model for enqueueing a background task."""
19
+
20
+ task_name: str = Field(..., description="Name of the task function to execute")
21
+ queue_type: str = Field(
22
+ "system", description="Functional queue type: media or system"
23
+ )
24
+ args: list[Any] = Field(
25
+ default_factory=list, description="Positional arguments for the task"
26
+ )
27
+ kwargs: Dict[str, Any] = Field(
28
+ default_factory=dict, description="Keyword arguments for the task"
29
+ )
30
+ delay_seconds: Optional[int] = Field(
31
+ None, description="Delay task execution by this many seconds"
32
+ )
33
+
34
+
35
+ class TaskResponse(BaseModel):
36
+ """Response model for task enqueue operations."""
37
+
38
+ task_id: str
39
+ task_name: str
40
+ queue_type: str
41
+ queued_at: datetime
42
+ estimated_start: Optional[datetime] = None
43
+ message: str
44
+
45
+
46
+ class TaskListResponse(BaseModel):
47
+ """Response model for listing available tasks."""
48
+
49
+ available_tasks: list[str]
50
+ total_count: int
51
+ queues: Dict[str, list[str]]
52
+
53
+
54
+ class TaskStatusResponse(BaseModel):
55
+ """Response model for task status checks."""
56
+
57
+ task_id: str
58
+ status: str = Field(
59
+ ...,
60
+ description="Task status: queued, in_progress, complete, failed, unknown",
61
+ )
62
+ enqueue_time: Optional[datetime] = Field(
63
+ None, description="When the task was enqueued"
64
+ )
65
+ start_time: Optional[datetime] = Field(
66
+ None, description="When the task started processing"
67
+ )
68
+ finish_time: Optional[datetime] = Field(None, description="When the task completed")
69
+ result_available: bool = Field(
70
+ False, description="Whether task result is available"
71
+ )
72
+ error: Optional[str] = Field(None, description="Error message if task failed")
73
+
74
+
75
+ class TaskResultResponse(BaseModel):
76
+ """Response model for completed task results."""
77
+
78
+ task_id: str
79
+ status: str = Field(..., description="Task completion status")
80
+ result: Any = Field(..., description="The actual task result data")
81
+ enqueue_time: Optional[datetime] = Field(
82
+ None, description="When the task was enqueued"
83
+ )
84
+ start_time: Optional[datetime] = Field(
85
+ None, description="When the task started processing"
86
+ )
87
+ finish_time: Optional[datetime] = Field(None, description="When the task completed")
88
+
89
+
90
+ class LoadTestRequest(BaseModel):
91
+ """Request model for orchestrating load tests that measure queue throughput."""
92
+
93
+ num_tasks: int = Field(
94
+ 100,
95
+ description="Number of tasks to spawn for the load test",
96
+ ge=10,
97
+ le=10000
98
+ )
99
+ task_type: LoadTestTypes = Field(
100
+ LoadTestTypes.CPU_INTENSIVE,
101
+ description="Type of worker task to spawn for load testing",
102
+ )
103
+ batch_size: int = Field(
104
+ 10,
105
+ description="How many tasks to send concurrently per batch",
106
+ ge=1,
107
+ le=100
108
+ )
109
+ delay_ms: int = Field(
110
+ 0,
111
+ description="Delay between batches in milliseconds",
112
+ ge=0,
113
+ le=5000
114
+ )
115
+ target_queue: str = Field(
116
+ "load_test",
117
+ description="Which queue to test (system, media, or load_test)",
118
+ pattern="^(system|media|load_test)$"
119
+ )
120
+
121
+ class Config:
122
+ schema_extra = {
123
+ "examples": [
124
+ {
125
+ "description": (
126
+ "Quick CPU load test - fibonacci calculations and operations"
127
+ ),
128
+ "num_tasks": 50,
129
+ "task_type": "cpu_intensive",
130
+ "batch_size": 10,
131
+ "delay_ms": 0,
132
+ "target_queue": "system",
133
+ "expected_work": (
134
+ "Computational: fibonacci, prime checking, math operations"
135
+ ),
136
+ "expected_metrics": [
137
+ "fibonacci_result",
138
+ "math_operations_sum",
139
+ "operations_per_ms",
140
+ ]
141
+ },
142
+ {
143
+ "description": (
144
+ "I/O stress test - async operations and concurrent handling"
145
+ ),
146
+ "num_tasks": 200,
147
+ "task_type": "io_simulation",
148
+ "batch_size": 20,
149
+ "delay_ms": 50,
150
+ "target_queue": "system",
151
+ "expected_work": (
152
+ "Async I/O: network delays, concurrent operations, files"
153
+ ),
154
+ "expected_metrics": [
155
+ "concurrent_operations",
156
+ "io_efficiency_percent",
157
+ "concurrency_benefit",
158
+ ]
159
+ },
160
+ {
161
+ "description": (
162
+ "Memory allocation test - data structures and GC pressure"
163
+ ),
164
+ "num_tasks": 500,
165
+ "task_type": "memory_operations",
166
+ "batch_size": 25,
167
+ "delay_ms": 0,
168
+ "target_queue": "media",
169
+ "expected_work": (
170
+ "Memory: allocation patterns, data manipulation, cleanup"
171
+ ),
172
+ "expected_metrics": [
173
+ "estimated_peak_memory_mb",
174
+ "memory_throughput_mb_per_sec",
175
+ "memory_operations_count",
176
+ ]
177
+ },
178
+ {
179
+ "description": (
180
+ "Failure resilience test - ~20% random failures for errors"
181
+ ),
182
+ "num_tasks": 100,
183
+ "task_type": "test_task_failure",
184
+ "batch_size": 15,
185
+ "delay_ms": 0,
186
+ "target_queue": "system",
187
+ "expected_work": (
188
+ "Failure testing: random errors, recovery patterns, resilience"
189
+ ),
190
+ "expected_metrics": [
191
+ "failure_roll",
192
+ "failure_threshold",
193
+ "would_have_failed",
194
+ ]
195
+ }
196
+ ]
197
+ }
198
+
199
+
200
+ class LoadTestStatus(BaseModel):
201
+ """Status model for monitoring active load tests."""
202
+
203
+ test_id: str = Field(..., description="Unique load test identifier")
204
+ status: str = Field(..., description="Test status: running, completed, failed")
205
+ tasks_sent: int = Field(..., description="Number of tasks sent to queue")
206
+ tasks_completed: int = Field(..., description="Number of tasks completed")
207
+ tasks_failed: int = Field(0, description="Number of tasks that failed")
208
+ elapsed_seconds: float = Field(..., description="Time elapsed since test start")
209
+ current_throughput: float = Field(..., description="Current tasks per second")
210
+ estimated_completion: Optional[datetime] = Field(
211
+ None, description="Estimated completion time"
212
+ )
213
+ queue_depth: int = Field(0, description="Current queue depth")
214
+
215
+
216
+ class LoadTestResults(BaseModel):
217
+ """Final results model for completed load tests."""
218
+
219
+ test_id: str = Field(..., description="Unique load test identifier")
220
+ status: str = Field(..., description="Final test status")
221
+ configuration: LoadTestRequest = Field(..., description="Test configuration used")
222
+
223
+ # Task metrics
224
+ tasks_sent: int = Field(..., description="Total tasks sent")
225
+ tasks_completed: int = Field(..., description="Total tasks completed")
226
+ tasks_failed: int = Field(..., description="Total tasks failed")
227
+
228
+ # Timing metrics
229
+ start_time: datetime = Field(..., description="Test start time")
230
+ end_time: Optional[datetime] = Field(None, description="Test end time")
231
+ total_duration_seconds: float = Field(..., description="Total test duration")
232
+
233
+ # Performance metrics
234
+ overall_throughput: float = Field(..., description="Overall tasks per second")
235
+ peak_throughput: float = Field(..., description="Peak throughput achieved")
236
+ failure_rate: float = Field(..., description="Percentage of tasks that failed")
237
+
238
+ # Queue metrics
239
+ final_queue_metrics: Dict[str, Any] = Field(
240
+ ..., description="Final arq queue health data"
241
+ )
242
+
243
+ # Summary
244
+ performance_summary: str = Field(
245
+ ..., description="Human readable performance summary"
246
+ )
247
+ {%- endif %}
@@ -0,0 +1,14 @@
1
+ from fastapi import FastAPI
2
+
3
+ from app.components.backend.api import health
4
+ {%- if cookiecutter.include_worker == "yes" %}
5
+ from app.components.backend.api import tasks
6
+ {%- endif %}
7
+
8
+
9
+ def include_routers(app: FastAPI) -> None:
10
+ """Include all API routers in the FastAPI app"""
11
+ app.include_router(health.router, prefix="/health", tags=["health"])
12
+ {%- if cookiecutter.include_worker == "yes" %}
13
+ app.include_router(tasks.router, prefix="/api/v1", tags=["tasks"])
14
+ {%- endif %}