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,265 @@
1
+ """
2
+ Pydantic models for load test data structures.
3
+
4
+ Provides type safety and validation for load test configurations,
5
+ results, and analysis data.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from app.components.worker.constants import LoadTestTypes
11
+ from app.core.config import get_load_test_queue
12
+ from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator
13
+
14
+
15
+ class LoadTestError(Exception):
16
+ """Custom exception for load test operations."""
17
+
18
+ pass
19
+
20
+
21
+ class LoadTestConfiguration(BaseModel):
22
+ """Load test configuration with validation and defaults."""
23
+
24
+ num_tasks: int = Field(
25
+ default=100, ge=10, le=10000, description="Number of tasks to spawn"
26
+ )
27
+ task_type: LoadTestTypes = Field(
28
+ default=LoadTestTypes.CPU_INTENSIVE, description="Type of load test to run"
29
+ )
30
+ batch_size: int = Field(default=10, ge=1, le=100, description="Tasks per batch")
31
+ delay_ms: int = Field(
32
+ default=0, ge=0, le=5000, description="Delay between batches (ms)"
33
+ )
34
+ target_queue: str | None = Field(
35
+ default=None, description="Target queue for testing"
36
+ )
37
+
38
+ @field_validator("target_queue")
39
+ @classmethod
40
+ def set_default_queue(cls, v: str | None) -> str:
41
+ """Set default queue if not specified."""
42
+ return v if v is not None else get_load_test_queue()
43
+
44
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
45
+ """Convert configuration to dictionary for task enqueueing."""
46
+ data = super().model_dump(**kwargs)
47
+ # Convert enum to string value for task enqueueing
48
+ data["task_type"] = self.task_type.value
49
+ return data
50
+
51
+
52
+ class LoadTestMetrics(BaseModel):
53
+ """Metrics from load test execution."""
54
+
55
+ tasks_sent: int = Field(..., ge=0, description="Total tasks enqueued")
56
+ tasks_completed: int = Field(..., ge=0, description="Successfully completed tasks")
57
+ tasks_failed: int = Field(0, ge=0, description="Failed tasks")
58
+ total_duration_seconds: float = Field(..., ge=0, description="Total test duration")
59
+ overall_throughput: float = Field(
60
+ 0, ge=0, description="Overall throughput (tasks/sec)"
61
+ )
62
+ failure_rate_percent: float = Field(
63
+ 0, ge=0, le=100, description="Failure rate percentage"
64
+ )
65
+ completion_percentage: float = Field(
66
+ 0, ge=0, le=100, description="Completion percentage"
67
+ )
68
+ average_throughput_per_second: float = Field(
69
+ 0, ge=0, description="Average throughput"
70
+ )
71
+ monitor_duration_seconds: float = Field(0, ge=0, description="Monitoring duration")
72
+
73
+ @field_validator("tasks_completed")
74
+ @classmethod
75
+ def completed_not_exceed_sent(cls, v: int, info: ValidationInfo) -> int:
76
+ """Ensure completed tasks don't exceed sent tasks."""
77
+ if info.data and "tasks_sent" in info.data and v > info.data["tasks_sent"]:
78
+ raise ValueError(
79
+ f"Completed tasks ({v}) cannot exceed sent tasks "
80
+ f"({info.data['tasks_sent']})"
81
+ )
82
+ return v
83
+
84
+ @field_validator("tasks_failed")
85
+ @classmethod
86
+ def failed_not_exceed_sent(cls, v: int, info: ValidationInfo) -> int:
87
+ """Ensure failed tasks don't exceed sent tasks."""
88
+ if info.data and "tasks_sent" in info.data and v > info.data["tasks_sent"]:
89
+ raise ValueError(
90
+ f"Failed tasks ({v}) cannot exceed sent tasks "
91
+ f"({info.data['tasks_sent']})"
92
+ )
93
+ return v
94
+
95
+ @field_validator("failure_rate_percent")
96
+ @classmethod
97
+ def validate_failure_rate_consistency(cls, v: float, info: ValidationInfo) -> float:
98
+ """Ensure failure rate percentage matches task counts."""
99
+ if info.data and "tasks_sent" in info.data and "tasks_failed" in info.data:
100
+ tasks_sent = info.data["tasks_sent"]
101
+ tasks_failed = info.data["tasks_failed"]
102
+ if tasks_sent > 0:
103
+ calculated_rate = (tasks_failed / tasks_sent) * 100
104
+ # Allow small floating point differences (within 0.1%)
105
+ if abs(v - calculated_rate) > 0.1:
106
+ raise ValueError(
107
+ f"Failure rate {v}% doesn't match task counts "
108
+ f"({tasks_failed}/{tasks_sent} = {calculated_rate:.1f}%)"
109
+ )
110
+ return v
111
+
112
+
113
+ class PerformanceAnalysis(BaseModel):
114
+ """Performance analysis results."""
115
+
116
+ throughput_rating: str = Field(
117
+ ...,
118
+ pattern=r"^(unknown|poor|fair|good|excellent)$",
119
+ description="Throughput performance rating",
120
+ )
121
+ efficiency_rating: str = Field(
122
+ ...,
123
+ pattern=r"^(unknown|poor|fair|good|excellent)$",
124
+ description="Task completion efficiency",
125
+ )
126
+ queue_pressure: str = Field(
127
+ ...,
128
+ pattern=r"^(unknown|low|medium|high)$",
129
+ description="Queue saturation level",
130
+ )
131
+
132
+
133
+ class ValidationStatus(BaseModel):
134
+ """Test execution validation status."""
135
+
136
+ test_type_verified: bool = Field(
137
+ default=False, description="Test type executed correctly"
138
+ )
139
+ expected_metrics_present: bool = Field(
140
+ default=False, description="Expected metrics are present"
141
+ )
142
+ performance_signature_match: str = Field(
143
+ default="unknown",
144
+ pattern=r"^(unknown|verified|partial|failed)$",
145
+ description="Performance matches expected patterns",
146
+ )
147
+ issues: list[str] = Field(default_factory=list, description="Validation issues")
148
+
149
+
150
+ class TestTypeInfo(BaseModel):
151
+ """Information about a specific test type."""
152
+
153
+ name: str = Field(..., description="Human-readable test name")
154
+ description: str = Field(..., description="Test description")
155
+ expected_metrics: list[str] = Field(..., description="Expected result metrics")
156
+ performance_signature: str = Field(..., description="Expected performance pattern")
157
+ typical_duration_ms: str = Field(..., description="Typical execution time")
158
+ concurrency_impact: str = Field(..., description="Concurrency characteristics")
159
+ validation_keys: list[str] = Field(..., description="Keys for result validation")
160
+
161
+
162
+ class LoadTestAnalysis(BaseModel):
163
+ """Complete load test analysis."""
164
+
165
+ test_type_info: TestTypeInfo = Field(..., description="Test type information")
166
+ performance_analysis: PerformanceAnalysis = Field(
167
+ ..., description="Performance analysis"
168
+ )
169
+ validation_status: ValidationStatus = Field(..., description="Validation results")
170
+ recommendations: list[str] = Field(..., description="Improvement recommendations")
171
+
172
+
173
+ class LoadTestResult(BaseModel):
174
+ """Complete load test result with analysis."""
175
+
176
+ task: str = Field(default="load_test_orchestrator", description="Task name")
177
+ status: str = Field(
178
+ ...,
179
+ pattern=r"^(completed|failed|timed_out)$",
180
+ description="Test execution status",
181
+ )
182
+ test_id: str = Field(..., description="Unique test identifier")
183
+ configuration: LoadTestConfiguration = Field(..., description="Test configuration")
184
+ metrics: LoadTestMetrics = Field(..., description="Execution metrics")
185
+ start_time: str | None = Field(None, description="Test start time")
186
+ end_time: str | None = Field(None, description="Test end time")
187
+ task_ids: list[str] = Field(default_factory=list, description="Individual task IDs")
188
+ error: str | None = Field(None, description="Error message if failed")
189
+ analysis: LoadTestAnalysis | None = Field(None, description="Performance analysis")
190
+
191
+ @model_validator(mode="after")
192
+ def validate_status_consistency(self) -> "LoadTestResult":
193
+ """Validate status consistency with error field."""
194
+ if self.status == "failed" and not self.error:
195
+ raise ValueError("Failed status requires error message")
196
+ return self
197
+
198
+
199
+ class OrchestratorRawResult(BaseModel):
200
+ """Raw orchestrator result format for transformation."""
201
+
202
+ test_id: str = Field(..., description="Test identifier")
203
+ task_type: str = Field(..., description="Task type executed")
204
+ tasks_sent: int = Field(..., description="Tasks enqueued")
205
+ tasks_completed: int = Field(0, description="Successfully completed")
206
+ tasks_failed: int = Field(0, description="Failed tasks")
207
+ total_duration_seconds: float = Field(..., description="Total duration")
208
+ overall_throughput_per_second: float = Field(0, description="Overall throughput")
209
+ failure_rate_percent: float = Field(0, description="Failure rate")
210
+ completion_percentage: float = Field(0, description="Completion rate")
211
+ average_throughput_per_second: float = Field(0, description="Average throughput")
212
+ monitor_duration_seconds: float = Field(0, description="Monitor duration")
213
+ batch_size: int = Field(1, description="Batch size used")
214
+ delay_ms: int = Field(0, description="Delay between batches")
215
+ target_queue: str = Field(..., description="Target queue")
216
+ start_time: str | None = Field(None, description="Start time")
217
+ end_time: str | None = Field(None, description="End time")
218
+ task_ids: list[str] = Field(default_factory=list, description="Task IDs")
219
+
220
+ def to_load_test_result(self) -> LoadTestResult:
221
+ """Transform to standard LoadTestResult format."""
222
+ configuration = LoadTestConfiguration(
223
+ task_type=LoadTestTypes(self.task_type),
224
+ num_tasks=self.tasks_sent,
225
+ batch_size=self.batch_size,
226
+ delay_ms=self.delay_ms,
227
+ target_queue=self.target_queue,
228
+ )
229
+
230
+ metrics = LoadTestMetrics(
231
+ tasks_sent=self.tasks_sent,
232
+ tasks_completed=self.tasks_completed,
233
+ tasks_failed=self.tasks_failed,
234
+ total_duration_seconds=self.total_duration_seconds,
235
+ overall_throughput=self.overall_throughput_per_second,
236
+ failure_rate_percent=self.failure_rate_percent,
237
+ completion_percentage=self.completion_percentage,
238
+ average_throughput_per_second=self.average_throughput_per_second,
239
+ monitor_duration_seconds=self.monitor_duration_seconds,
240
+ )
241
+
242
+ return LoadTestResult(
243
+ status="completed",
244
+ test_id=self.test_id,
245
+ configuration=configuration,
246
+ metrics=metrics,
247
+ start_time=self.start_time,
248
+ end_time=self.end_time,
249
+ task_ids=self.task_ids,
250
+ error=None,
251
+ analysis=None,
252
+ )
253
+
254
+
255
+ class LoadTestErrorModel(BaseModel):
256
+ """Load test error result with partial information."""
257
+
258
+ task: str = Field(default="load_test_orchestrator", description="Task name")
259
+ status: str = Field(
260
+ ..., pattern=r"^(failed|timed_out)$", description="Error status"
261
+ )
262
+ test_id: str = Field(..., description="Unique test identifier")
263
+ error: str = Field(..., description="Error message")
264
+ partial_info: str | None = Field(None, description="Partial completion info")
265
+ tasks_sent: int | None = Field(None, ge=0, description="Tasks that were sent")
@@ -0,0 +1,21 @@
1
+ {%- if scheduler_backend != "memory" %}
2
+ """Scheduler service layer for async task management."""
3
+
4
+ from .models import (
5
+ APSchedulerJob,
6
+ ScheduledTask,
7
+ TaskStatistics,
8
+ SchedulerHealthMetadata,
9
+ UpcomingTask,
10
+ )
11
+ from .scheduled_task_manager import ScheduledTaskManager
12
+
13
+ __all__ = [
14
+ "ScheduledTaskManager",
15
+ "ScheduledTask",
16
+ "TaskStatistics",
17
+ "APSchedulerJob",
18
+ "SchedulerHealthMetadata",
19
+ "UpcomingTask",
20
+ ]
21
+ {% endif %}
@@ -0,0 +1,119 @@
1
+ """Models for scheduled task management - both database and service layer."""
2
+
3
+ import pickle
4
+ from datetime import datetime
5
+ from typing import Any, Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+ {%- if scheduler_backend != "memory" %}
9
+ from sqlalchemy import Column, Float, LargeBinary, String
10
+ from sqlmodel import Field as SQLField, SQLModel
11
+ {% endif %}
12
+
13
+
14
+ {%- if scheduler_backend != "memory" %}
15
+ # ============================================================================
16
+ # DATABASE MODELS (SQLModel)
17
+ # ============================================================================
18
+
19
+ class APSchedulerJob(SQLModel, table=True):
20
+ """
21
+ Mirror of APScheduler's job persistence table.
22
+ This model allows us to read APScheduler data using SQLModel's async capabilities.
23
+
24
+ Note: APScheduler creates this table, we only read from it.
25
+ The table structure matches APScheduler 3.x exactly.
26
+ """
27
+ __tablename__ = "apscheduler_jobs"
28
+
29
+ # Use SA Column types for exact compatibility with APScheduler's table structure
30
+ id: str = SQLField(
31
+ sa_column=Column(String(191), primary_key=True)
32
+ )
33
+ next_run_time: float | None = SQLField(
34
+ sa_column=Column(Float(25), nullable=True, index=True)
35
+ )
36
+ job_state: bytes = SQLField(
37
+ sa_column=Column(LargeBinary, nullable=False)
38
+ )
39
+
40
+ def get_job_data(self) -> dict[str, Any]:
41
+ """
42
+ Deserialize the pickled job state.
43
+
44
+ Returns:
45
+ dict: Job data with standard APScheduler keys.
46
+
47
+ Note:
48
+ This is safe because we're reading APScheduler's own data format.
49
+ APScheduler writes pickled job data to this table - we're just reading it.
50
+ """
51
+ return pickle.loads(self.job_state) # type: ignore[no-any-return]
52
+
53
+
54
+ # ============================================================================
55
+ # SERVICE LAYER MODELS (Pydantic)
56
+ # ============================================================================
57
+
58
+ class ScheduledTask(BaseModel):
59
+ """
60
+ Represents a scheduled task with all relevant information.
61
+ This is the clean data model used by CLI/API consumers.
62
+ """
63
+ job_id: str = Field(..., description="Unique task identifier")
64
+ name: str = Field(..., description="Human-readable task name")
65
+ function: str = Field(..., description="Function reference (module:function)")
66
+ schedule: str = Field(..., description="Human-readable schedule (e.g., 'Every 5m')")
67
+ trigger_type: Literal["interval", "cron", "date", "unknown"] = Field(
68
+ ..., description="Type of scheduling trigger"
69
+ )
70
+ next_run_time: datetime | None = Field(
71
+ None, description="Next scheduled execution time"
72
+ )
73
+ status: Literal["active", "paused"] = Field(
74
+ ..., description="Current task status"
75
+ )
76
+ max_instances: int = Field(
77
+ 1, description="Maximum concurrent instances allowed"
78
+ )
79
+ coalesce: bool = Field(
80
+ True, description="Whether to coalesce missed runs"
81
+ )
82
+
83
+ @property
84
+ def is_active(self) -> bool:
85
+ """Check if the task is currently active."""
86
+ return self.status == "active"
87
+
88
+
89
+ class TaskStatistics(BaseModel):
90
+ """Statistics about scheduled tasks in the system."""
91
+ total_tasks: int = Field(0, description="Total number of scheduled tasks")
92
+ active_tasks: int = Field(0, description="Number of active tasks")
93
+ paused_tasks: int = Field(0, description="Number of paused tasks")
94
+
95
+
96
+ {% endif %}
97
+
98
+
99
+ # ============================================================================
100
+ # HEALTH MONITORING MODELS (Available for all scheduler modes)
101
+ # ============================================================================
102
+
103
+ class UpcomingTask(BaseModel):
104
+ """Information about an upcoming scheduled task."""
105
+ job_id: str = Field(..., description="Task identifier")
106
+ name: str = Field(..., description="Human-readable task name")
107
+ next_run: str = Field(..., description="ISO datetime string of next execution")
108
+ schedule: str = Field(..., description="Human-readable schedule description")
109
+
110
+
111
+ class SchedulerHealthMetadata(BaseModel):
112
+ """Health metadata for scheduler components."""
113
+ total_tasks: int = Field(0, description="Total number of scheduled tasks")
114
+ active_tasks: int = Field(0, description="Number of active tasks")
115
+ paused_tasks: int = Field(0, description="Number of paused tasks")
116
+ upcoming_tasks: list[UpcomingTask] = Field(
117
+ default_factory=list, description="List of upcoming tasks (max 5)"
118
+ )
119
+ scheduler_state: str = Field("unknown", description="Scheduler state description")
@@ -0,0 +1,273 @@
1
+ {%- if scheduler_backend != "memory" %}
2
+ """Async scheduled task manager using SQLModel for database abstraction."""
3
+
4
+ from datetime import datetime
5
+ from typing import Any
6
+
7
+ from sqlalchemy import inspect
8
+ from sqlmodel import select
9
+ from sqlmodel.ext.asyncio.session import AsyncSession
10
+
11
+ from app.core.db import async_engine, get_async_session
12
+ from app.core.log import logger
13
+
14
+ from .models import APSchedulerJob, ScheduledTask, TaskStatistics
15
+
16
+
17
+ class ScheduledTaskManager:
18
+ """
19
+ Manages scheduled tasks via async database operations.
20
+
21
+ Uses SQLModel for database abstraction, supporting SQLite, PostgreSQL, MySQL.
22
+ Only available when scheduler persistence is enabled - reads from
23
+ apscheduler_jobs table.
24
+ """
25
+ async def has_persistence(self) -> bool:
26
+ """Check if apscheduler_jobs table exists."""
27
+ try:
28
+ async with async_engine.begin() as conn:
29
+ # Use inspector to check tables
30
+ tables = await conn.run_sync(
31
+ lambda sync_conn: inspect(sync_conn).get_table_names()
32
+ )
33
+ return "apscheduler_jobs" in tables
34
+ except Exception as e:
35
+ logger.error(f"Error checking persistence: {e}")
36
+ return False
37
+ async def list_tasks(self) -> list[ScheduledTask]:
38
+ """
39
+ List all scheduled tasks from the database.
40
+
41
+ Returns:
42
+ list[ScheduledTask]: List of all scheduled tasks with their details.
43
+
44
+ Raises:
45
+ RuntimeError: If persistence is not available.
46
+ """
47
+ if not await self.has_persistence():
48
+ raise RuntimeError(
49
+ "Scheduled task listing requires persistence. "
50
+ "The apscheduler_jobs table was not found in the database."
51
+ )
52
+
53
+ async with get_async_session() as session:
54
+ # Query all jobs ordered by next run time (nulls last)
55
+ result = await session.exec(
56
+ select(APSchedulerJob).order_by(
57
+ APSchedulerJob.next_run_time.desc().nulls_last() # type: ignore[union-attr]
58
+ )
59
+ )
60
+ jobs = result.all()
61
+
62
+ tasks = []
63
+ for job in jobs:
64
+ try:
65
+ job_data = job.get_job_data()
66
+ task = ScheduledTask(
67
+ job_id=job.id,
68
+ name=job_data.get("name", job.id),
69
+ function=job_data.get("func", "unknown"),
70
+ schedule=self._format_trigger(job_data.get("trigger")),
71
+ trigger_type=self._get_trigger_type(job_data.get("trigger")),
72
+ next_run_time=(
73
+ datetime.fromtimestamp(job.next_run_time)
74
+ if job.next_run_time else None
75
+ ),
76
+ status="active" if job.next_run_time else "paused",
77
+ max_instances=job_data.get("max_instances", 1),
78
+ coalesce=job_data.get("coalesce", True),
79
+ )
80
+ tasks.append(task)
81
+ except Exception as e:
82
+ logger.error(f"Error processing job {job.id}: {e}")
83
+ continue
84
+
85
+ return tasks
86
+
87
+ async def get_task(self, task_id: str) -> ScheduledTask | None:
88
+ """
89
+ Get a specific task by ID.
90
+
91
+ Args:
92
+ task_id: The unique identifier of the task.
93
+
94
+ Returns:
95
+ ScheduledTask | None: The task if found, None otherwise.
96
+ """
97
+ async with get_async_session() as session:
98
+ result = await session.exec(
99
+ select(APSchedulerJob).where(APSchedulerJob.id == task_id)
100
+ )
101
+ job = result.first()
102
+
103
+ if not job:
104
+ return None
105
+
106
+ try:
107
+ job_data = job.get_job_data()
108
+ return ScheduledTask(
109
+ job_id=job.id,
110
+ name=job_data.get("name", job.id),
111
+ function=job_data.get("func", "unknown"),
112
+ schedule=self._format_trigger(job_data.get("trigger")),
113
+ trigger_type=self._get_trigger_type(job_data.get("trigger")),
114
+ next_run_time=(
115
+ datetime.fromtimestamp(job.next_run_time)
116
+ if job.next_run_time else None
117
+ ),
118
+ status="active" if job.next_run_time else "paused",
119
+ max_instances=job_data.get("max_instances", 1),
120
+ coalesce=job_data.get("coalesce", True),
121
+ )
122
+ except Exception as e:
123
+ logger.error(f"Error processing job {task_id}: {e}")
124
+ return None
125
+
126
+ async def get_statistics(self) -> TaskStatistics:
127
+ """
128
+ Get task statistics.
129
+
130
+ Returns:
131
+ TaskStatistics: Summary statistics about all scheduled tasks.
132
+ """
133
+ # TODO: Optimize with SQL aggregation queries instead of fetching all tasks
134
+ tasks = await self.list_tasks()
135
+ active = sum(1 for t in tasks if t.status == "active")
136
+ paused = sum(1 for t in tasks if t.status == "paused")
137
+
138
+ return TaskStatistics(
139
+ total_tasks=len(tasks),
140
+ active_tasks=active,
141
+ paused_tasks=paused,
142
+ )
143
+
144
+ def _format_trigger(self, trigger: Any) -> str:
145
+ """
146
+ Convert APScheduler trigger to human-readable string.
147
+
148
+ Args:
149
+ trigger: APScheduler trigger object (IntervalTrigger, CronTrigger, etc.)
150
+
151
+ Returns:
152
+ str: Human-readable schedule description.
153
+ """
154
+ if not trigger:
155
+ return "Unknown"
156
+
157
+ trigger_type = type(trigger).__name__
158
+
159
+ if trigger_type == "IntervalTrigger":
160
+ if hasattr(trigger, "interval"):
161
+ seconds = trigger.interval.total_seconds()
162
+ if seconds < 60:
163
+ return f"Every {int(seconds)}s"
164
+ elif seconds < 3600:
165
+ minutes = int(seconds / 60)
166
+ return f"Every {minutes}m"
167
+ elif seconds < 86400:
168
+ hours = seconds / 3600
169
+ if hours == int(hours):
170
+ return f"Every {int(hours)}h"
171
+ else:
172
+ return f"Every {hours:.1f}h"
173
+ else:
174
+ days = int(seconds / 86400)
175
+ return f"Every {days}d"
176
+
177
+ elif trigger_type == "CronTrigger":
178
+ if hasattr(trigger, "fields"):
179
+ parts = []
180
+ for field in trigger.fields:
181
+ field_str = str(field)
182
+ if field_str != "*":
183
+ parts.append(f"{field.name}={field_str}")
184
+ if parts:
185
+ return "Cron: " + ", ".join(parts)
186
+ return "Cron: * * * * *"
187
+
188
+ elif trigger_type == "DateTrigger":
189
+ if hasattr(trigger, "run_date"):
190
+ return f"Once at {trigger.run_date.strftime('%Y-%m-%d %H:%M:%S')}"
191
+
192
+ return trigger_type.replace("Trigger", "")
193
+
194
+ def _get_trigger_type(self, trigger: Any) -> str:
195
+ """
196
+ Extract trigger type as string.
197
+
198
+ Args:
199
+ trigger: APScheduler trigger object.
200
+
201
+ Returns:
202
+ str: Trigger type name (interval, cron, date, unknown).
203
+ """
204
+ if not trigger:
205
+ return "unknown"
206
+
207
+ trigger_name = type(trigger).__name__
208
+ if "Interval" in trigger_name:
209
+ return "interval"
210
+ elif "Cron" in trigger_name:
211
+ return "cron"
212
+ elif "Date" in trigger_name:
213
+ return "date"
214
+ return "unknown"
215
+
216
+ # Placeholder methods for CLI functionality
217
+ async def get_job_statistics(self, job_id: str) -> dict[str, Any]:
218
+ """Get statistics for a specific job."""
219
+ # TODO: Implement job-specific statistics
220
+ task = await self.get_task(job_id)
221
+ if not task:
222
+ return {}
223
+
224
+ return {
225
+ "job_id": job_id,
226
+ "total_executions": 0, # Would need execution history table
227
+ "successful_executions": 0,
228
+ "failed_executions": 0,
229
+ "success_rate_percent": 0.0,
230
+ "avg_duration_ms": 0.0,
231
+ "min_duration_ms": 0.0,
232
+ "max_duration_ms": 0.0,
233
+ "last_execution": "Never",
234
+ "next_run": (
235
+ task.next_run_time.isoformat()
236
+ if task.next_run_time
237
+ else "Not scheduled"
238
+ ),
239
+ }
240
+
241
+ async def get_overall_statistics(self) -> dict[str, Any]:
242
+ """Get overall scheduler statistics."""
243
+ stats = await self.get_statistics()
244
+ return {
245
+ "total_jobs": stats.total_tasks,
246
+ "active_jobs": stats.active_tasks,
247
+ "paused_jobs": stats.paused_tasks,
248
+ "total_executions": 0, # Would need execution history table
249
+ "successful_executions": 0,
250
+ "failed_executions": 0,
251
+ "success_rate_percent": 0.0,
252
+ "avg_duration_ms": 0.0,
253
+ "uptime": "Unknown",
254
+ "last_activity": "No recent activity",
255
+ }
256
+
257
+ async def get_job_history(
258
+ self, limit: int, job_id: str | None = None
259
+ ) -> list[dict[str, Any]]:
260
+ """Get recent job execution history."""
261
+ # TODO: Implement execution history - would need additional table
262
+ return []
263
+
264
+ async def trigger_job(
265
+ self, job_id: str, wait: bool = True, timeout: int = 30
266
+ ) -> dict[str, Any]:
267
+ """Trigger manual job execution."""
268
+ # TODO: Implement job triggering - would need scheduler integration
269
+ return {
270
+ "status": "error",
271
+ "error": "Manual job triggering not yet implemented",
272
+ }
273
+ {% endif %}