moai-adk 0.35.1__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.
Potentially problematic release.
This version of moai-adk might be problematic. Click here for more details.
- moai_adk/__init__.py +10 -0
- moai_adk/__main__.py +199 -0
- moai_adk/cli/__init__.py +6 -0
- moai_adk/cli/commands/__init__.py +17 -0
- moai_adk/cli/commands/analyze.py +116 -0
- moai_adk/cli/commands/doctor.py +272 -0
- moai_adk/cli/commands/init.py +372 -0
- moai_adk/cli/commands/language.py +248 -0
- moai_adk/cli/commands/status.py +104 -0
- moai_adk/cli/commands/update.py +2686 -0
- moai_adk/cli/main.py +13 -0
- moai_adk/cli/prompts/__init__.py +5 -0
- moai_adk/cli/prompts/init_prompts.py +219 -0
- moai_adk/cli/spec_status.py +263 -0
- moai_adk/cli/ui/__init__.py +44 -0
- moai_adk/cli/ui/progress.py +422 -0
- moai_adk/cli/ui/prompts.py +389 -0
- moai_adk/cli/ui/theme.py +129 -0
- moai_adk/cli/worktree/__init__.py +27 -0
- moai_adk/cli/worktree/__main__.py +31 -0
- moai_adk/cli/worktree/cli.py +683 -0
- moai_adk/cli/worktree/exceptions.py +89 -0
- moai_adk/cli/worktree/manager.py +493 -0
- moai_adk/cli/worktree/models.py +65 -0
- moai_adk/cli/worktree/registry.py +422 -0
- moai_adk/core/PHASE2_OPTIMIZATIONS.md +467 -0
- moai_adk/core/__init__.py +1 -0
- moai_adk/core/analysis/__init__.py +9 -0
- moai_adk/core/analysis/session_analyzer.py +400 -0
- moai_adk/core/claude_integration.py +393 -0
- moai_adk/core/command_helpers.py +270 -0
- moai_adk/core/comprehensive_monitoring_system.py +1183 -0
- moai_adk/core/config/__init__.py +19 -0
- moai_adk/core/config/auto_spec_config.py +340 -0
- moai_adk/core/config/migration.py +244 -0
- moai_adk/core/config/unified.py +436 -0
- moai_adk/core/context_manager.py +273 -0
- moai_adk/core/diagnostics/__init__.py +19 -0
- moai_adk/core/diagnostics/slash_commands.py +159 -0
- moai_adk/core/enterprise_features.py +1404 -0
- moai_adk/core/error_recovery_system.py +1902 -0
- moai_adk/core/event_driven_hook_system.py +1371 -0
- moai_adk/core/git/__init__.py +31 -0
- moai_adk/core/git/branch.py +25 -0
- moai_adk/core/git/branch_manager.py +129 -0
- moai_adk/core/git/checkpoint.py +134 -0
- moai_adk/core/git/commit.py +67 -0
- moai_adk/core/git/conflict_detector.py +413 -0
- moai_adk/core/git/event_detector.py +79 -0
- moai_adk/core/git/manager.py +216 -0
- moai_adk/core/hooks/post_tool_auto_spec_completion.py +901 -0
- moai_adk/core/input_validation_middleware.py +1006 -0
- moai_adk/core/integration/__init__.py +22 -0
- moai_adk/core/integration/engine.py +157 -0
- moai_adk/core/integration/integration_tester.py +226 -0
- moai_adk/core/integration/models.py +88 -0
- moai_adk/core/integration/utils.py +211 -0
- moai_adk/core/issue_creator.py +305 -0
- moai_adk/core/jit_context_loader.py +956 -0
- moai_adk/core/jit_enhanced_hook_manager.py +1987 -0
- moai_adk/core/language_config.py +202 -0
- moai_adk/core/language_config_resolver.py +572 -0
- moai_adk/core/language_validator.py +543 -0
- moai_adk/core/mcp/setup.py +116 -0
- moai_adk/core/merge/__init__.py +9 -0
- moai_adk/core/merge/analyzer.py +605 -0
- moai_adk/core/migration/__init__.py +18 -0
- moai_adk/core/migration/alfred_to_moai_migrator.py +383 -0
- moai_adk/core/migration/backup_manager.py +277 -0
- moai_adk/core/migration/custom_element_scanner.py +358 -0
- moai_adk/core/migration/file_migrator.py +209 -0
- moai_adk/core/migration/interactive_checkbox_ui.py +488 -0
- moai_adk/core/migration/selective_restorer.py +470 -0
- moai_adk/core/migration/template_utils.py +74 -0
- moai_adk/core/migration/user_selection_ui.py +338 -0
- moai_adk/core/migration/version_detector.py +139 -0
- moai_adk/core/migration/version_migrator.py +228 -0
- moai_adk/core/performance/__init__.py +6 -0
- moai_adk/core/performance/cache_system.py +316 -0
- moai_adk/core/performance/parallel_processor.py +116 -0
- moai_adk/core/phase_optimized_hook_scheduler.py +879 -0
- moai_adk/core/project/__init__.py +1 -0
- moai_adk/core/project/backup_utils.py +70 -0
- moai_adk/core/project/checker.py +300 -0
- moai_adk/core/project/detector.py +293 -0
- moai_adk/core/project/initializer.py +387 -0
- moai_adk/core/project/phase_executor.py +716 -0
- moai_adk/core/project/validator.py +139 -0
- moai_adk/core/quality/__init__.py +6 -0
- moai_adk/core/quality/trust_checker.py +377 -0
- moai_adk/core/quality/validators/__init__.py +6 -0
- moai_adk/core/quality/validators/base_validator.py +19 -0
- moai_adk/core/realtime_monitoring_dashboard.py +1724 -0
- moai_adk/core/robust_json_parser.py +611 -0
- moai_adk/core/rollback_manager.py +918 -0
- moai_adk/core/session_manager.py +651 -0
- moai_adk/core/skill_loading_system.py +579 -0
- moai_adk/core/spec/confidence_scoring.py +680 -0
- moai_adk/core/spec/ears_template_engine.py +1247 -0
- moai_adk/core/spec/quality_validator.py +687 -0
- moai_adk/core/spec_status_manager.py +478 -0
- moai_adk/core/template/__init__.py +7 -0
- moai_adk/core/template/backup.py +174 -0
- moai_adk/core/template/config.py +191 -0
- moai_adk/core/template/languages.py +43 -0
- moai_adk/core/template/merger.py +233 -0
- moai_adk/core/template/processor.py +1200 -0
- moai_adk/core/template_engine.py +310 -0
- moai_adk/core/template_variable_synchronizer.py +417 -0
- moai_adk/core/unified_permission_manager.py +745 -0
- moai_adk/core/user_behavior_analytics.py +851 -0
- moai_adk/core/version_sync.py +429 -0
- moai_adk/foundation/__init__.py +56 -0
- moai_adk/foundation/backend.py +1027 -0
- moai_adk/foundation/database.py +1115 -0
- moai_adk/foundation/devops.py +1585 -0
- moai_adk/foundation/ears.py +431 -0
- moai_adk/foundation/frontend.py +870 -0
- moai_adk/foundation/git/commit_templates.py +557 -0
- moai_adk/foundation/git.py +376 -0
- moai_adk/foundation/langs.py +484 -0
- moai_adk/foundation/ml_ops.py +1162 -0
- moai_adk/foundation/testing.py +1524 -0
- moai_adk/foundation/trust/trust_principles.py +676 -0
- moai_adk/foundation/trust/validation_checklist.py +1573 -0
- moai_adk/project/__init__.py +0 -0
- moai_adk/project/configuration.py +1084 -0
- moai_adk/project/documentation.py +566 -0
- moai_adk/project/schema.py +447 -0
- moai_adk/statusline/__init__.py +38 -0
- moai_adk/statusline/alfred_detector.py +105 -0
- moai_adk/statusline/config.py +376 -0
- moai_adk/statusline/enhanced_output_style_detector.py +372 -0
- moai_adk/statusline/git_collector.py +190 -0
- moai_adk/statusline/main.py +322 -0
- moai_adk/statusline/metrics_tracker.py +78 -0
- moai_adk/statusline/renderer.py +343 -0
- moai_adk/statusline/update_checker.py +129 -0
- moai_adk/statusline/version_reader.py +741 -0
- moai_adk/templates/.claude/agents/moai/ai-nano-banana.md +714 -0
- moai_adk/templates/.claude/agents/moai/builder-agent.md +474 -0
- moai_adk/templates/.claude/agents/moai/builder-command.md +1172 -0
- moai_adk/templates/.claude/agents/moai/builder-plugin.md +637 -0
- moai_adk/templates/.claude/agents/moai/builder-skill.md +666 -0
- moai_adk/templates/.claude/agents/moai/expert-backend.md +899 -0
- moai_adk/templates/.claude/agents/moai/expert-database.md +777 -0
- moai_adk/templates/.claude/agents/moai/expert-debug.md +401 -0
- moai_adk/templates/.claude/agents/moai/expert-devops.md +720 -0
- moai_adk/templates/.claude/agents/moai/expert-frontend.md +734 -0
- moai_adk/templates/.claude/agents/moai/expert-performance.md +657 -0
- moai_adk/templates/.claude/agents/moai/expert-security.md +513 -0
- moai_adk/templates/.claude/agents/moai/expert-testing.md +733 -0
- moai_adk/templates/.claude/agents/moai/expert-uiux.md +1041 -0
- moai_adk/templates/.claude/agents/moai/manager-claude-code.md +432 -0
- moai_adk/templates/.claude/agents/moai/manager-docs.md +573 -0
- moai_adk/templates/.claude/agents/moai/manager-git.md +1060 -0
- moai_adk/templates/.claude/agents/moai/manager-project.md +891 -0
- moai_adk/templates/.claude/agents/moai/manager-quality.md +624 -0
- moai_adk/templates/.claude/agents/moai/manager-spec.md +809 -0
- moai_adk/templates/.claude/agents/moai/manager-strategy.md +780 -0
- moai_adk/templates/.claude/agents/moai/manager-tdd.md +784 -0
- moai_adk/templates/.claude/agents/moai/mcp-context7.md +458 -0
- moai_adk/templates/.claude/agents/moai/mcp-figma.md +1607 -0
- moai_adk/templates/.claude/agents/moai/mcp-notion.md +789 -0
- moai_adk/templates/.claude/agents/moai/mcp-playwright.md +469 -0
- moai_adk/templates/.claude/agents/moai/mcp-sequential-thinking.md +1032 -0
- moai_adk/templates/.claude/commands/moai/0-project.md +1386 -0
- moai_adk/templates/.claude/commands/moai/1-plan.md +1427 -0
- moai_adk/templates/.claude/commands/moai/2-run.md +943 -0
- moai_adk/templates/.claude/commands/moai/3-sync.md +1324 -0
- moai_adk/templates/.claude/commands/moai/9-feedback.md +314 -0
- moai_adk/templates/.claude/hooks/__init__.py +8 -0
- moai_adk/templates/.claude/hooks/moai/__init__.py +8 -0
- moai_adk/templates/.claude/hooks/moai/lib/__init__.py +85 -0
- moai_adk/templates/.claude/hooks/moai/lib/checkpoint.py +244 -0
- moai_adk/templates/.claude/hooks/moai/lib/common.py +131 -0
- moai_adk/templates/.claude/hooks/moai/lib/config_manager.py +446 -0
- moai_adk/templates/.claude/hooks/moai/lib/config_validator.py +639 -0
- moai_adk/templates/.claude/hooks/moai/lib/example_config.json +104 -0
- moai_adk/templates/.claude/hooks/moai/lib/git_operations_manager.py +590 -0
- moai_adk/templates/.claude/hooks/moai/lib/language_validator.py +317 -0
- moai_adk/templates/.claude/hooks/moai/lib/models.py +102 -0
- moai_adk/templates/.claude/hooks/moai/lib/path_utils.py +28 -0
- moai_adk/templates/.claude/hooks/moai/lib/project.py +768 -0
- moai_adk/templates/.claude/hooks/moai/lib/test_hooks_improvements.py +443 -0
- moai_adk/templates/.claude/hooks/moai/lib/timeout.py +160 -0
- moai_adk/templates/.claude/hooks/moai/lib/unified_timeout_manager.py +530 -0
- moai_adk/templates/.claude/hooks/moai/session_end__auto_cleanup.py +862 -0
- moai_adk/templates/.claude/hooks/moai/session_start__show_project_info.py +1083 -0
- moai_adk/templates/.claude/output-styles/moai/r2d2.md +560 -0
- moai_adk/templates/.claude/output-styles/moai/yoda.md +359 -0
- moai_adk/templates/.claude/settings.json +172 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/SKILL.md +307 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/examples.md +431 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/scripts/batch_generate.py +560 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/scripts/generate_image.py +362 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/SKILL.md +249 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/examples.md +406 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/README.md +44 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/api-documentation.md +130 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/code-documentation.md +152 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/multi-format-output.md +178 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/user-guides.md +147 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/reference.md +328 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +320 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/examples.md +718 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/reference.md +464 -0
- moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +323 -0
- moai_adk/templates/.claude/skills/moai-domain-database/examples.md +830 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/README.md +53 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/mongodb.md +231 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/postgresql.md +169 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/redis.md +262 -0
- moai_adk/templates/.claude/skills/moai-domain-database/reference.md +545 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +497 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/examples.md +968 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/reference.md +664 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/SKILL.md +455 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/examples.md +560 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/accessibility-wcag.md +260 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/component-architecture.md +228 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/icon-libraries.md +401 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/theming-system.md +373 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/reference.md +243 -0
- moai_adk/templates/.claude/skills/moai-formats-data/SKILL.md +492 -0
- moai_adk/templates/.claude/skills/moai-formats-data/examples.md +804 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/README.md +98 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/SKILL-MODULARIZATION-TEMPLATE.md +278 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/caching-performance.md +459 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/data-validation.md +485 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/json-optimization.md +374 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/toon-encoding.md +308 -0
- moai_adk/templates/.claude/skills/moai-formats-data/reference.md +585 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/SKILL.md +202 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/examples.md +732 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/best-practices-checklist.md +616 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-custom-slash-commands-official.md +729 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-hooks-official.md +560 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-iam-official.md +635 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-memory-official.md +543 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-settings-official.md +663 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-skills-official.md +113 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-sub-agents-official.md +238 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/complete-configuration-guide.md +175 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/skill-examples.md +1674 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/skill-formatting-guide.md +729 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/sub-agents/sub-agent-examples.md +1513 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/sub-agents/sub-agent-formatting-guide.md +1086 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/sub-agents/sub-agent-integration-patterns.md +1100 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference.md +209 -0
- moai_adk/templates/.claude/skills/moai-foundation-context/SKILL.md +441 -0
- moai_adk/templates/.claude/skills/moai-foundation-context/examples.md +1048 -0
- moai_adk/templates/.claude/skills/moai-foundation-context/reference.md +246 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/SKILL.md +420 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/examples.md +358 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/README.md +296 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/agents-reference.md +359 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/commands-reference.md +432 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/delegation-patterns.md +757 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/execution-rules.md +687 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/modular-system.md +665 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/progressive-disclosure.md +649 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/spec-first-tdd.md +864 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/token-optimization.md +708 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/trust-5-framework.md +981 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/reference.md +478 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/SKILL.md +315 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/examples.md +228 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/assumption-matrix.md +80 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/cognitive-bias.md +199 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/first-principles.md +140 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/trade-off-analysis.md +154 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/reference.md +157 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/SKILL.md +364 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/examples.md +1232 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/best-practices.md +261 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/integration-patterns.md +194 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/proactive-analysis.md +229 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/trust5-validation.md +169 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/reference.md +1266 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/scripts/quality-gate.sh +668 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/templates/github-actions-quality.yml +481 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/templates/quality-config.yaml +519 -0
- moai_adk/templates/.claude/skills/moai-lang-cpp/SKILL.md +649 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +478 -0
- moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +612 -0
- moai_adk/templates/.claude/skills/moai-lang-flutter/SKILL.md +477 -0
- moai_adk/templates/.claude/skills/moai-lang-flutter/examples.md +1090 -0
- moai_adk/templates/.claude/skills/moai-lang-flutter/reference.md +686 -0
- moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +376 -0
- moai_adk/templates/.claude/skills/moai-lang-go/examples.md +919 -0
- moai_adk/templates/.claude/skills/moai-lang-go/reference.md +737 -0
- moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +385 -0
- moai_adk/templates/.claude/skills/moai-lang-java/examples.md +864 -0
- moai_adk/templates/.claude/skills/moai-lang-java/reference.md +291 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +382 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/examples.md +1006 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/reference.md +562 -0
- moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +644 -0
- moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +481 -0
- moai_adk/templates/.claude/skills/moai-lang-python/examples.md +977 -0
- moai_adk/templates/.claude/skills/moai-lang-python/reference.md +804 -0
- moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +579 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +687 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +372 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/examples.md +659 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/reference.md +504 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +497 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/examples.md +633 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/reference.md +423 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +497 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/examples.md +918 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/reference.md +672 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +368 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/examples.md +1089 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/reference.md +731 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/SKILL.md +300 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/advanced-patterns.md +465 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/examples.md +270 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/optimization.md +440 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/reference.md +228 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/SKILL.md +319 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/advanced-patterns.md +336 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/examples.md +592 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/advanced-deployment-patterns.md +182 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/advanced-patterns.md +17 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/configuration.md +57 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/content-architecture-optimization.md +162 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/deployment.md +52 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/framework-core-configuration.md +186 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/i18n-setup.md +55 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/mdx-components.md +52 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/optimization.md +303 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/reference.md +379 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/SKILL.md +372 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/examples.md +575 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/advanced-patterns.md +394 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/optimization.md +278 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/shadcn-components.md +457 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/shadcn-theming.md +373 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/reference.md +74 -0
- moai_adk/templates/.claude/skills/moai-mcp-figma/SKILL.md +402 -0
- moai_adk/templates/.claude/skills/moai-mcp-figma/advanced-patterns.md +607 -0
- moai_adk/templates/.claude/skills/moai-mcp-notion/SKILL.md +300 -0
- moai_adk/templates/.claude/skills/moai-mcp-notion/advanced-patterns.md +537 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/SKILL.md +291 -0
- moai_adk/templates/.claude/skills/moai-platform-clerk/SKILL.md +390 -0
- moai_adk/templates/.claude/skills/moai-platform-convex/SKILL.md +398 -0
- moai_adk/templates/.claude/skills/moai-platform-firebase-auth/SKILL.md +379 -0
- moai_adk/templates/.claude/skills/moai-platform-firestore/SKILL.md +358 -0
- moai_adk/templates/.claude/skills/moai-platform-neon/SKILL.md +467 -0
- moai_adk/templates/.claude/skills/moai-platform-railway/SKILL.md +377 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/SKILL.md +466 -0
- moai_adk/templates/.claude/skills/moai-platform-vercel/SKILL.md +482 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/SKILL.md +474 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/examples.md +621 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/migration.md +341 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/reference.md +463 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/validation.md +373 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/SKILL.md +275 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/adaptive-mfa.md +233 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/akamai-integration.md +215 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/application-credentials.md +280 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/attack-protection-log-events.md +225 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/attack-protection-overview.md +140 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/bot-detection.md +144 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/breached-password-detection.md +187 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/brute-force-protection.md +189 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/certifications.md +282 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/compliance-overview.md +263 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/continuous-session-protection.md +307 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/customize-mfa.md +178 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/dpop-implementation.md +283 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/fapi-implementation.md +259 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/gdpr-compliance.md +313 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/guardian-configuration.md +269 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/highly-regulated-identity.md +272 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/jwt-fundamentals.md +248 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mdl-verification.md +211 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mfa-api-management.md +278 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mfa-factors.md +226 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mfa-overview.md +174 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mtls-sender-constraining.md +316 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/ropg-flow-mfa.md +217 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/security-center.md +325 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/security-guidance.md +277 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/state-parameters.md +178 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/step-up-authentication.md +251 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/suspicious-ip-throttling.md +240 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/tenant-access-control.md +180 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/webauthn-fido.md +235 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/SKILL.md +449 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/advanced-patterns.md +379 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/examples.md +544 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/optimization.md +286 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/reference.md +307 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/README.md +190 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/SKILL.md +390 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/__init__.py +520 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/complete_workflow_demo_fixed.py +574 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/complete_project_setup.py +317 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/complete_workflow_demo.py +663 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/config-migration-example.json +190 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/question-examples.json +175 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/quick_start.py +196 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples.md +547 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/__init__.py +17 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/advanced-patterns.md +158 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/ask_user_integration.py +340 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/batch_questions.py +713 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/config_manager.py +538 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/documentation_manager.py +1336 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/language_initializer.py +730 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/migration_manager.py +608 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/template_optimizer.py +1005 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/reference.md +275 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/schemas/config-schema.json +316 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/schemas/tab_schema.json +1434 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/config-template.json +71 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/doc-templates/product-template.md +44 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/doc-templates/structure-template.md +48 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/doc-templates/tech-template.md +92 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/config-manager-setup.json +109 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/language-initializer.json +228 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/menu-project-config.json +130 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/project-batch-questions.json +97 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/spec-workflow-setup.json +150 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/test_integration_simple.py +436 -0
- moai_adk/templates/.claude/skills/moai-workflow-spec/SKILL.md +534 -0
- moai_adk/templates/.claude/skills/moai-workflow-spec/examples.md +900 -0
- moai_adk/templates/.claude/skills/moai-workflow-spec/reference.md +704 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/SKILL.md +377 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/examples.md +552 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/modules/code-templates.md +124 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/modules/feedback-templates.md +100 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/modules/template-optimizer.md +138 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/reference.md +346 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/LICENSE.txt +202 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/SKILL.md +456 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/advanced-patterns.md +576 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/ai-powered-testing.py +294 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/console_logging.py +35 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/element_discovery.py +40 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/static_html_automation.py +34 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples.md +672 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/README.md +220 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/ai-debugging.md +845 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review.md +1416 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization.md +1234 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/smart-refactoring.md +1243 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7.md +1260 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/optimization.md +505 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/reference/playwright-best-practices.md +57 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/reference.md +440 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/scripts/with_server.py +218 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/templates/alfred-integration.md +376 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/workflows/enterprise-testing-workflow.py +571 -0
- moai_adk/templates/.claude/skills/moai-worktree/SKILL.md +411 -0
- moai_adk/templates/.claude/skills/moai-worktree/examples.md +606 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/integration-patterns.md +982 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/parallel-development.md +778 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/worktree-commands.md +646 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/worktree-management.md +782 -0
- moai_adk/templates/.claude/skills/moai-worktree/reference.md +357 -0
- moai_adk/templates/.git-hooks/pre-commit +128 -0
- moai_adk/templates/.git-hooks/pre-push +365 -0
- moai_adk/templates/.github/workflows/ci-universal.yml +513 -0
- moai_adk/templates/.github/workflows/security-secrets-check.yml +179 -0
- moai_adk/templates/.github/workflows/spec-issue-sync.yml +337 -0
- moai_adk/templates/.gitignore +222 -0
- moai_adk/templates/.mcp.json +13 -0
- moai_adk/templates/.moai/config/config.yaml +58 -0
- moai_adk/templates/.moai/config/questions/_schema.yaml +174 -0
- moai_adk/templates/.moai/config/questions/tab0-init.yaml +251 -0
- moai_adk/templates/.moai/config/questions/tab1-user.yaml +107 -0
- moai_adk/templates/.moai/config/questions/tab2-project.yaml +79 -0
- moai_adk/templates/.moai/config/questions/tab3-git.yaml +632 -0
- moai_adk/templates/.moai/config/questions/tab4-quality.yaml +182 -0
- moai_adk/templates/.moai/config/questions/tab5-system.yaml +96 -0
- moai_adk/templates/.moai/config/sections/git-strategy.yaml +116 -0
- moai_adk/templates/.moai/config/sections/language.yaml +11 -0
- moai_adk/templates/.moai/config/sections/project.yaml +13 -0
- moai_adk/templates/.moai/config/sections/quality.yaml +17 -0
- moai_adk/templates/.moai/config/sections/system.yaml +24 -0
- moai_adk/templates/.moai/config/sections/user.yaml +5 -0
- moai_adk/templates/.moai/config/statusline-config.yaml +92 -0
- moai_adk/templates/.moai/scripts/setup-glm.py +136 -0
- moai_adk/templates/CLAUDE.md +642 -0
- moai_adk/utils/__init__.py +30 -0
- moai_adk/utils/banner.py +38 -0
- moai_adk/utils/common.py +294 -0
- moai_adk/utils/link_validator.py +241 -0
- moai_adk/utils/logger.py +147 -0
- moai_adk/utils/safe_file_reader.py +206 -0
- moai_adk/utils/timeout.py +160 -0
- moai_adk/utils/toon_utils.py +256 -0
- moai_adk/version.py +22 -0
- moai_adk-0.35.1.dist-info/METADATA +3018 -0
- moai_adk-0.35.1.dist-info/RECORD +502 -0
- moai_adk-0.35.1.dist-info/WHEEL +4 -0
- moai_adk-0.35.1.dist-info/entry_points.txt +3 -0
- moai_adk-0.35.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,977 @@
|
|
|
1
|
+
# Python Production-Ready Code Examples
|
|
2
|
+
|
|
3
|
+
## Complete FastAPI Application
|
|
4
|
+
|
|
5
|
+
### Project Structure
|
|
6
|
+
```
|
|
7
|
+
fastapi_app/
|
|
8
|
+
├── app/
|
|
9
|
+
│ ├── __init__.py
|
|
10
|
+
│ ├── main.py
|
|
11
|
+
│ ├── config.py
|
|
12
|
+
│ ├── database.py
|
|
13
|
+
│ ├── dependencies.py
|
|
14
|
+
│ ├── models/
|
|
15
|
+
│ │ ├── __init__.py
|
|
16
|
+
│ │ └── user.py
|
|
17
|
+
│ ├── schemas/
|
|
18
|
+
│ │ ├── __init__.py
|
|
19
|
+
│ │ └── user.py
|
|
20
|
+
│ ├── repositories/
|
|
21
|
+
│ │ ├── __init__.py
|
|
22
|
+
│ │ └── user_repository.py
|
|
23
|
+
│ ├── services/
|
|
24
|
+
│ │ ├── __init__.py
|
|
25
|
+
│ │ └── user_service.py
|
|
26
|
+
│ └── api/
|
|
27
|
+
│ ├── __init__.py
|
|
28
|
+
│ └── v1/
|
|
29
|
+
│ ├── __init__.py
|
|
30
|
+
│ ├── router.py
|
|
31
|
+
│ └── endpoints/
|
|
32
|
+
│ └── users.py
|
|
33
|
+
├── tests/
|
|
34
|
+
│ ├── conftest.py
|
|
35
|
+
│ ├── test_users.py
|
|
36
|
+
│ └── test_services.py
|
|
37
|
+
├── pyproject.toml
|
|
38
|
+
└── Dockerfile
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Main Application Entry
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
# app/main.py
|
|
45
|
+
from contextlib import asynccontextmanager
|
|
46
|
+
from fastapi import FastAPI
|
|
47
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
48
|
+
|
|
49
|
+
from app.config import get_settings
|
|
50
|
+
from app.database import init_db, close_db
|
|
51
|
+
from app.api.v1.router import api_router
|
|
52
|
+
|
|
53
|
+
settings = get_settings()
|
|
54
|
+
|
|
55
|
+
@asynccontextmanager
|
|
56
|
+
async def lifespan(app: FastAPI):
|
|
57
|
+
# Startup
|
|
58
|
+
await init_db()
|
|
59
|
+
yield
|
|
60
|
+
# Shutdown
|
|
61
|
+
await close_db()
|
|
62
|
+
|
|
63
|
+
app = FastAPI(
|
|
64
|
+
title=settings.app_name,
|
|
65
|
+
version="1.0.0",
|
|
66
|
+
lifespan=lifespan,
|
|
67
|
+
docs_url="/api/docs",
|
|
68
|
+
redoc_url="/api/redoc",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# CORS middleware
|
|
72
|
+
app.add_middleware(
|
|
73
|
+
CORSMiddleware,
|
|
74
|
+
allow_origins=settings.cors_origins,
|
|
75
|
+
allow_credentials=True,
|
|
76
|
+
allow_methods=["*"],
|
|
77
|
+
allow_headers=["*"],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Include API router
|
|
81
|
+
app.include_router(api_router, prefix="/api/v1")
|
|
82
|
+
|
|
83
|
+
@app.get("/health")
|
|
84
|
+
async def health_check():
|
|
85
|
+
return {"status": "healthy", "version": "1.0.0"}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Configuration
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# app/config.py
|
|
92
|
+
from functools import lru_cache
|
|
93
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
94
|
+
|
|
95
|
+
class Settings(BaseSettings):
|
|
96
|
+
model_config = SettingsConfigDict(
|
|
97
|
+
env_file=".env",
|
|
98
|
+
env_file_encoding="utf-8",
|
|
99
|
+
case_sensitive=False,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Application
|
|
103
|
+
app_name: str = "FastAPI App"
|
|
104
|
+
debug: bool = False
|
|
105
|
+
environment: str = "development"
|
|
106
|
+
|
|
107
|
+
# Database
|
|
108
|
+
database_url: str = "postgresql+asyncpg://user:pass@localhost/db"
|
|
109
|
+
db_pool_size: int = 5
|
|
110
|
+
db_max_overflow: int = 10
|
|
111
|
+
db_pool_timeout: int = 30
|
|
112
|
+
|
|
113
|
+
# Security
|
|
114
|
+
secret_key: str
|
|
115
|
+
access_token_expire_minutes: int = 30
|
|
116
|
+
refresh_token_expire_days: int = 7
|
|
117
|
+
algorithm: str = "HS256"
|
|
118
|
+
|
|
119
|
+
# CORS
|
|
120
|
+
cors_origins: list[str] = ["http://localhost:3000"]
|
|
121
|
+
|
|
122
|
+
# Redis (optional)
|
|
123
|
+
redis_url: str | None = None
|
|
124
|
+
|
|
125
|
+
@lru_cache
|
|
126
|
+
def get_settings() -> Settings:
|
|
127
|
+
return Settings()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Database Setup
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
# app/database.py
|
|
134
|
+
from sqlalchemy.ext.asyncio import (
|
|
135
|
+
create_async_engine,
|
|
136
|
+
async_sessionmaker,
|
|
137
|
+
AsyncSession,
|
|
138
|
+
AsyncEngine,
|
|
139
|
+
)
|
|
140
|
+
from sqlalchemy.orm import DeclarativeBase
|
|
141
|
+
|
|
142
|
+
from app.config import get_settings
|
|
143
|
+
|
|
144
|
+
settings = get_settings()
|
|
145
|
+
|
|
146
|
+
class Base(DeclarativeBase):
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
engine: AsyncEngine | None = None
|
|
150
|
+
async_session_factory: async_sessionmaker[AsyncSession] | None = None
|
|
151
|
+
|
|
152
|
+
async def init_db():
|
|
153
|
+
global engine, async_session_factory
|
|
154
|
+
|
|
155
|
+
engine = create_async_engine(
|
|
156
|
+
settings.database_url,
|
|
157
|
+
pool_size=settings.db_pool_size,
|
|
158
|
+
max_overflow=settings.db_max_overflow,
|
|
159
|
+
pool_timeout=settings.db_pool_timeout,
|
|
160
|
+
pool_pre_ping=True,
|
|
161
|
+
echo=settings.debug,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
async_session_factory = async_sessionmaker(
|
|
165
|
+
engine,
|
|
166
|
+
class_=AsyncSession,
|
|
167
|
+
expire_on_commit=False,
|
|
168
|
+
autoflush=False,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Create tables (for development only)
|
|
172
|
+
if settings.debug:
|
|
173
|
+
async with engine.begin() as conn:
|
|
174
|
+
await conn.run_sync(Base.metadata.create_all)
|
|
175
|
+
|
|
176
|
+
async def close_db():
|
|
177
|
+
global engine
|
|
178
|
+
if engine:
|
|
179
|
+
await engine.dispose()
|
|
180
|
+
|
|
181
|
+
async def get_db() -> AsyncSession:
|
|
182
|
+
if async_session_factory is None:
|
|
183
|
+
raise RuntimeError("Database not initialized")
|
|
184
|
+
|
|
185
|
+
async with async_session_factory() as session:
|
|
186
|
+
try:
|
|
187
|
+
yield session
|
|
188
|
+
await session.commit()
|
|
189
|
+
except Exception:
|
|
190
|
+
await session.rollback()
|
|
191
|
+
raise
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### SQLAlchemy Models
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
# app/models/user.py
|
|
198
|
+
from datetime import datetime
|
|
199
|
+
from sqlalchemy import String, Boolean, DateTime, func
|
|
200
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
201
|
+
|
|
202
|
+
from app.database import Base
|
|
203
|
+
|
|
204
|
+
class User(Base):
|
|
205
|
+
__tablename__ = "users"
|
|
206
|
+
|
|
207
|
+
id: Mapped[int] = mapped_column(primary_key=True, index=True)
|
|
208
|
+
email: Mapped[str] = mapped_column(
|
|
209
|
+
String(255), unique=True, index=True, nullable=False
|
|
210
|
+
)
|
|
211
|
+
hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
212
|
+
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
213
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
214
|
+
is_superuser: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
215
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
216
|
+
DateTime(timezone=True), server_default=func.now()
|
|
217
|
+
)
|
|
218
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
219
|
+
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def __repr__(self) -> str:
|
|
223
|
+
return f"<User(id={self.id}, email={self.email})>"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Pydantic Schemas
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
# app/schemas/user.py
|
|
230
|
+
from datetime import datetime
|
|
231
|
+
from pydantic import BaseModel, ConfigDict, EmailStr, Field
|
|
232
|
+
|
|
233
|
+
class UserBase(BaseModel):
|
|
234
|
+
email: EmailStr
|
|
235
|
+
name: str = Field(min_length=1, max_length=100)
|
|
236
|
+
|
|
237
|
+
class UserCreate(UserBase):
|
|
238
|
+
password: str = Field(min_length=8, max_length=100)
|
|
239
|
+
|
|
240
|
+
class UserUpdate(BaseModel):
|
|
241
|
+
name: str | None = Field(None, min_length=1, max_length=100)
|
|
242
|
+
password: str | None = Field(None, min_length=8, max_length=100)
|
|
243
|
+
|
|
244
|
+
class UserResponse(UserBase):
|
|
245
|
+
model_config = ConfigDict(from_attributes=True)
|
|
246
|
+
|
|
247
|
+
id: int
|
|
248
|
+
is_active: bool
|
|
249
|
+
created_at: datetime
|
|
250
|
+
updated_at: datetime
|
|
251
|
+
|
|
252
|
+
class UserListResponse(BaseModel):
|
|
253
|
+
users: list[UserResponse]
|
|
254
|
+
total: int
|
|
255
|
+
page: int
|
|
256
|
+
size: int
|
|
257
|
+
|
|
258
|
+
class Token(BaseModel):
|
|
259
|
+
access_token: str
|
|
260
|
+
refresh_token: str
|
|
261
|
+
token_type: str = "bearer"
|
|
262
|
+
|
|
263
|
+
class TokenPayload(BaseModel):
|
|
264
|
+
sub: int
|
|
265
|
+
exp: datetime
|
|
266
|
+
type: str # "access" or "refresh"
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Repository Pattern
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
# app/repositories/user_repository.py
|
|
273
|
+
from sqlalchemy import select, func
|
|
274
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
275
|
+
|
|
276
|
+
from app.models.user import User
|
|
277
|
+
from app.schemas.user import UserCreate, UserUpdate
|
|
278
|
+
|
|
279
|
+
class UserRepository:
|
|
280
|
+
def __init__(self, session: AsyncSession):
|
|
281
|
+
self.session = session
|
|
282
|
+
|
|
283
|
+
async def get_by_id(self, user_id: int) -> User | None:
|
|
284
|
+
result = await self.session.execute(
|
|
285
|
+
select(User).where(User.id == user_id)
|
|
286
|
+
)
|
|
287
|
+
return result.scalar_one_or_none()
|
|
288
|
+
|
|
289
|
+
async def get_by_email(self, email: str) -> User | None:
|
|
290
|
+
result = await self.session.execute(
|
|
291
|
+
select(User).where(User.email == email)
|
|
292
|
+
)
|
|
293
|
+
return result.scalar_one_or_none()
|
|
294
|
+
|
|
295
|
+
async def get_multi(
|
|
296
|
+
self,
|
|
297
|
+
skip: int = 0,
|
|
298
|
+
limit: int = 100,
|
|
299
|
+
is_active: bool | None = None,
|
|
300
|
+
) -> tuple[list[User], int]:
|
|
301
|
+
query = select(User)
|
|
302
|
+
count_query = select(func.count(User.id))
|
|
303
|
+
|
|
304
|
+
if is_active is not None:
|
|
305
|
+
query = query.where(User.is_active == is_active)
|
|
306
|
+
count_query = count_query.where(User.is_active == is_active)
|
|
307
|
+
|
|
308
|
+
# Get total count
|
|
309
|
+
total_result = await self.session.execute(count_query)
|
|
310
|
+
total = total_result.scalar_one()
|
|
311
|
+
|
|
312
|
+
# Get users
|
|
313
|
+
query = query.offset(skip).limit(limit).order_by(User.created_at.desc())
|
|
314
|
+
result = await self.session.execute(query)
|
|
315
|
+
users = result.scalars().all()
|
|
316
|
+
|
|
317
|
+
return list(users), total
|
|
318
|
+
|
|
319
|
+
async def create(self, user_create: UserCreate, hashed_password: str) -> User:
|
|
320
|
+
user = User(
|
|
321
|
+
email=user_create.email,
|
|
322
|
+
name=user_create.name,
|
|
323
|
+
hashed_password=hashed_password,
|
|
324
|
+
)
|
|
325
|
+
self.session.add(user)
|
|
326
|
+
await self.session.flush()
|
|
327
|
+
await self.session.refresh(user)
|
|
328
|
+
return user
|
|
329
|
+
|
|
330
|
+
async def update(self, user: User, user_update: UserUpdate) -> User:
|
|
331
|
+
update_data = user_update.model_dump(exclude_unset=True)
|
|
332
|
+
for field, value in update_data.items():
|
|
333
|
+
setattr(user, field, value)
|
|
334
|
+
await self.session.flush()
|
|
335
|
+
await self.session.refresh(user)
|
|
336
|
+
return user
|
|
337
|
+
|
|
338
|
+
async def delete(self, user: User) -> None:
|
|
339
|
+
await self.session.delete(user)
|
|
340
|
+
await self.session.flush()
|
|
341
|
+
|
|
342
|
+
async def deactivate(self, user: User) -> User:
|
|
343
|
+
user.is_active = False
|
|
344
|
+
await self.session.flush()
|
|
345
|
+
await self.session.refresh(user)
|
|
346
|
+
return user
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Service Layer
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
# app/services/user_service.py
|
|
353
|
+
from datetime import datetime, timedelta, timezone
|
|
354
|
+
from jose import jwt
|
|
355
|
+
from passlib.context import CryptContext
|
|
356
|
+
|
|
357
|
+
from app.config import get_settings
|
|
358
|
+
from app.models.user import User
|
|
359
|
+
from app.schemas.user import UserCreate, UserUpdate, Token, TokenPayload
|
|
360
|
+
from app.repositories.user_repository import UserRepository
|
|
361
|
+
|
|
362
|
+
settings = get_settings()
|
|
363
|
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
364
|
+
|
|
365
|
+
class UserService:
|
|
366
|
+
def __init__(self, repository: UserRepository):
|
|
367
|
+
self.repository = repository
|
|
368
|
+
|
|
369
|
+
@staticmethod
|
|
370
|
+
def hash_password(password: str) -> str:
|
|
371
|
+
return pwd_context.hash(password)
|
|
372
|
+
|
|
373
|
+
@staticmethod
|
|
374
|
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
375
|
+
return pwd_context.verify(plain_password, hashed_password)
|
|
376
|
+
|
|
377
|
+
@staticmethod
|
|
378
|
+
def create_token(user_id: int, token_type: str, expires_delta: timedelta) -> str:
|
|
379
|
+
expire = datetime.now(timezone.utc) + expires_delta
|
|
380
|
+
payload = TokenPayload(
|
|
381
|
+
sub=user_id,
|
|
382
|
+
exp=expire,
|
|
383
|
+
type=token_type,
|
|
384
|
+
)
|
|
385
|
+
return jwt.encode(
|
|
386
|
+
payload.model_dump(),
|
|
387
|
+
settings.secret_key,
|
|
388
|
+
algorithm=settings.algorithm,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def create_tokens(self, user: User) -> Token:
|
|
392
|
+
access_token = self.create_token(
|
|
393
|
+
user.id,
|
|
394
|
+
"access",
|
|
395
|
+
timedelta(minutes=settings.access_token_expire_minutes),
|
|
396
|
+
)
|
|
397
|
+
refresh_token = self.create_token(
|
|
398
|
+
user.id,
|
|
399
|
+
"refresh",
|
|
400
|
+
timedelta(days=settings.refresh_token_expire_days),
|
|
401
|
+
)
|
|
402
|
+
return Token(access_token=access_token, refresh_token=refresh_token)
|
|
403
|
+
|
|
404
|
+
async def authenticate(self, email: str, password: str) -> User | None:
|
|
405
|
+
user = await self.repository.get_by_email(email)
|
|
406
|
+
if not user:
|
|
407
|
+
return None
|
|
408
|
+
if not self.verify_password(password, user.hashed_password):
|
|
409
|
+
return None
|
|
410
|
+
if not user.is_active:
|
|
411
|
+
return None
|
|
412
|
+
return user
|
|
413
|
+
|
|
414
|
+
async def register(self, user_create: UserCreate) -> User:
|
|
415
|
+
hashed_password = self.hash_password(user_create.password)
|
|
416
|
+
return await self.repository.create(user_create, hashed_password)
|
|
417
|
+
|
|
418
|
+
async def update(self, user: User, user_update: UserUpdate) -> User:
|
|
419
|
+
if user_update.password:
|
|
420
|
+
user_update.password = self.hash_password(user_update.password)
|
|
421
|
+
return await self.repository.update(user, user_update)
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### API Endpoints
|
|
425
|
+
|
|
426
|
+
```python
|
|
427
|
+
# app/api/v1/endpoints/users.py
|
|
428
|
+
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
|
429
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
430
|
+
|
|
431
|
+
from app.database import get_db
|
|
432
|
+
from app.dependencies import get_current_user, get_current_active_superuser
|
|
433
|
+
from app.models.user import User
|
|
434
|
+
from app.schemas.user import (
|
|
435
|
+
UserCreate,
|
|
436
|
+
UserUpdate,
|
|
437
|
+
UserResponse,
|
|
438
|
+
UserListResponse,
|
|
439
|
+
Token,
|
|
440
|
+
)
|
|
441
|
+
from app.repositories.user_repository import UserRepository
|
|
442
|
+
from app.services.user_service import UserService
|
|
443
|
+
|
|
444
|
+
router = APIRouter(prefix="/users", tags=["users"])
|
|
445
|
+
|
|
446
|
+
def get_user_service(db: AsyncSession = Depends(get_db)) -> UserService:
|
|
447
|
+
repository = UserRepository(db)
|
|
448
|
+
return UserService(repository)
|
|
449
|
+
|
|
450
|
+
@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
|
451
|
+
async def register(
|
|
452
|
+
user_create: UserCreate,
|
|
453
|
+
service: UserService = Depends(get_user_service),
|
|
454
|
+
):
|
|
455
|
+
"""Register a new user."""
|
|
456
|
+
existing = await service.repository.get_by_email(user_create.email)
|
|
457
|
+
if existing:
|
|
458
|
+
raise HTTPException(
|
|
459
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
460
|
+
detail="Email already registered",
|
|
461
|
+
)
|
|
462
|
+
user = await service.register(user_create)
|
|
463
|
+
return user
|
|
464
|
+
|
|
465
|
+
@router.post("/login", response_model=Token)
|
|
466
|
+
async def login(
|
|
467
|
+
email: str,
|
|
468
|
+
password: str,
|
|
469
|
+
service: UserService = Depends(get_user_service),
|
|
470
|
+
):
|
|
471
|
+
"""Authenticate and get tokens."""
|
|
472
|
+
user = await service.authenticate(email, password)
|
|
473
|
+
if not user:
|
|
474
|
+
raise HTTPException(
|
|
475
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
476
|
+
detail="Invalid credentials",
|
|
477
|
+
)
|
|
478
|
+
return service.create_tokens(user)
|
|
479
|
+
|
|
480
|
+
@router.get("/me", response_model=UserResponse)
|
|
481
|
+
async def get_current_user_info(
|
|
482
|
+
current_user: User = Depends(get_current_user),
|
|
483
|
+
):
|
|
484
|
+
"""Get current user information."""
|
|
485
|
+
return current_user
|
|
486
|
+
|
|
487
|
+
@router.patch("/me", response_model=UserResponse)
|
|
488
|
+
async def update_current_user(
|
|
489
|
+
user_update: UserUpdate,
|
|
490
|
+
current_user: User = Depends(get_current_user),
|
|
491
|
+
service: UserService = Depends(get_user_service),
|
|
492
|
+
):
|
|
493
|
+
"""Update current user."""
|
|
494
|
+
return await service.update(current_user, user_update)
|
|
495
|
+
|
|
496
|
+
@router.get("", response_model=UserListResponse)
|
|
497
|
+
async def list_users(
|
|
498
|
+
page: int = Query(1, ge=1),
|
|
499
|
+
size: int = Query(20, ge=1, le=100),
|
|
500
|
+
is_active: bool | None = None,
|
|
501
|
+
current_user: User = Depends(get_current_active_superuser),
|
|
502
|
+
service: UserService = Depends(get_user_service),
|
|
503
|
+
):
|
|
504
|
+
"""List all users (admin only)."""
|
|
505
|
+
skip = (page - 1) * size
|
|
506
|
+
users, total = await service.repository.get_multi(
|
|
507
|
+
skip=skip, limit=size, is_active=is_active
|
|
508
|
+
)
|
|
509
|
+
return UserListResponse(users=users, total=total, page=page, size=size)
|
|
510
|
+
|
|
511
|
+
@router.get("/{user_id}", response_model=UserResponse)
|
|
512
|
+
async def get_user(
|
|
513
|
+
user_id: int,
|
|
514
|
+
current_user: User = Depends(get_current_active_superuser),
|
|
515
|
+
service: UserService = Depends(get_user_service),
|
|
516
|
+
):
|
|
517
|
+
"""Get user by ID (admin only)."""
|
|
518
|
+
user = await service.repository.get_by_id(user_id)
|
|
519
|
+
if not user:
|
|
520
|
+
raise HTTPException(
|
|
521
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
522
|
+
detail="User not found",
|
|
523
|
+
)
|
|
524
|
+
return user
|
|
525
|
+
|
|
526
|
+
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
527
|
+
async def deactivate_user(
|
|
528
|
+
user_id: int,
|
|
529
|
+
current_user: User = Depends(get_current_active_superuser),
|
|
530
|
+
service: UserService = Depends(get_user_service),
|
|
531
|
+
):
|
|
532
|
+
"""Deactivate user (admin only)."""
|
|
533
|
+
user = await service.repository.get_by_id(user_id)
|
|
534
|
+
if not user:
|
|
535
|
+
raise HTTPException(
|
|
536
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
537
|
+
detail="User not found",
|
|
538
|
+
)
|
|
539
|
+
await service.repository.deactivate(user)
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### Dependencies
|
|
543
|
+
|
|
544
|
+
```python
|
|
545
|
+
# app/dependencies.py
|
|
546
|
+
from fastapi import Depends, HTTPException, status
|
|
547
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
548
|
+
from jose import jwt, JWTError
|
|
549
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
550
|
+
|
|
551
|
+
from app.config import get_settings
|
|
552
|
+
from app.database import get_db
|
|
553
|
+
from app.models.user import User
|
|
554
|
+
from app.repositories.user_repository import UserRepository
|
|
555
|
+
from app.schemas.user import TokenPayload
|
|
556
|
+
|
|
557
|
+
settings = get_settings()
|
|
558
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/users/login")
|
|
559
|
+
|
|
560
|
+
async def get_current_user(
|
|
561
|
+
token: str = Depends(oauth2_scheme),
|
|
562
|
+
db: AsyncSession = Depends(get_db),
|
|
563
|
+
) -> User:
|
|
564
|
+
credentials_exception = HTTPException(
|
|
565
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
566
|
+
detail="Could not validate credentials",
|
|
567
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
try:
|
|
571
|
+
payload = jwt.decode(
|
|
572
|
+
token, settings.secret_key, algorithms=[settings.algorithm]
|
|
573
|
+
)
|
|
574
|
+
token_data = TokenPayload(**payload)
|
|
575
|
+
|
|
576
|
+
if token_data.type != "access":
|
|
577
|
+
raise credentials_exception
|
|
578
|
+
|
|
579
|
+
except JWTError:
|
|
580
|
+
raise credentials_exception
|
|
581
|
+
|
|
582
|
+
repository = UserRepository(db)
|
|
583
|
+
user = await repository.get_by_id(token_data.sub)
|
|
584
|
+
|
|
585
|
+
if user is None:
|
|
586
|
+
raise credentials_exception
|
|
587
|
+
|
|
588
|
+
return user
|
|
589
|
+
|
|
590
|
+
async def get_current_active_user(
|
|
591
|
+
current_user: User = Depends(get_current_user),
|
|
592
|
+
) -> User:
|
|
593
|
+
if not current_user.is_active:
|
|
594
|
+
raise HTTPException(
|
|
595
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
596
|
+
detail="Inactive user",
|
|
597
|
+
)
|
|
598
|
+
return current_user
|
|
599
|
+
|
|
600
|
+
async def get_current_active_superuser(
|
|
601
|
+
current_user: User = Depends(get_current_active_user),
|
|
602
|
+
) -> User:
|
|
603
|
+
if not current_user.is_superuser:
|
|
604
|
+
raise HTTPException(
|
|
605
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
606
|
+
detail="Insufficient permissions",
|
|
607
|
+
)
|
|
608
|
+
return current_user
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
## Complete pytest Test Suite
|
|
614
|
+
|
|
615
|
+
```python
|
|
616
|
+
# tests/conftest.py
|
|
617
|
+
import pytest
|
|
618
|
+
import pytest_asyncio
|
|
619
|
+
from httpx import AsyncClient, ASGITransport
|
|
620
|
+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
|
621
|
+
from sqlalchemy.orm import sessionmaker
|
|
622
|
+
from sqlalchemy.pool import StaticPool
|
|
623
|
+
|
|
624
|
+
from app.database import Base, get_db
|
|
625
|
+
from app.main import app
|
|
626
|
+
from app.config import get_settings
|
|
627
|
+
|
|
628
|
+
settings = get_settings()
|
|
629
|
+
|
|
630
|
+
@pytest.fixture(scope="session")
|
|
631
|
+
def event_loop():
|
|
632
|
+
import asyncio
|
|
633
|
+
loop = asyncio.new_event_loop()
|
|
634
|
+
yield loop
|
|
635
|
+
loop.close()
|
|
636
|
+
|
|
637
|
+
@pytest_asyncio.fixture(scope="session")
|
|
638
|
+
async def engine():
|
|
639
|
+
engine = create_async_engine(
|
|
640
|
+
"sqlite+aiosqlite:///:memory:",
|
|
641
|
+
connect_args={"check_same_thread": False},
|
|
642
|
+
poolclass=StaticPool,
|
|
643
|
+
)
|
|
644
|
+
async with engine.begin() as conn:
|
|
645
|
+
await conn.run_sync(Base.metadata.create_all)
|
|
646
|
+
yield engine
|
|
647
|
+
await engine.dispose()
|
|
648
|
+
|
|
649
|
+
@pytest_asyncio.fixture
|
|
650
|
+
async def db_session(engine):
|
|
651
|
+
async_session = sessionmaker(
|
|
652
|
+
engine, class_=AsyncSession, expire_on_commit=False
|
|
653
|
+
)
|
|
654
|
+
async with async_session() as session:
|
|
655
|
+
yield session
|
|
656
|
+
await session.rollback()
|
|
657
|
+
|
|
658
|
+
@pytest_asyncio.fixture
|
|
659
|
+
async def async_client(db_session):
|
|
660
|
+
async def override_get_db():
|
|
661
|
+
yield db_session
|
|
662
|
+
|
|
663
|
+
app.dependency_overrides[get_db] = override_get_db
|
|
664
|
+
|
|
665
|
+
async with AsyncClient(
|
|
666
|
+
transport=ASGITransport(app=app),
|
|
667
|
+
base_url="http://test",
|
|
668
|
+
) as client:
|
|
669
|
+
yield client
|
|
670
|
+
|
|
671
|
+
app.dependency_overrides.clear()
|
|
672
|
+
|
|
673
|
+
@pytest.fixture
|
|
674
|
+
def user_data():
|
|
675
|
+
return {
|
|
676
|
+
"email": "test@example.com",
|
|
677
|
+
"name": "Test User",
|
|
678
|
+
"password": "password123",
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
```python
|
|
683
|
+
# tests/test_users.py
|
|
684
|
+
import pytest
|
|
685
|
+
from httpx import AsyncClient
|
|
686
|
+
|
|
687
|
+
@pytest.mark.asyncio
|
|
688
|
+
async def test_register_user(async_client: AsyncClient, user_data: dict):
|
|
689
|
+
response = await async_client.post("/api/v1/users/register", json=user_data)
|
|
690
|
+
|
|
691
|
+
assert response.status_code == 201
|
|
692
|
+
data = response.json()
|
|
693
|
+
assert data["email"] == user_data["email"]
|
|
694
|
+
assert data["name"] == user_data["name"]
|
|
695
|
+
assert "id" in data
|
|
696
|
+
assert "password" not in data
|
|
697
|
+
|
|
698
|
+
@pytest.mark.asyncio
|
|
699
|
+
async def test_register_duplicate_email(async_client: AsyncClient, user_data: dict):
|
|
700
|
+
# First registration
|
|
701
|
+
await async_client.post("/api/v1/users/register", json=user_data)
|
|
702
|
+
|
|
703
|
+
# Second registration with same email
|
|
704
|
+
response = await async_client.post("/api/v1/users/register", json=user_data)
|
|
705
|
+
|
|
706
|
+
assert response.status_code == 400
|
|
707
|
+
assert "already registered" in response.json()["detail"]
|
|
708
|
+
|
|
709
|
+
@pytest.mark.asyncio
|
|
710
|
+
@pytest.mark.parametrize(
|
|
711
|
+
"invalid_data,expected_detail",
|
|
712
|
+
[
|
|
713
|
+
({"email": "invalid", "name": "Test", "password": "pass123"}, "email"),
|
|
714
|
+
({"email": "test@example.com", "name": "", "password": "pass123"}, "name"),
|
|
715
|
+
({"email": "test@example.com", "name": "Test", "password": "short"}, "password"),
|
|
716
|
+
],
|
|
717
|
+
ids=["invalid_email", "empty_name", "short_password"],
|
|
718
|
+
)
|
|
719
|
+
async def test_register_validation(
|
|
720
|
+
async_client: AsyncClient,
|
|
721
|
+
invalid_data: dict,
|
|
722
|
+
expected_detail: str,
|
|
723
|
+
):
|
|
724
|
+
response = await async_client.post("/api/v1/users/register", json=invalid_data)
|
|
725
|
+
|
|
726
|
+
assert response.status_code == 422
|
|
727
|
+
|
|
728
|
+
@pytest.mark.asyncio
|
|
729
|
+
async def test_login_success(async_client: AsyncClient, user_data: dict):
|
|
730
|
+
# Register user first
|
|
731
|
+
await async_client.post("/api/v1/users/register", json=user_data)
|
|
732
|
+
|
|
733
|
+
# Login
|
|
734
|
+
response = await async_client.post(
|
|
735
|
+
"/api/v1/users/login",
|
|
736
|
+
params={"email": user_data["email"], "password": user_data["password"]},
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
assert response.status_code == 200
|
|
740
|
+
data = response.json()
|
|
741
|
+
assert "access_token" in data
|
|
742
|
+
assert "refresh_token" in data
|
|
743
|
+
assert data["token_type"] == "bearer"
|
|
744
|
+
|
|
745
|
+
@pytest.mark.asyncio
|
|
746
|
+
async def test_get_current_user(async_client: AsyncClient, user_data: dict):
|
|
747
|
+
# Register and login
|
|
748
|
+
await async_client.post("/api/v1/users/register", json=user_data)
|
|
749
|
+
login_response = await async_client.post(
|
|
750
|
+
"/api/v1/users/login",
|
|
751
|
+
params={"email": user_data["email"], "password": user_data["password"]},
|
|
752
|
+
)
|
|
753
|
+
token = login_response.json()["access_token"]
|
|
754
|
+
|
|
755
|
+
# Get current user
|
|
756
|
+
response = await async_client.get(
|
|
757
|
+
"/api/v1/users/me",
|
|
758
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
assert response.status_code == 200
|
|
762
|
+
data = response.json()
|
|
763
|
+
assert data["email"] == user_data["email"]
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
768
|
+
## Async Patterns Examples
|
|
769
|
+
|
|
770
|
+
### Task Groups (Python 3.11+)
|
|
771
|
+
|
|
772
|
+
```python
|
|
773
|
+
import asyncio
|
|
774
|
+
from typing import Any
|
|
775
|
+
|
|
776
|
+
async def fetch_user(user_id: int) -> dict:
|
|
777
|
+
await asyncio.sleep(0.1) # Simulate API call
|
|
778
|
+
return {"id": user_id, "name": f"User {user_id}"}
|
|
779
|
+
|
|
780
|
+
async def fetch_all_users(user_ids: list[int]) -> list[dict]:
|
|
781
|
+
async with asyncio.TaskGroup() as tg:
|
|
782
|
+
tasks = [tg.create_task(fetch_user(uid)) for uid in user_ids]
|
|
783
|
+
|
|
784
|
+
return [task.result() for task in tasks]
|
|
785
|
+
|
|
786
|
+
# Exception handling with TaskGroup
|
|
787
|
+
async def fetch_with_error_handling(user_ids: list[int]) -> tuple[list[dict], list[Exception]]:
|
|
788
|
+
results = []
|
|
789
|
+
errors = []
|
|
790
|
+
|
|
791
|
+
async def safe_fetch(user_id: int):
|
|
792
|
+
try:
|
|
793
|
+
result = await fetch_user(user_id)
|
|
794
|
+
results.append(result)
|
|
795
|
+
except Exception as e:
|
|
796
|
+
errors.append(e)
|
|
797
|
+
|
|
798
|
+
async with asyncio.TaskGroup() as tg:
|
|
799
|
+
for uid in user_ids:
|
|
800
|
+
tg.create_task(safe_fetch(uid))
|
|
801
|
+
|
|
802
|
+
return results, errors
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### Semaphore for Rate Limiting
|
|
806
|
+
|
|
807
|
+
```python
|
|
808
|
+
import asyncio
|
|
809
|
+
from contextlib import asynccontextmanager
|
|
810
|
+
|
|
811
|
+
class RateLimiter:
|
|
812
|
+
def __init__(self, max_concurrent: int = 10):
|
|
813
|
+
self._semaphore = asyncio.Semaphore(max_concurrent)
|
|
814
|
+
|
|
815
|
+
@asynccontextmanager
|
|
816
|
+
async def acquire(self):
|
|
817
|
+
async with self._semaphore:
|
|
818
|
+
yield
|
|
819
|
+
|
|
820
|
+
rate_limiter = RateLimiter(max_concurrent=5)
|
|
821
|
+
|
|
822
|
+
async def rate_limited_fetch(url: str) -> dict:
|
|
823
|
+
async with rate_limiter.acquire():
|
|
824
|
+
async with httpx.AsyncClient() as client:
|
|
825
|
+
response = await client.get(url)
|
|
826
|
+
return response.json()
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
### Async Generator Streaming
|
|
830
|
+
|
|
831
|
+
```python
|
|
832
|
+
from typing import AsyncGenerator
|
|
833
|
+
|
|
834
|
+
async def stream_large_data(
|
|
835
|
+
db: AsyncSession,
|
|
836
|
+
batch_size: int = 1000,
|
|
837
|
+
) -> AsyncGenerator[list[User], None]:
|
|
838
|
+
offset = 0
|
|
839
|
+
while True:
|
|
840
|
+
result = await db.execute(
|
|
841
|
+
select(User)
|
|
842
|
+
.offset(offset)
|
|
843
|
+
.limit(batch_size)
|
|
844
|
+
.order_by(User.id)
|
|
845
|
+
)
|
|
846
|
+
users = result.scalars().all()
|
|
847
|
+
|
|
848
|
+
if not users:
|
|
849
|
+
break
|
|
850
|
+
|
|
851
|
+
yield users
|
|
852
|
+
offset += batch_size
|
|
853
|
+
|
|
854
|
+
# Usage
|
|
855
|
+
async def process_all_users(db: AsyncSession):
|
|
856
|
+
async for batch in stream_large_data(db):
|
|
857
|
+
for user in batch:
|
|
858
|
+
await process_user(user)
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
---
|
|
862
|
+
|
|
863
|
+
## Docker Production Dockerfile
|
|
864
|
+
|
|
865
|
+
```dockerfile
|
|
866
|
+
# Dockerfile
|
|
867
|
+
FROM python:3.13-slim AS builder
|
|
868
|
+
|
|
869
|
+
WORKDIR /app
|
|
870
|
+
|
|
871
|
+
# Install build dependencies
|
|
872
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
873
|
+
build-essential \
|
|
874
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
875
|
+
|
|
876
|
+
# Install Python dependencies
|
|
877
|
+
COPY pyproject.toml poetry.lock ./
|
|
878
|
+
RUN pip install poetry && \
|
|
879
|
+
poetry config virtualenvs.create false && \
|
|
880
|
+
poetry install --no-dev --no-interaction --no-ansi
|
|
881
|
+
|
|
882
|
+
FROM python:3.13-slim AS runtime
|
|
883
|
+
|
|
884
|
+
WORKDIR /app
|
|
885
|
+
|
|
886
|
+
# Create non-root user
|
|
887
|
+
RUN addgroup --system --gid 1001 appgroup && \
|
|
888
|
+
adduser --system --uid 1001 --gid 1001 appuser
|
|
889
|
+
|
|
890
|
+
# Copy dependencies from builder
|
|
891
|
+
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
|
|
892
|
+
COPY --from=builder /usr/local/bin /usr/local/bin
|
|
893
|
+
|
|
894
|
+
# Copy application code
|
|
895
|
+
COPY --chown=appuser:appgroup . .
|
|
896
|
+
|
|
897
|
+
# Switch to non-root user
|
|
898
|
+
USER appuser
|
|
899
|
+
|
|
900
|
+
# Expose port
|
|
901
|
+
EXPOSE 8000
|
|
902
|
+
|
|
903
|
+
# Health check
|
|
904
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
905
|
+
CMD python -c "import httpx; httpx.get('http://localhost:8000/health')"
|
|
906
|
+
|
|
907
|
+
# Run application
|
|
908
|
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
---
|
|
912
|
+
|
|
913
|
+
## pyproject.toml Complete Configuration
|
|
914
|
+
|
|
915
|
+
```toml
|
|
916
|
+
[tool.poetry]
|
|
917
|
+
name = "fastapi-app"
|
|
918
|
+
version = "1.0.0"
|
|
919
|
+
description = "Production FastAPI Application"
|
|
920
|
+
authors = ["Developer <dev@example.com>"]
|
|
921
|
+
python = "^3.13"
|
|
922
|
+
|
|
923
|
+
[tool.poetry.dependencies]
|
|
924
|
+
python = "^3.13"
|
|
925
|
+
fastapi = "^0.115.0"
|
|
926
|
+
uvicorn = {extras = ["standard"], version = "^0.32.0"}
|
|
927
|
+
pydantic = "^2.9.0"
|
|
928
|
+
pydantic-settings = "^2.6.0"
|
|
929
|
+
sqlalchemy = {extras = ["asyncio"], version = "^2.0.0"}
|
|
930
|
+
asyncpg = "^0.30.0"
|
|
931
|
+
python-jose = {extras = ["cryptography"], version = "^3.3.0"}
|
|
932
|
+
passlib = {extras = ["bcrypt"], version = "^1.7.4"}
|
|
933
|
+
httpx = "^0.28.0"
|
|
934
|
+
|
|
935
|
+
[tool.poetry.group.dev.dependencies]
|
|
936
|
+
pytest = "^8.3.0"
|
|
937
|
+
pytest-asyncio = "^0.24.0"
|
|
938
|
+
pytest-cov = "^6.0.0"
|
|
939
|
+
aiosqlite = "^0.20.0"
|
|
940
|
+
ruff = "^0.8.0"
|
|
941
|
+
mypy = "^1.13.0"
|
|
942
|
+
|
|
943
|
+
[tool.ruff]
|
|
944
|
+
line-length = 100
|
|
945
|
+
target-version = "py313"
|
|
946
|
+
|
|
947
|
+
[tool.ruff.lint]
|
|
948
|
+
select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"]
|
|
949
|
+
ignore = ["E501"]
|
|
950
|
+
|
|
951
|
+
[tool.ruff.lint.isort]
|
|
952
|
+
known-first-party = ["app"]
|
|
953
|
+
|
|
954
|
+
[tool.pytest.ini_options]
|
|
955
|
+
asyncio_mode = "auto"
|
|
956
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
957
|
+
testpaths = ["tests"]
|
|
958
|
+
addopts = "-v --tb=short --cov=app --cov-report=term-missing"
|
|
959
|
+
|
|
960
|
+
[tool.mypy]
|
|
961
|
+
python_version = "3.13"
|
|
962
|
+
strict = true
|
|
963
|
+
plugins = ["pydantic.mypy"]
|
|
964
|
+
|
|
965
|
+
[tool.coverage.run]
|
|
966
|
+
source = ["app"]
|
|
967
|
+
omit = ["tests/*"]
|
|
968
|
+
|
|
969
|
+
[build-system]
|
|
970
|
+
requires = ["poetry-core>=1.0.0"]
|
|
971
|
+
build-backend = "poetry.core.masonry.api"
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
---
|
|
975
|
+
|
|
976
|
+
Last Updated: 2025-12-07
|
|
977
|
+
Version: 1.0.0
|