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,478 @@
1
+ {%- if cookiecutter.include_worker == "yes" %}
2
+ """Worker task API endpoints."""
3
+
4
+ from datetime import datetime, timedelta
5
+ from typing import Any
6
+
7
+ from fastapi import APIRouter, HTTPException
8
+
9
+ from app.components.backend.api.models import (
10
+ LoadTestRequest,
11
+ TaskListResponse,
12
+ TaskRequest,
13
+ TaskResponse,
14
+ TaskResultResponse,
15
+ TaskStatusResponse,
16
+ )
17
+ from app.components.worker.constants import LoadTestTypes
18
+ from app.components.worker.pools import get_queue_pool
19
+ from app.components.worker.tasks import get_task_by_name, list_available_tasks
20
+ from app.core.config import get_default_queue
21
+ from app.core.log import logger
22
+
23
+ router = APIRouter(prefix="/tasks", tags=["worker"])
24
+
25
+
26
+ @router.get("/", response_model=TaskListResponse)
27
+ async def list_tasks() -> TaskListResponse:
28
+ """Get list of all available background tasks."""
29
+ available_tasks = list_available_tasks()
30
+
31
+ from app.components.worker.tasks import get_queue_for_task
32
+ from app.core.config import get_available_queues
33
+
34
+ queues: dict[str, list[str]] = {
35
+ queue_type: [] for queue_type in get_available_queues()
36
+ }
37
+
38
+ for task in available_tasks:
39
+ queue_type = get_queue_for_task(task)
40
+ queues[queue_type].append(task)
41
+
42
+ return TaskListResponse(
43
+ available_tasks=available_tasks,
44
+ total_count=len(available_tasks),
45
+ queues=queues,
46
+ )
47
+
48
+
49
+ @router.post("/enqueue", response_model=TaskResponse)
50
+ async def enqueue_task(task_request: TaskRequest) -> TaskResponse:
51
+ """Enqueue a background task for processing."""
52
+ logger.info(
53
+ f"Enqueueing task: {task_request.task_name} (queue: {task_request.queue_type})"
54
+ )
55
+
56
+ task_func = get_task_by_name(task_request.task_name)
57
+ if not task_func:
58
+ available_tasks = list_available_tasks()
59
+ raise HTTPException(
60
+ status_code=400,
61
+ detail={
62
+ "error": "invalid_task_name",
63
+ "message": f"Task '{task_request.task_name}' not found",
64
+ "available_tasks": available_tasks,
65
+ },
66
+ )
67
+
68
+ from app.core.config import is_valid_queue, get_available_queues
69
+
70
+ if not is_valid_queue(task_request.queue_type):
71
+ available_queues = get_available_queues()
72
+ raise HTTPException(
73
+ status_code=400,
74
+ detail={
75
+ "error": "invalid_queue_type",
76
+ "message": f"Queue type must be one of: {available_queues}",
77
+ },
78
+ )
79
+
80
+ try:
81
+ pool, queue_name = await get_queue_pool(task_request.queue_type)
82
+
83
+ job = await pool.enqueue_job(
84
+ task_request.task_name,
85
+ *task_request.args,
86
+ _queue_name=queue_name,
87
+ _defer_by=task_request.delay_seconds,
88
+ **task_request.task_kwargs,
89
+ )
90
+
91
+ queued_at = datetime.now()
92
+ estimated_start = None
93
+ if task_request.delay_seconds:
94
+ estimated_start = queued_at + timedelta(seconds=task_request.delay_seconds)
95
+
96
+ await pool.aclose()
97
+
98
+ if job is None:
99
+ raise HTTPException(status_code=500, detail="Failed to enqueue task")
100
+
101
+ logger.info(f"✅ Task enqueued: {job.job_id} ({task_request.task_name})")
102
+
103
+ return TaskResponse(
104
+ task_id=job.job_id,
105
+ task_name=task_request.task_name,
106
+ queue_type=task_request.queue_type,
107
+ queued_at=queued_at,
108
+ estimated_start=estimated_start,
109
+ message=(
110
+ f"Task '{task_request.task_name}' enqueued to "
111
+ f"{task_request.queue_type} queue"
112
+ ),
113
+ )
114
+
115
+ except Exception as e:
116
+ logger.error(f"Failed to enqueue task {task_request.task_name}: {e}")
117
+ raise HTTPException(
118
+ status_code=500,
119
+ detail={
120
+ "error": "enqueue_failed",
121
+ "message": f"Failed to enqueue task: {str(e)}",
122
+ },
123
+ )
124
+
125
+
126
+ @router.get("/status/{task_id}", response_model=TaskStatusResponse)
127
+ async def get_task_status(task_id: str) -> TaskStatusResponse:
128
+ """Get the status of a background task."""
129
+ try:
130
+ pool, _ = await get_queue_pool(get_default_queue())
131
+
132
+ job_key = f"arq:job:{task_id}"
133
+ result_key = f"arq:result:{task_id}"
134
+
135
+ job_exists = await pool.exists(job_key)
136
+ result_exists = await pool.exists(result_key)
137
+
138
+ if not job_exists and not result_exists:
139
+ await pool.aclose()
140
+ raise HTTPException(
141
+ status_code=404,
142
+ detail={
143
+ "error": "task_not_found",
144
+ "message": f"Task {task_id} not found",
145
+ },
146
+ )
147
+
148
+ if result_exists:
149
+ result_data = await pool.get(result_key)
150
+ if result_data:
151
+ try:
152
+ import pickle
153
+
154
+ result = pickle.loads(result_data)
155
+ if isinstance(result, dict) and result.get("error"):
156
+ status = "failed"
157
+ error = result.get("error")
158
+ else:
159
+ status = "complete"
160
+ error = None
161
+ except Exception:
162
+ status = "complete"
163
+ error = None
164
+ else:
165
+ status = "complete"
166
+ error = None
167
+ elif job_exists:
168
+ status = "queued"
169
+ error = None
170
+ else:
171
+ status = "unknown"
172
+ error = None
173
+
174
+ await pool.aclose()
175
+
176
+ return TaskStatusResponse(
177
+ task_id=task_id,
178
+ status=status,
179
+ result_available=result_exists,
180
+ error=error,
181
+ enqueue_time=None,
182
+ start_time=None,
183
+ finish_time=None
184
+ )
185
+
186
+ except Exception as e:
187
+ logger.error(f"Failed to get task status for {task_id}: {e}")
188
+ raise HTTPException(
189
+ status_code=500, detail={"error": "status_check_failed", "message": str(e)}
190
+ )
191
+
192
+
193
+ @router.get("/result/{task_id}", response_model=TaskResultResponse)
194
+ async def get_task_result(task_id: str) -> TaskResultResponse:
195
+ """Get the result of a completed background task."""
196
+ try:
197
+ pool, _ = await get_queue_pool(get_default_queue())
198
+
199
+ result_key = f"arq:result:{task_id}"
200
+ result_exists = await pool.exists(result_key)
201
+
202
+ if not result_exists:
203
+ job_key = f"arq:job:{task_id}"
204
+ job_exists = await pool.exists(job_key)
205
+ await pool.aclose()
206
+
207
+ if not job_exists:
208
+ raise HTTPException(
209
+ status_code=404,
210
+ detail={
211
+ "error": "task_not_found",
212
+ "message": f"Task {task_id} not found",
213
+ },
214
+ )
215
+ else:
216
+ raise HTTPException(
217
+ status_code=400,
218
+ detail={
219
+ "error": "task_not_completed",
220
+ "message": f"Task {task_id} has not completed yet",
221
+ "current_status": "queued or in_progress",
222
+ },
223
+ )
224
+
225
+ result_data = await pool.get(result_key)
226
+ await pool.aclose()
227
+
228
+ if not result_data:
229
+ raise HTTPException(
230
+ status_code=500,
231
+ detail={
232
+ "error": "result_data_missing",
233
+ "message": "Result data is missing",
234
+ },
235
+ )
236
+
237
+ try:
238
+ import pickle
239
+
240
+ result = pickle.loads(result_data)
241
+
242
+ if isinstance(result, Exception):
243
+ result_data = {
244
+ "error_type": type(result).__name__,
245
+ "error_message": str(result),
246
+ "task_failed": True,
247
+ }
248
+ task_status = "failed"
249
+ else:
250
+ try:
251
+ import json
252
+
253
+ json.dumps(result)
254
+ result_data = result
255
+ task_status = "completed"
256
+ except (TypeError, ValueError):
257
+ result_data = {
258
+ "result_type": type(result).__name__,
259
+ "result_str": str(result),
260
+ "note": "Result was not JSON-serializable, converted to string",
261
+ }
262
+ task_status = "completed"
263
+
264
+ return TaskResultResponse(
265
+ task_id=task_id,
266
+ status=task_status,
267
+ result=result_data,
268
+ enqueue_time=None,
269
+ start_time=None,
270
+ finish_time=None
271
+ )
272
+
273
+ except Exception as e:
274
+ raise HTTPException(
275
+ status_code=500,
276
+ detail={
277
+ "error": "result_deserialization_failed",
278
+ "message": f"Failed to deserialize result: {str(e)}",
279
+ },
280
+ )
281
+
282
+ except HTTPException:
283
+ raise
284
+ except Exception as e:
285
+ logger.error(f"Failed to get task result for {task_id}: {e}")
286
+ raise HTTPException(
287
+ status_code=500, detail={"error": "result_fetch_failed", "message": str(e)}
288
+ )
289
+
290
+
291
+ @router.post("/load-test", response_model=TaskResponse)
292
+ async def start_load_test(load_test_config: LoadTestRequest) -> TaskResponse:
293
+ """Start a comprehensive load test that measures queue throughput."""
294
+ from app.components.worker.constants import TaskNames
295
+ from app.services.load_test import LoadTestConfiguration, LoadTestService
296
+
297
+ logger.info(
298
+ f"🚀 Starting load test: {load_test_config.num_tasks} "
299
+ f"{load_test_config.task_type} tasks"
300
+ )
301
+
302
+ valid_types = [
303
+ LoadTestTypes.CPU_INTENSIVE,
304
+ LoadTestTypes.IO_SIMULATION,
305
+ LoadTestTypes.MEMORY_OPERATIONS,
306
+ LoadTestTypes.FAILURE_TESTING,
307
+ ]
308
+ if load_test_config.task_type not in valid_types:
309
+ raise HTTPException(
310
+ status_code=400,
311
+ detail={
312
+ "error": "invalid_task_type",
313
+ "message": f"Invalid task type: {load_test_config.task_type}",
314
+ "valid_types": valid_types,
315
+ },
316
+ )
317
+
318
+ config = LoadTestConfiguration(
319
+ num_tasks=load_test_config.num_tasks,
320
+ task_type=load_test_config.task_type,
321
+ batch_size=load_test_config.batch_size,
322
+ delay_ms=load_test_config.delay_ms,
323
+ target_queue=load_test_config.target_queue,
324
+ )
325
+
326
+ try:
327
+ task_id = await LoadTestService.enqueue_load_test(config)
328
+
329
+ return TaskResponse(
330
+ task_id=task_id,
331
+ task_name=TaskNames.LOAD_TEST_ORCHESTRATOR,
332
+ queue_type=load_test_config.target_queue,
333
+ queued_at=datetime.now(),
334
+ estimated_start=None,
335
+ message=(
336
+ f"Load test '{load_test_config.task_type}' enqueued: "
337
+ f"{load_test_config.num_tasks} tasks to "
338
+ f"{load_test_config.target_queue} queue"
339
+ ),
340
+ )
341
+
342
+ except Exception as e:
343
+ logger.error(f"Failed to enqueue load test: {e}")
344
+ raise HTTPException(
345
+ status_code=500,
346
+ detail={
347
+ "error": "load_test_failed",
348
+ "message": f"Failed to start load test: {str(e)}",
349
+ },
350
+ )
351
+
352
+
353
+ @router.post("/examples/load-test-small", response_model=TaskResponse)
354
+ async def enqueue_small_load_test() -> TaskResponse:
355
+ """Example: Small load test with 50 CPU tasks."""
356
+ load_test_config = LoadTestRequest(
357
+ num_tasks=50,
358
+ task_type=LoadTestTypes.CPU_INTENSIVE,
359
+ batch_size=10,
360
+ delay_ms=0,
361
+ target_queue=get_default_queue(),
362
+ )
363
+ return await start_load_test(load_test_config)
364
+
365
+
366
+ @router.post("/examples/load-test-medium", response_model=TaskResponse)
367
+ async def enqueue_medium_load_test() -> TaskResponse:
368
+ """Example: Medium load test with 200 I/O tasks."""
369
+ load_test_config = LoadTestRequest(
370
+ num_tasks=200,
371
+ task_type=LoadTestTypes.IO_SIMULATION,
372
+ batch_size=20,
373
+ delay_ms=50,
374
+ target_queue=get_default_queue(),
375
+ )
376
+ return await start_load_test(load_test_config)
377
+
378
+
379
+ @router.post("/examples/load-test-large", response_model=TaskResponse)
380
+ async def enqueue_large_load_test() -> TaskResponse:
381
+ """Example: Large load test with 1000 memory tasks."""
382
+ load_test_config = LoadTestRequest(
383
+ num_tasks=1000,
384
+ task_type=LoadTestTypes.MEMORY_OPERATIONS,
385
+ batch_size=50,
386
+ delay_ms=0,
387
+ target_queue=get_default_queue(),
388
+ )
389
+ return await start_load_test(load_test_config)
390
+
391
+
392
+ @router.get("/load-test-result/{task_id}")
393
+ async def get_load_test_result(
394
+ task_id: str, target_queue: str | None = None
395
+ ) -> dict[str, Any]:
396
+ """Get enhanced load test results with analysis and verification."""
397
+ from app.services.load_test import LoadTestService
398
+
399
+ try:
400
+ result = await LoadTestService.get_load_test_result(task_id, target_queue)
401
+
402
+ if not result:
403
+ raise HTTPException(
404
+ status_code=404,
405
+ detail={
406
+ "error": "load_test_not_found",
407
+ "message": f"No load test results found for task {task_id}",
408
+ "task_id": task_id,
409
+ "target_queue": target_queue,
410
+ },
411
+ )
412
+
413
+ return result
414
+
415
+ except HTTPException:
416
+ raise
417
+ except Exception as e:
418
+ logger.error(f"Failed to get load test result for {task_id}: {e}")
419
+ raise HTTPException(
420
+ status_code=500,
421
+ detail={
422
+ "error": "result_retrieval_failed",
423
+ "message": f"Failed to retrieve load test results: {str(e)}",
424
+ },
425
+ )
426
+
427
+
428
+ @router.get("/load-test-types")
429
+ async def get_load_test_types() -> dict[str, Any]:
430
+ """Get information about available load test types."""
431
+ from app.components.worker.constants import LoadTestTypes
432
+ from app.services.load_test import LoadTestService
433
+
434
+ test_types = {}
435
+
436
+ all_types = [
437
+ LoadTestTypes.CPU_INTENSIVE,
438
+ LoadTestTypes.IO_SIMULATION,
439
+ LoadTestTypes.MEMORY_OPERATIONS,
440
+ LoadTestTypes.FAILURE_TESTING,
441
+ ]
442
+ for test_type in all_types:
443
+ test_types[test_type] = LoadTestService.get_test_type_info(test_type)
444
+
445
+ return {
446
+ "available_test_types": test_types,
447
+ "usage_examples": {
448
+ "quick_cpu_test": {
449
+ "description": "Quick CPU test with 50 tasks",
450
+ "parameters": {
451
+ "num_tasks": 50,
452
+ "task_type": "cpu_intensive",
453
+ "batch_size": 10,
454
+ "target_queue": "load_test",
455
+ },
456
+ },
457
+ "io_stress_test": {
458
+ "description": "I/O stress test with concurrent operations",
459
+ "parameters": {
460
+ "num_tasks": 200,
461
+ "task_type": "io_simulation",
462
+ "batch_size": 20,
463
+ "delay_ms": 50,
464
+ "target_queue": "load_test",
465
+ },
466
+ },
467
+ "memory_load_test": {
468
+ "description": "Memory allocation test with GC pressure",
469
+ "parameters": {
470
+ "num_tasks": 500,
471
+ "task_type": "memory_operations",
472
+ "batch_size": 25,
473
+ "target_queue": "media",
474
+ },
475
+ },
476
+ },
477
+ }
478
+ {%- endif %}
@@ -0,0 +1,144 @@
1
+ # app/components/backend/hooks.py
2
+ """
3
+ Backend-specific hook management system for drop-in extensibility.
4
+
5
+ This system automatically discovers and registers:
6
+ - Middleware from app/components/backend/middleware/
7
+ - Startup hooks from app/components/backend/startup/
8
+ - Shutdown hooks from app/components/backend/shutdown/
9
+
10
+ Just drop files in the appropriate folders - no central registration required.
11
+ """
12
+
13
+ import importlib
14
+ import inspect
15
+ from collections.abc import Callable
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from app.core.log import logger
20
+ from fastapi import FastAPI
21
+
22
+
23
+ class BackendHooks:
24
+ """Backend-specific hook management system."""
25
+
26
+ def __init__(self) -> None:
27
+ self.startup_hooks: list[Callable[[], Any]] = []
28
+ self.shutdown_hooks: list[Callable[[], Any]] = []
29
+
30
+ def discover_and_register_middleware(self, app: FastAPI) -> None:
31
+ """
32
+ Auto-discover and register middleware. This must be called
33
+ before the application starts.
34
+ """
35
+ middleware_dir = Path(__file__).parent / "middleware"
36
+ if not middleware_dir.exists():
37
+ return
38
+
39
+ for middleware_file in middleware_dir.glob("*.py"):
40
+ if middleware_file.name.startswith("_"):
41
+ continue
42
+
43
+ module_name = f"app.components.backend.middleware.{middleware_file.stem}"
44
+ try:
45
+ module = importlib.import_module(module_name)
46
+ if hasattr(module, "register_middleware"):
47
+ logger.info(f"Registering middleware from {module_name}")
48
+ # Middleware registration is synchronous
49
+ module.register_middleware(app)
50
+ except Exception as e:
51
+ logger.error(f"Failed to load middleware {module_name}: {e}")
52
+
53
+ async def discover_lifespan_hooks(self) -> None:
54
+ """Discover startup and shutdown hooks for the lifespan event."""
55
+ await self._discover_startup_hooks()
56
+ await self._discover_shutdown_hooks()
57
+
58
+ async def _discover_startup_hooks(self) -> None:
59
+ """Auto-discover startup hooks from app/components/backend/startup/."""
60
+ startup_dir = Path(__file__).parent / "startup"
61
+ if not startup_dir.exists():
62
+ logger.info("No backend startup directory found")
63
+ return
64
+
65
+ for startup_file in startup_dir.glob("*.py"):
66
+ if startup_file.name.startswith("_"):
67
+ continue
68
+
69
+ module_name = f"app.components.backend.startup.{startup_file.stem}"
70
+ try:
71
+ module = importlib.import_module(module_name)
72
+
73
+ # Look for startup_hook function
74
+ if hasattr(module, "startup_hook"):
75
+ # Prevent duplicate registration
76
+ if module.startup_hook not in self.startup_hooks:
77
+ logger.info(f"Registered startup hook from {module_name}")
78
+ self.startup_hooks.append(module.startup_hook)
79
+ else:
80
+ logger.debug(
81
+ f"Startup hook from {module_name} already registered"
82
+ )
83
+
84
+ except Exception as e:
85
+ logger.error(f"Failed to load startup hook {module_name}: {e}")
86
+
87
+ async def _discover_shutdown_hooks(self) -> None:
88
+ """Auto-discover shutdown hooks from app/components/backend/shutdown/."""
89
+ shutdown_dir = Path(__file__).parent / "shutdown"
90
+ if not shutdown_dir.exists():
91
+ logger.info("No backend shutdown directory found")
92
+ return
93
+
94
+ for shutdown_file in shutdown_dir.glob("*.py"):
95
+ if shutdown_file.name.startswith("_"):
96
+ continue
97
+
98
+ module_name = f"app.components.backend.shutdown.{shutdown_file.stem}"
99
+ try:
100
+ module = importlib.import_module(module_name)
101
+
102
+ # Look for shutdown_hook function
103
+ if hasattr(module, "shutdown_hook"):
104
+ # Prevent duplicate registration
105
+ if module.shutdown_hook not in self.shutdown_hooks:
106
+ logger.info(f"Registered shutdown hook from {module_name}")
107
+ self.shutdown_hooks.append(module.shutdown_hook)
108
+ else:
109
+ logger.debug(
110
+ f"Shutdown hook from {module_name} already registered"
111
+ )
112
+
113
+ except Exception as e:
114
+ logger.error(f"Failed to load shutdown hook {module_name}: {e}")
115
+
116
+ async def execute_startup_hooks(self) -> None:
117
+ """Execute all discovered startup hooks."""
118
+ logger.info(f"Executing {len(self.startup_hooks)} backend startup hooks")
119
+ for hook in self.startup_hooks:
120
+ try:
121
+ if inspect.iscoroutinefunction(hook):
122
+ await hook()
123
+ else:
124
+ hook()
125
+ except Exception as e:
126
+ logger.error(f"Startup hook failed: {e}")
127
+ raise
128
+
129
+ async def execute_shutdown_hooks(self) -> None:
130
+ """Execute all discovered shutdown hooks in reverse order."""
131
+ logger.info(f"Executing {len(self.shutdown_hooks)} backend shutdown hooks")
132
+ for hook in reversed(self.shutdown_hooks):
133
+ try:
134
+ if inspect.iscoroutinefunction(hook):
135
+ await hook()
136
+ else:
137
+ hook()
138
+ except Exception as e:
139
+ logger.error(f"Shutdown hook failed: {e}")
140
+ # Continue with other shutdown hooks even if one fails
141
+
142
+
143
+ # Global backend hooks instance
144
+ backend_hooks = BackendHooks()
@@ -0,0 +1,31 @@
1
+ from app.components.backend.api.routing import include_routers
2
+ from app.components.backend.hooks import backend_hooks
3
+ from fastapi import FastAPI
4
+
5
+ # Store the configured FastAPI app instance for introspection
6
+ _configured_app: FastAPI | None = None
7
+
8
+
9
+ def create_backend_app(app: FastAPI) -> FastAPI:
10
+ """Configure FastAPI app with all backend concerns"""
11
+ global _configured_app
12
+
13
+ # Store the app instance for later introspection
14
+ _configured_app = app
15
+
16
+ # Auto-discover and register middleware
17
+ backend_hooks.discover_and_register_middleware(app)
18
+
19
+ # Include all routes
20
+ include_routers(app)
21
+
22
+ return app
23
+
24
+
25
+ def get_configured_app() -> FastAPI | None:
26
+ """
27
+ Get the configured backend FastAPI app instance.
28
+ Returns:
29
+ The configured FastAPI app instance, or None if not yet configured.
30
+ """
31
+ return _configured_app
@@ -0,0 +1,20 @@
1
+ # app/components/backend/middleware/cors.py
2
+ """
3
+ Auto-discovered CORS middleware for development.
4
+
5
+ This middleware is automatically registered with FastAPI when the backend starts.
6
+ """
7
+
8
+ from fastapi import FastAPI
9
+ from fastapi.middleware.cors import CORSMiddleware
10
+
11
+
12
+ def register_middleware(app: FastAPI) -> None:
13
+ """Auto-discovered middleware registration."""
14
+ app.add_middleware(
15
+ CORSMiddleware,
16
+ allow_origins=["http://localhost:3000", "http://localhost:8080"],
17
+ allow_credentials=True,
18
+ allow_methods=["*"],
19
+ allow_headers=["*"],
20
+ )
@@ -0,0 +1,14 @@
1
+ # app/components/backend/shutdown/cleanup.py
2
+ """
3
+ Auto-discovered cleanup shutdown hook.
4
+
5
+ This hook performs cleanup when the backend shuts down.
6
+ """
7
+
8
+ from app.core.log import logger
9
+
10
+
11
+ async def shutdown_hook() -> None:
12
+ """Auto-discovered shutdown hook for cleanup."""
13
+ logger.info("🧹 Running backend cleanup...")
14
+ logger.info("✅ Backend shutdown cleanup complete")