cfsa-antigravity 1.0.0
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.
- package/bin/cli.mjs +324 -0
- package/package.json +34 -0
- package/template/.agent/instructions/commands.md +48 -0
- package/template/.agent/instructions/patterns.md +61 -0
- package/template/.agent/instructions/structure.md +29 -0
- package/template/.agent/instructions/tech-stack.md +43 -0
- package/template/.agent/instructions/workflow.md +41 -0
- package/template/.agent/kit-sync.md +15 -0
- package/template/.agent/rules/boundary-not-placeholder.md +146 -0
- package/template/.agent/rules/completion-checklist.md +48 -0
- package/template/.agent/rules/decision-classification.md +103 -0
- package/template/.agent/rules/extensibility.md +47 -0
- package/template/.agent/rules/question-vs-command.md +81 -0
- package/template/.agent/rules/security-first.md +43 -0
- package/template/.agent/rules/specificity-standards.md +54 -0
- package/template/.agent/rules/tdd-contract-first.md +57 -0
- package/template/.agent/rules/vertical-slices.md +42 -0
- package/template/.agent/skill-library/MANIFEST.md +480 -0
- package/template/.agent/skill-library/README.md +38 -0
- package/template/.agent/skill-library/meta/brand-guidelines/SKILL.md +73 -0
- package/template/.agent/skill-library/meta/claude-code/README.md +9 -0
- package/template/.agent/skill-library/meta/claude-code/agent-development/SKILL.md +415 -0
- package/template/.agent/skill-library/meta/claude-code/hook-development/SKILL.md +712 -0
- package/template/.agent/skill-library/meta/claude-code/plugin-structure/SKILL.md +476 -0
- package/template/.agent/skill-library/meta/git-advanced/SKILL.md +972 -0
- package/template/.agent/skill-library/meta/mcp-builder/SKILL.md +236 -0
- package/template/.agent/skill-library/meta/product-marketing-context/SKILL.md +241 -0
- package/template/.agent/skill-library/meta/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skill-library/meta/tmux-processes/SKILL.md +210 -0
- package/template/.agent/skill-library/meta/using-tmux-for-interactive-commands/SKILL.md +178 -0
- package/template/.agent/skill-library/stack/3d/threejs-pro/SKILL.md +300 -0
- package/template/.agent/skill-library/stack/ai/ai-sdk/SKILL.md +77 -0
- package/template/.agent/skill-library/stack/ai/langchain/SKILL.md +530 -0
- package/template/.agent/skill-library/stack/ai/ollama/SKILL.md +321 -0
- package/template/.agent/skill-library/stack/ai/openai-sdk/SKILL.md +549 -0
- package/template/.agent/skill-library/stack/analytics/google-analytics/SKILL.md +153 -0
- package/template/.agent/skill-library/stack/api/graphql/SKILL.md +1061 -0
- package/template/.agent/skill-library/stack/api/trpc/SKILL.md +576 -0
- package/template/.agent/skill-library/stack/auth/authjs/SKILL.md +569 -0
- package/template/.agent/skill-library/stack/auth/clerk/SKILL.md +590 -0
- package/template/.agent/skill-library/stack/auth/firebase-auth/SKILL.md +734 -0
- package/template/.agent/skill-library/stack/cms/payload-cms/SKILL.md +573 -0
- package/template/.agent/skill-library/stack/cms/shopify/SKILL.md +1193 -0
- package/template/.agent/skill-library/stack/cms/wordpress/SKILL.md +1104 -0
- package/template/.agent/skill-library/stack/css/sass-scss/SKILL.md +1121 -0
- package/template/.agent/skill-library/stack/css/tailwind-css-patterns/SKILL.md +863 -0
- package/template/.agent/skill-library/stack/css/tailwind-design-system/SKILL.md +490 -0
- package/template/.agent/skill-library/stack/css/vanilla-css/SKILL.md +1078 -0
- package/template/.agent/skill-library/stack/databases/clickhouse/SKILL.md +311 -0
- package/template/.agent/skill-library/stack/databases/influxdb/SKILL.md +280 -0
- package/template/.agent/skill-library/stack/databases/lancedb/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/databases/mongodb/SKILL.md +1169 -0
- package/template/.agent/skill-library/stack/databases/neo4j/SKILL.md +839 -0
- package/template/.agent/skill-library/stack/databases/pgvector/SKILL.md +241 -0
- package/template/.agent/skill-library/stack/databases/pinecone/SKILL.md +212 -0
- package/template/.agent/skill-library/stack/databases/postgresql/SKILL.md +658 -0
- package/template/.agent/skill-library/stack/databases/qdrant/SKILL.md +312 -0
- package/template/.agent/skill-library/stack/databases/redis/SKILL.md +1079 -0
- package/template/.agent/skill-library/stack/databases/spacetimedb/SKILL.md +532 -0
- package/template/.agent/skill-library/stack/databases/sqlite/SKILL.md +1132 -0
- package/template/.agent/skill-library/stack/databases/supabase/SKILL.md +640 -0
- package/template/.agent/skill-library/stack/databases/surrealdb-expert/SKILL.md +945 -0
- package/template/.agent/skill-library/stack/databases/timescaledb/SKILL.md +745 -0
- package/template/.agent/skill-library/stack/databases/weaviate/SKILL.md +218 -0
- package/template/.agent/skill-library/stack/devops/github-actions/SKILL.md +554 -0
- package/template/.agent/skill-library/stack/devops/kubernetes/SKILL.md +950 -0
- package/template/.agent/skill-library/stack/devops/nginx/SKILL.md +841 -0
- package/template/.agent/skill-library/stack/devops/terraform/SKILL.md +860 -0
- package/template/.agent/skill-library/stack/email/resend/SKILL.md +391 -0
- package/template/.agent/skill-library/stack/engines/godot/SKILL.md +488 -0
- package/template/.agent/skill-library/stack/extensions/chrome-extension/SKILL.md +375 -0
- package/template/.agent/skill-library/stack/extensions/vscode-extension/SKILL.md +453 -0
- package/template/.agent/skill-library/stack/frameworks/astro-framework/SKILL.md +162 -0
- package/template/.agent/skill-library/stack/frameworks/electron/SKILL.md +1286 -0
- package/template/.agent/skill-library/stack/frameworks/fastapi/SKILL.md +650 -0
- package/template/.agent/skill-library/stack/frameworks/hono/SKILL.md +90 -0
- package/template/.agent/skill-library/stack/frameworks/nestjs/SKILL.md +878 -0
- package/template/.agent/skill-library/stack/frameworks/nextjs/SKILL.md +635 -0
- package/template/.agent/skill-library/stack/frameworks/nuxt/SKILL.md +564 -0
- package/template/.agent/skill-library/stack/frameworks/sveltekit/SKILL.md +614 -0
- package/template/.agent/skill-library/stack/frameworks/tauri/SKILL.md +920 -0
- package/template/.agent/skill-library/stack/gamedev/godot/SKILL.md +1032 -0
- package/template/.agent/skill-library/stack/gamedev/unity/SKILL.md +1175 -0
- package/template/.agent/skill-library/stack/hosting/aws/SKILL.md +467 -0
- package/template/.agent/skill-library/stack/hosting/cloudflare/SKILL.md +201 -0
- package/template/.agent/skill-library/stack/hosting/docker-expert/SKILL.md +409 -0
- package/template/.agent/skill-library/stack/hosting/vercel/SKILL.md +484 -0
- package/template/.agent/skill-library/stack/languages/bash-scripting/SKILL.md +773 -0
- package/template/.agent/skill-library/stack/languages/c-cpp/SKILL.md +712 -0
- package/template/.agent/skill-library/stack/languages/gdscript/SKILL.md +789 -0
- package/template/.agent/skill-library/stack/languages/go/SKILL.md +664 -0
- package/template/.agent/skill-library/stack/languages/java/SKILL.md +778 -0
- package/template/.agent/skill-library/stack/languages/kotlin/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/languages/python/SKILL.md +678 -0
- package/template/.agent/skill-library/stack/languages/rust/SKILL.md +673 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/SKILL.md +141 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/advanced-generics.md +90 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/branded-types.md +57 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/builder-pattern.md +71 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/common-pitfalls.md +135 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/conditional-types.md +27 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/decorators.md +98 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/discriminated-unions.md +62 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/mapped-types.md +53 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/performance-best-practices.md +104 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/template-literal-types.md +49 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/testing-types.md +112 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-guards.md +70 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-inference.md +101 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/utility-types.md +98 -0
- package/template/.agent/skill-library/stack/languages/vanilla-javascript/SKILL.md +803 -0
- package/template/.agent/skill-library/stack/messaging/kafka/SKILL.md +235 -0
- package/template/.agent/skill-library/stack/mobile/expo-react-native/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/mobile/flutter/SKILL.md +316 -0
- package/template/.agent/skill-library/stack/mobile/react-native/SKILL.md +337 -0
- package/template/.agent/skill-library/stack/monitoring/posthog/SKILL.md +396 -0
- package/template/.agent/skill-library/stack/monitoring/sentry/SKILL.md +509 -0
- package/template/.agent/skill-library/stack/observability/datadog/SKILL.md +179 -0
- package/template/.agent/skill-library/stack/observability/distributed-tracing/SKILL.md +140 -0
- package/template/.agent/skill-library/stack/observability/logging-best-practices/SKILL.md +168 -0
- package/template/.agent/skill-library/stack/observability/opentelemetry/SKILL.md +164 -0
- package/template/.agent/skill-library/stack/observability/prometheus-grafana/SKILL.md +246 -0
- package/template/.agent/skill-library/stack/observability/python-observability/SKILL.md +158 -0
- package/template/.agent/skill-library/stack/orm/drizzle-orm/SKILL.md +613 -0
- package/template/.agent/skill-library/stack/orm/prisma/SKILL.md +744 -0
- package/template/.agent/skill-library/stack/payments/lemonsqueezy/SKILL.md +393 -0
- package/template/.agent/skill-library/stack/payments/stripe-integration/SKILL.md +457 -0
- package/template/.agent/skill-library/stack/queue/bullmq/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/queue/inngest/SKILL.md +438 -0
- package/template/.agent/skill-library/stack/realtime/socketio/SKILL.md +595 -0
- package/template/.agent/skill-library/stack/search/elasticsearch/SKILL.md +248 -0
- package/template/.agent/skill-library/stack/search/meilisearch/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/security/crypto-patterns/SKILL.md +437 -0
- package/template/.agent/skill-library/stack/security/csp-cors-headers/SKILL.md +588 -0
- package/template/.agent/skill-library/stack/security/dependency-auditing/SKILL.md +560 -0
- package/template/.agent/skill-library/stack/security/input-sanitization/SKILL.md +430 -0
- package/template/.agent/skill-library/stack/security/owasp-web-security/SKILL.md +421 -0
- package/template/.agent/skill-library/stack/state/tanstack-query/SKILL.md +637 -0
- package/template/.agent/skill-library/stack/state/zustand/SKILL.md +483 -0
- package/template/.agent/skill-library/stack/storage/aws-s3/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/testing/playwright/SKILL.md +641 -0
- package/template/.agent/skill-library/stack/testing/storybook/SKILL.md +923 -0
- package/template/.agent/skill-library/stack/testing/testing-library/SKILL.md +872 -0
- package/template/.agent/skill-library/stack/testing/vitest/SKILL.md +714 -0
- package/template/.agent/skill-library/stack/ui/react-best-practices/SKILL.md +877 -0
- package/template/.agent/skill-library/stack/ui/react-composition-patterns/SKILL.md +1107 -0
- package/template/.agent/skill-library/stack/ui/react-flow/SKILL.md +425 -0
- package/template/.agent/skill-library/stack/ui/shadcn-ui/SKILL.md +703 -0
- package/template/.agent/skill-library/surface/api/api-caching/SKILL.md +458 -0
- package/template/.agent/skill-library/surface/api/api-documentation-openapi/SKILL.md +697 -0
- package/template/.agent/skill-library/surface/api/api-error-handling/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/api-security-checklist/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/api-versioning/SKILL.md +420 -0
- package/template/.agent/skill-library/surface/api/email-best-practices/SKILL.md +59 -0
- package/template/.agent/skill-library/surface/api/rate-limiting-abuse-protection/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/webhook-design/SKILL.md +752 -0
- package/template/.agent/skill-library/surface/cli/cli-configuration-management/SKILL.md +445 -0
- package/template/.agent/skill-library/surface/cli/cli-error-diagnostics/SKILL.md +515 -0
- package/template/.agent/skill-library/surface/cli/cli-shell-integration/SKILL.md +479 -0
- package/template/.agent/skill-library/surface/cli/cli-ux-design/SKILL.md +477 -0
- package/template/.agent/skill-library/surface/desktop/desktop-app-distribution/SKILL.md +416 -0
- package/template/.agent/skill-library/surface/desktop/desktop-security-sandboxing/SKILL.md +407 -0
- package/template/.agent/skill-library/surface/desktop/desktop-ux-conventions/SKILL.md +361 -0
- package/template/.agent/skill-library/surface/desktop/native-os-integration/SKILL.md +563 -0
- package/template/.agent/skill-library/surface/extension/browser-extension-patterns/SKILL.md +482 -0
- package/template/.agent/skill-library/surface/extension/plugin-architecture-design/SKILL.md +632 -0
- package/template/.agent/skill-library/surface/extension/vscode-extension-development/SKILL.md +728 -0
- package/template/.agent/skill-library/surface/mobile/app-store-submission/SKILL.md +304 -0
- package/template/.agent/skill-library/surface/mobile/mobile-offline-sync/SKILL.md +443 -0
- package/template/.agent/skill-library/surface/mobile/mobile-responsive-patterns/SKILL.md +432 -0
- package/template/.agent/skill-library/surface/mobile/push-notifications/SKILL.md +495 -0
- package/template/.agent/skill-library/surface/web/accessibility-compliance/SKILL.md +827 -0
- package/template/.agent/skill-library/surface/web/ai-seo/SKILL.md +398 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/content-patterns.md +285 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/platform-ranking-factors.md +152 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/SKILL.md +309 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/event-library.md +260 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/ga4-implementation.md +300 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/gtm-implementation.md +390 -0
- package/template/.agent/skill-library/surface/web/authentication-ui-flows/SKILL.md +530 -0
- package/template/.agent/skill-library/surface/web/dark-mode-theming/SKILL.md +516 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/SKILL.md +105 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/charts.csv +26 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/colors.csv +97 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/landing.csv +31 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/styles.csv +59 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/typography.csv +58 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/ux-guidelines.csv +100 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/core.py +258 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/design_system.py +1067 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/search.py +106 -0
- package/template/.agent/skill-library/surface/web/form-handling-validation/SKILL.md +675 -0
- package/template/.agent/skill-library/surface/web/frontend-design/SKILL.md +1393 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/cppn-hero.tsx +299 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/wave-hero.tsx +875 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/SKILL.md +111 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/scripts/ux_audit.py +739 -0
- package/template/.agent/skill-library/surface/web/i18n-localization/SKILL.md +154 -0
- package/template/.agent/skill-library/surface/web/offline-first-pwa/SKILL.md +657 -0
- package/template/.agent/skill-library/surface/web/page-cro/SKILL.md +182 -0
- package/template/.agent/skill-library/surface/web/page-cro/references/experiments.md +248 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/SKILL.md +238 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/references/playbooks.md +308 -0
- package/template/.agent/skill-library/surface/web/schema-markup/SKILL.md +179 -0
- package/template/.agent/skill-library/surface/web/schema-markup/references/schema-examples.md +398 -0
- package/template/.agent/skill-library/surface/web/seo-audit/SKILL.md +394 -0
- package/template/.agent/skill-library/surface/web/seo-audit/references/ai-writing-detection.md +200 -0
- package/template/.agent/skill-library/surface/web/web-performance-optimization/SKILL.md +646 -0
- package/template/.agent/skill-library/surface/web/web-scraping/SKILL.md +58 -0
- package/template/.agent/skills/accessibility/SKILL.md +522 -0
- package/template/.agent/skills/accessibility/references/WCAG.md +162 -0
- package/template/.agent/skills/adversarial-review/SKILL.md +90 -0
- package/template/.agent/skills/antigravity-workflows/SKILL.md +81 -0
- package/template/.agent/skills/antigravity-workflows/resources/implementation-playbook.md +36 -0
- package/template/.agent/skills/api-design-principles/SKILL.md +37 -0
- package/template/.agent/skills/api-design-principles/assets/api-design-checklist.md +155 -0
- package/template/.agent/skills/api-design-principles/assets/rest-api-template.py +182 -0
- package/template/.agent/skills/api-design-principles/references/graphql-schema-design.md +583 -0
- package/template/.agent/skills/api-design-principles/references/rest-best-practices.md +408 -0
- package/template/.agent/skills/api-design-principles/resources/implementation-playbook.md +513 -0
- package/template/.agent/skills/api-versioning/SKILL.md +420 -0
- package/template/.agent/skills/architecture-mapping/SKILL.md +219 -0
- package/template/.agent/skills/bootstrap-agents/SKILL.md +259 -0
- package/template/.agent/skills/brainstorming/SKILL.md +236 -0
- package/template/.agent/skills/brand-guidelines/SKILL.md +44 -0
- package/template/.agent/skills/clean-code/SKILL.md +94 -0
- package/template/.agent/skills/code-review-pro/SKILL.md +152 -0
- package/template/.agent/skills/concise-planning/SKILL.md +68 -0
- package/template/.agent/skills/cross-layer-consistency/SKILL.md +117 -0
- package/template/.agent/skills/database-schema-design/SKILL.md +429 -0
- package/template/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/template/.agent/skills/design-anti-cliche/SKILL.md +159 -0
- package/template/.agent/skills/design-direction/SKILL.md +45 -0
- package/template/.agent/skills/error-handling-patterns/SKILL.md +721 -0
- package/template/.agent/skills/find-skills/SKILL.md +145 -0
- package/template/.agent/skills/git-advanced/SKILL.md +972 -0
- package/template/.agent/skills/git-workflow/SKILL.md +420 -0
- package/template/.agent/skills/idea-extraction/SKILL.md +271 -0
- package/template/.agent/skills/logging-best-practices/SKILL.md +851 -0
- package/template/.agent/skills/migration-management/SKILL.md +384 -0
- package/template/.agent/skills/minimalist-surgical-development/SKILL.md +69 -0
- package/template/.agent/skills/parallel-agents/SKILL.md +165 -0
- package/template/.agent/skills/parallel-debugging/SKILL.md +135 -0
- package/template/.agent/skills/parallel-feature-development/SKILL.md +166 -0
- package/template/.agent/skills/performance-budgeting/SKILL.md +144 -0
- package/template/.agent/skills/pipeline-rubrics/SKILL.md +51 -0
- package/template/.agent/skills/pipeline-rubrics/references/architecture-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/be-rubric.md +21 -0
- package/template/.agent/skills/pipeline-rubrics/references/fe-rubric.md +20 -0
- package/template/.agent/skills/pipeline-rubrics/references/ia-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/scoring.md +28 -0
- package/template/.agent/skills/pipeline-rubrics/references/vision-rubric.md +11 -0
- package/template/.agent/skills/prd-templates/SKILL.md +88 -0
- package/template/.agent/skills/prd-templates/references/architecture-design-template.md +88 -0
- package/template/.agent/skills/prd-templates/references/be-spec-template.md +101 -0
- package/template/.agent/skills/prd-templates/references/data-placement-template.md +74 -0
- package/template/.agent/skills/prd-templates/references/decomposition-templates.md +211 -0
- package/template/.agent/skills/prd-templates/references/design-system-decisions.md +198 -0
- package/template/.agent/skills/prd-templates/references/engineering-standards-template.md +124 -0
- package/template/.agent/skills/prd-templates/references/fe-classification-procedures.md +47 -0
- package/template/.agent/skills/prd-templates/references/fe-spec-template.md +84 -0
- package/template/.agent/skills/prd-templates/references/infrastructure-report-template.md +71 -0
- package/template/.agent/skills/prd-templates/references/operational-templates.md +116 -0
- package/template/.agent/skills/prd-templates/references/placeholder-guard-template.md +21 -0
- package/template/.agent/skills/prd-templates/references/surface-model.md +61 -0
- package/template/.agent/skills/prd-templates/references/vision-template.md +66 -0
- package/template/.agent/skills/prompt-engineer/README.md +659 -0
- package/template/.agent/skills/prompt-engineer/SKILL.md +249 -0
- package/template/.agent/skills/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skills/resolve-ambiguity/SKILL.md +278 -0
- package/template/.agent/skills/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skills/security-scanning-security-hardening/SKILL.md +231 -0
- package/template/.agent/skills/session-continuity/SKILL.md +730 -0
- package/template/.agent/skills/session-continuity/protocols/01-session-resumption.md +38 -0
- package/template/.agent/skills/session-continuity/protocols/02-progress-generation.md +85 -0
- package/template/.agent/skills/session-continuity/protocols/03-progress-update.md +70 -0
- package/template/.agent/skills/session-continuity/protocols/04-pattern-extraction.md +60 -0
- package/template/.agent/skills/session-continuity/protocols/05-session-close.md +37 -0
- package/template/.agent/skills/session-continuity/protocols/06-decision-analysis.md +84 -0
- package/template/.agent/skills/session-continuity/protocols/07-spec-pipeline-generation.md +48 -0
- package/template/.agent/skills/session-continuity/protocols/08-spec-pipeline-update.md +43 -0
- package/template/.agent/skills/session-continuity/protocols/09-parallel-claim.md +122 -0
- package/template/.agent/skills/session-continuity/protocols/10-placeholder-verification-gate.md +104 -0
- package/template/.agent/skills/session-continuity/protocols/ambiguity-gates.md +48 -0
- package/template/.agent/skills/skill-creator/LICENSE.txt +202 -0
- package/template/.agent/skills/skill-creator/README.md +270 -0
- package/template/.agent/skills/skill-creator/SKILL.md +590 -0
- package/template/.agent/skills/skill-creator/references/output-patterns.md +82 -0
- package/template/.agent/skills/skill-creator/references/workflows.md +28 -0
- package/template/.agent/skills/skill-creator/scripts/init_skill.py +303 -0
- package/template/.agent/skills/skill-creator/scripts/package_skill.py +110 -0
- package/template/.agent/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/template/.agent/skills/spec-writing/SKILL.md +110 -0
- package/template/.agent/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/template/.agent/skills/systematic-debugging/SKILL.md +297 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/template/.agent/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/template/.agent/skills/systematic-debugging/find-polluter.sh +63 -0
- package/template/.agent/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/template/.agent/skills/systematic-debugging/test-academic.md +14 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/template/.agent/skills/tdd-workflow/SKILL.md +409 -0
- package/template/.agent/skills/tech-stack-catalog/SKILL.md +49 -0
- package/template/.agent/skills/tech-stack-catalog/references/constraint-questions.md +21 -0
- package/template/.agent/skills/tech-stack-catalog/references/dev-tooling-decisions.md +37 -0
- package/template/.agent/skills/tech-stack-catalog/references/surface-decision-tables.md +69 -0
- package/template/.agent/skills/technical-writer/SKILL.md +242 -0
- package/template/.agent/skills/testing-strategist/SKILL.md +932 -0
- package/template/.agent/skills/verification-before-completion/SKILL.md +145 -0
- package/template/.agent/skills/workflow-automation/SKILL.md +73 -0
- package/template/.agent/workflows/audit-ambiguity-execute.md +165 -0
- package/template/.agent/workflows/audit-ambiguity-rubrics.md +83 -0
- package/template/.agent/workflows/audit-ambiguity.md +64 -0
- package/template/.agent/workflows/bootstrap-agents-fill.md +201 -0
- package/template/.agent/workflows/bootstrap-agents-provision.md +197 -0
- package/template/.agent/workflows/bootstrap-agents.md +66 -0
- package/template/.agent/workflows/create-prd-architecture.md +119 -0
- package/template/.agent/workflows/create-prd-compile.md +138 -0
- package/template/.agent/workflows/create-prd-design-system.md +135 -0
- package/template/.agent/workflows/create-prd-security.md +113 -0
- package/template/.agent/workflows/create-prd-stack.md +91 -0
- package/template/.agent/workflows/create-prd.md +168 -0
- package/template/.agent/workflows/decompose-architecture-structure.md +82 -0
- package/template/.agent/workflows/decompose-architecture-validate.md +119 -0
- package/template/.agent/workflows/decompose-architecture.md +111 -0
- package/template/.agent/workflows/evolve-contract.md +98 -0
- package/template/.agent/workflows/evolve-feature-cascade.md +140 -0
- package/template/.agent/workflows/evolve-feature-classify.md +116 -0
- package/template/.agent/workflows/evolve-feature.md +56 -0
- package/template/.agent/workflows/ideate-discover.md +144 -0
- package/template/.agent/workflows/ideate-extract.md +129 -0
- package/template/.agent/workflows/ideate-validate.md +117 -0
- package/template/.agent/workflows/ideate.md +113 -0
- package/template/.agent/workflows/implement-slice-setup.md +113 -0
- package/template/.agent/workflows/implement-slice-tdd.md +198 -0
- package/template/.agent/workflows/implement-slice.md +50 -0
- package/template/.agent/workflows/plan-phase.md +202 -0
- package/template/.agent/workflows/propagate-decision-apply.md +135 -0
- package/template/.agent/workflows/propagate-decision-scan.md +147 -0
- package/template/.agent/workflows/propagate-decision.md +56 -0
- package/template/.agent/workflows/remediate-pipeline-assess.md +138 -0
- package/template/.agent/workflows/remediate-pipeline-execute.md +135 -0
- package/template/.agent/workflows/remediate-pipeline.md +55 -0
- package/template/.agent/workflows/resolve-ambiguity.md +82 -0
- package/template/.agent/workflows/sync-kit.md +209 -0
- package/template/.agent/workflows/update-architecture-map.md +74 -0
- package/template/.agent/workflows/validate-phase.md +219 -0
- package/template/.agent/workflows/verify-infrastructure.md +207 -0
- package/template/.agent/workflows/write-architecture-spec-deepen.md +139 -0
- package/template/.agent/workflows/write-architecture-spec-design.md +202 -0
- package/template/.agent/workflows/write-architecture-spec.md +63 -0
- package/template/.agent/workflows/write-be-spec-classify.md +165 -0
- package/template/.agent/workflows/write-be-spec-write.md +98 -0
- package/template/.agent/workflows/write-be-spec.md +76 -0
- package/template/.agent/workflows/write-fe-spec-classify.md +170 -0
- package/template/.agent/workflows/write-fe-spec-write.md +94 -0
- package/template/.agent/workflows/write-fe-spec.md +71 -0
- package/template/AGENTS.md +176 -0
- package/template/GEMINI.md +177 -0
- package/template/docs/README.md +187 -0
- package/template/docs/audits/.gitkeep +0 -0
- package/template/docs/audits/README.md +10 -0
- package/template/docs/plans/.gitkeep +0 -0
- package/template/docs/plans/README.md +21 -0
- package/template/docs/plans/be/.gitkeep +0 -0
- package/template/docs/plans/be/README.md +11 -0
- package/template/docs/plans/fe/.gitkeep +0 -0
- package/template/docs/plans/fe/README.md +11 -0
- package/template/docs/plans/ia/.gitkeep +0 -0
- package/template/docs/plans/ia/README.md +17 -0
- package/template/docs/plans/ia/deep-dives/.gitkeep +0 -0
- package/template/docs/plans/ia/deep-dives/README.md +5 -0
- package/template/docs/plans/phases/.gitkeep +0 -0
- package/template/docs/plans/phases/README.md +11 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fastapi
|
|
3
|
+
description: Comprehensive FastAPI skill covering route definition, path/query/body parameters, Pydantic models, dependency injection, middleware, authentication (OAuth2, JWT), async endpoints, background tasks, WebSocket support, CORS, testing, OpenAPI generation, and deployment. Use when building Python APIs with FastAPI.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# FastAPI
|
|
8
|
+
|
|
9
|
+
High-performance Python web framework for building APIs with automatic OpenAPI documentation, type validation via Pydantic, and native async support.
|
|
10
|
+
|
|
11
|
+
## Project Setup
|
|
12
|
+
|
|
13
|
+
### Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install fastapi uvicorn[standard] pydantic[email] python-multipart
|
|
17
|
+
# Or with all extras
|
|
18
|
+
pip install "fastapi[all]"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Application Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
project/
|
|
25
|
+
app/
|
|
26
|
+
__init__.py
|
|
27
|
+
main.py # FastAPI app instance, root router
|
|
28
|
+
config.py # Settings via pydantic-settings
|
|
29
|
+
dependencies.py # Shared dependencies
|
|
30
|
+
models/ # Pydantic models (request/response)
|
|
31
|
+
__init__.py
|
|
32
|
+
user.py
|
|
33
|
+
routers/ # Route modules
|
|
34
|
+
__init__.py
|
|
35
|
+
users.py
|
|
36
|
+
items.py
|
|
37
|
+
services/ # Business logic
|
|
38
|
+
__init__.py
|
|
39
|
+
user_service.py
|
|
40
|
+
db/ # Database layer
|
|
41
|
+
__init__.py
|
|
42
|
+
session.py
|
|
43
|
+
models.py # SQLAlchemy/ORM models
|
|
44
|
+
tests/
|
|
45
|
+
__init__.py
|
|
46
|
+
conftest.py
|
|
47
|
+
test_users.py
|
|
48
|
+
requirements.txt
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Main Application
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# app/main.py
|
|
55
|
+
from fastapi import FastAPI
|
|
56
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
57
|
+
from contextlib import asynccontextmanager
|
|
58
|
+
|
|
59
|
+
from app.routers import users, items
|
|
60
|
+
from app.db.session import engine, Base
|
|
61
|
+
|
|
62
|
+
@asynccontextmanager
|
|
63
|
+
async def lifespan(app: FastAPI):
|
|
64
|
+
# Startup: create tables, warm caches, etc.
|
|
65
|
+
async with engine.begin() as conn:
|
|
66
|
+
await conn.run_sync(Base.metadata.create_all)
|
|
67
|
+
yield
|
|
68
|
+
# Shutdown: close connections, flush buffers
|
|
69
|
+
await engine.dispose()
|
|
70
|
+
|
|
71
|
+
app = FastAPI(
|
|
72
|
+
title="My API",
|
|
73
|
+
version="1.0.0",
|
|
74
|
+
lifespan=lifespan,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
app.add_middleware(
|
|
78
|
+
CORSMiddleware,
|
|
79
|
+
allow_origins=["https://example.com"],
|
|
80
|
+
allow_credentials=True,
|
|
81
|
+
allow_methods=["*"],
|
|
82
|
+
allow_headers=["*"],
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
app.include_router(users.router, prefix="/api/users", tags=["users"])
|
|
86
|
+
app.include_router(items.router, prefix="/api/items", tags=["items"])
|
|
87
|
+
|
|
88
|
+
@app.get("/health")
|
|
89
|
+
async def health_check():
|
|
90
|
+
return {"status": "healthy"}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Pydantic Models
|
|
94
|
+
|
|
95
|
+
### Request and Response Models
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# app/models/user.py
|
|
99
|
+
from pydantic import BaseModel, EmailStr, Field, ConfigDict
|
|
100
|
+
from datetime import datetime
|
|
101
|
+
from uuid import UUID
|
|
102
|
+
|
|
103
|
+
class UserCreate(BaseModel):
|
|
104
|
+
email: EmailStr
|
|
105
|
+
password: str = Field(min_length=8, max_length=128)
|
|
106
|
+
display_name: str = Field(min_length=1, max_length=50)
|
|
107
|
+
|
|
108
|
+
class UserUpdate(BaseModel):
|
|
109
|
+
display_name: str | None = Field(None, min_length=1, max_length=50)
|
|
110
|
+
bio: str | None = Field(None, max_length=500)
|
|
111
|
+
|
|
112
|
+
class UserResponse(BaseModel):
|
|
113
|
+
model_config = ConfigDict(from_attributes=True)
|
|
114
|
+
|
|
115
|
+
id: UUID
|
|
116
|
+
email: EmailStr
|
|
117
|
+
display_name: str
|
|
118
|
+
bio: str | None = None
|
|
119
|
+
created_at: datetime
|
|
120
|
+
|
|
121
|
+
class UserList(BaseModel):
|
|
122
|
+
users: list[UserResponse]
|
|
123
|
+
total: int
|
|
124
|
+
page: int
|
|
125
|
+
per_page: int
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Model Validation
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from pydantic import BaseModel, field_validator, model_validator
|
|
132
|
+
from typing import Self
|
|
133
|
+
|
|
134
|
+
class OrderCreate(BaseModel):
|
|
135
|
+
quantity: int = Field(gt=0, le=1000)
|
|
136
|
+
price: float = Field(gt=0)
|
|
137
|
+
discount: float = Field(ge=0, le=1, default=0)
|
|
138
|
+
|
|
139
|
+
@field_validator("price")
|
|
140
|
+
@classmethod
|
|
141
|
+
def price_must_have_two_decimals(cls, v: float) -> float:
|
|
142
|
+
if round(v, 2) != v:
|
|
143
|
+
raise ValueError("Price must have at most 2 decimal places")
|
|
144
|
+
return v
|
|
145
|
+
|
|
146
|
+
@model_validator(mode="after")
|
|
147
|
+
def check_total_positive(self) -> Self:
|
|
148
|
+
total = self.quantity * self.price * (1 - self.discount)
|
|
149
|
+
if total <= 0:
|
|
150
|
+
raise ValueError("Order total must be positive")
|
|
151
|
+
return self
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Route Definition
|
|
155
|
+
|
|
156
|
+
### Path, Query, and Body Parameters
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
# app/routers/users.py
|
|
160
|
+
from fastapi import APIRouter, Query, Path, Body, HTTPException, status
|
|
161
|
+
from app.models.user import UserCreate, UserResponse, UserList, UserUpdate
|
|
162
|
+
|
|
163
|
+
router = APIRouter()
|
|
164
|
+
|
|
165
|
+
@router.get("/", response_model=UserList)
|
|
166
|
+
async def list_users(
|
|
167
|
+
page: int = Query(default=1, ge=1, description="Page number"),
|
|
168
|
+
per_page: int = Query(default=20, ge=1, le=100, description="Items per page"),
|
|
169
|
+
search: str | None = Query(default=None, max_length=100),
|
|
170
|
+
):
|
|
171
|
+
# Query parameters with validation and documentation
|
|
172
|
+
offset = (page - 1) * per_page
|
|
173
|
+
users = await user_service.list_users(offset=offset, limit=per_page, search=search)
|
|
174
|
+
total = await user_service.count_users(search=search)
|
|
175
|
+
return UserList(users=users, total=total, page=page, per_page=per_page)
|
|
176
|
+
|
|
177
|
+
@router.get("/{user_id}", response_model=UserResponse)
|
|
178
|
+
async def get_user(
|
|
179
|
+
user_id: UUID = Path(description="The user's unique identifier"),
|
|
180
|
+
):
|
|
181
|
+
user = await user_service.get_user(user_id)
|
|
182
|
+
if not user:
|
|
183
|
+
raise HTTPException(status_code=404, detail="User not found")
|
|
184
|
+
return user
|
|
185
|
+
|
|
186
|
+
@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
|
187
|
+
async def create_user(user_data: UserCreate):
|
|
188
|
+
# Body parameter automatically parsed from JSON
|
|
189
|
+
existing = await user_service.get_by_email(user_data.email)
|
|
190
|
+
if existing:
|
|
191
|
+
raise HTTPException(status_code=409, detail="Email already registered")
|
|
192
|
+
return await user_service.create_user(user_data)
|
|
193
|
+
|
|
194
|
+
@router.patch("/{user_id}", response_model=UserResponse)
|
|
195
|
+
async def update_user(
|
|
196
|
+
user_id: UUID = Path(),
|
|
197
|
+
user_data: UserUpdate = Body(),
|
|
198
|
+
):
|
|
199
|
+
updated = await user_service.update_user(user_id, user_data)
|
|
200
|
+
if not updated:
|
|
201
|
+
raise HTTPException(status_code=404, detail="User not found")
|
|
202
|
+
return updated
|
|
203
|
+
|
|
204
|
+
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
205
|
+
async def delete_user(user_id: UUID = Path()):
|
|
206
|
+
deleted = await user_service.delete_user(user_id)
|
|
207
|
+
if not deleted:
|
|
208
|
+
raise HTTPException(status_code=404, detail="User not found")
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Response Models and Status Codes
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from fastapi.responses import JSONResponse
|
|
215
|
+
|
|
216
|
+
@router.post(
|
|
217
|
+
"/",
|
|
218
|
+
response_model=UserResponse,
|
|
219
|
+
status_code=201,
|
|
220
|
+
responses={
|
|
221
|
+
409: {"description": "Email already exists"},
|
|
222
|
+
422: {"description": "Validation error"},
|
|
223
|
+
},
|
|
224
|
+
)
|
|
225
|
+
async def create_user(user_data: UserCreate):
|
|
226
|
+
...
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Dependency Injection
|
|
230
|
+
|
|
231
|
+
### Basic Dependencies
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
# app/dependencies.py
|
|
235
|
+
from fastapi import Depends, Header, HTTPException
|
|
236
|
+
from app.db.session import AsyncSession, get_session
|
|
237
|
+
|
|
238
|
+
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
239
|
+
async with get_session() as session:
|
|
240
|
+
yield session
|
|
241
|
+
|
|
242
|
+
async def get_api_key(x_api_key: str = Header()):
|
|
243
|
+
if x_api_key != settings.api_key:
|
|
244
|
+
raise HTTPException(status_code=403, detail="Invalid API key")
|
|
245
|
+
return x_api_key
|
|
246
|
+
|
|
247
|
+
# Use in routes
|
|
248
|
+
@router.get("/users")
|
|
249
|
+
async def list_users(db: AsyncSession = Depends(get_db)):
|
|
250
|
+
result = await db.execute(select(User))
|
|
251
|
+
return result.scalars().all()
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Dependency Classes
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
class Pagination:
|
|
258
|
+
def __init__(
|
|
259
|
+
self,
|
|
260
|
+
page: int = Query(default=1, ge=1),
|
|
261
|
+
per_page: int = Query(default=20, ge=1, le=100),
|
|
262
|
+
):
|
|
263
|
+
self.offset = (page - 1) * per_page
|
|
264
|
+
self.limit = per_page
|
|
265
|
+
self.page = page
|
|
266
|
+
self.per_page = per_page
|
|
267
|
+
|
|
268
|
+
@router.get("/items")
|
|
269
|
+
async def list_items(pagination: Pagination = Depends()):
|
|
270
|
+
items = await service.list_items(offset=pagination.offset, limit=pagination.limit)
|
|
271
|
+
return items
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Nested Dependencies
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
async def get_current_user(
|
|
278
|
+
token: str = Depends(oauth2_scheme),
|
|
279
|
+
db: AsyncSession = Depends(get_db),
|
|
280
|
+
) -> User:
|
|
281
|
+
user = await auth_service.verify_token(token, db)
|
|
282
|
+
if not user:
|
|
283
|
+
raise HTTPException(status_code=401, detail="Invalid token")
|
|
284
|
+
return user
|
|
285
|
+
|
|
286
|
+
async def get_admin_user(
|
|
287
|
+
current_user: User = Depends(get_current_user),
|
|
288
|
+
) -> User:
|
|
289
|
+
if current_user.role != "admin":
|
|
290
|
+
raise HTTPException(status_code=403, detail="Admin access required")
|
|
291
|
+
return current_user
|
|
292
|
+
|
|
293
|
+
@router.delete("/users/{user_id}")
|
|
294
|
+
async def delete_user(
|
|
295
|
+
user_id: UUID,
|
|
296
|
+
admin: User = Depends(get_admin_user),
|
|
297
|
+
db: AsyncSession = Depends(get_db),
|
|
298
|
+
):
|
|
299
|
+
...
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Authentication
|
|
303
|
+
|
|
304
|
+
### OAuth2 with JWT
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
# app/auth.py
|
|
308
|
+
from fastapi import Depends, HTTPException, status
|
|
309
|
+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
|
310
|
+
from jose import JWTError, jwt
|
|
311
|
+
from passlib.context import CryptContext
|
|
312
|
+
from datetime import datetime, timedelta, timezone
|
|
313
|
+
|
|
314
|
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
315
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token")
|
|
316
|
+
|
|
317
|
+
SECRET_KEY = settings.jwt_secret
|
|
318
|
+
ALGORITHM = "HS256"
|
|
319
|
+
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
|
320
|
+
|
|
321
|
+
def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str:
|
|
322
|
+
to_encode = data.copy()
|
|
323
|
+
expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=15))
|
|
324
|
+
to_encode.update({"exp": expire})
|
|
325
|
+
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
|
326
|
+
|
|
327
|
+
async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
|
|
328
|
+
credentials_exception = HTTPException(
|
|
329
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
330
|
+
detail="Could not validate credentials",
|
|
331
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
332
|
+
)
|
|
333
|
+
try:
|
|
334
|
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
335
|
+
user_id: str | None = payload.get("sub")
|
|
336
|
+
if user_id is None:
|
|
337
|
+
raise credentials_exception
|
|
338
|
+
except JWTError:
|
|
339
|
+
raise credentials_exception
|
|
340
|
+
|
|
341
|
+
user = await user_service.get_user(user_id)
|
|
342
|
+
if user is None:
|
|
343
|
+
raise credentials_exception
|
|
344
|
+
return user
|
|
345
|
+
|
|
346
|
+
# Token endpoint
|
|
347
|
+
@router.post("/token")
|
|
348
|
+
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
|
349
|
+
user = await user_service.authenticate(form_data.username, form_data.password)
|
|
350
|
+
if not user:
|
|
351
|
+
raise HTTPException(
|
|
352
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
353
|
+
detail="Incorrect email or password",
|
|
354
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
355
|
+
)
|
|
356
|
+
access_token = create_access_token(
|
|
357
|
+
data={"sub": str(user.id)},
|
|
358
|
+
expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
|
|
359
|
+
)
|
|
360
|
+
return {"access_token": access_token, "token_type": "bearer"}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Middleware
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
from fastapi import Request
|
|
367
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
368
|
+
import time
|
|
369
|
+
import logging
|
|
370
|
+
|
|
371
|
+
logger = logging.getLogger(__name__)
|
|
372
|
+
|
|
373
|
+
class TimingMiddleware(BaseHTTPMiddleware):
|
|
374
|
+
async def dispatch(self, request: Request, call_next):
|
|
375
|
+
start = time.perf_counter()
|
|
376
|
+
response = await call_next(request)
|
|
377
|
+
duration = time.perf_counter() - start
|
|
378
|
+
response.headers["X-Process-Time"] = f"{duration:.4f}"
|
|
379
|
+
logger.info(f"{request.method} {request.url.path} - {response.status_code} - {duration:.4f}s")
|
|
380
|
+
return response
|
|
381
|
+
|
|
382
|
+
app.add_middleware(TimingMiddleware)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Rate Limiting (with slowapi)
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
from slowapi import Limiter, _rate_limit_exceeded_handler
|
|
389
|
+
from slowapi.util import get_remote_address
|
|
390
|
+
from slowapi.errors import RateLimitExceeded
|
|
391
|
+
|
|
392
|
+
limiter = Limiter(key_func=get_remote_address)
|
|
393
|
+
app.state.limiter = limiter
|
|
394
|
+
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
|
395
|
+
|
|
396
|
+
@router.post("/login")
|
|
397
|
+
@limiter.limit("5/minute")
|
|
398
|
+
async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
|
|
399
|
+
...
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Background Tasks
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
from fastapi import BackgroundTasks
|
|
406
|
+
|
|
407
|
+
async def send_welcome_email(email: str, name: str):
|
|
408
|
+
# Long-running operation
|
|
409
|
+
await email_service.send(to=email, template="welcome", context={"name": name})
|
|
410
|
+
|
|
411
|
+
@router.post("/users", status_code=201)
|
|
412
|
+
async def create_user(
|
|
413
|
+
user_data: UserCreate,
|
|
414
|
+
background_tasks: BackgroundTasks,
|
|
415
|
+
):
|
|
416
|
+
user = await user_service.create_user(user_data)
|
|
417
|
+
background_tasks.add_task(send_welcome_email, user.email, user.display_name)
|
|
418
|
+
return user
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## WebSocket Support
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
from fastapi import WebSocket, WebSocketDisconnect
|
|
425
|
+
|
|
426
|
+
class ConnectionManager:
|
|
427
|
+
def __init__(self):
|
|
428
|
+
self.active_connections: dict[str, WebSocket] = {}
|
|
429
|
+
|
|
430
|
+
async def connect(self, user_id: str, websocket: WebSocket):
|
|
431
|
+
await websocket.accept()
|
|
432
|
+
self.active_connections[user_id] = websocket
|
|
433
|
+
|
|
434
|
+
def disconnect(self, user_id: str):
|
|
435
|
+
self.active_connections.pop(user_id, None)
|
|
436
|
+
|
|
437
|
+
async def broadcast(self, message: dict):
|
|
438
|
+
for ws in self.active_connections.values():
|
|
439
|
+
await ws.send_json(message)
|
|
440
|
+
|
|
441
|
+
manager = ConnectionManager()
|
|
442
|
+
|
|
443
|
+
@app.websocket("/ws/{user_id}")
|
|
444
|
+
async def websocket_endpoint(websocket: WebSocket, user_id: str):
|
|
445
|
+
await manager.connect(user_id, websocket)
|
|
446
|
+
try:
|
|
447
|
+
while True:
|
|
448
|
+
data = await websocket.receive_json()
|
|
449
|
+
await manager.broadcast({"user": user_id, "message": data})
|
|
450
|
+
except WebSocketDisconnect:
|
|
451
|
+
manager.disconnect(user_id)
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## File Uploads
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
from fastapi import UploadFile, File
|
|
458
|
+
|
|
459
|
+
@router.post("/upload")
|
|
460
|
+
async def upload_file(
|
|
461
|
+
file: UploadFile = File(description="File to upload"),
|
|
462
|
+
):
|
|
463
|
+
if file.size and file.size > 10 * 1024 * 1024: # 10 MB limit
|
|
464
|
+
raise HTTPException(status_code=413, detail="File too large")
|
|
465
|
+
|
|
466
|
+
allowed_types = {"image/jpeg", "image/png", "image/webp"}
|
|
467
|
+
if file.content_type not in allowed_types:
|
|
468
|
+
raise HTTPException(status_code=422, detail="Invalid file type")
|
|
469
|
+
|
|
470
|
+
contents = await file.read()
|
|
471
|
+
path = await storage_service.save(file.filename, contents)
|
|
472
|
+
return {"path": path, "size": len(contents)}
|
|
473
|
+
|
|
474
|
+
@router.post("/upload-multiple")
|
|
475
|
+
async def upload_multiple(files: list[UploadFile] = File()):
|
|
476
|
+
results = []
|
|
477
|
+
for f in files:
|
|
478
|
+
contents = await f.read()
|
|
479
|
+
path = await storage_service.save(f.filename, contents)
|
|
480
|
+
results.append({"filename": f.filename, "path": path})
|
|
481
|
+
return results
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
## Error Handling
|
|
485
|
+
|
|
486
|
+
```python
|
|
487
|
+
from fastapi import Request
|
|
488
|
+
from fastapi.responses import JSONResponse
|
|
489
|
+
|
|
490
|
+
class AppError(Exception):
|
|
491
|
+
def __init__(self, status_code: int, detail: str, error_code: str):
|
|
492
|
+
self.status_code = status_code
|
|
493
|
+
self.detail = detail
|
|
494
|
+
self.error_code = error_code
|
|
495
|
+
|
|
496
|
+
@app.exception_handler(AppError)
|
|
497
|
+
async def app_error_handler(request: Request, exc: AppError):
|
|
498
|
+
return JSONResponse(
|
|
499
|
+
status_code=exc.status_code,
|
|
500
|
+
content={
|
|
501
|
+
"error": exc.error_code,
|
|
502
|
+
"detail": exc.detail,
|
|
503
|
+
},
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
@app.exception_handler(Exception)
|
|
507
|
+
async def global_exception_handler(request: Request, exc: Exception):
|
|
508
|
+
logger.exception(f"Unhandled error on {request.method} {request.url.path}")
|
|
509
|
+
return JSONResponse(
|
|
510
|
+
status_code=500,
|
|
511
|
+
content={"error": "internal_error", "detail": "An unexpected error occurred"},
|
|
512
|
+
)
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
## Testing
|
|
516
|
+
|
|
517
|
+
```python
|
|
518
|
+
# tests/conftest.py
|
|
519
|
+
import pytest
|
|
520
|
+
from httpx import AsyncClient, ASGITransport
|
|
521
|
+
from app.main import app
|
|
522
|
+
|
|
523
|
+
@pytest.fixture
|
|
524
|
+
async def client():
|
|
525
|
+
transport = ASGITransport(app=app)
|
|
526
|
+
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
527
|
+
yield ac
|
|
528
|
+
|
|
529
|
+
@pytest.fixture
|
|
530
|
+
async def authenticated_client(client: AsyncClient):
|
|
531
|
+
response = await client.post("/api/auth/token", data={
|
|
532
|
+
"username": "test@example.com",
|
|
533
|
+
"password": "testpassword",
|
|
534
|
+
})
|
|
535
|
+
token = response.json()["access_token"]
|
|
536
|
+
client.headers["Authorization"] = f"Bearer {token}"
|
|
537
|
+
yield client
|
|
538
|
+
|
|
539
|
+
# tests/test_users.py
|
|
540
|
+
import pytest
|
|
541
|
+
from httpx import AsyncClient
|
|
542
|
+
|
|
543
|
+
@pytest.mark.anyio
|
|
544
|
+
async def test_create_user(client: AsyncClient):
|
|
545
|
+
response = await client.post("/api/users", json={
|
|
546
|
+
"email": "new@example.com",
|
|
547
|
+
"password": "securepass123",
|
|
548
|
+
"display_name": "New User",
|
|
549
|
+
})
|
|
550
|
+
assert response.status_code == 201
|
|
551
|
+
data = response.json()
|
|
552
|
+
assert data["email"] == "new@example.com"
|
|
553
|
+
assert "id" in data
|
|
554
|
+
assert "password" not in data # never expose password
|
|
555
|
+
|
|
556
|
+
@pytest.mark.anyio
|
|
557
|
+
async def test_create_user_duplicate_email(client: AsyncClient):
|
|
558
|
+
# First creation succeeds
|
|
559
|
+
await client.post("/api/users", json={
|
|
560
|
+
"email": "dup@example.com",
|
|
561
|
+
"password": "securepass123",
|
|
562
|
+
"display_name": "User 1",
|
|
563
|
+
})
|
|
564
|
+
# Second creation fails
|
|
565
|
+
response = await client.post("/api/users", json={
|
|
566
|
+
"email": "dup@example.com",
|
|
567
|
+
"password": "securepass123",
|
|
568
|
+
"display_name": "User 2",
|
|
569
|
+
})
|
|
570
|
+
assert response.status_code == 409
|
|
571
|
+
|
|
572
|
+
@pytest.mark.anyio
|
|
573
|
+
async def test_list_users_requires_auth(client: AsyncClient):
|
|
574
|
+
response = await client.get("/api/users")
|
|
575
|
+
assert response.status_code == 401
|
|
576
|
+
|
|
577
|
+
@pytest.mark.anyio
|
|
578
|
+
async def test_list_users(authenticated_client: AsyncClient):
|
|
579
|
+
response = await authenticated_client.get("/api/users?page=1&per_page=10")
|
|
580
|
+
assert response.status_code == 200
|
|
581
|
+
data = response.json()
|
|
582
|
+
assert "users" in data
|
|
583
|
+
assert "total" in data
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
## Configuration with pydantic-settings
|
|
587
|
+
|
|
588
|
+
```python
|
|
589
|
+
# app/config.py
|
|
590
|
+
from pydantic_settings import BaseSettings
|
|
591
|
+
from functools import lru_cache
|
|
592
|
+
|
|
593
|
+
class Settings(BaseSettings):
|
|
594
|
+
model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
|
|
595
|
+
|
|
596
|
+
app_name: str = "My API"
|
|
597
|
+
debug: bool = False
|
|
598
|
+
database_url: str
|
|
599
|
+
jwt_secret: str
|
|
600
|
+
jwt_algorithm: str = "HS256"
|
|
601
|
+
cors_origins: list[str] = ["http://localhost:3000"]
|
|
602
|
+
redis_url: str = "redis://localhost:6379"
|
|
603
|
+
|
|
604
|
+
@lru_cache
|
|
605
|
+
def get_settings() -> Settings:
|
|
606
|
+
return Settings()
|
|
607
|
+
|
|
608
|
+
settings = get_settings()
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
## Deployment
|
|
612
|
+
|
|
613
|
+
### Uvicorn (Production)
|
|
614
|
+
|
|
615
|
+
```bash
|
|
616
|
+
# Direct
|
|
617
|
+
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
|
|
618
|
+
|
|
619
|
+
# With Gunicorn (recommended for production)
|
|
620
|
+
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Dockerfile
|
|
624
|
+
|
|
625
|
+
```dockerfile
|
|
626
|
+
FROM python:3.12-slim
|
|
627
|
+
|
|
628
|
+
WORKDIR /app
|
|
629
|
+
COPY requirements.txt .
|
|
630
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
631
|
+
COPY app/ app/
|
|
632
|
+
|
|
633
|
+
EXPOSE 8000
|
|
634
|
+
CMD ["gunicorn", "app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
## Anti-Patterns
|
|
638
|
+
|
|
639
|
+
| Anti-Pattern | Correct Approach |
|
|
640
|
+
|-------------|-----------------|
|
|
641
|
+
| Using `def` for I/O-bound routes | Use `async def` for database calls, HTTP requests, file I/O |
|
|
642
|
+
| Putting business logic in route handlers | Extract to service layer, keep routes thin |
|
|
643
|
+
| Not using `response_model` | Always declare response models for automatic serialization and docs |
|
|
644
|
+
| Catching broad `Exception` in routes | Use specific exception types, let global handler catch the rest |
|
|
645
|
+
| Using `@app.on_event("startup")` | Use the `lifespan` context manager (on_event is deprecated) |
|
|
646
|
+
| Hardcoding secrets in source | Use pydantic-settings with `.env` files or environment variables |
|
|
647
|
+
| Returning dict instead of Pydantic model | Always use Pydantic models for type safety and validation |
|
|
648
|
+
| Not validating file uploads | Check size, content type, and filename before processing |
|
|
649
|
+
| Blocking the event loop with sync code | Use `run_in_executor` for CPU-bound work or sync libraries |
|
|
650
|
+
| Mutable default arguments in dependencies | Use `Depends()` or `Query(default=...)` instead of mutable defaults |
|