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,169 @@
1
+ """
2
+ System domain Pydantic models.
3
+
4
+ Type-safe data models for system health monitoring, alerts, and status reporting.
5
+ All models provide runtime validation and automatic FastAPI integration.
6
+ """
7
+
8
+ from datetime import datetime
9
+ from enum import Enum
10
+ from typing import Any
11
+
12
+ from pydantic import BaseModel, Field, computed_field
13
+
14
+
15
+ class ComponentStatusType(str, Enum):
16
+ """Component status levels."""
17
+ HEALTHY = "healthy"
18
+ INFO = "info"
19
+ WARNING = "warning"
20
+ UNHEALTHY = "unhealthy"
21
+
22
+
23
+ class ComponentStatus(BaseModel):
24
+ """Status of a single system component."""
25
+
26
+ name: str = Field(..., description="Component name")
27
+ status: ComponentStatusType = Field(
28
+ default=ComponentStatusType.HEALTHY,
29
+ description="Detailed status level (healthy/info/warning/unhealthy)"
30
+ )
31
+ message: str = Field(..., description="Status message or error description")
32
+ response_time_ms: float | None = Field(
33
+ None, description="Response time in milliseconds"
34
+ )
35
+ metadata: dict[str, Any] = Field(
36
+ default_factory=dict, description="Additional component metadata"
37
+ )
38
+ sub_components: dict[str, "ComponentStatus"] = Field(
39
+ default_factory=dict,
40
+ description="Sub-components (e.g., system metrics under backend)"
41
+ )
42
+
43
+ @computed_field # type: ignore[prop-decorator]
44
+ @property
45
+ def healthy(self) -> bool:
46
+ """
47
+ Component is healthy if status is not UNHEALTHY.
48
+
49
+ WARNING and INFO statuses are considered healthy (functional but with concerns),
50
+ only UNHEALTHY is considered not healthy (non-functional).
51
+ """
52
+ return self.status != ComponentStatusType.UNHEALTHY
53
+
54
+
55
+ class SystemStatus(BaseModel):
56
+ """Overall system status with component details."""
57
+
58
+ components: dict[str, ComponentStatus] = Field(
59
+ ..., description="Component status by name"
60
+ )
61
+ overall_healthy: bool = Field(..., description="Whether all components are healthy")
62
+ timestamp: datetime = Field(..., description="When status was generated")
63
+ system_info: dict[str, Any] = Field(
64
+ default_factory=dict, description="System information"
65
+ )
66
+
67
+ def _get_all_components_flat(self) -> list[tuple[str, ComponentStatus]]:
68
+ """Get all components including sub-components as a flat list."""
69
+ all_components = []
70
+ for name, component in self.components.items():
71
+ all_components.append((name, component))
72
+ # Add sub-components with parent.child naming
73
+ for sub_name, sub_component in component.sub_components.items():
74
+ all_components.append((f"{name}.{sub_name}", sub_component))
75
+ return all_components
76
+
77
+ @property
78
+ def healthy_components(self) -> list[str]:
79
+ """List of healthy component names (including sub-components)."""
80
+ return [
81
+ name for name, component in self._get_all_components_flat()
82
+ if component.healthy
83
+ ]
84
+
85
+ @property
86
+ def unhealthy_components(self) -> list[str]:
87
+ """List of unhealthy component names (including sub-components)."""
88
+ return [
89
+ name for name, component in self._get_all_components_flat()
90
+ if not component.healthy
91
+ ]
92
+
93
+ @property
94
+ def health_percentage(self) -> float:
95
+ """Percentage of healthy components (including sub-components)."""
96
+ all_components = self._get_all_components_flat()
97
+ if not all_components:
98
+ return 100.0
99
+ healthy_count = len([c for _, c in all_components if c.healthy])
100
+ return (healthy_count / len(all_components)) * 100
101
+
102
+ @property
103
+ def total_components(self) -> int:
104
+ """Total number of top-level components."""
105
+ return len(self.components)
106
+
107
+ @property
108
+ def healthy_top_level_components(self) -> list[str]:
109
+ """List of healthy top-level component names."""
110
+ return [name for name, comp in self.components.items() if comp.healthy]
111
+
112
+
113
+ class HealthResponse(BaseModel):
114
+ """Basic health check API response."""
115
+
116
+ healthy: bool = Field(..., description="Whether system is healthy")
117
+ status: str = Field(..., description="Overall status text")
118
+ components: dict[str, ComponentStatus] = Field(
119
+ default_factory=dict, description="Component statuses"
120
+ )
121
+ timestamp: str = Field(..., description="ISO timestamp when checked")
122
+
123
+
124
+ class DetailedHealthResponse(BaseModel):
125
+ """Detailed health check API response with system information."""
126
+
127
+ healthy: bool = Field(..., description="Whether system is healthy")
128
+ status: str = Field(..., description="Overall status text")
129
+ service: str = Field(..., description="Service name")
130
+ version: str = Field(..., description="Service version")
131
+ components: dict[str, ComponentStatus] = Field(
132
+ ..., description="Component statuses"
133
+ )
134
+ system_info: dict[str, Any] = Field(..., description="System information")
135
+ timestamp: str = Field(..., description="ISO timestamp when checked")
136
+ healthy_components: list[str] = Field(
137
+ ..., description="List of healthy component names"
138
+ )
139
+ unhealthy_components: list[str] = Field(
140
+ ..., description="List of unhealthy component names"
141
+ )
142
+ health_percentage: float = Field(
143
+ ..., description="Percentage of healthy components"
144
+ )
145
+
146
+
147
+ class AlertSeverity(BaseModel):
148
+ """Alert severity levels as constants."""
149
+
150
+ INFO: str = Field(default="info", description="Informational alert")
151
+ WARNING: str = Field(default="warning", description="Warning alert")
152
+ ERROR: str = Field(default="error", description="Error alert")
153
+ CRITICAL: str = Field(default="critical", description="Critical alert")
154
+
155
+
156
+ class Alert(BaseModel):
157
+ """System alert model."""
158
+
159
+ severity: str = Field(..., description="Alert severity level")
160
+ title: str = Field(..., min_length=1, description="Alert title")
161
+ message: str = Field(..., min_length=1, description="Alert message")
162
+ timestamp: datetime = Field(..., description="When alert was created")
163
+ metadata: dict[str, Any] = Field(
164
+ default_factory=dict, description="Additional alert data"
165
+ )
166
+
167
+
168
+ # Singleton for alert severity constants
169
+ alert_severity = AlertSeverity()
@@ -0,0 +1,52 @@
1
+ """
2
+ Shared UI helpers for status presentation across CLI and frontend.
3
+
4
+ Provides a single source of truth for mapping ComponentStatusType to
5
+ icons and semantic colors, so CLI and dashboard remain consistent.
6
+ """
7
+
8
+ from .models import ComponentStatusType
9
+
10
+
11
+ def get_status_icon(status: ComponentStatusType) -> str:
12
+ """Return the display icon for a given status.
13
+
14
+ Note: INFO icon intentionally includes a trailing space for terminal alignment.
15
+ """
16
+ if status == ComponentStatusType.HEALTHY:
17
+ return "✅"
18
+ if status == ComponentStatusType.INFO:
19
+ return "ℹ️ "
20
+ if status == ComponentStatusType.WARNING:
21
+ return "⚠️"
22
+ if status == ComponentStatusType.UNHEALTHY:
23
+ return "❌"
24
+ return "❓"
25
+
26
+
27
+ def get_status_color_name(status: ComponentStatusType) -> str:
28
+ """Return a semantic color name for a given status (CLI friendly).
29
+
30
+ Frontend can adapt these semantic names to theme-specific colors.
31
+ """
32
+ if status == ComponentStatusType.HEALTHY:
33
+ return "green"
34
+ if status == ComponentStatusType.INFO:
35
+ return "blue"
36
+ if status == ComponentStatusType.WARNING:
37
+ return "yellow"
38
+ if status == ComponentStatusType.UNHEALTHY:
39
+ return "red"
40
+ return "white"
41
+
42
+
43
+ def get_component_label(component_name: str) -> str:
44
+ """Map component keys to user-facing labels (brand or friendly name)."""
45
+ mapping = {
46
+ "backend": "FastAPI",
47
+ "frontend": "Flet",
48
+ "cache": "Redis",
49
+ "worker": "arq",
50
+ "scheduler": "APScheduler",
51
+ }
52
+ return mapping.get(component_name, component_name.title())
@@ -0,0 +1,195 @@
1
+
2
+ x-app: &app
3
+ image: ${AEGIS_STACK_TAG:-aegis-stack:latest}
4
+ build:
5
+ context: .
6
+ args:
7
+ - AEGIS_STACK_VERSION=${AEGIS_STACK_VERSION:-dev}
8
+ - PORT=${PORT:-8000}
9
+ env_file:
10
+ - ${AEGIS_STACK_ENV_FILE:-.env}
11
+ volumes:
12
+ - .:/code
13
+ # Exclude .venv to prevent host/container virtual environment conflicts
14
+ - /code/.venv
15
+ environment:
16
+ # Docker container marker for entrypoint script
17
+ - DOCKER_CONTAINER=1
18
+ - PORT=${PORT:-8000}
19
+ labels:
20
+ logging: "aegis-stack"
21
+
22
+ services:
23
+ # DEVELOPMENT PROFILE
24
+
25
+ webserver:
26
+ <<: *app
27
+ command:
28
+ - webserver
29
+ ports:
30
+ - "${PORT:-8000}:${PORT:-8000}"
31
+ {%- if cookiecutter.include_redis == "yes" %}
32
+ depends_on:
33
+ redis:
34
+ condition: service_healthy
35
+ environment:
36
+ # Redis connection for health checks and task enqueueing
37
+ - REDIS_URL=${REDIS_URL:-redis://redis:6379}
38
+ {%- endif %}
39
+ healthcheck:
40
+ test: ["CMD", "curl", "-f", "http://localhost:${PORT:-8000}/health/"]
41
+ interval: 30s
42
+ timeout: 10s
43
+ retries: 3
44
+ start_period: 40s
45
+ restart: unless-stopped
46
+ deploy:
47
+ resources:
48
+ limits:
49
+ memory: 512M
50
+ cpus: '0.5'
51
+ reservations:
52
+ memory: 256M
53
+ cpus: '0.25'
54
+ profiles:
55
+ - dev
56
+
57
+
58
+ {%- if cookiecutter.include_scheduler == "yes" %}
59
+ # Scheduler service (runs scheduler tasks independently)
60
+ scheduler:
61
+ <<: *app
62
+ command:
63
+ - scheduler
64
+ {%- if cookiecutter.include_redis == "yes" %}
65
+ depends_on:
66
+ redis:
67
+ condition: service_healthy
68
+ environment:
69
+ # Redis connection for future scheduler integration
70
+ - REDIS_URL=${REDIS_URL:-redis://redis:6379}
71
+ {%- endif %}
72
+ restart: unless-stopped
73
+ deploy:
74
+ resources:
75
+ limits:
76
+ memory: 256M
77
+ cpus: '0.25'
78
+ reservations:
79
+ memory: 128M
80
+ cpus: '0.1'
81
+ profiles:
82
+ - dev
83
+ - prod
84
+ {%- endif %}
85
+
86
+ {%- if cookiecutter.include_worker == "yes" %}
87
+ # Load Test Worker (high-performance load testing tasks)
88
+ worker-load-test:
89
+ <<: *app
90
+ command:
91
+ - worker
92
+ - load_test
93
+ depends_on:
94
+ redis:
95
+ condition: service_healthy
96
+ restart: unless-stopped
97
+ tty: true
98
+ environment:
99
+ # Load test worker configuration (high concurrency)
100
+ - WORKER_QUEUE_TYPE=load_test
101
+ - WORKER_CONCURRENCY=${LOAD_TEST_WORKER_CONCURRENCY:-20}
102
+ - WORKER_TIMEOUT_SECONDS=${LOAD_TEST_WORKER_TIMEOUT:-60}
103
+ - WORKER_MAX_TRIES=${LOAD_TEST_WORKER_MAX_TRIES:-1}
104
+ - REDIS_URL=${REDIS_URL:-redis://redis:6379}
105
+ # Docker file watching fix - force polling for volume mounts
106
+ - WATCHFILES_FORCE_POLLING=true
107
+ - WATCHFILES_POLL_DELAY_MS=800
108
+ deploy:
109
+ resources:
110
+ limits:
111
+ memory: 1G
112
+ cpus: '1.0'
113
+ reservations:
114
+ memory: 512M
115
+ cpus: '0.5'
116
+ profiles:
117
+ - dev
118
+ - prod
119
+
120
+
121
+ # System Worker (maintenance and administrative tasks)
122
+ worker-system:
123
+ <<: *app
124
+ command:
125
+ - worker
126
+ - system
127
+ depends_on:
128
+ redis:
129
+ condition: service_healthy
130
+ restart: unless-stopped
131
+ tty: true
132
+ environment:
133
+ # System worker configuration (low concurrency, high reliability)
134
+ - WORKER_QUEUE_TYPE=system
135
+ - WORKER_CONCURRENCY=${SYSTEM_WORKER_CONCURRENCY:-2}
136
+ - WORKER_TIMEOUT_SECONDS=${SYSTEM_WORKER_TIMEOUT:-1800}
137
+ - WORKER_MAX_TRIES=${SYSTEM_WORKER_MAX_TRIES:-5}
138
+ - REDIS_URL=${REDIS_URL:-redis://redis:6379}
139
+ # Docker file watching fix - force polling for volume mounts
140
+ - WATCHFILES_FORCE_POLLING=true
141
+ - WATCHFILES_POLL_DELAY_MS=800
142
+ deploy:
143
+ resources:
144
+ limits:
145
+ memory: 256M
146
+ cpus: '0.25'
147
+ reservations:
148
+ memory: 128M
149
+ cpus: '0.1'
150
+ profiles:
151
+ - dev
152
+ - prod
153
+ {%- endif %}
154
+
155
+
156
+ {%- if cookiecutter.include_redis == "yes" %}
157
+ # REDIS SERVICE
158
+
159
+ redis:
160
+ image: redis:7-alpine
161
+ ports:
162
+ - "6379:6379"
163
+ command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
164
+ healthcheck:
165
+ test: ["CMD", "redis-cli", "ping"]
166
+ interval: 10s
167
+ timeout: 5s
168
+ retries: 3
169
+ start_period: 10s
170
+ restart: unless-stopped
171
+ deploy:
172
+ resources:
173
+ limits:
174
+ memory: 512M
175
+ cpus: '0.25'
176
+ reservations:
177
+ memory: 256M
178
+ cpus: '0.1'
179
+ profiles:
180
+ - dev
181
+ {%- endif %}
182
+
183
+ # TEST PROFILE
184
+
185
+ test_runner:
186
+ <<: *app
187
+ command:
188
+ - test
189
+ env_file:
190
+ - ${AEGIS_STACK_ENV_FILE:-.env}
191
+ profiles:
192
+ - test
193
+
194
+ volumes:
195
+ aegis-data:
@@ -0,0 +1,191 @@
1
+ # API Reference
2
+
3
+ {{ cookiecutter.project_name }} provides a FastAPI-based REST API with automatic documentation.
4
+
5
+ ## Interactive Documentation
6
+
7
+ When running the application, interactive API documentation is available at:
8
+
9
+ - **Swagger UI**: [http://localhost:8000/docs](http://localhost:8000/docs)
10
+ - **ReDoc**: [http://localhost:8000/redoc](http://localhost:8000/redoc)
11
+ - **OpenAPI Schema**: [http://localhost:8000/openapi.json](http://localhost:8000/openapi.json)
12
+
13
+ ## Health Endpoints
14
+
15
+ ### Basic Health Check
16
+
17
+ **GET** `/health/`
18
+
19
+ Returns basic health status of the application.
20
+
21
+ ```bash
22
+ curl http://localhost:8000/health/
23
+ ```
24
+
25
+ **Response:**
26
+ ```json
27
+ {
28
+ "healthy": true,
29
+ "status": "healthy",
30
+ "components": {},
31
+ "timestamp": "2024-01-01T00:00:00Z"
32
+ }
33
+ ```
34
+
35
+ ### Detailed Health Check
36
+
37
+ **GET** `/health/detailed`
38
+
39
+ Returns comprehensive health information including system metrics.
40
+
41
+ ```bash
42
+ curl http://localhost:8000/health/detailed
43
+ ```
44
+
45
+ **Response:**
46
+ ```json
47
+ {
48
+ "healthy": true,
49
+ "status": "healthy",
50
+ "components": {
51
+ "system": {
52
+ "status": "healthy",
53
+ "cpu_percent": 15.2,
54
+ "memory_percent": 45.8,
55
+ "disk_percent": 32.1,
56
+ "response_time_ms": 2.1
57
+ }{% if cookiecutter.include_scheduler == "yes" %},
58
+ "scheduler": {
59
+ "status": "healthy",
60
+ "active_jobs": 2,
61
+ "next_run": "2024-01-01T02:00:00Z",
62
+ "response_time_ms": 1.5
63
+ }{% endif %}
64
+ },
65
+ "timestamp": "2024-01-01T00:00:00Z",
66
+ "uptime_seconds": 3600
67
+ }
68
+ ```
69
+
70
+ ## Authentication
71
+
72
+ Currently, {{ cookiecutter.project_name }} does not implement authentication.
73
+
74
+ To add authentication:
75
+
76
+ 1. **Install dependencies:**
77
+ ```bash
78
+ uv add python-jose[cryptography] passlib[bcrypt]
79
+ ```
80
+
81
+ 2. **Add authentication routes:**
82
+ ```python
83
+ # app/components/backend/api/auth.py
84
+ from fastapi import APIRouter, Depends, HTTPException, status
85
+ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
86
+
87
+ router = APIRouter()
88
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
89
+
90
+ @router.post("/token")
91
+ async def login(form_data: OAuth2PasswordRequestForm = Depends()):
92
+ # Implement login logic
93
+ pass
94
+ ```
95
+
96
+ 3. **Register in routing:**
97
+ ```python
98
+ # app/components/backend/api/routing.py
99
+ from app.components.backend.api import auth
100
+
101
+ def include_routers(app: FastAPI) -> None:
102
+ app.include_router(auth.router, prefix="/auth", tags=["authentication"])
103
+ ```
104
+
105
+ ## CORS Configuration
106
+
107
+ CORS is pre-configured to allow all origins in development. For production, update the CORS middleware in `app/components/backend/middleware/cors.py`:
108
+
109
+ ```python
110
+ from fastapi.middleware.cors import CORSMiddleware
111
+
112
+ app.add_middleware(
113
+ CORSMiddleware,
114
+ allow_origins=["https://yourdomain.com"], # Update for production
115
+ allow_credentials=True,
116
+ allow_methods=["*"],
117
+ allow_headers=["*"],
118
+ )
119
+ ```
120
+
121
+ ## Error Handling
122
+
123
+ The API includes standard HTTP error responses:
124
+
125
+ - **400 Bad Request**: Invalid request data
126
+ - **404 Not Found**: Resource not found
127
+ - **422 Unprocessable Entity**: Validation errors
128
+ - **500 Internal Server Error**: Server errors
129
+
130
+ Custom error handlers can be added in `app/components/backend/main.py`:
131
+
132
+ ```python
133
+ from fastapi import HTTPException, Request
134
+ from fastapi.responses import JSONResponse
135
+
136
+ @app.exception_handler(404)
137
+ async def not_found_handler(request: Request, exc: HTTPException):
138
+ return JSONResponse(
139
+ status_code=404,
140
+ content={"message": "Resource not found"}
141
+ )
142
+ ```
143
+
144
+ ## Adding Custom Endpoints
145
+
146
+ ### 1. Create Router Module
147
+
148
+ ```python
149
+ # app/components/backend/api/my_api.py
150
+ from fastapi import APIRouter, HTTPException
151
+ from app.services.my_service import get_data, create_item
152
+
153
+ router = APIRouter()
154
+
155
+ @router.get("/items")
156
+ async def list_items():
157
+ """Get all items."""
158
+ items = await get_data()
159
+ return {"items": items}
160
+
161
+ @router.post("/items")
162
+ async def create_item_endpoint(name: str):
163
+ """Create a new item."""
164
+ try:
165
+ item = await create_item(name)
166
+ return {"item": item}
167
+ except ValueError as e:
168
+ raise HTTPException(status_code=400, detail=str(e))
169
+ ```
170
+
171
+ ### 2. Register Router
172
+
173
+ ```python
174
+ # app/components/backend/api/routing.py
175
+ from app.components.backend.api import my_api
176
+
177
+ def include_routers(app: FastAPI) -> None:
178
+ app.include_router(my_api.router, prefix="/api", tags=["items"])
179
+ ```
180
+
181
+ ### 3. Add Tests
182
+
183
+ ```python
184
+ # tests/api/test_my_api.py
185
+ def test_list_items(client):
186
+ response = client.get("/api/items")
187
+ assert response.status_code == 200
188
+ assert "items" in response.json()
189
+ ```
190
+
191
+ The API will automatically include your endpoints in the interactive documentation at `/docs`.