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