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,224 @@
1
+ {%- if cookiecutter.include_worker == "yes" %}
2
+ """
3
+ Tests for worker health registration and integration.
4
+
5
+ Tests that worker components are properly registered in the health system
6
+ and appear in health check results.
7
+ """
8
+
9
+ from unittest.mock import AsyncMock, MagicMock, patch
10
+ from typing import Any
11
+
12
+ import pytest
13
+
14
+ from app.services.system import ComponentStatus, ComponentStatusType
15
+ from app.services.system.health import get_system_status, register_health_check, _health_checks
16
+
17
+
18
+ class TestWorkerHealthRegistration:
19
+ """Test worker health registration mechanism."""
20
+
21
+ def test_worker_health_check_is_registered(self) -> None:
22
+ """Test that worker health check gets registered during startup."""
23
+ # This test verifies that the startup hook actually registers the worker
24
+ # In a real app, this would be done during startup
25
+
26
+ # Clear any existing registrations for clean test
27
+ original_checks = _health_checks.copy()
28
+ _health_checks.clear()
29
+
30
+ try:
31
+ # Import and register worker health check (simulating startup)
32
+ from app.services.system.health import check_worker_health
33
+ register_health_check("worker", check_worker_health)
34
+
35
+ # Verify registration worked
36
+ assert "worker" in _health_checks
37
+ assert _health_checks["worker"] == check_worker_health
38
+
39
+ finally:
40
+ # Restore original state
41
+ _health_checks.clear()
42
+ _health_checks.update(original_checks)
43
+
44
+ @pytest.mark.asyncio
45
+ async def test_worker_appears_in_system_status(self) -> None:
46
+ """Test that registered worker appears in get_system_status results."""
47
+ # Mock worker health check to avoid Redis dependency
48
+ mock_worker_status = ComponentStatus(
49
+ name="worker",
50
+ status=ComponentStatusType.HEALTHY,
51
+ message="arq worker infrastructure: 2/2 workers active",
52
+ response_time_ms=50.0,
53
+ metadata={
54
+ "total_queued": 0,
55
+ "total_completed": 5,
56
+ "total_failed": 0,
57
+ "active_workers": 2,
58
+ },
59
+ sub_components={
60
+ "queues": ComponentStatus(
61
+ name="queues",
62
+ status=ComponentStatusType.HEALTHY,
63
+ message="2 functional queues configured (2 active)",
64
+ response_time_ms=None,
65
+ metadata={"configured_queues": 2, "active_workers": 2},
66
+ sub_components={
67
+ "system": ComponentStatus(
68
+ name="system",
69
+ status=ComponentStatusType.HEALTHY,
70
+ message="System maintenance: idle, 2 completed",
71
+ response_time_ms=None,
72
+ metadata={
73
+ "queue_type": "system",
74
+ "worker_alive": True,
75
+ "jobs_completed": 2,
76
+ "jobs_failed": 0,
77
+ },
78
+ ),
79
+ "load_test": ComponentStatus(
80
+ name="load_test",
81
+ status=ComponentStatusType.HEALTHY,
82
+ message="Performance testing: idle, 3 completed",
83
+ response_time_ms=None,
84
+ metadata={
85
+ "queue_type": "load_test",
86
+ "worker_alive": True,
87
+ "jobs_completed": 3,
88
+ "jobs_failed": 0,
89
+ },
90
+ ),
91
+ },
92
+ )
93
+ },
94
+ )
95
+
96
+ # Clear and register mock worker
97
+ original_checks = _health_checks.copy()
98
+ _health_checks.clear()
99
+
100
+ try:
101
+ # Register mock worker health check
102
+ async def mock_worker_health() -> ComponentStatus:
103
+ return mock_worker_status
104
+
105
+ register_health_check("worker", mock_worker_health)
106
+
107
+ # Mock system metrics to avoid psutil dependency
108
+ with patch(
109
+ "app.services.system.health._get_cached_system_metrics"
110
+ ) as mock_metrics:
111
+ mock_metrics.return_value = {
112
+ "memory": ComponentStatus(
113
+ name="memory",
114
+ message="Memory usage: 45.2%",
115
+ response_time_ms=None,
116
+ ),
117
+ "disk": ComponentStatus(
118
+ name="disk",
119
+ message="Disk usage: 12.1%",
120
+ response_time_ms=None,
121
+ ),
122
+ "cpu": ComponentStatus(
123
+ name="cpu",
124
+ message="CPU usage: 5.3%",
125
+ response_time_ms=None,
126
+ ),
127
+ }
128
+
129
+ # Get system status
130
+ system_status = await get_system_status()
131
+
132
+ # Verify worker appears in results
133
+ assert system_status.overall_healthy
134
+ assert "aegis" in system_status.components
135
+
136
+ aegis_component = system_status.components["aegis"]
137
+ assert "worker" in aegis_component.sub_components
138
+
139
+ worker_component = aegis_component.sub_components["worker"]
140
+ assert worker_component.name == "worker"
141
+ assert worker_component.healthy
142
+ assert "workers active" in worker_component.message
143
+ assert "queues" in worker_component.sub_components
144
+
145
+ # Verify queue sub-components
146
+ queues_component = worker_component.sub_components["queues"]
147
+ assert "system" in queues_component.sub_components
148
+ assert "load_test" in queues_component.sub_components
149
+
150
+ finally:
151
+ # Restore original state
152
+ _health_checks.clear()
153
+ _health_checks.update(original_checks)
154
+
155
+ @pytest.mark.asyncio
156
+ async def test_system_status_without_worker_registration(self) -> None:
157
+ """Test that system status works when worker is not registered."""
158
+ # Clear registrations
159
+ original_checks = _health_checks.copy()
160
+ _health_checks.clear()
161
+
162
+ try:
163
+ # Register only basic components (no worker)
164
+ async def mock_backend_health() -> ComponentStatus:
165
+ return ComponentStatus(
166
+ name="backend",
167
+ message="FastAPI backend active",
168
+ response_time_ms=25.0,
169
+ )
170
+
171
+ register_health_check("backend", mock_backend_health)
172
+
173
+ # Mock system metrics
174
+ with patch(
175
+ "app.services.system.health._get_cached_system_metrics"
176
+ ) as mock_metrics:
177
+ mock_metrics.return_value = {
178
+ "memory": ComponentStatus(
179
+ name="memory",
180
+ message="Memory usage: 45.2%",
181
+ response_time_ms=None,
182
+ ),
183
+ }
184
+
185
+ # Get system status
186
+ system_status = await get_system_status()
187
+
188
+ # Verify worker does NOT appear in results
189
+ aegis_component = system_status.components["aegis"]
190
+ assert "worker" not in aegis_component.sub_components
191
+ assert "backend" in aegis_component.sub_components
192
+
193
+ finally:
194
+ # Restore original state
195
+ _health_checks.clear()
196
+ _health_checks.update(original_checks)
197
+
198
+ def test_health_registration_is_idempotent(self) -> None:
199
+ """Test that registering the same health check multiple times works."""
200
+ original_checks = _health_checks.copy()
201
+ _health_checks.clear()
202
+
203
+ try:
204
+ async def dummy_health_check() -> ComponentStatus:
205
+ return ComponentStatus(
206
+ name="test",
207
+ message="Test",
208
+ response_time_ms=None,
209
+ )
210
+
211
+ # Register same check multiple times
212
+ register_health_check("test", dummy_health_check)
213
+ register_health_check("test", dummy_health_check)
214
+ register_health_check("test", dummy_health_check)
215
+
216
+ # Should only be registered once
217
+ assert len(_health_checks) == 1
218
+ assert "test" in _health_checks
219
+
220
+ finally:
221
+ # Restore original state
222
+ _health_checks.clear()
223
+ _health_checks.update(original_checks)
224
+ {%- endif %}
@@ -0,0 +1,50 @@
1
+ # tests/test_core.py
2
+ """
3
+ Core functionality tests.
4
+
5
+ These basic tests ensure the fundamental application components work correctly.
6
+ """
7
+
8
+ from collections.abc import Generator
9
+
10
+ from fastapi.testclient import TestClient
11
+ import pytest
12
+
13
+ from app.integrations.main import create_integrated_app
14
+
15
+
16
+ @pytest.fixture
17
+ def client() -> Generator[TestClient, None, None]:
18
+ """Create a test client for the FastAPI app."""
19
+ app = create_integrated_app()
20
+ with TestClient(app) as test_client:
21
+ yield test_client
22
+
23
+
24
+ def test_health_endpoint(client: TestClient) -> None:
25
+ """Test that the health endpoint works."""
26
+ response = client.get("/health/")
27
+ # Should return 200 for healthy or 503 for unhealthy, both are valid responses
28
+ assert response.status_code in [200, 503]
29
+
30
+ data = response.json()
31
+ # Check the enhanced health endpoint format
32
+ assert isinstance(data["healthy"], bool)
33
+ assert "status" in data
34
+ assert "components" in data
35
+ assert "timestamp" in data
36
+
37
+
38
+ def test_flet_app_mount(client: TestClient) -> None:
39
+ """Test that the Flet frontend is mounted at /dashboard."""
40
+ response = client.get("/dashboard", follow_redirects=False)
41
+ # Should serve the Flet app (might be redirect or direct serve)
42
+ assert response.status_code in [200, 301, 302, 307, 308]
43
+
44
+
45
+ @pytest.mark.asyncio
46
+ async def test_app_creation() -> None:
47
+ """Test that the app can be created without errors."""
48
+ app = create_integrated_app()
49
+ assert app is not None
50
+ assert hasattr(app, "router")