fastapi-fullstack 0.1.7__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 (241) hide show
  1. fastapi_fullstack-0.1.7.dist-info/METADATA +739 -0
  2. fastapi_fullstack-0.1.7.dist-info/RECORD +241 -0
  3. fastapi_fullstack-0.1.7.dist-info/WHEEL +4 -0
  4. fastapi_fullstack-0.1.7.dist-info/entry_points.txt +2 -0
  5. fastapi_fullstack-0.1.7.dist-info/licenses/LICENSE +21 -0
  6. fastapi_gen/__init__.py +3 -0
  7. fastapi_gen/cli.py +442 -0
  8. fastapi_gen/config.py +356 -0
  9. fastapi_gen/generator.py +207 -0
  10. fastapi_gen/prompts.py +874 -0
  11. fastapi_gen/template/VARIABLES.md +276 -0
  12. fastapi_gen/template/cookiecutter.json +93 -0
  13. fastapi_gen/template/hooks/post_gen_project.py +355 -0
  14. fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example +56 -0
  15. fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
  16. fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +109 -0
  17. fastapi_gen/template/{{cookiecutter.project_slug}}/AGENTS.md +55 -0
  18. fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +99 -0
  19. fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +315 -0
  20. fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +768 -0
  21. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
  22. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example +155 -0
  23. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
  24. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
  25. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
  26. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
  27. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
  28. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
  29. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
  30. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +447 -0
  31. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +23 -0
  32. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +226 -0
  33. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langchain_assistant.py +226 -0
  34. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/prompts.py +10 -0
  35. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
  36. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
  37. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
  38. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +541 -0
  39. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +98 -0
  40. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
  41. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
  42. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
  43. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +902 -0
  44. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
  45. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +498 -0
  46. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
  47. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
  48. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
  49. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
  50. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
  51. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
  52. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
  53. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
  54. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
  55. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
  56. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
  57. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
  58. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
  59. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
  60. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
  61. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
  62. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +267 -0
  63. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
  64. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
  65. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
  66. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
  67. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
  68. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
  69. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
  70. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
  71. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
  72. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
  73. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
  74. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
  75. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
  76. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
  77. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
  78. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
  79. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +130 -0
  80. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +334 -0
  81. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
  82. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
  83. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
  84. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
  85. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +838 -0
  86. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
  87. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
  88. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
  89. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
  90. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
  91. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
  92. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +192 -0
  93. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
  94. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
  95. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
  96. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
  97. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
  98. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
  99. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +850 -0
  100. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
  101. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
  102. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
  103. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
  104. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
  105. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
  106. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
  107. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
  108. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
  109. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
  110. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
  111. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
  112. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
  113. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +180 -0
  114. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
  115. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
  116. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
  117. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
  118. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
  119. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
  120. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
  121. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
  122. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
  123. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_admin.py +890 -0
  124. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +261 -0
  125. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
  126. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
  127. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
  128. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
  129. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
  130. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
  131. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
  132. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
  133. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
  134. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
  135. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +435 -0
  136. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
  137. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/adding_features.md +132 -0
  138. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/architecture.md +63 -0
  139. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/patterns.md +161 -0
  140. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/testing.md +120 -0
  141. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.dockerignore +40 -0
  142. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
  143. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
  144. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
  145. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
  146. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
  147. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +648 -0
  148. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
  149. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
  150. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
  151. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
  152. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
  153. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
  154. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
  155. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
  156. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +69 -0
  157. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
  158. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
  159. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/layout.tsx +11 -0
  160. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/login/page.tsx +5 -0
  161. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/register/page.tsx +5 -0
  162. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/chat/page.tsx +48 -0
  163. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/dashboard/page.tsx +99 -0
  164. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/layout.tsx +17 -0
  165. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/profile/page.tsx +152 -0
  166. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/auth/callback/page.tsx +113 -0
  167. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/layout.tsx +46 -0
  168. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/page.tsx +73 -0
  169. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
  170. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
  171. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
  172. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
  173. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
  174. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
  175. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
  176. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
  177. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
  178. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
  179. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +323 -0
  180. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +22 -0
  181. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
  182. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
  183. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
  184. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
  185. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +234 -0
  186. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +72 -0
  187. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +328 -0
  188. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/copy-button.tsx +46 -0
  189. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +11 -0
  190. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/local-conversation-sidebar.tsx +295 -0
  191. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/markdown-content.tsx +167 -0
  192. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +79 -0
  193. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
  194. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +79 -0
  195. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
  196. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
  197. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
  198. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +65 -0
  199. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
  200. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +82 -0
  201. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
  202. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
  203. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +105 -0
  204. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
  205. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
  206. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +56 -0
  207. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
  208. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +13 -0
  209. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
  210. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
  211. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/sheet.tsx +109 -0
  212. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +7 -0
  213. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
  214. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
  215. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +181 -0
  216. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-local-chat.ts +165 -0
  217. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
  218. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +37 -0
  219. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
  220. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
  221. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
  222. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
  223. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
  224. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +31 -0
  225. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
  226. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +64 -0
  227. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-sidebar-store.ts +17 -0
  228. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
  229. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
  230. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +9 -0
  231. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/local-chat-store.ts +255 -0
  232. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/sidebar-store.ts +17 -0
  233. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
  234. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
  235. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
  236. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +83 -0
  237. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
  238. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
  239. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
  240. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
  241. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.setup.ts +56 -0
