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.
- fastapi_fullstack-0.1.7.dist-info/METADATA +739 -0
- fastapi_fullstack-0.1.7.dist-info/RECORD +241 -0
- fastapi_fullstack-0.1.7.dist-info/WHEEL +4 -0
- fastapi_fullstack-0.1.7.dist-info/entry_points.txt +2 -0
- fastapi_fullstack-0.1.7.dist-info/licenses/LICENSE +21 -0
- fastapi_gen/__init__.py +3 -0
- fastapi_gen/cli.py +442 -0
- fastapi_gen/config.py +356 -0
- fastapi_gen/generator.py +207 -0
- fastapi_gen/prompts.py +874 -0
- fastapi_gen/template/VARIABLES.md +276 -0
- fastapi_gen/template/cookiecutter.json +93 -0
- fastapi_gen/template/hooks/post_gen_project.py +355 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +109 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/AGENTS.md +55 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +315 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +768 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example +155 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +447 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +226 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langchain_assistant.py +226 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/prompts.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +541 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +98 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +902 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +498 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +267 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +130 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +334 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +838 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +192 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +850 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +180 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_admin.py +890 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +261 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +435 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/adding_features.md +132 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/architecture.md +63 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/patterns.md +161 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/testing.md +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.dockerignore +40 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +648 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +69 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/layout.tsx +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/login/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/register/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/chat/page.tsx +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/dashboard/page.tsx +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/layout.tsx +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/profile/page.tsx +152 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/auth/callback/page.tsx +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/layout.tsx +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/page.tsx +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +323 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +22 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +234 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +328 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/copy-button.tsx +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/local-conversation-sidebar.tsx +295 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/markdown-content.tsx +167 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +79 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +79 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/sheet.tsx +109 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-local-chat.ts +165 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +37 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-sidebar-store.ts +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/local-chat-store.ts +255 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/sidebar-store.ts +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +83 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
- 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
|
+
)
|