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,656 @@
1
+ """
2
+ Load testing CLI commands.
3
+
4
+ Provides command-line interface for running and managing load tests,
5
+ with full parameter configuration and result analysis.
6
+ """
7
+
8
+ import asyncio
9
+ from enum import Enum
10
+ import json
11
+ import os
12
+ import time
13
+ from typing import Any
14
+
15
+ from rich import print as rprint
16
+ from rich.console import Console
17
+ from rich.panel import Panel
18
+ from rich.progress import Progress, SpinnerColumn, TextColumn
19
+ from rich.table import Table
20
+ import typer
21
+
22
+ from app.core.config import settings
23
+
24
+ from app.components.worker.constants import LoadTestTypes
25
+ from app.core.config import get_load_test_queue
26
+ from app.core.log import logger
27
+ from app.services.load_test import (
28
+ LoadTestConfiguration,
29
+ LoadTestService,
30
+ quick_cpu_test,
31
+ quick_io_test,
32
+ quick_memory_test,
33
+ )
34
+
35
+ app = typer.Typer(
36
+ name="load-test",
37
+ help="Load testing commands for worker performance analysis",
38
+ no_args_is_help=True,
39
+ )
40
+
41
+ console = Console()
42
+
43
+ # Force CLI to use local Redis URL (from REDIS_URL_LOCAL config)
44
+ # This ensures CLI commands work when run locally outside Docker
45
+ if settings.REDIS_URL_LOCAL:
46
+ os.environ["REDIS_URL"] = settings.REDIS_URL_LOCAL
47
+
48
+
49
+ class QueueChoice(str, Enum):
50
+ """Available queue types for load testing."""
51
+
52
+ load_test = "load_test"
53
+ system = "system" # Legacy option
54
+ media = "media" # Legacy option
55
+
56
+ @classmethod
57
+ def get_default(cls) -> str:
58
+ """Get the default queue from config."""
59
+ return get_load_test_queue()
60
+
61
+
62
+ @app.command("run")
63
+ def run_load_test(
64
+ num_tasks: int = typer.Option(
65
+ 100, "--tasks", "-n", help="Number of tasks to spawn", min=10, max=10000
66
+ ),
67
+ task_type: LoadTestTypes = typer.Option(
68
+ LoadTestTypes.CPU_INTENSIVE, "--type", "-t", help="Type of load test to run"
69
+ ),
70
+ batch_size: int = typer.Option(
71
+ 10, "--batch", "-b", help="Tasks per batch", min=1, max=100
72
+ ),
73
+ delay_ms: int = typer.Option(
74
+ 0, "--delay", "-d", help="Delay between batches (ms)", min=0, max=5000
75
+ ),
76
+ target_queue: QueueChoice = typer.Option(
77
+ QueueChoice.load_test, "--queue", "-q", help="Target queue for testing"
78
+ ),
79
+ wait: bool = typer.Option(
80
+ True, "--wait/--no-wait", help="Wait for test completion and show results"
81
+ ),
82
+ timeout: int = typer.Option(
83
+ 600, "--timeout", help="Timeout for waiting (seconds)", min=10, max=3600
84
+ ),
85
+ ) -> None:
86
+ """
87
+ Run a customizable load test with specified parameters.
88
+
89
+ This command allows full control over load test configuration including
90
+ task count, type, batching, and queue targeting.
91
+ """
92
+
93
+ config = LoadTestConfiguration(
94
+ num_tasks=num_tasks,
95
+ task_type=task_type,
96
+ batch_size=batch_size,
97
+ delay_ms=delay_ms,
98
+ target_queue=target_queue.value,
99
+ )
100
+
101
+ # Display test configuration
102
+ _display_test_configuration(config, task_type.value)
103
+
104
+ # Enqueue the load test
105
+ rprint("šŸš€ [bold blue]Starting load test...[/bold blue]")
106
+
107
+ try:
108
+ task_id = asyncio.run(LoadTestService.enqueue_load_test(config))
109
+ rprint("āœ… [green]Load test enqueued successfully![/green]")
110
+ rprint(f"šŸ“‹ [cyan]Task ID:[/cyan] {task_id}")
111
+
112
+ if wait:
113
+ _wait_for_completion_and_display_results(
114
+ task_id, target_queue.value, timeout
115
+ )
116
+ else:
117
+ rprint("\nšŸ’” [yellow]Use this command to check results later:[/yellow]")
118
+ rprint(
119
+ f" [bold]{{ cookiecutter.project_slug }} load-test results "
120
+ f"{task_id}[/bold]"
121
+ )
122
+
123
+ except Exception as e:
124
+ rprint(f"āŒ [red]Failed to start load test:[/red] {e}")
125
+ raise typer.Exit(1)
126
+
127
+
128
+ @app.command("cpu")
129
+ def quick_cpu_test_cmd(
130
+ num_tasks: int = typer.Option(
131
+ 50, "--tasks", "-n", help="Number of CPU tasks", min=10, max=1000
132
+ ),
133
+ wait: bool = typer.Option(True, "--wait/--no-wait", help="Wait for completion"),
134
+ ) -> None:
135
+ """
136
+ Quick CPU-intensive load test with optimized defaults.
137
+
138
+ Tests computational performance with fibonacci calculations,
139
+ mathematical operations, and prime number checking.
140
+ """
141
+
142
+ rprint("šŸ–„ļø [bold blue]Quick CPU Load Test[/bold blue]")
143
+ rprint(f"šŸ“Š [cyan]Tasks:[/cyan] {num_tasks} CPU-intensive tasks")
144
+ rprint("šŸ”¢ [cyan]Work type:[/cyan] Fibonacci + math operations + prime checking")
145
+
146
+ try:
147
+ task_id = asyncio.run(quick_cpu_test(num_tasks))
148
+ rprint(f"āœ… [green]CPU test started![/green] Task ID: {task_id}")
149
+
150
+ if wait:
151
+ _wait_for_completion_and_display_results(
152
+ task_id, get_load_test_queue(), 600
153
+ )
154
+ else:
155
+ rprint(
156
+ f"šŸ’” [yellow]Check results:[/yellow] "
157
+ f"{{ cookiecutter.project_slug }} load-test results {task_id}"
158
+ )
159
+
160
+ except Exception as e:
161
+ rprint(f"āŒ [red]CPU test failed:[/red] {e}")
162
+ raise typer.Exit(1)
163
+
164
+
165
+ @app.command("io")
166
+ def quick_io_test_cmd(
167
+ num_tasks: int = typer.Option(
168
+ 100, "--tasks", "-n", help="Number of I/O tasks", min=10, max=1000
169
+ ),
170
+ wait: bool = typer.Option(True, "--wait/--no-wait", help="Wait for completion"),
171
+ ) -> None:
172
+ """
173
+ Quick I/O simulation load test with optimized defaults.
174
+
175
+ Tests async I/O performance with simulated network delays,
176
+ concurrent operations, and file I/O patterns.
177
+ """
178
+
179
+ rprint("šŸ’¾ [bold blue]Quick I/O Load Test[/bold blue]")
180
+ rprint(f"šŸ“Š [cyan]Tasks:[/cyan] {num_tasks} I/O simulation tasks")
181
+ rprint(
182
+ "🌐 [cyan]Work type:[/cyan] Network delays + concurrent async + file operations"
183
+ )
184
+
185
+ try:
186
+ task_id = asyncio.run(quick_io_test(num_tasks))
187
+ rprint(f"āœ… [green]I/O test started![/green] Task ID: {task_id}")
188
+
189
+ if wait:
190
+ _wait_for_completion_and_display_results(
191
+ task_id, get_load_test_queue(), 600
192
+ )
193
+ else:
194
+ rprint(
195
+ f"šŸ’” [yellow]Check results:[/yellow] "
196
+ f"{{ cookiecutter.project_slug }} load-test results {task_id}"
197
+ )
198
+
199
+ except Exception as e:
200
+ rprint(f"āŒ [red]I/O test failed:[/red] {e}")
201
+ raise typer.Exit(1)
202
+
203
+
204
+ @app.command("memory")
205
+ def quick_memory_test_cmd(
206
+ num_tasks: int = typer.Option(
207
+ 200, "--tasks", "-n", help="Number of memory tasks", min=10, max=1000
208
+ ),
209
+ wait: bool = typer.Option(True, "--wait/--no-wait", help="Wait for completion"),
210
+ ) -> None:
211
+ """
212
+ Quick memory allocation load test with optimized defaults.
213
+
214
+ Tests memory performance with data structure allocation,
215
+ manipulation, and garbage collection patterns.
216
+ """
217
+
218
+ rprint("🧠 [bold blue]Quick Memory Load Test[/bold blue]")
219
+ rprint(f"šŸ“Š [cyan]Tasks:[/cyan] {num_tasks} memory allocation tasks")
220
+ rprint(
221
+ "šŸ“ˆ [cyan]Work type:[/cyan] Data structures + allocation patterns + GC testing"
222
+ )
223
+
224
+ try:
225
+ task_id = asyncio.run(quick_memory_test(num_tasks))
226
+ rprint(f"āœ… [green]Memory test started![/green] Task ID: {task_id}")
227
+
228
+ if wait:
229
+ _wait_for_completion_and_display_results(
230
+ task_id, get_load_test_queue(), 600
231
+ )
232
+ else:
233
+ rprint(
234
+ f"šŸ’” [yellow]Check results:[/yellow] "
235
+ f"{{ cookiecutter.project_slug }} load-test results {task_id}"
236
+ )
237
+
238
+ except Exception as e:
239
+ rprint(f"āŒ [red]Memory test failed:[/red] {e}")
240
+ raise typer.Exit(1)
241
+
242
+
243
+ @app.command("results")
244
+ def show_results(
245
+ task_id: str = typer.Argument(..., help="Load test task ID"),
246
+ target_queue: QueueChoice = typer.Option(
247
+ QueueChoice.system, "--queue", "-q", help="Queue where test was run"
248
+ ),
249
+ detailed: bool = typer.Option(
250
+ False, "--detailed", "-d", help="Show detailed analysis and metrics"
251
+ ),
252
+ json_output: bool = typer.Option(
253
+ False, "--json", help="Output results in JSON format"
254
+ ),
255
+ ) -> None:
256
+ """
257
+ Display results and analysis for a completed load test.
258
+
259
+ Retrieves and analyzes load test results, showing performance metrics,
260
+ test type verification, and recommendations.
261
+ """
262
+
263
+ try:
264
+ result = asyncio.run(
265
+ LoadTestService.get_load_test_result(task_id, target_queue.value)
266
+ )
267
+
268
+ if not result:
269
+ rprint(f"āŒ [red]No results found for task ID:[/red] {task_id}")
270
+ rprint(
271
+ "šŸ’” [yellow]Check that the task ID is correct and the test has "
272
+ "completed[/yellow]"
273
+ )
274
+ raise typer.Exit(1)
275
+
276
+ if json_output:
277
+ print(json.dumps(result, indent=2, default=str))
278
+ return
279
+
280
+ _display_load_test_results(result, detailed)
281
+
282
+ except Exception as e:
283
+ rprint(f"āŒ [red]Failed to retrieve results:[/red] {e}")
284
+ raise typer.Exit(1)
285
+
286
+
287
+ @app.command("info")
288
+ def show_test_type_info(
289
+ test_type: str | None = typer.Argument(
290
+ None, help="Specific test type to show info for"
291
+ ),
292
+ ) -> None:
293
+ """
294
+ Display information about available load test types and their characteristics.
295
+
296
+ Shows what each test type does, expected performance signatures,
297
+ and guidance for choosing the right test for your needs.
298
+ """
299
+
300
+ if test_type:
301
+ # Show detailed info for specific test type
302
+ try:
303
+ test_type_enum = LoadTestTypes(test_type)
304
+ info = LoadTestService.get_test_type_info(test_type_enum)
305
+ _display_test_type_info(test_type, info)
306
+ except ValueError:
307
+ rprint(f"āŒ [red]Unknown test type:[/red] {test_type}")
308
+ rprint(
309
+ "šŸ’” [yellow]Available types:[/yellow] cpu_intensive, io_simulation, "
310
+ "memory_operations, failure_testing"
311
+ )
312
+ raise typer.Exit(1)
313
+ else:
314
+ # Show overview of all test types
315
+ rprint("🧪 [bold blue]Available Load Test Types[/bold blue]\n")
316
+
317
+ for load_test_type in [
318
+ LoadTestTypes.CPU_INTENSIVE,
319
+ LoadTestTypes.IO_SIMULATION,
320
+ LoadTestTypes.MEMORY_OPERATIONS,
321
+ LoadTestTypes.FAILURE_TESTING,
322
+ ]:
323
+ info = LoadTestService.get_test_type_info(load_test_type)
324
+ rprint(
325
+ f"šŸ”¹ [bold cyan]{load_test_type}[/bold cyan] - "
326
+ f"{info.get('name', 'Unknown')}"
327
+ )
328
+ rprint(f" {info.get('description', 'No description available')}")
329
+ rprint(
330
+ f" [dim]Typical duration: "
331
+ f"{info.get('typical_duration_ms', 'Unknown')}[/dim]\n"
332
+ )
333
+
334
+ rprint("šŸ’” [yellow]Use --help with specific commands for more details[/yellow]")
335
+ rprint("šŸ“– [cyan]Examples:[/cyan]")
336
+ rprint(" {{ cookiecutter.project_slug }} load-test info cpu_intensive")
337
+ rprint(" {{ cookiecutter.project_slug }} load-test info io_simulation")
338
+ rprint(" {{ cookiecutter.project_slug }} load-test info memory_operations")
339
+
340
+
341
+ def _display_test_configuration(config: LoadTestConfiguration, test_type: str) -> None:
342
+ """Display load test configuration in a formatted panel."""
343
+
344
+ test_info = LoadTestService.get_test_type_info(LoadTestTypes(test_type))
345
+
346
+ config_text = f"""
347
+ [bold cyan]Test Configuration[/bold cyan]
348
+
349
+ šŸ”¢ [cyan]Tasks:[/cyan] {config.num_tasks} {test_type} tasks
350
+ šŸ“¦ [cyan]Batching:[/cyan] {config.batch_size} tasks per batch
351
+ ā±ļø [cyan]Delay:[/cyan] {config.delay_ms}ms between batches
352
+ šŸŽÆ [cyan]Queue:[/cyan] {config.target_queue}
353
+
354
+ [bold yellow]Test Type Details[/bold yellow]
355
+ šŸ“‹ [yellow]Name:[/yellow] {test_info.get("name", "Unknown")}
356
+ šŸ“ [yellow]Description:[/yellow] {test_info.get("description", "No description")}
357
+ ⚔ [yellow]Performance:[/yellow] {test_info.get("performance_signature", "Unknown")}
358
+ šŸƒ [yellow]Concurrency:[/yellow] {test_info.get("concurrency_impact", "Unknown")}
359
+ """.strip()
360
+
361
+ console.print(Panel(config_text, title="šŸš€ Load Test Setup", border_style="blue"))
362
+
363
+
364
+ def _display_test_type_info(test_type: str, info: dict[str, Any]) -> None:
365
+ """Display detailed information about a specific test type."""
366
+
367
+ info_text = f"""
368
+ [bold cyan]{info.get("name", "Unknown Test Type")}[/bold cyan]
369
+
370
+ [bold yellow]Description[/bold yellow]
371
+ {info.get("description", "No description available")}
372
+
373
+ [bold yellow]Performance Characteristics[/bold yellow]
374
+ šŸ” [yellow]Signature:[/yellow] {info.get("performance_signature", "Unknown")}
375
+ ā±ļø [yellow]Duration:[/yellow] {info.get("typical_duration_ms", "Unknown")}
376
+ šŸ”„ [yellow]Concurrency:[/yellow] {info.get("concurrency_impact", "Unknown")}
377
+
378
+ [bold yellow]Expected Metrics[/bold yellow]
379
+ šŸ“Š {", ".join(info.get("expected_metrics", ["No metrics specified"]))}
380
+
381
+ [bold yellow]Validation Keys[/bold yellow]
382
+ šŸ”‘ {", ".join(info.get("validation_keys", ["No validation keys"]))}
383
+ """.strip()
384
+
385
+ console.print(
386
+ Panel(info_text, title=f"🧪 {test_type} Test Type", border_style="green")
387
+ )
388
+
389
+
390
+ def _wait_for_completion_and_display_results(
391
+ task_id: str, target_queue: str, timeout: int
392
+ ) -> None:
393
+ """Wait for load test completion and display results."""
394
+
395
+ rprint(
396
+ f"\nā³ [yellow]Waiting for load test completion (timeout: {timeout}s)..."
397
+ f"[/yellow]"
398
+ )
399
+
400
+ start_time = time.time()
401
+
402
+ with Progress(
403
+ SpinnerColumn(),
404
+ TextColumn("[progress.description]{task.description}"),
405
+ console=console,
406
+ ) as progress:
407
+ wait_task = progress.add_task(
408
+ "Waiting for load test to complete...", total=None
409
+ )
410
+
411
+ while True:
412
+ elapsed = time.time() - start_time
413
+
414
+ if elapsed > timeout:
415
+ progress.update(wait_task, description="āŒ Timeout reached")
416
+ rprint(f"\nā° [red]Timeout reached after {timeout}s[/red]")
417
+ rprint(
418
+ f"šŸ’” [yellow]Check results manually:[/yellow] "
419
+ f"{{ cookiecutter.project_slug }} load-test results {task_id}"
420
+ )
421
+ return
422
+
423
+ try:
424
+ result = asyncio.run(
425
+ LoadTestService.get_load_test_result(task_id, target_queue)
426
+ )
427
+
428
+ if result:
429
+ progress.update(wait_task, description="āœ… Load test completed!")
430
+ break
431
+
432
+ except Exception as e:
433
+ logger.debug(f"Error checking results: {e}")
434
+
435
+ # Update progress description with elapsed time
436
+ progress.update(
437
+ wait_task, description=f"Waiting for completion... ({elapsed:.1f}s)"
438
+ )
439
+ time.sleep(2)
440
+
441
+ rprint("\nšŸŽ‰ [bold green]Load test completed![/bold green]")
442
+ _display_load_test_results(result, detailed=True)
443
+
444
+
445
+ def _display_load_test_results(result: dict[str, Any], detailed: bool = False) -> None:
446
+ """Display formatted load test results."""
447
+
448
+ # Basic results
449
+ status = result.get("status", "unknown")
450
+
451
+ # Handle timeout and failure cases
452
+ if status in ["timed_out", "failed"]:
453
+ _display_error_result(result, status)
454
+ return
455
+
456
+ # Handle successful results
457
+ metrics = result.get("metrics", {})
458
+
459
+ # Create summary table for successful results
460
+ table = Table(
461
+ title="šŸ“Š Load Test Results Summary",
462
+ show_header=True,
463
+ header_style="bold magenta",
464
+ )
465
+ table.add_column("Metric", style="cyan", no_wrap=True)
466
+ table.add_column("Value", style="green")
467
+ table.add_column("Details", style="dim")
468
+
469
+ # Add basic metrics
470
+ table.add_row("Status", status, "Test execution status")
471
+ table.add_row(
472
+ "Tasks Sent", str(metrics.get("tasks_sent", "unknown")), "Total tasks enqueued"
473
+ )
474
+ table.add_row(
475
+ "Tasks Completed",
476
+ str(metrics.get("tasks_completed", "unknown")),
477
+ "Successfully completed",
478
+ )
479
+ table.add_row(
480
+ "Tasks Failed",
481
+ str(metrics.get("tasks_failed", "unknown")),
482
+ "Failed during execution",
483
+ )
484
+ table.add_row(
485
+ "Duration",
486
+ f"{metrics.get('total_duration_seconds', 0):.2f}s",
487
+ "Total test duration",
488
+ )
489
+ table.add_row(
490
+ "Throughput",
491
+ f"{metrics.get('overall_throughput', 0):.2f} tasks/sec",
492
+ "Average task completion rate",
493
+ )
494
+ table.add_row(
495
+ "Failure Rate",
496
+ f"{metrics.get('failure_rate_percent', 0):.1f}%",
497
+ "Percentage of failed tasks",
498
+ )
499
+
500
+ console.print(table)
501
+
502
+ # Show performance analysis if available
503
+ analysis = result.get("analysis", {})
504
+ if analysis and detailed:
505
+ _display_performance_analysis(analysis)
506
+
507
+ # Show performance summary
508
+ perf_summary = result.get("performance_summary", "No summary available")
509
+ console.print("\nšŸ’” [bold yellow]Performance Summary[/bold yellow]")
510
+ console.print(f" {perf_summary}")
511
+
512
+ # Show recommendations if available
513
+ recommendations = analysis.get("recommendations", []) if analysis else []
514
+ if recommendations:
515
+ console.print("\nšŸ”§ [bold blue]Recommendations[/bold blue]")
516
+ for i, rec in enumerate(recommendations, 1):
517
+ console.print(f" {i}. {rec}")
518
+
519
+
520
+ def _display_error_result(result: dict[str, Any], status: str) -> None:
521
+ """Display results for failed or timed out load tests."""
522
+
523
+ test_id = result.get("test_id", "unknown")
524
+ error = result.get("error", "Unknown error")
525
+ partial_info = result.get("partial_info", "")
526
+
527
+ if status == "timed_out":
528
+ console.print(
529
+ Panel(
530
+ f"[bold red]ā° Load Test Timed Out[/bold red]\n\n"
531
+ f"[cyan]Test ID:[/cyan] {test_id}\n"
532
+ f"[cyan]Error:[/cyan] {error}\n\n"
533
+ f"[yellow]What this means:[/yellow]\n"
534
+ f"The orchestrator task timed out, but individual worker tasks "
535
+ f"may have completed successfully.\n"
536
+ f"This often happens with large load tests that exceed the "
537
+ f"queue timeout.\n\n"
538
+ f"[blue]To investigate:[/blue]\n"
539
+ f"• Check worker logs for individual task completion\n"
540
+ f"• Consider using smaller batch sizes for large tests\n"
541
+ f"• Check queue metrics for actual task completion counts",
542
+ title="šŸ” Load Test Analysis",
543
+ border_style="red",
544
+ )
545
+ )
546
+
547
+ if partial_info:
548
+ console.print(f"\nšŸ“ [dim]{partial_info}[/dim]")
549
+
550
+ elif status == "failed":
551
+ console.print(
552
+ Panel(
553
+ f"[bold red]āŒ Load Test Failed[/bold red]\n\n"
554
+ f"[cyan]Test ID:[/cyan] {test_id}\n"
555
+ f"[cyan]Error:[/cyan] {error}\n\n"
556
+ f"[blue]Next steps:[/blue]\n"
557
+ f"• Check worker logs for detailed error information\n"
558
+ f"• Verify queue connectivity and worker status\n"
559
+ f"• Try a smaller test to isolate the issue",
560
+ title="šŸ” Load Test Analysis",
561
+ border_style="red",
562
+ )
563
+ )
564
+
565
+ # Show basic troubleshooting info
566
+ console.print("\nšŸ’” [bold yellow]Troubleshooting Tips[/bold yellow]")
567
+ console.print(" 1. Check worker container logs: docker compose logs worker")
568
+ console.print(
569
+ f" 2. Verify system health: {{ cookiecutter.project_slug }} health check"
570
+ )
571
+ console.print(
572
+ " 3. Try a smaller load test first: "
573
+ f"{{ cookiecutter.project_slug }} load-test cpu --tasks 10"
574
+ )
575
+
576
+
577
+ def _display_performance_analysis(analysis: dict[str, Any]) -> None:
578
+ """Display detailed performance analysis."""
579
+
580
+ perf_analysis = analysis.get("performance_analysis", {})
581
+ validation = analysis.get("validation_status", {})
582
+
583
+ # Performance ratings table
584
+ perf_table = Table(
585
+ title="šŸŽÆ Performance Analysis",
586
+ show_header=True,
587
+ header_style="bold blue",
588
+ )
589
+ perf_table.add_column("Aspect", style="cyan")
590
+ perf_table.add_column("Rating", style="bold")
591
+ perf_table.add_column("Description", style="dim")
592
+
593
+ # Add performance ratings with color coding
594
+ throughput_rating = perf_analysis.get("throughput_rating", "unknown")
595
+ throughput_color = _get_rating_color(throughput_rating)
596
+ perf_table.add_row(
597
+ "Throughput",
598
+ f"[{throughput_color}]{throughput_rating}[/{throughput_color}]",
599
+ "Task processing speed",
600
+ )
601
+
602
+ efficiency_rating = perf_analysis.get("efficiency_rating", "unknown")
603
+ efficiency_color = _get_rating_color(efficiency_rating)
604
+ perf_table.add_row(
605
+ "Efficiency",
606
+ f"[{efficiency_color}]{efficiency_rating}[/{efficiency_color}]",
607
+ "Task completion rate",
608
+ )
609
+
610
+ queue_pressure = perf_analysis.get("queue_pressure", "unknown")
611
+ pressure_color = (
612
+ "red"
613
+ if queue_pressure == "high"
614
+ else "yellow"
615
+ if queue_pressure == "medium"
616
+ else "green"
617
+ )
618
+ perf_table.add_row(
619
+ "Queue Pressure",
620
+ f"[{pressure_color}]{queue_pressure}[/{pressure_color}]",
621
+ "Queue saturation level",
622
+ )
623
+
624
+ console.print(perf_table)
625
+
626
+ # Validation status
627
+ if validation:
628
+ console.print("\nāœ… [bold green]Test Validation[/bold green]")
629
+ console.print(
630
+ f" šŸ” Test type verified: "
631
+ f"{validation.get('test_type_verified', 'unknown')}"
632
+ )
633
+ console.print(
634
+ f" šŸ“Š Expected metrics present: "
635
+ f"{validation.get('expected_metrics_present', 'unknown')}"
636
+ )
637
+ console.print(
638
+ f" šŸ“ˆ Performance signature match: "
639
+ f"{validation.get('performance_signature_match', 'unknown')}"
640
+ )
641
+
642
+
643
+ def _get_rating_color(rating: str) -> str:
644
+ """Get color for performance rating."""
645
+ color_map = {
646
+ "excellent": "green",
647
+ "good": "blue",
648
+ "fair": "yellow",
649
+ "poor": "red",
650
+ "unknown": "dim",
651
+ }
652
+ return color_map.get(rating, "dim")
653
+
654
+
655
+ if __name__ == "__main__":
656
+ app()