fastapi_gen/prompts.py ADDED
@@ -0,0 +1,874 @@
1
+ """Interactive prompts for project configuration."""
2
+
3
+ import re
4
+ from typing import Any, cast
5
+
6
+ import questionary
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.text import Text
10
+
11
+ from .config import (
12
+ AdminEnvironmentType,
13
+ AIFrameworkType,
14
+ AuthType,
15
+ BackgroundTaskType,
16
+ CIType,
17
+ DatabaseType,
18
+ FrontendType,
19
+ LLMProviderType,
20
+ LogfireFeatures,
21
+ OAuthProvider,
22
+ ProjectConfig,
23
+ RateLimitStorageType,
24
+ ReverseProxyType,
25
+ WebSocketAuthType,
26
+ )
27
+
28
+ console = Console()
29
+
30
+
31
+ def show_header() -> None:
32
+ """Display the generator header."""
33
+ header = Text()
34
+ header.append("FastAPI Project Generator", style="bold cyan")
35
+ header.append("\n")
36
+ header.append("with Logfire Observability", style="dim")
37
+ console.print(Panel(header, title="[bold green]fastapi-gen[/]", border_style="green"))
38
+ console.print()
39
+
40
+
41
+ def _check_cancelled(value: Any) -> Any:
42
+ """Check if the user cancelled the prompt and raise KeyboardInterrupt if so."""
43
+ if value is None:
44
+ raise KeyboardInterrupt
45
+ return value
46
+
47
+
48
+ def _validate_project_name(name: str) -> bool | str:
49
+ """Validate project name input.
50
+
51
+ Returns True if valid, or an error message string if invalid.
52
+ Allows alphanumeric characters, underscores, spaces, and dashes.
53
+ First character must be a letter.
54
+ """
55
+ if not name:
56
+ return "Project name cannot be empty"
57
+ if not name[0].isalpha():
58
+ return "Project name must start with a letter"
59
+ if not all(c.isalnum() or c in "_- " for c in name):
60
+ return "Project name can only contain letters, numbers, underscores, spaces, and dashes"
61
+ return True
62
+
63
+
64
+ def _normalize_project_name(name: str) -> str:
65
+ """Normalize project name to lowercase with underscores."""
66
+ return name.lower().replace(" ", "_").replace("-", "_")
67
+
68
+
69
+ def _validate_email(email: str) -> bool | str:
70
+ """Validate email format.
71
+
72
+ Returns True if valid, or an error message string if invalid.
73
+ """
74
+ if not email:
75
+ return "Email cannot be empty"
76
+ pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
77
+ if not re.match(pattern, email):
78
+ return "Please enter a valid email address"
79
+ return True
80
+
81
+
82
+ def prompt_basic_info() -> dict[str, str]:
83
+ """Prompt for basic project information."""
84
+ console.print("[bold cyan]Basic Information[/]")
85
+ console.print()
86
+
87
+ raw_project_name = _check_cancelled(
88
+ questionary.text(
89
+ "Project name:",
90
+ validate=_validate_project_name,
91
+ ).ask()
92
+ )
93
+ project_name = _normalize_project_name(raw_project_name)
94
+
95
+ # Show converted name if it differs from input
96
+ if project_name != raw_project_name:
97
+ console.print(f" [dim]→ Will be saved as:[/] [cyan]{project_name}[/]")
98
+ console.print()
99
+
100
+ project_description = _check_cancelled(
101
+ questionary.text(
102
+ "Project description:",
103
+ default="My FastAPI project",
104
+ ).ask()
105
+ )
106
+
107
+ author_name = _check_cancelled(
108
+ questionary.text(
109
+ "Author name:",
110
+ default="Your Name",
111
+ ).ask()
112
+ )
113
+
114
+ author_email = _check_cancelled(
115
+ questionary.text(
116
+ "Author email:",
117
+ default="your@email.com",
118
+ validate=_validate_email,
119
+ ).ask()
120
+ )
121
+
122
+ return {
123
+ "project_name": project_name,
124
+ "project_description": project_description,
125
+ "author_name": author_name,
126
+ "author_email": author_email,
127
+ }
128
+
129
+
130
+ def prompt_database() -> DatabaseType:
131
+ """Prompt for database selection."""
132
+ console.print()
133
+ console.print("[bold cyan]Database Configuration[/]")
134
+ console.print()
135
+
136
+ choices = [
137
+ questionary.Choice("PostgreSQL (async - asyncpg)", value=DatabaseType.POSTGRESQL),
138
+ questionary.Choice("MongoDB (async - motor)", value=DatabaseType.MONGODB),
139
+ questionary.Choice("SQLite (sync)", value=DatabaseType.SQLITE),
140
+ questionary.Choice("None", value=DatabaseType.NONE),
141
+ ]
142
+
143
+ return cast(
144
+ DatabaseType,
145
+ _check_cancelled(
146
+ questionary.select(
147
+ "Select database:",
148
+ choices=choices,
149
+ default=choices[0],
150
+ ).ask()
151
+ ),
152
+ )
153
+
154
+
155
+ def prompt_auth() -> AuthType:
156
+ """Prompt for authentication method."""
157
+ console.print()
158
+ console.print("[bold cyan]Authentication[/]")
159
+ console.print()
160
+
161
+ choices = [
162
+ questionary.Choice("JWT + User Management", value=AuthType.JWT),
163
+ questionary.Choice("API Key (header-based)", value=AuthType.API_KEY),
164
+ questionary.Choice("Both (JWT + API Key fallback)", value=AuthType.BOTH),
165
+ questionary.Choice("None", value=AuthType.NONE),
166
+ ]
167
+
168
+ return cast(
169
+ AuthType,
170
+ _check_cancelled(
171
+ questionary.select(
172
+ "Select auth method:",
173
+ choices=choices,
174
+ default=choices[0],
175
+ ).ask()
176
+ ),
177
+ )
178
+
179
+
180
+ def prompt_oauth() -> OAuthProvider:
181
+ """Prompt for OAuth provider selection."""
182
+ console.print()
183
+ console.print("[bold cyan]OAuth2 Social Login[/]")
184
+ console.print()
185
+
186
+ choices = [
187
+ questionary.Choice("None (email/password only)", value=OAuthProvider.NONE),
188
+ questionary.Choice("Google OAuth2", value=OAuthProvider.GOOGLE),
189
+ ]
190
+
191
+ return cast(
192
+ OAuthProvider,
193
+ _check_cancelled(
194
+ questionary.select(
195
+ "Enable social login?",
196
+ choices=choices,
197
+ default=choices[0],
198
+ ).ask()
199
+ ),
200
+ )
201
+
202
+
203
+ def prompt_logfire() -> tuple[bool, LogfireFeatures]:
204
+ """Prompt for Logfire configuration."""
205
+ console.print()
206
+ console.print("[bold cyan]Observability (Logfire)[/]")
207
+ console.print()
208
+
209
+ enable_logfire = _check_cancelled(
210
+ questionary.confirm(
211
+ "Enable Logfire integration?",
212
+ default=True,
213
+ ).ask()
214
+ )
215
+
216
+ if not enable_logfire:
217
+ return False, LogfireFeatures()
218
+
219
+ features = _check_cancelled(
220
+ questionary.checkbox(
221
+ "Logfire features:",
222
+ choices=[
223
+ questionary.Choice("FastAPI instrumentation", value="fastapi", checked=True),
224
+ questionary.Choice("Database instrumentation", value="database", checked=True),
225
+ questionary.Choice("Redis instrumentation", value="redis", checked=False),
226
+ questionary.Choice("Celery/Taskiq instrumentation", value="celery", checked=False),
227
+ questionary.Choice("HTTPX instrumentation", value="httpx", checked=False),
228
+ ],
229
+ ).ask()
230
+ )
231
+
232
+ return True, LogfireFeatures(
233
+ fastapi="fastapi" in features,
234
+ database="database" in features,
235
+ redis="redis" in features,
236
+ celery="celery" in features,
237
+ httpx="httpx" in features,
238
+ )
239
+
240
+
241
+ def prompt_background_tasks() -> BackgroundTaskType:
242
+ """Prompt for background task system."""
243
+ console.print()
244
+ console.print("[bold cyan]Background Tasks[/]")
245
+ console.print()
246
+
247
+ choices = [
248
+ questionary.Choice("None (use FastAPI BackgroundTasks)", value=BackgroundTaskType.NONE),
249
+ questionary.Choice("Celery (classic, battle-tested)", value=BackgroundTaskType.CELERY),
250
+ questionary.Choice("Taskiq (async-native, modern)", value=BackgroundTaskType.TASKIQ),
251
+ questionary.Choice("ARQ (lightweight async Redis)", value=BackgroundTaskType.ARQ),
252
+ ]
253
+
254
+ return cast(
255
+ BackgroundTaskType,
256
+ _check_cancelled(
257
+ questionary.select(
258
+ "Select background task system:",
259
+ choices=choices,
260
+ default=choices[0],
261
+ ).ask()
262
+ ),
263
+ )
264
+
265
+
266
+ def prompt_integrations() -> dict[str, bool]:
267
+ """Prompt for optional integrations."""
268
+ console.print()
269
+ console.print("[bold cyan]Optional Integrations[/]")
270
+ console.print()
271
+
272
+ features = _check_cancelled(
273
+ questionary.checkbox(
274
+ "Select additional features:",
275
+ choices=[
276
+ questionary.Choice("Redis (caching/sessions)", value="redis"),
277
+ questionary.Choice("Caching (fastapi-cache2)", value="caching"),
278
+ questionary.Choice("Rate limiting (slowapi)", value="rate_limiting"),
279
+ questionary.Choice(
280
+ "Pagination (fastapi-pagination)", value="pagination", checked=True
281
+ ),
282
+ questionary.Choice("Sentry (error tracking)", value="sentry"),
283
+ questionary.Choice("Prometheus (metrics)", value="prometheus"),
284
+ questionary.Choice("Admin Panel (SQLAdmin)", value="admin_panel"),
285
+ questionary.Choice("WebSockets", value="websockets"),
286
+ questionary.Choice("File Storage (S3/MinIO)", value="file_storage"),
287
+ questionary.Choice("AI Agent (PydanticAI/LangChain)", value="ai_agent"),
288
+ questionary.Choice("Webhooks (outbound events)", value="webhooks"),
289
+ questionary.Choice("Example CRUD (Item model)", value="example_crud", checked=True),
290
+ questionary.Choice("CORS middleware", value="cors", checked=True),
291
+ questionary.Choice("orjson (faster JSON)", value="orjson", checked=True),
292
+ ],
293
+ ).ask()
294
+ )
295
+
296
+ return {
297
+ "enable_redis": "redis" in features,
298
+ "enable_caching": "caching" in features,
299
+ "enable_rate_limiting": "rate_limiting" in features,
300
+ "enable_pagination": "pagination" in features,
301
+ "enable_sentry": "sentry" in features,
302
+ "enable_prometheus": "prometheus" in features,
303
+ "enable_admin_panel": "admin_panel" in features,
304
+ "enable_websockets": "websockets" in features,
305
+ "enable_file_storage": "file_storage" in features,
306
+ "enable_ai_agent": "ai_agent" in features,
307
+ "enable_webhooks": "webhooks" in features,
308
+ "include_example_crud": "example_crud" in features,
309
+ "enable_cors": "cors" in features,
310
+ "enable_orjson": "orjson" in features,
311
+ }
312
+
313
+
314
+ def _validate_positive_integer(value: str) -> bool | str:
315
+ """Validate that input is a positive integer.
316
+
317
+ Returns True if valid, or an error message string if invalid.
318
+ """
319
+ if not value:
320
+ return "Value cannot be empty"
321
+ if not value.isdigit():
322
+ return "Must be a positive number"
323
+ if int(value) <= 0:
324
+ return "Must be greater than 0"
325
+ return True
326
+
327
+
328
+ def prompt_rate_limit_config(redis_enabled: bool) -> tuple[int, int, RateLimitStorageType]:
329
+ """Prompt for rate limiting configuration.
330
+
331
+ Args:
332
+ redis_enabled: Whether Redis is enabled (affects storage choices).
333
+
334
+ Returns:
335
+ Tuple of (requests, period, storage).
336
+ """
337
+ console.print()
338
+ console.print("[bold cyan]Rate Limiting Configuration[/]")
339
+ console.print()
340
+
341
+ requests_str = _check_cancelled(
342
+ questionary.text(
343
+ "Requests per period:",
344
+ default="100",
345
+ validate=_validate_positive_integer,
346
+ ).ask()
347
+ )
348
+
349
+ period_str = _check_cancelled(
350
+ questionary.text(
351
+ "Period in seconds:",
352
+ default="60",
353
+ validate=_validate_positive_integer,
354
+ ).ask()
355
+ )
356
+
357
+ # Build storage choices based on whether Redis is enabled
358
+ storage_choices = [
359
+ questionary.Choice("Memory (single instance)", value=RateLimitStorageType.MEMORY),
360
+ ]
361
+ if redis_enabled:
362
+ storage_choices.append(
363
+ questionary.Choice("Redis (distributed)", value=RateLimitStorageType.REDIS)
364
+ )
365
+
366
+ storage = cast(
367
+ RateLimitStorageType,
368
+ _check_cancelled(
369
+ questionary.select(
370
+ "Storage backend:",
371
+ choices=storage_choices,
372
+ default=storage_choices[0],
373
+ ).ask()
374
+ ),
375
+ )
376
+
377
+ return int(requests_str), int(period_str), storage
378
+
379
+
380
+ def prompt_dev_tools() -> dict[str, Any]:
381
+ """Prompt for development tools."""
382
+ console.print()
383
+ console.print("[bold cyan]Development Tools[/]")
384
+ console.print()
385
+
386
+ features = _check_cancelled(
387
+ questionary.checkbox(
388
+ "Include dev tools:",
389
+ choices=[
390
+ questionary.Choice("pytest + fixtures", value="pytest", checked=True),
391
+ questionary.Choice("pre-commit hooks", value="precommit", checked=True),
392
+ questionary.Choice("Docker + docker-compose", value="docker", checked=True),
393
+ questionary.Choice("Kubernetes manifests", value="kubernetes"),
394
+ ],
395
+ ).ask()
396
+ )
397
+
398
+ ci_type = _check_cancelled(
399
+ questionary.select(
400
+ "CI/CD system:",
401
+ choices=[
402
+ questionary.Choice("GitHub Actions", value=CIType.GITHUB),
403
+ questionary.Choice("GitLab CI", value=CIType.GITLAB),
404
+ questionary.Choice("None", value=CIType.NONE),
405
+ ],
406
+ ).ask()
407
+ )
408
+
409
+ return {
410
+ "enable_pytest": "pytest" in features,
411
+ "enable_precommit": "precommit" in features,
412
+ "enable_docker": "docker" in features,
413
+ "enable_kubernetes": "kubernetes" in features,
414
+ "ci_type": ci_type,
415
+ }
416
+
417
+
418
+ def prompt_reverse_proxy() -> ReverseProxyType:
419
+ """Prompt for reverse proxy configuration."""
420
+ console.print()
421
+ console.print("[bold cyan]Reverse Proxy (Production)[/]")
422
+ console.print()
423
+
424
+ choices = [
425
+ questionary.Choice(
426
+ "Traefik (included in docker-compose)", value=ReverseProxyType.TRAEFIK_INCLUDED
427
+ ),
428
+ questionary.Choice(
429
+ "External Traefik (shared between projects)", value=ReverseProxyType.TRAEFIK_EXTERNAL
430
+ ),
431
+ questionary.Choice(
432
+ "None (expose ports directly, use own proxy)", value=ReverseProxyType.NONE
433
+ ),
434
+ ]
435
+
436
+ return cast(
437
+ ReverseProxyType,
438
+ _check_cancelled(
439
+ questionary.select(
440
+ "Select reverse proxy configuration:",
441
+ choices=choices,
442
+ default=choices[0],
443
+ ).ask()
444
+ ),
445
+ )
446
+
447
+
448
+ def prompt_frontend() -> FrontendType:
449
+ """Prompt for frontend framework selection."""
450
+ console.print()
451
+ console.print("[bold cyan]Frontend Framework[/]")
452
+ console.print()
453
+
454
+ choices = [
455
+ questionary.Choice("None (API only)", value=FrontendType.NONE),
456
+ questionary.Choice("Next.js 15 (App Router, TypeScript, Bun)", value=FrontendType.NEXTJS),
457
+ ]
458
+
459
+ return cast(
460
+ FrontendType,
461
+ _check_cancelled(
462
+ questionary.select(
463
+ "Select frontend framework:",
464
+ choices=choices,
465
+ default=choices[0],
466
+ ).ask()
467
+ ),
468
+ )
469
+
470
+
471
+ def prompt_frontend_features() -> dict[str, bool]:
472
+ """Prompt for frontend-specific features."""
473
+ console.print()
474
+ console.print("[bold cyan]Frontend Features[/]")
475
+ console.print()
476
+
477
+ features = _check_cancelled(
478
+ questionary.checkbox(
479
+ "Select frontend features:",
480
+ choices=[
481
+ questionary.Choice("i18n (internationalization with next-intl)", value="i18n"),
482
+ ],
483
+ ).ask()
484
+ )
485
+
486
+ return {
487
+ "enable_i18n": "i18n" in features,
488
+ }
489
+
490
+
491
+ def prompt_ai_framework() -> AIFrameworkType:
492
+ """Prompt for AI framework selection."""
493
+ console.print()
494
+ console.print("[bold cyan]AI Agent Framework[/]")
495
+ console.print()
496
+
497
+ choices = [
498
+ questionary.Choice("PydanticAI (recommended)", value=AIFrameworkType.PYDANTIC_AI),
499
+ questionary.Choice("LangChain", value=AIFrameworkType.LANGCHAIN),
500
+ ]
501
+
502
+ return cast(
503
+ AIFrameworkType,
504
+ _check_cancelled(
505
+ questionary.select(
506
+ "Select AI framework:",
507
+ choices=choices,
508
+ default=choices[0],
509
+ ).ask()
510
+ ),
511
+ )
512
+
513
+
514
+ def prompt_llm_provider(ai_framework: AIFrameworkType) -> LLMProviderType:
515
+ """Prompt for LLM provider selection.
516
+
517
+ Args:
518
+ ai_framework: The selected AI framework. OpenRouter is only
519
+ available for PydanticAI.
520
+ """
521
+ console.print()
522
+ console.print("[bold cyan]LLM Provider[/]")
523
+ console.print()
524
+
525
+ choices = [
526
+ questionary.Choice("OpenAI (gpt-4o-mini)", value=LLMProviderType.OPENAI),
527
+ questionary.Choice("Anthropic (claude-sonnet-4-5)", value=LLMProviderType.ANTHROPIC),
528
+ ]
529
+
530
+ # OpenRouter only available for PydanticAI
531
+ if ai_framework == AIFrameworkType.PYDANTIC_AI:
532
+ choices.append(
533
+ questionary.Choice("OpenRouter (multi-provider)", value=LLMProviderType.OPENROUTER)
534
+ )
535
+
536
+ return cast(
537
+ LLMProviderType,
538
+ _check_cancelled(
539
+ questionary.select(
540
+ "Select LLM provider:",
541
+ choices=choices,
542
+ default=choices[0],
543
+ ).ask()
544
+ ),
545
+ )
546
+
547
+
548
+ def prompt_websocket_auth() -> WebSocketAuthType:
549
+ """Prompt for WebSocket authentication method for AI Agent."""
550
+ console.print()
551
+ console.print("[bold cyan]AI Agent WebSocket Authentication[/]")
552
+ console.print()
553
+
554
+ choices = [
555
+ questionary.Choice("None (public access)", value=WebSocketAuthType.NONE),
556
+ questionary.Choice("JWT token required", value=WebSocketAuthType.JWT),
557
+ questionary.Choice("API Key required (query param)", value=WebSocketAuthType.API_KEY),
558
+ ]
559
+
560
+ return cast(
561
+ WebSocketAuthType,
562
+ _check_cancelled(
563
+ questionary.select(
564
+ "Select WebSocket authentication:",
565
+ choices=choices,
566
+ default=choices[0],
567
+ ).ask()
568
+ ),
569
+ )
570
+
571
+
572
+ def prompt_admin_config() -> tuple[AdminEnvironmentType, bool]:
573
+ """Prompt for admin panel configuration."""
574
+ console.print()
575
+ console.print("[bold cyan]Admin Panel Configuration[/]")
576
+ console.print()
577
+
578
+ env_choices = [
579
+ questionary.Choice(
580
+ "Development + Staging (recommended)", value=AdminEnvironmentType.DEV_STAGING
581
+ ),
582
+ questionary.Choice("Development only", value=AdminEnvironmentType.DEV_ONLY),
583
+ questionary.Choice("All environments", value=AdminEnvironmentType.ALL),
584
+ questionary.Choice("Disabled", value=AdminEnvironmentType.DISABLED),
585
+ ]
586
+
587
+ admin_environments = cast(
588
+ AdminEnvironmentType,
589
+ _check_cancelled(
590
+ questionary.select(
591
+ "Enable admin panel in which environments?",
592
+ choices=env_choices,
593
+ default=env_choices[0],
594
+ ).ask()
595
+ ),
596
+ )
597
+
598
+ # If disabled, skip auth question
599
+ if admin_environments == AdminEnvironmentType.DISABLED:
600
+ return admin_environments, False
601
+
602
+ require_auth = _check_cancelled(
603
+ questionary.confirm(
604
+ "Require authentication for admin panel? (superuser login)",
605
+ default=True,
606
+ ).ask()
607
+ )
608
+
609
+ return admin_environments, require_auth
610
+
611
+
612
+ def prompt_python_version() -> str:
613
+ """Prompt for Python version selection."""
614
+ console.print()
615
+ console.print("[bold cyan]Python Version[/]")
616
+ console.print()
617
+
618
+ choices = [
619
+ questionary.Choice("Python 3.12 (recommended)", value="3.12"),
620
+ questionary.Choice("Python 3.11", value="3.11"),
621
+ questionary.Choice("Python 3.13", value="3.13"),
622
+ ]
623
+
624
+ return cast(
625
+ str,
626
+ _check_cancelled(
627
+ questionary.select(
628
+ "Select Python version:",
629
+ choices=choices,
630
+ default=choices[0],
631
+ ).ask()
632
+ ),
633
+ )
634
+
635
+
636
+ def prompt_ports(has_frontend: bool) -> dict[str, int]:
637
+ """Prompt for port configuration."""
638
+ console.print()
639
+ console.print("[bold cyan]Port Configuration[/]")
640
+ console.print()
641
+
642
+ def validate_port(value: str) -> bool:
643
+ try:
644
+ port = int(value)
645
+ return 1024 <= port <= 65535
646
+ except ValueError:
647
+ return False
648
+
649
+ backend_port_str = _check_cancelled(
650
+ questionary.text(
651
+ "Backend port:",
652
+ default="8000",
653
+ validate=validate_port,
654
+ ).ask()
655
+ )
656
+
657
+ result = {"backend_port": int(backend_port_str)}
658
+
659
+ if has_frontend:
660
+ frontend_port_str = _check_cancelled(
661
+ questionary.text(
662
+ "Frontend port:",
663
+ default="3000",
664
+ validate=validate_port,
665
+ ).ask()
666
+ )
667
+ result["frontend_port"] = int(frontend_port_str)
668
+
669
+ return result
670
+
671
+
672
+ def run_interactive_prompts() -> ProjectConfig:
673
+ """Run all interactive prompts and return configuration."""
674
+ show_header()
675
+
676
+ # Basic info
677
+ basic_info = prompt_basic_info()
678
+
679
+ # Database
680
+ database = prompt_database()
681
+
682
+ # Auth
683
+ auth = prompt_auth()
684
+
685
+ # OAuth (only if JWT auth is enabled)
686
+ oauth_provider = OAuthProvider.NONE
687
+ enable_session_management = False
688
+ if auth in (AuthType.JWT, AuthType.BOTH):
689
+ oauth_provider = prompt_oauth()
690
+ # Session management (only if JWT and database enabled)
691
+ if database != DatabaseType.NONE:
692
+ enable_session_management = _check_cancelled(
693
+ questionary.confirm(
694
+ "Enable session management? (track active sessions, logout from devices)",
695
+ default=False,
696
+ ).ask()
697
+ )
698
+
699
+ # Logfire
700
+ enable_logfire, logfire_features = prompt_logfire()
701
+
702
+ # Background tasks
703
+ background_tasks = prompt_background_tasks()
704
+
705
+ # Integrations
706
+ integrations = prompt_integrations()
707
+
708
+ # Dev tools
709
+ dev_tools = prompt_dev_tools()
710
+
711
+ # Reverse proxy (only if Docker is enabled)
712
+ reverse_proxy = ReverseProxyType.TRAEFIK_INCLUDED
713
+ if dev_tools.get("enable_docker"):
714
+ reverse_proxy = prompt_reverse_proxy()
715
+
716
+ # Frontend
717
+ frontend = prompt_frontend()
718
+
719
+ # Python version
720
+ python_version = prompt_python_version()
721
+
722
+ # Port configuration
723
+ ports = prompt_ports(has_frontend=frontend != FrontendType.NONE)
724
+
725
+ # Auto-enable Redis for Celery/Taskiq/ARQ (they require Redis as broker)
726
+ if background_tasks in (
727
+ BackgroundTaskType.CELERY,
728
+ BackgroundTaskType.TASKIQ,
729
+ BackgroundTaskType.ARQ,
730
+ ):
731
+ integrations["enable_redis"] = True
732
+
733
+ # AI framework, LLM provider, WebSocket auth and conversation persistence for AI Agent
734
+ ai_framework = AIFrameworkType.PYDANTIC_AI
735
+ llm_provider = LLMProviderType.OPENAI
736
+ websocket_auth = WebSocketAuthType.NONE
737
+ enable_conversation_persistence = False
738
+ if integrations.get("enable_ai_agent"):
739
+ ai_framework = prompt_ai_framework()
740
+ llm_provider = prompt_llm_provider(ai_framework)
741
+ websocket_auth = prompt_websocket_auth()
742
+ # Only offer persistence if database is enabled
743
+ if database != DatabaseType.NONE:
744
+ enable_conversation_persistence = _check_cancelled(
745
+ questionary.confirm(
746
+ "Enable conversation persistence (save chat history to database)?",
747
+ default=True,
748
+ ).ask()
749
+ )
750
+
751
+ # Admin panel configuration (when enabled and SQL database - PostgreSQL or SQLite)
752
+ admin_environments = AdminEnvironmentType.DEV_STAGING
753
+ admin_require_auth = True
754
+ if integrations.get("enable_admin_panel") and database in (
755
+ DatabaseType.POSTGRESQL,
756
+ DatabaseType.SQLITE,
757
+ ):
758
+ admin_environments, admin_require_auth = prompt_admin_config()
759
+
760
+ # Rate limit configuration (when rate limiting is enabled)
761
+ rate_limit_requests = 100
762
+ rate_limit_period = 60
763
+ rate_limit_storage = RateLimitStorageType.MEMORY
764
+ if integrations.get("enable_rate_limiting"):
765
+ rate_limit_requests, rate_limit_period, rate_limit_storage = prompt_rate_limit_config(
766
+ redis_enabled=integrations.get("enable_redis", False)
767
+ )
768
+
769
+ # Frontend features (i18n, etc.)
770
+ frontend_features: dict[str, bool] = {}
771
+ if frontend != FrontendType.NONE:
772
+ frontend_features = prompt_frontend_features()
773
+
774
+ # Extract ci_type separately for type safety
775
+ ci_type = cast(CIType, dev_tools.pop("ci_type"))
776
+
777
+ # Build config
778
+ config = ProjectConfig(
779
+ project_name=basic_info["project_name"],
780
+ project_description=basic_info["project_description"],
781
+ author_name=basic_info["author_name"],
782
+ author_email=basic_info["author_email"],
783
+ database=database,
784
+ auth=auth,
785
+ oauth_provider=oauth_provider,
786
+ enable_session_management=enable_session_management,
787
+ enable_logfire=enable_logfire,
788
+ logfire_features=logfire_features,
789
+ background_tasks=background_tasks,
790
+ ai_framework=ai_framework,
791
+ llm_provider=llm_provider,
792
+ websocket_auth=websocket_auth,
793
+ enable_conversation_persistence=enable_conversation_persistence,
794
+ admin_environments=admin_environments,
795
+ admin_require_auth=admin_require_auth,
796
+ rate_limit_requests=rate_limit_requests,
797
+ rate_limit_period=rate_limit_period,
798
+ rate_limit_storage=rate_limit_storage,
799
+ python_version=python_version,
800
+ ci_type=ci_type,
801
+ reverse_proxy=reverse_proxy,
802
+ frontend=frontend,
803
+ backend_port=ports["backend_port"],
804
+ frontend_port=ports.get("frontend_port", 3000),
805
+ **integrations,
806
+ **dev_tools,
807
+ **frontend_features,
808
+ )
809
+
810
+ return config
811
+
812
+
813
+ def show_summary(config: ProjectConfig) -> None:
814
+ """Display configuration summary."""
815
+ console.print()
816
+ console.print("[bold green]Configuration Summary[/]")
817
+ console.print()
818
+
819
+ console.print(f" [cyan]Project:[/] {config.project_name}")
820
+ console.print(f" [cyan]Database:[/] {config.database.value}")
821
+ auth_str = config.auth.value
822
+ if config.oauth_provider != OAuthProvider.NONE:
823
+ auth_str += f" + {config.oauth_provider.value} OAuth"
824
+ console.print(f" [cyan]Auth:[/] {auth_str}")
825
+ console.print(f" [cyan]Logfire:[/] {'enabled' if config.enable_logfire else 'disabled'}")
826
+ console.print(f" [cyan]Background Tasks:[/] {config.background_tasks.value}")
827
+ console.print(f" [cyan]Frontend:[/] {config.frontend.value}")
828
+
829
+ enabled_features = []
830
+ if config.enable_redis:
831
+ enabled_features.append("Redis")
832
+ if config.enable_caching:
833
+ enabled_features.append("Caching")
834
+ if config.enable_rate_limiting:
835
+ rate_info = f"Rate Limiting ({config.rate_limit_requests}/{config.rate_limit_period}s, {config.rate_limit_storage.value})"
836
+ enabled_features.append(rate_info)
837
+ if config.enable_admin_panel:
838
+ admin_info = "Admin Panel"
839
+ if config.admin_environments.value != "all":
840
+ admin_info += f" ({config.admin_environments.value})"
841
+ if config.admin_require_auth:
842
+ admin_info += " [auth]"
843
+ enabled_features.append(admin_info)
844
+ if config.enable_websockets:
845
+ enabled_features.append("WebSockets")
846
+ if config.enable_ai_agent:
847
+ ai_info = f"AI Agent ({config.ai_framework.value}, {config.llm_provider.value})"
848
+ enabled_features.append(ai_info)
849
+ if config.enable_webhooks:
850
+ enabled_features.append("Webhooks")
851
+ if config.enable_i18n:
852
+ enabled_features.append("i18n")
853
+ if config.include_example_crud:
854
+ enabled_features.append("Example CRUD")
855
+ if config.enable_docker:
856
+ enabled_features.append("Docker")
857
+
858
+ if enabled_features:
859
+ console.print(f" [cyan]Features:[/] {', '.join(enabled_features)}")
860
+
861
+ console.print()
862
+
863
+
864
+ def confirm_generation() -> bool:
865
+ """Confirm project generation."""
866
+ return cast(
867
+ bool,
868
+ _check_cancelled(
869
+ questionary.confirm(
870
+ "Generate project with this configuration?",
871
+ default=True,
872
+ ).ask()
873
+ ),
874
+ )