aegis-stack 0.2.0rc2__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 (392) hide show
  1. aegis/__init__.py +5 -0
  2. aegis/__main__.py +51 -0
  3. aegis/cli/__init__.py +6 -0
  4. aegis/cli/callbacks.py +114 -0
  5. aegis/cli/interactive.py +611 -0
  6. aegis/cli/utils.py +70 -0
  7. aegis/cli/validators.py +34 -0
  8. aegis/commands/__init__.py +6 -0
  9. aegis/commands/add.py +353 -0
  10. aegis/commands/add_service.py +332 -0
  11. aegis/commands/components.py +35 -0
  12. aegis/commands/init.py +370 -0
  13. aegis/commands/remove.py +227 -0
  14. aegis/commands/services.py +52 -0
  15. aegis/commands/update.py +252 -0
  16. aegis/commands/version.py +12 -0
  17. aegis/config/__init__.py +1 -0
  18. aegis/config/shared_files.py +136 -0
  19. aegis/core/CLAUDE.md +377 -0
  20. aegis/core/__init__.py +6 -0
  21. aegis/core/component_files.py +228 -0
  22. aegis/core/component_utils.py +220 -0
  23. aegis/core/components.py +127 -0
  24. aegis/core/copier_manager.py +315 -0
  25. aegis/core/copier_updater.py +475 -0
  26. aegis/core/dependency_resolver.py +119 -0
  27. aegis/core/manual_updater.py +554 -0
  28. aegis/core/post_gen_tasks.py +547 -0
  29. aegis/core/service_resolver.py +261 -0
  30. aegis/core/services.py +157 -0
  31. aegis/core/template_generator.py +266 -0
  32. aegis/core/version_compatibility.py +259 -0
  33. aegis/templates/CLAUDE.md +591 -0
  34. aegis/templates/cookiecutter-aegis-project/cookiecutter.json +39 -0
  35. aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +214 -0
  36. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
  37. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +130 -0
  38. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +131 -0
  39. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
  40. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +236 -0
  41. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
  42. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/alembic.ini.j2 +111 -0
  43. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/env.py.j2 +91 -0
  44. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/script.py.mako +25 -0
  45. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/versions/001_initial_auth.py.j2 +51 -0
  46. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
  47. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
  48. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai.py.j2 +700 -0
  49. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai_rendering.py +361 -0
  50. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/auth.py.j2 +253 -0
  51. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py.j2 +419 -0
  52. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py.j2 +656 -0
  53. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py.j2 +65 -0
  54. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/marko_terminal_renderer.py +489 -0
  55. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/tasks.py.j2 +328 -0
  56. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/{% if cookiecutter.include_scheduler == /"yes/" %}tasks.py{% endif %}" +340 -0
  57. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
  58. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
  59. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/__init__.py +8 -0
  60. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/router.py +329 -0
  61. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/__init__.py +1 -0
  62. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/router.py +64 -0
  63. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/deps.py +58 -0
  64. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +163 -0
  65. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +280 -0
  66. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +32 -0
  67. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/scheduler.py.j2 +121 -0
  68. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/worker.py.j2 +478 -0
  69. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +144 -0
  70. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +31 -0
  71. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
  72. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
  73. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
  74. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
  75. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
  76. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +418 -0
  77. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/database_init.py.j2 +83 -0
  78. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +5 -0
  79. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/__init__.py +27 -0
  80. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/table.py +78 -0
  81. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/text.py +142 -0
  82. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/__init__.py.j2 +47 -0
  83. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
  84. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
  85. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/base_card.py +256 -0
  86. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
  87. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
  88. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/database_card.py +420 -0
  89. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
  90. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
  91. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
  92. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
  93. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/services_card.py +233 -0
  94. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
  95. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py.j2 +653 -0
  96. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/theme.py +48 -0
  97. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
  98. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py.j2 +156 -0
  99. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md.j2 +213 -0
  100. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
  101. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
  102. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +97 -0
  103. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
  104. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +55 -0
  105. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +49 -0
  106. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +44 -0
  107. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
  108. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +120 -0
  109. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +507 -0
  110. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +33 -0
  111. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +281 -0
  112. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +178 -0
  113. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +58 -0
  114. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py.j2 +176 -0
  115. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +92 -0
  116. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/security.py +62 -0
  117. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
  118. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
  119. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
  120. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
  121. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +62 -0
  122. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/__init__.py +1 -0
  123. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/user.py +44 -0
  124. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
  125. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
  126. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/__init__.py +8 -0
  127. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/config.py +130 -0
  128. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/conversation.py +213 -0
  129. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/health.py +96 -0
  130. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/models.py +229 -0
  131. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/providers.py +370 -0
  132. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/service.py +388 -0
  133. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/__init__.py +1 -0
  134. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/auth_service.py +41 -0
  135. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/health.py +164 -0
  136. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/user_service.py +83 -0
  137. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/middleware_inspector.py.j2 +223 -0
  138. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/models.py.j2 +70 -0
  139. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/route_inspector.py.j2 +155 -0
  140. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +679 -0
  141. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +266 -0
  142. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/__init__.py.j2 +21 -0
  143. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/models.py.j2 +119 -0
  144. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/scheduled_task_manager.py.j2 +273 -0
  145. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/task_monitor.py.j2 +189 -0
  146. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
  147. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
  148. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
  149. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
  150. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/backup.py.j2 +119 -0
  151. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1333 -0
  152. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +243 -0
  153. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
  154. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-dark.png +0 -0
  155. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-square-backup.png +0 -0
  156. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto.png +0 -0
  157. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.dockerignore +71 -0
  158. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.env.example.j2 +64 -0
  159. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.gitignore +131 -0
  160. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Dockerfile +53 -0
  161. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Makefile +211 -0
  162. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/README.md.j2 +172 -0
  163. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/docker-compose.yml.j2 +78 -0
  164. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/mkdocs.yml.j2 +62 -0
  165. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/pyproject.toml.j2 +120 -0
  166. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/uv.lock +1673 -0
  167. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +200 -0
  168. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
  169. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +0 -0
  170. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md.j2 +621 -0
  171. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
  172. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
  173. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
  174. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
  175. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
  176. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +131 -0
  177. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
  178. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +93 -0
  179. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
  180. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
  181. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_auth_endpoints.py.j2 +307 -0
  182. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +262 -0
  183. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_scheduler_endpoints.py.j2 +214 -0
  184. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_worker_endpoints.py.j2 +165 -0
  185. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_ai_rendering.py +427 -0
  186. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_conversation_memory.py +465 -0
  187. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +43 -0
  188. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +195 -0
  189. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
  190. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/__init__.py +1 -0
  191. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/conftest.py +78 -0
  192. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_health.py +157 -0
  193. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_models.py +164 -0
  194. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_service.py +198 -0
  195. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_auth_integration.py.j2 +528 -0
  196. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +387 -0
  197. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_conversation_persistence.py +342 -0
  198. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +663 -0
  199. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +619 -0
  200. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +603 -0
  201. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_middleware_inspector.py.j2 +248 -0
  202. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_scheduled_task_manager.py.j2 +292 -0
  203. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +98 -0
  204. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +257 -0
  205. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +49 -0
  206. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
  207. aegis/templates/copier-aegis-project/{{ project_slug }}/.copier-answers.yml.jinja +21 -0
  208. aegis/templates/copier-aegis-project/{{ project_slug }}/.dockerignore +71 -0
  209. aegis/templates/copier-aegis-project/{{ project_slug }}/.env.example.jinja +130 -0
  210. aegis/templates/copier-aegis-project/{{ project_slug }}/.gitignore +131 -0
  211. aegis/templates/copier-aegis-project/{{ project_slug }}/Dockerfile +53 -0
  212. aegis/templates/copier-aegis-project/{{ project_slug }}/Makefile.jinja +236 -0
  213. aegis/templates/copier-aegis-project/{{ project_slug }}/README.md.jinja +196 -0
  214. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/alembic.ini.jinja +111 -0
  215. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/env.py.jinja +91 -0
  216. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/script.py.mako +25 -0
  217. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/versions/001_initial_auth.py.jinja +51 -0
  218. aegis/templates/copier-aegis-project/{{ project_slug }}/app/__init__.py.jinja +5 -0
  219. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/__init__.py.jinja +6 -0
  220. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja +700 -0
  221. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai_rendering.py +360 -0
  222. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/auth.py.jinja +253 -0
  223. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/health.py.jinja +419 -0
  224. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/load_test.py.jinja +656 -0
  225. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/main.py.jinja +65 -0
  226. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/marko_terminal_renderer.py +489 -0
  227. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/tasks.py.jinja +328 -0
  228. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/__init__.py +0 -0
  229. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/__init__.py +0 -0
  230. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/__init__.py +8 -0
  231. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/router.py +329 -0
  232. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/__init__.py +1 -0
  233. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/router.py +64 -0
  234. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/deps.py +58 -0
  235. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/health.py.jinja +163 -0
  236. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/models.py.jinja +280 -0
  237. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/routing.py.jinja +32 -0
  238. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/scheduler.py.jinja +121 -0
  239. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/worker.py.jinja +478 -0
  240. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/hooks.py +144 -0
  241. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/main.py +31 -0
  242. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/__init__.py +1 -0
  243. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/cors.py +20 -0
  244. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/__init__.py +1 -0
  245. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/cleanup.py +14 -0
  246. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/__init__.py +1 -0
  247. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/component_health.py.jinja +418 -0
  248. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/database_init.py.jinja +83 -0
  249. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/__init__.py +5 -0
  250. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/__init__.py +27 -0
  251. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/table.py +78 -0
  252. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/text.py +142 -0
  253. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/__init__.py.jinja +47 -0
  254. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
  255. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
  256. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/base_card.py +256 -0
  257. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
  258. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
  259. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/database_card.py +420 -0
  260. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
  261. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
  262. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
  263. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
  264. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/services_card.py +233 -0
  265. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
  266. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/main.py.jinja +653 -0
  267. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/theme.py +48 -0
  268. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/__init__.py +1 -0
  269. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/main.py.jinja +156 -0
  270. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/CLAUDE.md.jinja +213 -0
  271. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/__init__.py +6 -0
  272. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/constants.py.jinja +30 -0
  273. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/pools.py +97 -0
  274. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/__init__.py +1 -0
  275. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/load_test.py +55 -0
  276. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/media.py +49 -0
  277. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/system.py +44 -0
  278. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/registry.py +139 -0
  279. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/__init__.py +120 -0
  280. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/load_tasks.py +507 -0
  281. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/simple_system_tasks.py +33 -0
  282. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/system_tasks.py +281 -0
  283. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/config.py.jinja +178 -0
  284. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/constants.py +58 -0
  285. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/db.py.jinja +176 -0
  286. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/log.py +92 -0
  287. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/security.py +62 -0
  288. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/__init__.py +1 -0
  289. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/scheduler.py.jinja +21 -0
  290. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/webserver.py +39 -0
  291. aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/__init__.py +0 -0
  292. aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/main.py +61 -0
  293. aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/__init__.py +1 -0
  294. aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/user.py +44 -0
  295. aegis/templates/copier-aegis-project/{{ project_slug }}/app/py.typed +0 -0
  296. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/__init__.py +1 -0
  297. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/__init__.py +8 -0
  298. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/config.py +130 -0
  299. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/conversation.py +213 -0
  300. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/health.py +96 -0
  301. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/models.py +229 -0
  302. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/providers.py.jinja +370 -0
  303. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/service.py +387 -0
  304. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/__init__.py +1 -0
  305. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/auth_service.py +40 -0
  306. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/health.py +162 -0
  307. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/user_service.py +82 -0
  308. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/middleware_inspector.py.jinja +223 -0
  309. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/models.py.jinja +70 -0
  310. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/route_inspector.py.jinja +155 -0
  311. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test.py +678 -0
  312. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test_models.py +265 -0
  313. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/__init__.py.jinja +21 -0
  314. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/models.py.jinja +119 -0
  315. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/scheduled_task_manager.py.jinja +273 -0
  316. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/task_monitor.py.jinja +189 -0
  317. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/__init__.py +15 -0
  318. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/models.py +26 -0
  319. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/__init__.py +52 -0
  320. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/alerts.py +94 -0
  321. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/backup.py.jinja +119 -0
  322. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/health.py.jinja +1333 -0
  323. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/models.py +243 -0
  324. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/ui.py +52 -0
  325. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57223!aegis-manifesto.png +0 -0
  326. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57224!aegis-manifesto-dark.png +0 -0
  327. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57225!aegis-manifesto-square-backup.png +0 -0
  328. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57533!aegis-manifesto.png +0 -0
  329. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57534!aegis-manifesto-dark.png +0 -0
  330. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57538!aegis-manifesto-square-backup.png +0 -0
  331. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57897!aegis-manifesto.png +0 -0
  332. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57898!aegis-manifesto-dark.png +0 -0
  333. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57904!aegis-manifesto-square-backup.png +0 -0
  334. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58315!aegis-manifesto.png +0 -0
  335. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58316!aegis-manifesto-dark.png +0 -0
  336. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58324!aegis-manifesto-square-backup.png +0 -0
  337. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58837!aegis-manifesto.png +0 -0
  338. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58838!aegis-manifesto-dark.png +0 -0
  339. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58849!aegis-manifesto-square-backup.png +0 -0
  340. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-dark.png +0 -0
  341. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-square-backup.png +0 -0
  342. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto.png +0 -0
  343. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/.env.example.jinja +64 -0
  344. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/README.md.jinja +172 -0
  345. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/docker-compose.yml.jinja +78 -0
  346. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/mkdocs.yml.jinja +62 -0
  347. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/pyproject.toml.jinja +120 -0
  348. aegis/templates/copier-aegis-project/{{ project_slug }}/docker-compose.yml.jinja +200 -0
  349. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/api.md.jinja +191 -0
  350. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md +0 -0
  351. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md.jinja +621 -0
  352. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/development.md.jinja +215 -0
  353. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/health.md.jinja +240 -0
  354. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/javascripts/mermaid-config.js +62 -0
  355. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/stylesheets/mermaid.css +95 -0
  356. aegis/templates/copier-aegis-project/{{ project_slug }}/mkdocs.yml.jinja +62 -0
  357. aegis/templates/copier-aegis-project/{{ project_slug }}/pyproject.toml.jinja +131 -0
  358. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh +87 -0
  359. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh.jinja +93 -0
  360. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/gen_docs.py +16 -0
  361. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/__init__.py +1 -0
  362. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_auth_endpoints.py.jinja +307 -0
  363. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_health_endpoints.py.jinja +262 -0
  364. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_scheduler_endpoints.py.jinja +214 -0
  365. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_worker_endpoints.py.jinja +165 -0
  366. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_ai_rendering.py +427 -0
  367. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_conversation_memory.py +465 -0
  368. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/components/test_scheduler.py +43 -0
  369. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/conftest.py.jinja +195 -0
  370. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/__init__.py +1 -0
  371. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/__init__.py +1 -0
  372. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/conftest.py +78 -0
  373. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_health.py +157 -0
  374. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_models.py +164 -0
  375. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_service.py +198 -0
  376. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_auth_integration.py.jinja +528 -0
  377. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_component_integration.py.jinja +387 -0
  378. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_conversation_persistence.py +342 -0
  379. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_health_logic.py.jinja +663 -0
  380. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_models.py +619 -0
  381. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_service.py +603 -0
  382. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_middleware_inspector.py.jinja +248 -0
  383. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_scheduled_task_manager.py.jinja +292 -0
  384. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_system_service.py +98 -0
  385. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_worker_health_registration.py.jinja +257 -0
  386. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/test_core.py +49 -0
  387. aegis/templates/copier-aegis-project/{{ project_slug }}/uv.lock +1673 -0
  388. aegis_stack-0.2.0rc2.dist-info/METADATA +165 -0
  389. aegis_stack-0.2.0rc2.dist-info/RECORD +392 -0
  390. aegis_stack-0.2.0rc2.dist-info/WHEEL +4 -0
  391. aegis_stack-0.2.0rc2.dist-info/entry_points.txt +3 -0
  392. aegis_stack-0.2.0rc2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,679 @@
