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,131 @@
1
+ [project]
2
+ name = "{{ cookiecutter.project_slug }}"
3
+ version = "{{ cookiecutter.version }}"
4
+ description = "{{ cookiecutter.project_description }}"
5
+ readme = "README.md"
6
+ requires-python = ">={{ cookiecutter.python_version }}"
7
+ authors = [
8
+ {name = "{{ cookiecutter.author_name }}", email = "{{ cookiecutter.author_email }}"}
9
+ ]
10
+
11
+ dependencies = [
12
+ "fastapi==0.116.1",
13
+ "flet[all]==0.28.3",
14
+ "uvicorn==0.35.0",
15
+ "structlog==25.4.0",
16
+ "pydantic-settings==2.10.1",
17
+ "typer==0.16.0",
18
+ # Pin click to avoid yanked versions (typer dependency)
19
+ "click==8.2.1",
20
+ # Rich CLI formatting
21
+ "rich==14.1.0",
22
+ # HTTP client for CLI API calls
23
+ "httpx==0.28.1",
24
+ # System monitoring
25
+ "psutil==7.0.0",
26
+ {%- if cookiecutter.include_scheduler == "yes" %}
27
+ "apscheduler==3.10.4",
28
+ {%- endif %}
29
+ {%- if cookiecutter.include_redis == "yes" %}
30
+ "redis==5.0.8",
31
+ {%- endif %}
32
+ {%- if cookiecutter.include_worker == "yes" %}
33
+ "arq==0.25.0",
34
+ {%- endif %}
35
+ {%- if cookiecutter.include_database == "yes" %}
36
+ "sqlmodel>=0.0.14",
37
+ "sqlalchemy>=2.0.0",
38
+ "aiosqlite>=0.19.0",
39
+ "greenlet>=1.1.0",
40
+ {%- endif %}
41
+ {%- if cookiecutter.include_cache == "yes" %}
42
+ "redis[hiredis]==5.0.8",
43
+ {%- endif %}
44
+ {%- if cookiecutter.include_auth == "yes" %}
45
+ "python-jose[cryptography]==3.3.0",
46
+ "passlib[bcrypt]==1.7.4",
47
+ "bcrypt<5.0.0", # Pin to 4.x due to passlib compatibility
48
+ "python-multipart==0.0.9",
49
+ "email-validator==2.2.0",
50
+ "alembic==1.16.5",
51
+ {%- endif %}
52
+ {%- if cookiecutter.include_ai == "yes" %}
53
+ "pydantic-ai-slim[{{cookiecutter.ai_providers}}]==1.0.10",
54
+ "marko==2.1.3",
55
+ {%- endif %}
56
+ ]
57
+
58
+ [project.scripts]
59
+ {{cookiecutter.project_slug}} = "app.cli.main:main"
60
+
61
+ [project.optional-dependencies]
62
+ dev = [
63
+ "pytest==8.4.1",
64
+ "pytest-asyncio==1.1.0",
65
+ "ruff==0.12.7",
66
+ "ty",
67
+ "types-psutil==7.0.0.20250801",
68
+ "pre-commit==4.2.0",
69
+ "pip-audit==2.9.0",
70
+ {%- if cookiecutter.include_worker == "yes" %}
71
+ # Development auto-reload for workers
72
+ "watchdog==4.0.2",
73
+ {%- endif %}
74
+ ]
75
+ docs = [
76
+ "mkdocs==1.6.1",
77
+ "mkdocs-material==9.6.16",
78
+ "mkdocstrings[python]==0.30.0",
79
+ "mkdocs-gen-files==0.5.0",
80
+ "pymdown-extensions==10.16.1",
81
+ ]
82
+
83
+ [build-system]
84
+ requires = ["hatchling"]
85
+ build-backend = "hatchling.build"
86
+
87
+ [tool.hatch.build.targets.wheel]
88
+ packages = ["app"]
89
+
90
+ [tool.ruff]
91
+ line-length = 88
92
+ target-version = "py{{ cookiecutter.python_version.replace('.', '') }}"
93
+
94
+ [tool.ruff.lint]
95
+ select = ["E", "F", "I", "N", "W", "UP"] # Added UP for pyupgrade (modern Python syntax)
96
+ ignore = []
97
+ fixable = ["ALL"]
98
+ unfixable = []
99
+
100
+ [tool.ruff.format]
101
+ quote-style = "double"
102
+ indent-style = "space"
103
+ skip-magic-trailing-comma = false
104
+ line-ending = "auto"
105
+
106
+ [tool.ruff.lint.isort]
107
+ force-single-line = false
108
+ force-sort-within-sections = true
109
+ known-first-party = ["app"]
110
+
111
+ [tool.ty.rules]
112
+ # Configure ty to ignore only genuine false positives or unavoidable issues
113
+ # Real type issues should be fixed, not ignored
114
+ unresolved-import = "ignore" # Missing third-party type stubs (unavoidable)
115
+ unresolved-attribute = "ignore" # Some dynamic attribute access in Flet is unavoidable
116
+ invalid-argument-type = "ignore" # Flet dynamic attributes and colors
117
+ non-subscriptable = "ignore" # Flet page.controls access patterns
118
+ unsupported-operator = "ignore" # Flet value comparisons with None
119
+ possibly-unbound-attribute = "ignore" # Flet dynamic component properties
120
+ call-non-callable = "ignore" # Dynamic middleware registration patterns
121
+ invalid-return-type = "ignore" # Flet component return type flexibility
122
+
123
+
124
+ [tool.pytest.ini_options]
125
+ asyncio_mode = "auto"
126
+ testpaths = ["tests"]
127
+
128
+ [dependency-groups]
129
+ dev = [
130
+ "httpx>=0.28.1",
131
+ ]
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # More comprehensive venv cleanup to prevent Docker container conflicts
6
+ if [ -d ".venv" ]; then
7
+ echo "🧹 Found existing .venv directory, checking compatibility..."
8
+
9
+ # Check if .venv has issues (broken symlinks, wrong Python version, etc.)
10
+ if [ -L ".venv/bin/python3" ] && [ ! -e ".venv/bin/python3" ]; then
11
+ echo "🧹 Cleaning up broken venv symlinks..."
12
+ rm -rf .venv
13
+ elif [ -f ".venv/bin/python3" ]; then
14
+ # Check if the Python executable is compatible and accessible
15
+ if ! .venv/bin/python3 --version > /dev/null 2>&1; then
16
+ echo "🧹 Cleaning up incompatible venv..."
17
+ rm -rf .venv
18
+ fi
19
+ elif [ ! -w ".venv" ] || [ ! -x ".venv" ]; then
20
+ # Check for permission issues in Docker containers
21
+ echo "🧹 Cleaning up venv with permission issues..."
22
+ rm -rf .venv
23
+ else
24
+ # If directory exists but has no python executable, clean it up
25
+ if [ ! -f ".venv/bin/python3" ] && [ ! -f ".venv/bin/python" ]; then
26
+ echo "🧹 Cleaning up incomplete venv..."
27
+ rm -rf .venv
28
+ fi
29
+ fi
30
+ fi
31
+
32
+ # Configure UV environment based on execution context
33
+ if [ -n "$DOCKER_CONTAINER" ] || [ "$USER" = "root" ]; then
34
+ echo "🐳 Running in Docker container, configuring UV for containerized environment..."
35
+
36
+ # Set Docker-specific UV configuration
37
+ export UV_PROJECT_ENVIRONMENT=/code/.venv
38
+ export UV_LINK_MODE=copy
39
+ export VIRTUAL_ENV=/code/.venv
40
+
41
+ # Ensure .venv path is in PATH for CLI commands
42
+ export PATH="/code/.venv/bin:$PATH"
43
+
44
+ echo "✅ UV configured for Docker: UV_PROJECT_ENVIRONMENT=/code/.venv"
45
+ else
46
+ echo "🖥️ Running in local environment, UV will use project defaults"
47
+
48
+ # Ensure we don't inherit Docker environment variables
49
+ unset UV_PROJECT_ENVIRONMENT
50
+ unset UV_SYSTEM_PYTHON
51
+
52
+ # Let UV auto-detect local .venv
53
+ echo "✅ UV configured for local development"
54
+ fi
55
+
56
+ # Pop run_command from arguments
57
+ run_command="$1"
58
+ shift
59
+
60
+ if [ "$run_command" = "webserver" ]; then
61
+ # Web server (FastAPI + Flet)
62
+ uv run python -m app.entrypoints.webserver
63
+ elif [ "$run_command" = "scheduler" ]; then
64
+ # Scheduler component
65
+ uv run python -m app.entrypoints.scheduler
66
+ elif [ "$run_command" = "lint" ]; then
67
+ uv run ruff check .
68
+ elif [ "$run_command" = "typecheck" ]; then
69
+ uv run mypy .
70
+ elif [ "$run_command" = "test" ]; then
71
+ uv run pytest "$@"
72
+ elif [ "$run_command" = "health" ]; then
73
+ uv run python -m app.cli.health check "$@"
74
+ elif [ "$run_command" = "help" ]; then
75
+ echo "Available commands:"
76
+ echo " webserver - Run FastAPI + Flet web server"
77
+ echo " scheduler - Run scheduler component"
78
+ echo " health - Check system health status"
79
+ echo " lint - Run ruff linting"
80
+ echo " typecheck - Run mypy type checking"
81
+ echo " test - Run pytest test suite"
82
+ echo " help - Show this help message"
83
+ else
84
+ echo "Unknown command: $run_command"
85
+ echo "Available commands: webserver, scheduler, health, lint, typecheck, test, help"
86
+ exit 1
87
+ fi
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # More comprehensive venv cleanup to prevent Docker container conflicts
6
+ if [ -d ".venv" ]; then
7
+ echo "🧹 Found existing .venv directory, checking compatibility..."
8
+
9
+ # Check if .venv has issues (broken symlinks, wrong Python version, etc.)
10
+ if [ -L ".venv/bin/python3" ] && [ ! -e ".venv/bin/python3" ]; then
11
+ echo "🧹 Cleaning up broken venv symlinks..."
12
+ rm -rf .venv
13
+ elif [ -f ".venv/bin/python3" ]; then
14
+ # Check if the Python executable is compatible and accessible
15
+ if ! .venv/bin/python3 --version > /dev/null 2>&1; then
16
+ echo "🧹 Cleaning up incompatible venv..."
17
+ rm -rf .venv
18
+ fi
19
+ elif [ ! -w ".venv" ] || [ ! -x ".venv" ]; then
20
+ # Check for permission issues in Docker containers
21
+ echo "🧹 Cleaning up venv with permission issues..."
22
+ rm -rf .venv
23
+ else
24
+ # If directory exists but has no python executable, clean it up
25
+ if [ ! -f ".venv/bin/python3" ] && [ ! -f ".venv/bin/python" ]; then
26
+ echo "🧹 Cleaning up incomplete venv..."
27
+ rm -rf .venv
28
+ fi
29
+ fi
30
+ fi
31
+
32
+ # Configure UV for Docker container (this is always a Docker entrypoint)
33
+ echo "🐳 Configuring UV for Docker container..."
34
+
35
+ # Set Docker-specific UV configuration
36
+ export UV_PROJECT_ENVIRONMENT=/code/.venv
37
+ export UV_LINK_MODE=copy
38
+ export VIRTUAL_ENV=/code/.venv
39
+
40
+ # Ensure .venv path is in PATH for CLI commands
41
+ export PATH="/code/.venv/bin:$PATH"
42
+
43
+ echo "✅ UV configured for Docker: UV_PROJECT_ENVIRONMENT=/code/.venv"
44
+
45
+ # Pop run_command from arguments
46
+ run_command="$1"
47
+ shift
48
+
49
+ if [ "$run_command" = "webserver" ]; then
50
+ # Web server (FastAPI + Flet)
51
+ uv run python -m app.entrypoints.webserver
52
+ elif [ "$run_command" = "scheduler" ]; then
53
+ # Scheduler component
54
+ uv run python -m app.entrypoints.scheduler
55
+ elif [ "$run_command" = "worker" ]; then
56
+ # Worker component using STANDARD arq CLI
57
+ queue_type="${1:-system}" # Default to system queue if not specified
58
+ shift
59
+
60
+ # Build the module path for the queue
61
+ worker_module="app.components.worker.queues.${queue_type}.WorkerSettings"
62
+
63
+ # Development mode auto-reload (APP_ENV from .env or WORKER_WATCH override)
64
+ if [ "$APP_ENV" = "dev" ] || [ "$WORKER_WATCH" = "true" ]; then
65
+ echo "🔄 Starting ${queue_type} worker with auto-reload..."
66
+ exec uv run python -m arq "${worker_module}" --watch /code/app "$@"
67
+ else
68
+ echo "🚀 Starting ${queue_type} worker..."
69
+ exec uv run python -m arq "${worker_module}" "$@"
70
+ fi
71
+ elif [ "$run_command" = "lint" ]; then
72
+ uv run ruff check .
73
+ elif [ "$run_command" = "typecheck" ]; then
74
+ uv run mypy .
75
+ elif [ "$run_command" = "test" ]; then
76
+ uv run pytest "$@"
77
+ elif [ "$run_command" = "health" ]; then
78
+ uv run python -m app.cli.health check "$@"
79
+ elif [ "$run_command" = "help" ]; then
80
+ echo "Available commands:"
81
+ echo " webserver - Run FastAPI + Flet web server"
82
+ echo " scheduler - Run scheduler component"
83
+ echo " worker - Run arq worker (standard arq CLI patterns)"
84
+ echo " health - Check system health status"
85
+ echo " lint - Run ruff linting"
86
+ echo " typecheck - Run mypy type checking"
87
+ echo " test - Run pytest test suite"
88
+ echo " help - Show this help message"
89
+ else
90
+ echo "Unknown command: $run_command"
91
+ echo "Available commands: webserver, scheduler, worker, health, lint, typecheck, test, help"
92
+ exit 1
93
+ fi
@@ -0,0 +1,16 @@
1
+ # scripts/gen_docs.py
2
+ """
3
+ A script to dynamically generate documentation files for MkDocs.
4
+ This is run automatically by the mkdocs-gen-files plugin.
5
+ """
6
+
7
+ import mkdocs_gen_files # noqa: F401
8
+
9
+ print("--- Running gen_docs.py ---")
10
+
11
+ # Copy the root README.md to be the documentation's index page.
12
+ # This allows us to maintain a single source of truth for the project's
13
+ # main landing page, which is visible on both GitHub and the docs site.
14
+ with open("README.md") as readme, open("docs/index.md", "w") as index:
15
+ index.write(readme.read())
16
+ print("✓ Copied README.md to docs/index.md")
@@ -0,0 +1,307 @@
1
+ """
2
+ Tests for authentication API endpoints.
3
+
4
+ This module tests the auth endpoints including user registration,
5
+ login, token validation, and protected endpoint access.
6
+ """
7
+
8
+ import pytest
9
+ from app.core.security import create_access_token
10
+ from app.models.user import UserCreate
11
+ from app.services.auth.user_service import UserService
12
+ from fastapi import status
13
+ from fastapi.testclient import TestClient
14
+ from sqlmodel import Session
15
+ from sqlmodel.ext.asyncio.session import AsyncSession
16
+
17
+
18
+ class TestAuthEndpoints:
19
+ """Test auth API endpoints."""
20
+
21
+ @pytest.mark.asyncio
22
+ async def test_register_new_user(
23
+ self, async_client_with_db: TestClient, async_db_session: AsyncSession
24
+ ):
25
+ """Test user registration with valid data."""
26
+ user_data = {
27
+ "email": "test@example.com",
28
+ "full_name": "Test User",
29
+ "password": "testpassword123"
30
+ }
31
+
32
+ response = async_client_with_db.post("/api/v1/auth/register", json=user_data)
33
+
34
+ assert response.status_code == status.HTTP_200_OK
35
+ data = response.json()
36
+ assert data["email"] == user_data["email"]
37
+ assert data["full_name"] == user_data["full_name"]
38
+ assert data["is_active"] is True
39
+ assert "id" in data
40
+ assert "created_at" in data
41
+ # Ensure password is not returned
42
+ assert "password" not in data
43
+ assert "hashed_password" not in data
44
+
45
+ @pytest.mark.asyncio
46
+ async def test_register_duplicate_email(
47
+ self, async_client_with_db: TestClient, async_db_session: AsyncSession
48
+ ):
49
+ """Test registration with already existing email."""
50
+ # Create a user first
51
+ user_service = UserService(async_db_session)
52
+ existing_user = UserCreate(
53
+ email="existing@example.com",
54
+ full_name="Existing User",
55
+ password="password123"
56
+ )
57
+ await user_service.create_user(existing_user)
58
+
59
+ # Try to register with same email
60
+ duplicate_user_data = {
61
+ "email": "existing@example.com",
62
+ "full_name": "Another User",
63
+ "password": "differentpassword"
64
+ }
65
+
66
+ response = async_client_with_db.post(
67
+ "/api/v1/auth/register", json=duplicate_user_data
68
+ )
69
+
70
+ assert response.status_code == status.HTTP_400_BAD_REQUEST
71
+ assert "already registered" in response.json()["detail"].lower()
72
+
73
+ @pytest.mark.asyncio
74
+ async def test_register_invalid_data(self, async_client_with_db: TestClient):
75
+ """Test registration with invalid data."""
76
+ invalid_user_data = {
77
+ "email": "not-an-email", # Invalid email format
78
+ "password": "123" # Too short
79
+ }
80
+
81
+ response = async_client_with_db.post(
82
+ "/api/v1/auth/register", json=invalid_user_data
83
+ )
84
+
85
+ assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
86
+
87
+ @pytest.mark.asyncio
88
+ async def test_login_valid_credentials(
89
+ self, async_client_with_db: TestClient, async_db_session: AsyncSession
90
+ ):
91
+ """Test login with valid credentials."""
92
+ # Create a user first
93
+ user_service = UserService(async_db_session)
94
+ user_data = UserCreate(
95
+ email="login@example.com",
96
+ full_name="Login User",
97
+ password="loginpassword123"
98
+ )
99
+ await user_service.create_user(user_data)
100
+
101
+ # Login with valid credentials
102
+ login_data = {
103
+ "username": "login@example.com", # OAuth2 uses username field
104
+ "password": "loginpassword123"
105
+ }
106
+
107
+ response = async_client_with_db.post("/api/v1/auth/token", data=login_data)
108
+
109
+ assert response.status_code == status.HTTP_200_OK
110
+ data = response.json()
111
+ assert "access_token" in data
112
+ assert data["token_type"] == "bearer"
113
+ assert len(data["access_token"]) > 0
114
+
115
+ @pytest.mark.asyncio
116
+ async def test_login_invalid_email(self, async_client_with_db: TestClient):
117
+ """Test login with non-existent email."""
118
+ login_data = {
119
+ "username": "nonexistent@example.com",
120
+ "password": "somepassword"
121
+ }
122
+
123
+ response = async_client_with_db.post("/api/v1/auth/token", data=login_data)
124
+
125
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
126
+ assert "incorrect" in response.json()["detail"].lower()
127
+
128
+ @pytest.mark.asyncio
129
+ async def test_login_invalid_password(
130
+ self, async_client_with_db: TestClient, async_db_session: AsyncSession
131
+ ):
132
+ """Test login with wrong password."""
133
+ # Create a user first
134
+ user_service = UserService(async_db_session)
135
+ user_data = UserCreate(
136
+ email="wrongpass@example.com",
137
+ full_name="Wrong Pass User",
138
+ password="correctpassword"
139
+ )
140
+ await user_service.create_user(user_data)
141
+
142
+ # Login with wrong password
143
+ login_data = {
144
+ "username": "wrongpass@example.com",
145
+ "password": "wrongpassword"
146
+ }
147
+
148
+ response = async_client_with_db.post("/api/v1/auth/token", data=login_data)
149
+
150
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
151
+ assert "incorrect" in response.json()["detail"].lower()
152
+
153
+ @pytest.mark.asyncio
154
+ async def test_get_current_user_valid_token(
155
+ self, async_client_with_db: TestClient, async_db_session: AsyncSession
156
+ ):
157
+ """Test getting current user with valid token."""
158
+ # Create a user first
159
+ user_service = UserService(async_db_session)
160
+ user_data = UserCreate(
161
+ email="currentuser@example.com",
162
+ full_name="Current User",
163
+ password="password123"
164
+ )
165
+ created_user = await user_service.create_user(user_data)
166
+
167
+ # Create a valid token
168
+ token = create_access_token(data={"sub": created_user.email})
169
+
170
+ # Get current user
171
+ response = async_client_with_db.get(
172
+ "/api/v1/auth/me",
173
+ headers={"Authorization": f"Bearer {token}"}
174
+ )
175
+
176
+ assert response.status_code == status.HTTP_200_OK
177
+ data = response.json()
178
+ assert data["email"] == created_user.email
179
+ assert data["full_name"] == created_user.full_name
180
+ assert data["id"] == created_user.id
181
+
182
+ @pytest.mark.asyncio
183
+ async def test_get_current_user_invalid_token(
184
+ self, async_client_with_db: TestClient
185
+ ):
186
+ """Test getting current user with invalid token."""
187
+ response = async_client_with_db.get(
188
+ "/api/v1/auth/me",
189
+ headers={"Authorization": "Bearer invalid_token"}
190
+ )
191
+
192
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
193
+
194
+ @pytest.mark.asyncio
195
+ async def test_get_current_user_no_token(self, async_client_with_db: TestClient):
196
+ """Test getting current user without token."""
197
+ response = async_client_with_db.get("/api/v1/auth/me")
198
+
199
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
200
+
201
+ @pytest.mark.asyncio
202
+ async def test_get_current_user_expired_token(
203
+ self, async_client_with_db: TestClient, async_db_session: AsyncSession
204
+ ):
205
+ """Test getting current user with expired token."""
206
+ # Create a user first
207
+ user_service = UserService(async_db_session)
208
+ user_data = UserCreate(
209
+ email="expiredtoken@example.com",
210
+ full_name="Expired Token User",
211
+ password="password123"
212
+ )
213
+ created_user = await user_service.create_user(user_data)
214
+
215
+ # Create an expired token (negative expiry)
216
+ from datetime import timedelta
217
+ expired_token = create_access_token(
218
+ data={"sub": created_user.email},
219
+ expires_delta=timedelta(seconds=-1)
220
+ )
221
+
222
+ # Try to get current user with expired token
223
+ response = async_client_with_db.get(
224
+ "/api/v1/auth/me",
225
+ headers={"Authorization": f"Bearer {expired_token}"}
226
+ )
227
+
228
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
229
+
230
+
231
+ class TestAuthIntegration:
232
+ """Integration tests for auth flow."""
233
+
234
+ @pytest.mark.asyncio
235
+ async def test_full_auth_flow(
236
+ self, async_client_with_db: TestClient, async_db_session: AsyncSession
237
+ ):
238
+ """Test complete auth flow: register -> login -> protected endpoint."""
239
+ # Step 1: Register new user
240
+ user_data = {
241
+ "email": "fullflow@example.com",
242
+ "full_name": "Full Flow User",
243
+ "password": "fullflowpassword123"
244
+ }
245
+
246
+ register_response = async_client_with_db.post(
247
+ "/api/v1/auth/register", json=user_data
248
+ )
249
+ assert register_response.status_code == status.HTTP_200_OK
250
+
251
+ # Step 2: Login with credentials
252
+ login_data = {
253
+ "username": user_data["email"],
254
+ "password": user_data["password"]
255
+ }
256
+
257
+ login_response = async_client_with_db.post(
258
+ "/api/v1/auth/token", data=login_data
259
+ )
260
+ assert login_response.status_code == status.HTTP_200_OK
261
+
262
+ token = login_response.json()["access_token"]
263
+
264
+ # Step 3: Access protected endpoint
265
+ me_response = async_client_with_db.get(
266
+ "/api/v1/auth/me",
267
+ headers={"Authorization": f"Bearer {token}"}
268
+ )
269
+
270
+ assert me_response.status_code == status.HTTP_200_OK
271
+ user_info = me_response.json()
272
+ assert user_info["email"] == user_data["email"]
273
+ assert user_info["full_name"] == user_data["full_name"]
274
+
275
+ @pytest.mark.asyncio
276
+ async def test_user_persistence_across_requests(
277
+ self, async_client_with_db: TestClient, async_db_session: AsyncSession
278
+ ):
279
+ """Test that users are properly persisted in database."""
280
+ # Register user
281
+ user_data = {
282
+ "email": "persistent@example.com",
283
+ "full_name": "Persistent User",
284
+ "password": "persistentpassword"
285
+ }
286
+
287
+ async_client_with_db.post("/api/v1/auth/register", json=user_data)
288
+
289
+ # Login in separate request
290
+ login_data = {
291
+ "username": user_data["email"],
292
+ "password": user_data["password"]
293
+ }
294
+
295
+ login_response = async_client_with_db.post(
296
+ "/api/v1/auth/token", data=login_data
297
+ )
298
+ assert login_response.status_code == status.HTTP_200_OK
299
+
300
+ # Verify user exists in database
301
+ user_service = UserService(async_db_session)
302
+ db_user = await user_service.get_user_by_email(user_data["email"])
303
+
304
+ assert db_user is not None
305
+ assert db_user.email == user_data["email"]
306
+ assert db_user.full_name == user_data["full_name"]
307
+ assert db_user.is_active is True