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,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")
@@ -0,0 +1,418 @@
1
+ """
2
+ Component health registration startup hook.
3
+
4
+ Automatically detects available components and registers their health checks
5
+ with the system health service using Python's import system.
6
+ """
7
+
8
+ from app.core.log import logger
9
+ from app.services.system.health import register_health_check
10
+ from app.services.system.models import ComponentStatus, ComponentStatusType
11
+ from app.components.backend.main import get_configured_app
12
+ from app.services.backend.route_inspector import get_fastapi_route_metadata, RouteMetadata
13
+ from app.services.backend.middleware_inspector import get_fastapi_middleware_metadata, MiddlewareMetadata
14
+
15
+ # Cache for route metadata - initialized once at startup
16
+ _cached_route_metadata: RouteMetadata | None = None
17
+ # Cache for middleware metadata - initialized once at startup
18
+ _cached_middleware_metadata: MiddlewareMetadata | None = None
19
+
20
+
21
+ def _initialize_route_metadata_cache() -> None:
22
+ """
23
+ Initialize route and middleware metadata caches using the configured FastAPI app.
24
+ Called once during startup to avoid recreating the app on every health check.
25
+ """
26
+ global _cached_route_metadata, _cached_middleware_metadata
27
+
28
+ if _cached_route_metadata is not None and _cached_middleware_metadata is not None:
29
+ return # Already initialized
30
+
31
+ try:
32
+ app = get_configured_app()
33
+ if app is not None:
34
+ # Cache route metadata
35
+ if _cached_route_metadata is None:
36
+ _cached_route_metadata = get_fastapi_route_metadata(app)
37
+ logger.info(
38
+ f"Route metadata cached: "
39
+ f"{_cached_route_metadata.total_routes} routes, "
40
+ f"{_cached_route_metadata.total_endpoints} endpoints"
41
+ )
42
+
43
+ # Cache middleware metadata
44
+ if _cached_middleware_metadata is None:
45
+ _cached_middleware_metadata = get_fastapi_middleware_metadata(app)
46
+ logger.info(
47
+ f"Middleware metadata cached: "
48
+ f"{_cached_middleware_metadata.total_middleware} middleware, "
49
+ f"{_cached_middleware_metadata.security_count} security layers"
50
+ )
51
+ else:
52
+ logger.warning(
53
+ "FastAPI app not yet configured - route and middleware metadata "
54
+ "cache will be initialized later"
55
+ )
56
+ except Exception as e:
57
+ logger.error(f"Failed to initialize route and middleware metadata cache: {e}")
58
+
59
+
60
+ async def _backend_component_health() -> ComponentStatus:
61
+ """
62
+ FastAPI backend health check with route and middleware introspection.
63
+
64
+ In test environment, reports as healthy since the app is loaded.
65
+ In production, uses internal check to avoid circular dependency.
66
+ Includes comprehensive route and middleware metadata for dashboard display.
67
+ """
68
+ import os
69
+ global _cached_route_metadata, _cached_middleware_metadata
70
+
71
+ # Check if we're in test environment
72
+ if os.getenv("PYTEST_CURRENT_TEST") or "pytest" in os.getenv("_", ""):
73
+ # Even in test mode, try to get route and middleware information if possible
74
+ try:
75
+
76
+ # Initialize cache if not already done (might happen in tests)
77
+ if _cached_route_metadata is None or _cached_middleware_metadata is None:
78
+ _initialize_route_metadata_cache()
79
+
80
+ route_metadata = _cached_route_metadata
81
+ middleware_metadata = _cached_middleware_metadata
82
+
83
+ # Handle case where metadata is not available in test mode
84
+ if route_metadata is None or middleware_metadata is None:
85
+ return ComponentStatus(
86
+ name="backend",
87
+ status=ComponentStatusType.HEALTHY,
88
+ message="FastAPI backend available (test mode)",
89
+ response_time_ms=None,
90
+ metadata={
91
+ "type": "component_check",
92
+ "environment": "test",
93
+ "note": "Backend component loaded successfully",
94
+ "route_introspection": "unavailable",
95
+ "middleware_introspection": "unavailable",
96
+ },
97
+ )
98
+
99
+ # Create message with both route and middleware info
100
+ message_parts = [f"{route_metadata.total_routes} routes"]
101
+ if middleware_metadata.security_count > 0:
102
+ message_parts.append(
103
+ f"{middleware_metadata.security_count} security layers"
104
+ )
105
+
106
+ return ComponentStatus(
107
+ name="backend",
108
+ status=ComponentStatusType.HEALTHY,
109
+ message=(
110
+ f"FastAPI backend available (test mode): "
111
+ f"{', '.join(message_parts)}"
112
+ ),
113
+ response_time_ms=None,
114
+ metadata={
115
+ "type": "component_check",
116
+ "environment": "test",
117
+ "note": "Backend component loaded successfully",
118
+ **route_metadata.model_dump_for_metadata(),
119
+ **middleware_metadata.model_dump_for_metadata(),
120
+ },
121
+ )
122
+ except Exception as e:
123
+ logger.warning(
124
+ f"Could not get route and middleware metadata in test mode: {e}"
125
+ )
126
+ return ComponentStatus(
127
+ name="backend",
128
+ status=ComponentStatusType.HEALTHY,
129
+ message="FastAPI backend available (test mode)",
130
+ response_time_ms=None,
131
+ metadata={
132
+ "type": "component_check",
133
+ "environment": "test",
134
+ "note": "Backend component loaded successfully",
135
+ "route_introspection_error": str(e),
136
+ "middleware_introspection_error": str(e),
137
+ },
138
+ )
139
+
140
+ # In production, use cached route and middleware information
141
+ try:
142
+ # Initialize cache if not already done (fallback safety)
143
+ if _cached_route_metadata is None or _cached_middleware_metadata is None:
144
+ logger.warning(
145
+ "Route and middleware metadata cache not initialized at startup, "
146
+ "initializing now..."
147
+ )
148
+ _initialize_route_metadata_cache()
149
+
150
+ route_metadata = _cached_route_metadata
151
+ middleware_metadata = _cached_middleware_metadata
152
+
153
+ # Handle case where metadata is still not available
154
+ if route_metadata is None or middleware_metadata is None:
155
+ return ComponentStatus(
156
+ name="backend",
157
+ status=ComponentStatusType.HEALTHY,
158
+ message=(
159
+ "FastAPI backend active (introspection unavailable)"
160
+ ),
161
+ response_time_ms=None,
162
+ metadata={
163
+ "type": "internal_component_check",
164
+ "note": (
165
+ "Backend is running but route/middleware metadata unavailable"
166
+ ),
167
+ "check_method": "internal_execution",
168
+ },
169
+ )
170
+
171
+ # Create descriptive message based on route and middleware data
172
+ total_routes = route_metadata.total_routes
173
+ total_endpoints = route_metadata.total_endpoints
174
+ method_counts = route_metadata.method_counts
175
+ security_count = middleware_metadata.security_count
176
+
177
+ # Format method summary (e.g., "12 GET, 5 POST")
178
+ method_summary = ", ".join([
179
+ f"{count} {method}"
180
+ for method, count in sorted(method_counts.items())
181
+ ])
182
+
183
+ message_parts = [f"{total_routes} routes"]
184
+ if total_endpoints != total_routes:
185
+ message_parts.append(f"{total_endpoints} endpoints")
186
+ if security_count > 0:
187
+ message_parts.append(f"{security_count} security layers")
188
+ if method_summary:
189
+ message_parts.append(f"({method_summary})")
190
+
191
+ message = f"FastAPI backend active: {', '.join(message_parts)}"
192
+
193
+ return ComponentStatus(
194
+ name="backend",
195
+ status=ComponentStatusType.HEALTHY,
196
+ message=message,
197
+ response_time_ms=None,
198
+ metadata={
199
+ "type": "internal_component_check",
200
+ "note": (
201
+ "Backend is running since this health check executed"
202
+ ),
203
+ "check_method": "internal_execution",
204
+ **route_metadata.model_dump_for_metadata(),
205
+ **middleware_metadata.model_dump_for_metadata(),
206
+ },
207
+ )
208
+ except Exception as e:
209
+ return ComponentStatus(
210
+ name="backend",
211
+ status=ComponentStatusType.UNHEALTHY,
212
+ message=f"Backend component check failed: {str(e)}",
213
+ response_time_ms=None,
214
+ metadata={
215
+ "type": "internal_component_check",
216
+ "error": "import_or_execution_error",
217
+ "error_details": str(e),
218
+ },
219
+ )
220
+
221
+
222
+ async def _frontend_component_health() -> ComponentStatus:
223
+ """
224
+ Flet frontend health check.
225
+
226
+ Since the frontend runs in the same process as the backend,
227
+ we check if the frontend component is properly initialized.
228
+ """
229
+ try:
230
+ # Check if frontend component is available
231
+ from app.components.frontend.main import create_frontend_app
232
+
233
+ # Verify the frontend app factory function works
234
+ create_frontend_app()
235
+
236
+ return ComponentStatus(
237
+ name="frontend",
238
+ status=ComponentStatusType.HEALTHY,
239
+ message="Flet frontend component available",
240
+ response_time_ms=None,
241
+ metadata={
242
+ "type": "component_check",
243
+ "framework": "flet",
244
+ "note": "Frontend integrated with FastAPI",
245
+ },
246
+ )
247
+
248
+ except ImportError as e:
249
+ return ComponentStatus(
250
+ name="frontend",
251
+ status=ComponentStatusType.UNHEALTHY,
252
+ message="Frontend component not found",
253
+ response_time_ms=None,
254
+ metadata={
255
+ "type": "component_check",
256
+ "error": "import_error",
257
+ "error_details": str(e),
258
+ },
259
+ )
260
+ except Exception as e:
261
+ return ComponentStatus(
262
+ name="frontend",
263
+ status=ComponentStatusType.UNHEALTHY,
264
+ message=f"Frontend component error: {str(e)}",
265
+ response_time_ms=None,
266
+ metadata={
267
+ "type": "component_check",
268
+ "error": "unexpected_error",
269
+ "error_details": str(e),
270
+ },
271
+ )
272
+
273
+ {%- if include_scheduler %}
274
+
275
+
276
+ async def _scheduler_component_health() -> ComponentStatus:
277
+ """
278
+ Check scheduler component health from backend.
279
+ {%- if scheduler_backend != "memory" %}
280
+
281
+ With persistence: Gets real task data from database
282
+ {%- else %}
283
+
284
+ Without persistence: Shows component as activated only
285
+ {%- endif %}
286
+ """
287
+ {% if scheduler_backend != "memory" %}
288
+ try:
289
+ from app.services.scheduler.task_monitor import TaskHealthMonitor
290
+ monitor = TaskHealthMonitor()
291
+ # No scheduler instance available in backend
292
+ health_data = await monitor.get_health_metadata(None)
293
+
294
+ # Format message based on task data
295
+ total_tasks = health_data.total_tasks
296
+ if total_tasks > 0:
297
+ message = f"Scheduler running with {total_tasks} tasks"
298
+ else:
299
+ message = "Scheduler running (no tasks)"
300
+
301
+ return ComponentStatus(
302
+ name="scheduler",
303
+ status=ComponentStatusType.HEALTHY,
304
+ message=message,
305
+ response_time_ms=None,
306
+ metadata=health_data.model_dump(),
307
+ )
308
+ except Exception as e:
309
+ logger.error("Failed to get scheduler health data", error=str(e))
310
+ # Fallback to basic status
311
+ return ComponentStatus(
312
+ name="scheduler",
313
+ status=ComponentStatusType.WARNING,
314
+ message="Scheduler enabled, health data unavailable",
315
+ response_time_ms=None,
316
+ metadata={
317
+ "type": "component_status",
318
+ "error": "health_check_failed",
319
+ "error_details": str(e),
320
+ },
321
+ )
322
+ {% else %}
323
+ return ComponentStatus(
324
+ name="scheduler",
325
+ status=ComponentStatusType.HEALTHY,
326
+ message="Scheduler component activated",
327
+ response_time_ms=None,
328
+ metadata={
329
+ "type": "component_status",
330
+ "deployment": "separate_container",
331
+ "status": "activated",
332
+ "note": "Memory-only mode - no cross-container health monitoring",
333
+ },
334
+ )
335
+ {% endif %}
336
+ {%- endif %}
337
+
338
+
339
+ async def startup_hook() -> None:
340
+ """
341
+ Auto-detect available components and register their health checks.
342
+
343
+ Always registers core components (backend, frontend) and detects
344
+ optional components using Python's import system.
345
+ """
346
+ logger.info("Registering component health checks...")
347
+
348
+ # Initialize route metadata cache once at startup
349
+ _initialize_route_metadata_cache()
350
+
351
+ # Always register core components
352
+ register_health_check("backend", _backend_component_health)
353
+ logger.info("Backend component health check registered")
354
+
355
+ register_health_check("frontend", _frontend_component_health)
356
+ logger.info("️Frontend component health check registered")
357
+
358
+ {%- if include_scheduler %}
359
+ # Register scheduler component health check
360
+ register_health_check("scheduler", _scheduler_component_health)
361
+ {% if scheduler_backend != "memory" %}
362
+ logger.info("Scheduler component health check registered (with task data)")
363
+ {% else %}
364
+ logger.info("Scheduler component enabled (shows as activated)")
365
+ {% endif %}
366
+ {%- endif %}
367
+
368
+ {%- if include_worker %}
369
+ # Register worker health check (shows queue status and job metrics)
370
+ from app.services.system.health import check_worker_health
371
+ register_health_check("worker", check_worker_health)
372
+ logger.info("Worker component health check registered")
373
+ {%- endif %}
374
+
375
+ {%- if include_redis %}
376
+ # Register cache health check (Redis connectivity and operations)
377
+ from app.services.system.health import check_cache_health
378
+ register_health_check("cache", check_cache_health)
379
+ logger.info("Cache component health check registered")
380
+ {%- endif %}
381
+
382
+ {%- if include_database %}
383
+ # Register database health check (SQLite connectivity and basic operations)
384
+ from app.services.system.health import check_database_health
385
+ register_health_check("database", check_database_health)
386
+ logger.info("Database component health check registered")
387
+ {% endif %}
388
+
389
+ logger.info("✅ Component health detection complete")
390
+
391
+ # ==========================================
392
+ # Service Health Checks Registration
393
+ # ==========================================
394
+
395
+ from app.services.system.health import register_service_health_check
396
+
397
+ logger.info("🔧 Registering service health checks...")
398
+
399
+ {%- if include_auth %}
400
+ # Register auth service health check
401
+ from app.services.auth.health import check_auth_service_health
402
+ register_service_health_check("auth", check_auth_service_health)
403
+ logger.info("Auth service health check registered")
404
+ {%- endif %}
405
+
406
+ {%- if include_ai %}
407
+ # Register AI service health check
408
+ from app.services.ai.health import check_ai_service_health
409
+ register_service_health_check("ai", check_ai_service_health)
410
+ logger.info("AI service health check registered")
411
+ {%- endif %}
412
+
413
+ # Future services will be registered here:
414
+ # Example:
415
+ # from app.services.payment.health import check_payment_service_health
416
+ # register_service_health_check("payment", check_payment_service_health)
417
+
418
+ logger.info("✅ Service health detection complete")