1
+ """
2
+ Load testing service module.
3
+
4
+ This module provides business logic for orchestrating and analyzing load tests,
5
+ separating concerns from API endpoints and worker tasks.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from pydantic import ValidationError
11
+
12
+ from app.components.worker.constants import LoadTestTypes
13
+ from app.components.worker.pools import get_queue_pool
14
+ from app.core.config import get_load_test_queue
15
+ from app.core.log import logger
16
+ from app.services.load_test_models import (
17
+ LoadTestConfiguration,
18
+ LoadTestMetrics,
19
+ LoadTestResult,
20
+ OrchestratorRawResult,
21
+ PerformanceAnalysis,
22
+ TestTypeInfo,
23
+ ValidationStatus,
24
+ )
25
+
26
+ __all__ = [
27
+ "LoadTestConfiguration",
28
+ "LoadTestService",
29
+ "quick_cpu_test",
30
+ "quick_io_test",
31
+ "quick_memory_test",
32
+ ]
33
+
34
+
35
+ class LoadTestService:
36
+ """Service for managing load test operations."""
37
+
38
+ @staticmethod
39
+ def get_test_type_info(test_type: LoadTestTypes | str) -> dict[str, Any]:
40
+ """Get detailed information about a specific test type."""
41
+ test_info = {
42
+ LoadTestTypes.CPU_INTENSIVE: {
43
+ "name": "CPU Intensive",
44
+ "description": (
45
+ "Tests worker CPU processing with fibonacci calculations"
46
+ ),
47
+ "expected_metrics": [
48
+ "fibonacci_n",
49
+ "fibonacci_result",
50
+ "cpu_operations",
51
+ ],
52
+ "performance_signature": (
53
+ "CPU bound - should show computation time scaling with problem size"
54
+ ),
55
+ "typical_duration_ms": "1-10ms per task",
56
+ "concurrency_impact": (
57
+ "Limited by CPU cores, benefits from parallel processing"
58
+ ),
59
+ "validation_keys": ["fibonacci_n", "fibonacci_result"],
60
+ },
61
+ LoadTestTypes.IO_SIMULATION: {
62
+ "name": "I/O Simulation",
63
+ "description": "Tests async I/O handling with simulated delays",
64
+ "expected_metrics": [
65
+ "simulated_delay_ms",
66
+ "io_operations",
67
+ "async_operations",
68
+ ],
69
+ "performance_signature": (
70
+ "I/O bound - should show async concurrency benefits"
71
+ ),
72
+ "typical_duration_ms": ("5-30ms per task (includes simulated delays)"),
73
+ "concurrency_impact": (
74
+ "Excellent with async - many tasks can run concurrently"
75
+ ),
76
+ "validation_keys": ["simulated_delay_ms", "io_operations"],
77
+ },
78
+ LoadTestTypes.MEMORY_OPERATIONS: {
79
+ "name": "Memory Operations",
80
+ "description": "Tests memory allocation and data structure operations",
81
+ "expected_metrics": [
82
+ "allocation_size",
83
+ "list_sum",
84
+ "dict_keys",
85
+ "max_value",
86
+ ],
87
+ "performance_signature": (
88
+ "Memory bound - should show allocation/deallocation patterns"
89
+ ),
90
+ "typical_duration_ms": "1-5ms per task",
91
+ "concurrency_impact": (
92
+ "Moderate - limited by memory bandwidth and GC pressure"
93
+ ),
94
+ "validation_keys": [
95
+ "allocation_size",
96
+ "list_sum",
97
+ "dict_keys",
98
+ ],
99
+ },
100
+ LoadTestTypes.FAILURE_TESTING: {
101
+ "name": "Failure Testing",
102
+ "description": "Tests error handling with ~20% random failures",
103
+ "expected_metrics": ["failure_rate", "error_types"],
104
+ "performance_signature": (
105
+ "Mixed - tests resilience and error handling paths"
106
+ ),
107
+ "typical_duration_ms": "1-10ms per task (when successful)",
108
+ "concurrency_impact": ("Tests worker recovery and error isolation"),
109
+ "validation_keys": ["status"],
110
+ },
111
+ }
112
+ return test_info.get(test_type, {})
113
+
114
+ @staticmethod
115
+ async def enqueue_load_test(config: LoadTestConfiguration) -> str:
116
+ """
117
+ Enqueue a load test orchestrator task.
118
+
119
+ Args:
120
+ config: Load test configuration
121
+
122
+ Returns:
123
+ Task ID for the orchestrator job
124
+ """
125
+ from app.components.worker.pools import get_queue_pool
126
+
127
+ logger.info(
128
+ f"🚀 Enqueueing load test: {config.num_tasks} {config.task_type} tasks"
129
+ )
130
+
131
+ # Get appropriate queue pool
132
+ pool, queue_name = await get_queue_pool(config.target_queue)
133
+
134
+ try:
135
+ # Enqueue the orchestrator task with enum preserved
136
+ job = await pool.enqueue_job(
137
+ "load_test_orchestrator",
138
+ _queue_name=queue_name,
139
+ num_tasks=config.num_tasks,
140
+ task_type=config.task_type, # Preserve enum instead of serializing
141
+ batch_size=config.batch_size,
142
+ delay_ms=config.delay_ms,
143
+ target_queue=config.target_queue,
144
+ )
145
+
146
+ await pool.aclose()
147
+
148
+ if job is None:
149
+ raise RuntimeError("Failed to enqueue job - returned None")
150
+
151
+ logger.info(f"✅ Load test orchestrator enqueued: {job.job_id}")
152
+ return str(job.job_id)
153
+
154
+ except Exception as e:
155
+ await pool.aclose()
156
+ logger.error(f"❌ Failed to enqueue load test: {e}")
157
+ raise
158
+
159
+ @staticmethod
160
+ async def get_load_test_result(
161
+ task_id: str, target_queue: str | None = None
162
+ ) -> dict[str, Any] | None:
163
+ """
164
+ Retrieve and analyze load test results.
165
+
166
+ Args:
167
+ task_id: The orchestrator task ID
168
+ target_queue: Queue where the test was run (defaults to configured
169
+ load_test queue)
170
+
171
+ Returns:
172
+ Analyzed load test results or None if not found
173
+ """
174
+ # Use configured load test queue if not specified
175
+ if target_queue is None:
176
+ target_queue = get_load_test_queue()
177
+
178
+ pool = None
179
+ try:
180
+ pool, _ = await get_queue_pool(target_queue)
181
+ # Check if result exists
182
+ result_key = f"arq:result:{task_id}"
183
+ result_exists = await pool.exists(result_key)
184
+
185
+ if not result_exists:
186
+ return None
187
+
188
+ # Get the result data
189
+ result_data = await pool.get(result_key)
190
+ if not result_data:
191
+ return None
192
+
193
+ # Deserialize the result
194
+ import pickle
195
+
196
+ result = pickle.loads(result_data)
197
+
198
+ # Handle different result formats
199
+ if isinstance(result, Exception):
200
+ # Task failed completely
201
+ return {
202
+ "task": "load_test_orchestrator",
203
+ "status": "failed",
204
+ "error": str(result),
205
+ "test_id": task_id,
206
+ }
207
+ elif isinstance(result, dict):
208
+ # Check if it's a direct load test result
209
+ if result.get("task") == "load_test_orchestrator":
210
+ analyzed_result = LoadTestService._analyze_load_test_result(result)
211
+ return analyzed_result.model_dump()
212
+ # Check if it's an arq job result with embedded data
213
+ elif "r" in result and isinstance(result["r"], dict):
214
+ # Extract the actual result
215
+ actual_result = result["r"]
216
+ # Check if this looks like a load test orchestrator result
217
+ if (
218
+ "test_id" in actual_result
219
+ and "task_type" in actual_result
220
+ and "tasks_sent" in actual_result
221
+ ):
222
+ try:
223
+ # Validate and transform using Pydantic models
224
+ orchestrator_result = OrchestratorRawResult(**actual_result)
225
+ load_test_result = orchestrator_result.to_load_test_result()
226
+ analyzed_result = LoadTestService._analyze_load_test_result(
227
+ load_test_result
228
+ )
229
+ return analyzed_result.model_dump()
230
+ except ValidationError as e:
231
+ logger.error(f"Failed to validate orchestrator result: {e}")
232
+ # Fall back to manual transformation if validation fails
233
+ transformed_result = (
234
+ LoadTestService._transform_orchestrator_result(
235
+ actual_result
236
+ )
237
+ )
238
+ analyzed_result = LoadTestService._analyze_load_test_result(
239
+ transformed_result
240
+ )
241
+ return analyzed_result.model_dump()
242
+ elif actual_result.get("task") == "load_test_orchestrator":
243
+ analyzed_result = LoadTestService._analyze_load_test_result(
244
+ actual_result
245
+ )
246
+ return analyzed_result.model_dump()
247
+ elif "r" in result and isinstance(result["r"], Exception):
248
+ # Task timed out or failed
249
+ return {
250
+ "task": "load_test_orchestrator",
251
+ "status": "timed_out",
252
+ "error": str(result["r"]),
253
+ "test_id": task_id,
254
+ "partial_info": (
255
+ "Task may have completed work but timed out at "
256
+ "orchestrator level"
257
+ ),
258
+ }
259
+
260
+ # result is already dict[str, Any] at this point
261
+ return result # type: ignore[no-any-return]
262
+
263
+ except Exception as e:
264
+ logger.error(f"Failed to get load test result for {task_id}: {e}")
265
+ return None
266
+ finally:
267
+ if pool is not None:
268
+ await pool.aclose()
269
+
270
+ @staticmethod
271
+ def _transform_orchestrator_result(
272
+ orchestrator_result: dict[str, Any],
273
+ ) -> dict[str, Any]:
274
+ """Transform orchestrator result to expected analysis format."""
275
+
276
+ # Create the configuration object from orchestrator parameters
277
+ configuration = {
278
+ "task_type": orchestrator_result.get("task_type", "unknown"),
279
+ "num_tasks": orchestrator_result.get("tasks_sent", 0),
280
+ "batch_size": orchestrator_result.get("batch_size", 0),
281
+ "delay_ms": orchestrator_result.get("delay_ms", 0),
282
+ "target_queue": orchestrator_result.get("target_queue", "unknown"),
283
+ }
284
+
285
+ # Create the metrics object from orchestrator result data
286
+ metrics = {
287
+ "tasks_sent": orchestrator_result.get("tasks_sent", 0),
288
+ "tasks_completed": orchestrator_result.get("tasks_completed", 0),
289
+ "tasks_failed": orchestrator_result.get("tasks_failed", 0),
290
+ "total_duration_seconds": orchestrator_result.get(
291
+ "total_duration_seconds", 0
292
+ ),
293
+ "overall_throughput": orchestrator_result.get(
294
+ "overall_throughput_per_second", 0
295
+ ),
296
+ "failure_rate_percent": orchestrator_result.get("failure_rate_percent", 0),
297
+ "completion_percentage": orchestrator_result.get(
298
+ "completion_percentage", 0
299
+ ),
300
+ "average_throughput_per_second": orchestrator_result.get(
301
+ "average_throughput_per_second", 0
302
+ ),
303
+ "monitor_duration_seconds": orchestrator_result.get(
304
+ "monitor_duration_seconds", 0
305
+ ),
306
+ }
307
+
308
+ # Create the transformed result
309
+ transformed = {
310
+ "task": "load_test_orchestrator",
311
+ "status": "completed",
312
+ "test_id": orchestrator_result.get("test_id", "unknown"),
313
+ "configuration": configuration,
314
+ "metrics": metrics,
315
+ "start_time": orchestrator_result.get("start_time"),
316
+ "end_time": orchestrator_result.get("end_time"),
317
+ "task_ids": orchestrator_result.get("task_ids", []),
318
+ }
319
+
320
+ return transformed
321
+
322
+ @staticmethod
323
+ def _analyze_load_test_result(
324
+ result: LoadTestResult | dict[str, Any],
325
+ ) -> LoadTestResult:
326
+ """Add analysis and validation to load test results."""
327
+
328
+ # Convert dict to model if needed
329
+ if isinstance(result, dict):
330
+ try:
331
+ result = LoadTestResult(**result)
332
+ except ValidationError as e:
333
+ logger.error(f"Failed to validate result as LoadTestResult: {e}")
334
+ # Return a basic error result
335
+ return LoadTestResult(
336
+ status="failed",
337
+ test_id=(
338
+ result.get("test_id", "unknown")
339
+ if isinstance(result, dict)
340
+ else "unknown"
341
+ ),
342
+ configuration=LoadTestConfiguration(
343
+ task_type=LoadTestTypes.CPU_INTENSIVE, # Safe default enum
344
+ num_tasks=10, # Minimum valid value
345
+ batch_size=1,
346
+ delay_ms=0,
347
+ target_queue="unknown",
348
+ ),
349
+ metrics=LoadTestMetrics(
350
+ tasks_sent=0,
351
+ tasks_completed=0,
352
+ tasks_failed=0,
353
+ total_duration_seconds=0.0,
354
+ overall_throughput=0.0,
355
+ failure_rate_percent=0.0,
356
+ completion_percentage=0.0,
357
+ average_throughput_per_second=0.0,
358
+ monitor_duration_seconds=0.0,
359
+ ),
360
+ start_time=None,
361
+ end_time=None,
362
+ error=f"Validation failed: {e}",
363
+ analysis=None,
364
+ )
365
+
366
+ # Verify result is LoadTestResult and handle unexpected types
367
+ if not isinstance(result, LoadTestResult):
368
+ logger.error(f"Expected LoadTestResult but got {type(result)}")
369
+ return LoadTestResult(
370
+ status="failed",
371
+ test_id="unknown",
372
+ configuration=LoadTestConfiguration(
373
+ task_type=LoadTestTypes.CPU_INTENSIVE,
374
+ num_tasks=10,
375
+ batch_size=1,
376
+ delay_ms=0,
377
+ target_queue="unknown",
378
+ ),
379
+ metrics=LoadTestMetrics(
380
+ tasks_sent=0,
381
+ tasks_completed=0,
382
+ tasks_failed=0,
383
+ total_duration_seconds=0.0,
384
+ overall_throughput=0.0,
385
+ failure_rate_percent=0.0,
386
+ completion_percentage=0.0,
387
+ average_throughput_per_second=0.0,
388
+ monitor_duration_seconds=0.0,
389
+ ),
390
+ start_time=None,
391
+ end_time=None,
392
+ error=f"Unexpected result type: {type(result)}",
393
+ analysis=None,
394
+ )
395
+
396
+ task_type = result.configuration.task_type
397
+
398
+ # Get expected characteristics for this test type
399
+ # Validate task type against known types
400
+ if task_type not in [
401
+ LoadTestTypes.CPU_INTENSIVE,
402
+ LoadTestTypes.IO_SIMULATION,
403
+ LoadTestTypes.MEMORY_OPERATIONS,
404
+ LoadTestTypes.FAILURE_TESTING,
405
+ ]:
406
+ task_type = LoadTestTypes.CPU_INTENSIVE # Default fallback
407
+
408
+ test_info_dict = LoadTestService.get_test_type_info(task_type)
409
+ test_info = TestTypeInfo(**test_info_dict)
410
+
411
+ # Create analysis components
412
+ performance_analysis = LoadTestService._analyze_performance_pydantic(result)
413
+ validation_status = LoadTestService._validate_test_execution_pydantic(
414
+ result, test_info
415
+ )
416
+ recommendations = LoadTestService._generate_recommendations_pydantic(result)
417
+
418
+ # Add analysis to result
419
+ from app.services.load_test_models import LoadTestAnalysis
420
+
421
+ analysis = LoadTestAnalysis(
422
+ test_type_info=test_info,
423
+ performance_analysis=performance_analysis,
424
+ validation_status=validation_status,
425
+ recommendations=recommendations,
426
+ )
427
+
428
+ result.analysis = analysis
429
+ return result
430
+
431
+ @staticmethod
432
+ def _analyze_performance(result: dict[str, Any]) -> dict[str, Any]:
433
+ """Analyze performance characteristics of the load test."""
434
+ metrics = result.get("metrics", {})
435
+
436
+ analysis = {
437
+ "throughput_rating": "unknown",
438
+ "efficiency_rating": "unknown",
439
+ "queue_pressure": "unknown",
440
+ }
441
+
442
+ # Analyze throughput
443
+ throughput = metrics.get("overall_throughput", 0)
444
+ if throughput >= 50:
445
+ analysis["throughput_rating"] = "excellent"
446
+ elif throughput >= 20:
447
+ analysis["throughput_rating"] = "good"
448
+ elif throughput >= 10:
449
+ analysis["throughput_rating"] = "fair"
450
+ else:
451
+ analysis["throughput_rating"] = "poor"
452
+
453
+ # Analyze efficiency (completion rate)
454
+ tasks_sent = metrics.get("tasks_sent", 1)
455
+ tasks_completed = metrics.get("tasks_completed", 0)
456
+ completion_rate = (tasks_completed / tasks_sent) * 100 if tasks_sent > 0 else 0
457
+
458
+ if completion_rate >= 95:
459
+ analysis["efficiency_rating"] = "excellent"
460
+ elif completion_rate >= 90:
461
+ analysis["efficiency_rating"] = "good"
462
+ elif completion_rate >= 80:
463
+ analysis["efficiency_rating"] = "fair"
464
+ else:
465
+ analysis["efficiency_rating"] = "poor"
466
+
467
+ # Analyze queue pressure (based on duration vs expected)
468
+ duration = metrics.get("total_duration_seconds", 0)
469
+ if duration > 60:
470
+ analysis["queue_pressure"] = "high"
471
+ elif duration > 30:
472
+ analysis["queue_pressure"] = "medium"
473
+ else:
474
+ analysis["queue_pressure"] = "low"
475
+
476
+ return analysis
477
+
478
+ @staticmethod
479
+ def _validate_test_execution(
480
+ result: dict[str, Any], test_info: dict[str, Any]
481
+ ) -> dict[str, Any]:
482
+ """Validate that the test executed as expected."""
483
+ validation: dict[str, Any] = {
484
+ "test_type_verified": False,
485
+ "expected_metrics_present": False,
486
+ "performance_signature_match": "unknown",
487
+ "issues": [],
488
+ }
489
+
490
+ # This would need actual task result inspection to verify test type
491
+ # For now, we assume the test executed correctly if it completed
492
+ status = result.get("status", "unknown")
493
+ if status == "completed":
494
+ validation["test_type_verified"] = True
495
+ validation["expected_metrics_present"] = True
496
+ validation["performance_signature_match"] = "verified"
497
+ else:
498
+ validation["issues"].append(f"Test status: {status}")
499
+
500
+ return validation
501
+
502
+ @staticmethod
503
+ def _generate_recommendations(result: dict[str, Any]) -> list[str]:
504
+ """Generate recommendations based on test results."""
505
+ recommendations = []
506
+
507
+ metrics = result.get("metrics", {})
508
+ throughput = metrics.get("overall_throughput", 0)
509
+ failure_rate = metrics.get("failure_rate_percent", 0)
510
+
511
+ if throughput < 10:
512
+ recommendations.append(
513
+ "Low throughput detected. Consider reducing task complexity or "
514
+ "increasing worker concurrency."
515
+ )
516
+
517
+ if failure_rate > 5:
518
+ recommendations.append(
519
+ f"High failure rate ({failure_rate:.1f}%). Check worker logs "
520
+ f"for error patterns."
521
+ )
522
+
523
+ duration = metrics.get("total_duration_seconds", 0)
524
+ tasks_sent = metrics.get("tasks_sent", 1)
525
+
526
+ if duration > 60 and tasks_sent < 200:
527
+ recommendations.append(
528
+ "Long execution time for relatively few tasks suggests queue "
529
+ "saturation. Consider testing with smaller batches or "
530
+ "different queues."
531
+ )
532
+
533
+ return recommendations
534
+
535
+ @staticmethod
536
+ def _analyze_performance_pydantic(result: LoadTestResult) -> PerformanceAnalysis:
537
+ """Analyze performance characteristics using Pydantic models."""
538
+
539
+ # Analyze throughput
540
+ throughput = result.metrics.overall_throughput
541
+ if throughput >= 50:
542
+ throughput_rating = "excellent"
543
+ elif throughput >= 20:
544
+ throughput_rating = "good"
545
+ elif throughput >= 10:
546
+ throughput_rating = "fair"
547
+ else:
548
+ throughput_rating = "poor"
549
+
550
+ # Analyze efficiency (completion rate)
551
+ tasks_sent = result.metrics.tasks_sent
552
+ tasks_completed = result.metrics.tasks_completed
553
+ completion_rate = (tasks_completed / tasks_sent) * 100 if tasks_sent > 0 else 0
554
+
555
+ if completion_rate >= 95:
556
+ efficiency_rating = "excellent"
557
+ elif completion_rate >= 90:
558
+ efficiency_rating = "good"
559
+ elif completion_rate >= 80:
560
+ efficiency_rating = "fair"
561
+ else:
562
+ efficiency_rating = "poor"
563
+
564
+ # Analyze queue pressure (based on duration vs expected)
565
+ duration = result.metrics.total_duration_seconds
566
+ if duration > 60:
567
+ queue_pressure = "high"
568
+ elif duration > 30:
569
+ queue_pressure = "medium"
570
+ else:
571
+ queue_pressure = "low"
572
+
573
+ return PerformanceAnalysis(
574
+ throughput_rating=throughput_rating,
575
+ efficiency_rating=efficiency_rating,
576
+ queue_pressure=queue_pressure,
577
+ )
578
+
579
+ @staticmethod
580
+ def _validate_test_execution_pydantic(
581
+ result: LoadTestResult, test_info: TestTypeInfo
582
+ ) -> ValidationStatus:
583
+ """Validate test execution using Pydantic models."""
584
+
585
+ issues = []
586
+
587
+ # Basic validation - if we got here, the test at least completed
588
+ test_type_verified = result.status == "completed"
589
+ expected_metrics_present = result.status == "completed"
590
+
591
+ if result.status == "completed":
592
+ performance_signature_match = "verified"
593
+ else:
594
+ performance_signature_match = "unknown"
595
+ issues.append(f"Test status: {result.status}")
596
+
597
+ # Additional validation based on metrics
598
+ if result.metrics.tasks_completed == 0 and result.metrics.tasks_sent > 0:
599
+ issues.append("No tasks completed despite tasks being sent")
600
+
601
+ if result.metrics.failure_rate_percent > 50:
602
+ issues.append(
603
+ f"High failure rate: {result.metrics.failure_rate_percent:.1f}%"
604
+ )
605
+
606
+ return ValidationStatus(
607
+ test_type_verified=test_type_verified,
608
+ expected_metrics_present=expected_metrics_present,
609
+ performance_signature_match=performance_signature_match,
610
+ issues=issues,
611
+ )
612
+
613
+ @staticmethod
614
+ def _generate_recommendations_pydantic(result: LoadTestResult) -> list[str]:
615
+ """Generate recommendations using Pydantic models."""
616
+
617
+ recommendations = []
618
+
619
+ throughput = result.metrics.overall_throughput
620
+ failure_rate = result.metrics.failure_rate_percent
621
+
622
+ if throughput < 10:
623
+ recommendations.append(
624
+ "Low throughput detected. Consider reducing task complexity "
625
+ "or increasing worker concurrency."
626
+ )
627
+
628
+ if failure_rate > 5:
629
+ recommendations.append(
630
+ f"High failure rate ({failure_rate:.1f}%). Check worker logs "
631
+ f"for error patterns."
632
+ )
633
+
634
+ duration = result.metrics.total_duration_seconds
635
+ tasks_sent = result.metrics.tasks_sent
636
+
637
+ if duration > 60 and tasks_sent < 200:
638
+ recommendations.append(
639
+ "Long execution time for relatively few tasks suggests queue "
640
+ "saturation. Consider testing with smaller batches or "
641
+ "different queues."
642
+ )
643
+
644
+ return recommendations
645
+
646
+
647
+ # Convenience functions for common load test patterns
648
+ async def quick_cpu_test(num_tasks: int = 50) -> str:
649
+ """Quick CPU load test with sensible defaults."""
650
+ config = LoadTestConfiguration(
651
+ num_tasks=num_tasks,
652
+ task_type=LoadTestTypes.CPU_INTENSIVE,
653
+ batch_size=10,
654
+ target_queue=get_load_test_queue(),
655
+ )
656
+ return await LoadTestService.enqueue_load_test(config)
657
+
658
+
659
+ async def quick_io_test(num_tasks: int = 100) -> str:
660
+ """Quick I/O load test with sensible defaults."""
661
+ config = LoadTestConfiguration(
662
+ num_tasks=num_tasks,
663
+ task_type=LoadTestTypes.IO_SIMULATION,
664
+ batch_size=20,
665
+ delay_ms=50,
666
+ target_queue=get_load_test_queue(),
667
+ )
668
+ return await LoadTestService.enqueue_load_test(config)
669
+
670
+
671
+ async def quick_memory_test(num_tasks: int = 200) -> str:
672
+ """Quick memory load test with sensible defaults."""
673
+ config = LoadTestConfiguration(
674
+ num_tasks=num_tasks,
675
+ task_type=LoadTestTypes.MEMORY_OPERATIONS,
676
+ batch_size=25,
677
+ target_queue=get_load_test_queue(),
678
+ )
679
+ return await LoadTestService.enqueue_load_test(config)