moai-adk 0.8.0__py3-none-any.whl → 0.34.0__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.
- moai_adk/__init__.py +2 -6
- moai_adk/__main__.py +136 -21
- moai_adk/cli/__init__.py +6 -2
- moai_adk/cli/commands/__init__.py +1 -4
- moai_adk/cli/commands/analyze.py +116 -0
- moai_adk/cli/commands/doctor.py +17 -5
- moai_adk/cli/commands/init.py +118 -48
- moai_adk/cli/commands/language.py +248 -0
- moai_adk/cli/commands/status.py +8 -13
- moai_adk/cli/commands/update.py +1978 -149
- moai_adk/cli/main.py +3 -2
- moai_adk/cli/prompts/init_prompts.py +144 -91
- 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 +0 -1
- 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 +6 -0
- moai_adk/core/config/auto_spec_config.py +340 -0
- moai_adk/core/config/migration.py +148 -17
- moai_adk/core/config/unified.py +436 -0
- moai_adk/core/context_manager.py +273 -0
- moai_adk/core/diagnostics/slash_commands.py +0 -1
- 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 +8 -1
- moai_adk/core/git/branch.py +0 -1
- moai_adk/core/git/branch_manager.py +2 -10
- moai_adk/core/git/checkpoint.py +1 -7
- moai_adk/core/git/commit.py +0 -1
- moai_adk/core/git/conflict_detector.py +413 -0
- moai_adk/core/git/event_detector.py +3 -5
- moai_adk/core/git/manager.py +91 -2
- 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 +481 -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 +0 -1
- moai_adk/core/project/backup_utils.py +2 -7
- moai_adk/core/project/checker.py +2 -4
- moai_adk/core/project/detector.py +189 -22
- moai_adk/core/project/initializer.py +218 -27
- moai_adk/core/project/phase_executor.py +416 -44
- moai_adk/core/project/validator.py +7 -32
- moai_adk/core/quality/__init__.py +1 -1
- moai_adk/core/quality/trust_checker.py +37 -101
- moai_adk/core/quality/validators/__init__.py +1 -1
- moai_adk/core/quality/validators/base_validator.py +1 -1
- 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 +0 -1
- moai_adk/core/template/backup.py +82 -17
- moai_adk/core/template/config.py +112 -40
- moai_adk/core/template/languages.py +0 -1
- moai_adk/core/template/merger.py +75 -26
- moai_adk/core/template/processor.py +750 -72
- 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 +670 -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-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 +509 -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 +1020 -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 +1384 -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/{alfred/core → moai/lib}/checkpoint.py +10 -37
- 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 +1075 -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 +78 -50
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/SKILL.md +438 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/examples.md +431 -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 +313 -283
- moai_adk/templates/.claude/skills/moai-domain-backend/examples.md +610 -1525
- moai_adk/templates/.claude/skills/moai-domain-backend/reference.md +423 -619
- moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +295 -95
- moai_adk/templates/.claude/skills/moai-domain-database/examples.md +817 -16
- 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 +532 -17
- moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +470 -97
- moai_adk/templates/.claude/skills/moai-domain-frontend/examples.md +955 -16
- moai_adk/templates/.claude/skills/moai-domain-frontend/reference.md +651 -18
- 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/design-system-tokens.md +405 -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 +618 -93
- moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +446 -91
- 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 +346 -94
- moai_adk/templates/.claude/skills/moai-lang-go/examples.md +906 -16
- moai_adk/templates/.claude/skills/moai-lang-go/reference.md +721 -15
- moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +352 -91
- moai_adk/templates/.claude/skills/moai-lang-java/examples.md +851 -16
- moai_adk/templates/.claude/skills/moai-lang-java/reference.md +278 -18
- moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +344 -86
- moai_adk/templates/.claude/skills/moai-lang-kotlin/examples.md +993 -16
- moai_adk/templates/.claude/skills/moai-lang-kotlin/reference.md +549 -18
- moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +617 -96
- moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +364 -314
- moai_adk/templates/.claude/skills/moai-lang-python/examples.md +849 -496
- moai_adk/templates/.claude/skills/moai-lang-python/reference.md +731 -243
- moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +545 -89
- moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +650 -87
- moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +341 -93
- moai_adk/templates/.claude/skills/moai-lang-rust/examples.md +646 -16
- moai_adk/templates/.claude/skills/moai-lang-rust/reference.md +491 -18
- moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +463 -89
- moai_adk/templates/.claude/skills/moai-lang-scala/examples.md +620 -16
- moai_adk/templates/.claude/skills/moai-lang-scala/reference.md +410 -17
- moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +486 -112
- moai_adk/templates/.claude/skills/moai-lang-swift/examples.md +905 -16
- moai_adk/templates/.claude/skills/moai-lang-swift/reference.md +659 -17
- moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +333 -92
- moai_adk/templates/.claude/skills/moai-lang-typescript/examples.md +1076 -16
- moai_adk/templates/.claude/skills/moai-lang-typescript/reference.md +718 -21
- 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 +290 -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-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 +1462 -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 +206 -36
- moai_adk/templates/.gitignore +194 -13
- moai_adk/templates/.mcp.json +31 -0
- moai_adk/templates/.moai/config/config.yaml +58 -0
- moai_adk/templates/.moai/config/questions/_schema.yaml +151 -0
- moai_adk/templates/.moai/config/questions/tab0-init.yaml +251 -0
- moai_adk/templates/.moai/config/questions/tab1-user.yaml +108 -0
- moai_adk/templates/.moai/config/questions/tab2-project.yaml +81 -0
- moai_adk/templates/.moai/config/questions/tab3-git.yaml +634 -0
- moai_adk/templates/.moai/config/questions/tab4-quality.yaml +170 -0
- moai_adk/templates/.moai/config/questions/tab5-system.yaml +87 -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 +14 -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 +571 -244
- moai_adk/utils/__init__.py +24 -2
- moai_adk/utils/banner.py +9 -13
- moai_adk/utils/common.py +294 -0
- moai_adk/utils/link_validator.py +241 -0
- moai_adk/utils/logger.py +4 -9
- 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.34.0.dist-info/METADATA +2999 -0
- moai_adk-0.34.0.dist-info/RECORD +463 -0
- {moai_adk-0.8.0.dist-info → moai_adk-0.34.0.dist-info}/WHEEL +1 -1
- {moai_adk-0.8.0.dist-info → moai_adk-0.34.0.dist-info}/entry_points.txt +1 -0
- moai_adk/cli/commands/backup.py +0 -80
- moai_adk/templates/.claude/agents/alfred/cc-manager.md +0 -293
- moai_adk/templates/.claude/agents/alfred/debug-helper.md +0 -196
- moai_adk/templates/.claude/agents/alfred/doc-syncer.md +0 -207
- moai_adk/templates/.claude/agents/alfred/git-manager.md +0 -375
- moai_adk/templates/.claude/agents/alfred/implementation-planner.md +0 -343
- moai_adk/templates/.claude/agents/alfred/project-manager.md +0 -246
- moai_adk/templates/.claude/agents/alfred/quality-gate.md +0 -320
- moai_adk/templates/.claude/agents/alfred/skill-factory.md +0 -837
- moai_adk/templates/.claude/agents/alfred/spec-builder.md +0 -272
- moai_adk/templates/.claude/agents/alfred/tag-agent.md +0 -265
- moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +0 -311
- moai_adk/templates/.claude/agents/alfred/trust-checker.md +0 -352
- moai_adk/templates/.claude/commands/alfred/0-project.md +0 -1184
- moai_adk/templates/.claude/commands/alfred/1-plan.md +0 -665
- moai_adk/templates/.claude/commands/alfred/2-run.md +0 -488
- moai_adk/templates/.claude/commands/alfred/3-sync.md +0 -623
- moai_adk/templates/.claude/hooks/alfred/HOOK_SCHEMA_VALIDATION.md +0 -313
- moai_adk/templates/.claude/hooks/alfred/README.md +0 -230
- moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +0 -174
- moai_adk/templates/.claude/hooks/alfred/core/__init__.py +0 -170
- moai_adk/templates/.claude/hooks/alfred/core/context.py +0 -67
- moai_adk/templates/.claude/hooks/alfred/core/project.py +0 -416
- moai_adk/templates/.claude/hooks/alfred/core/tags.py +0 -198
- moai_adk/templates/.claude/hooks/alfred/handlers/__init__.py +0 -21
- moai_adk/templates/.claude/hooks/alfred/handlers/notification.py +0 -25
- moai_adk/templates/.claude/hooks/alfred/handlers/session.py +0 -161
- moai_adk/templates/.claude/hooks/alfred/handlers/tool.py +0 -90
- moai_adk/templates/.claude/hooks/alfred/handlers/user.py +0 -42
- moai_adk/templates/.claude/hooks/alfred/test_hook_output.py +0 -175
- moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +0 -640
- moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +0 -696
- moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +0 -474
- moai_adk/templates/.claude/skills/moai-alfred-ears-authoring/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-alfred-ears-authoring/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-alfred-ears-authoring/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-alfred-git-workflow/SKILL.md +0 -122
- moai_adk/templates/.claude/skills/moai-alfred-git-workflow/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-alfred-git-workflow/reference.md +0 -29
- moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/SKILL.md +0 -237
- moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/examples.md +0 -615
- moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/reference.md +0 -653
- moai_adk/templates/.claude/skills/moai-alfred-language-detection/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-alfred-language-detection/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-alfred-language-detection/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-validation/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-validation/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-validation/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-alfred-tag-scanning/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-alfred-tag-scanning/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-alfred-tag-scanning/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-alfred-trust-validation/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-alfred-trust-validation/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-alfred-trust-validation/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-cc-agents/SKILL.md +0 -269
- moai_adk/templates/.claude/skills/moai-cc-agents/templates/agent-template.md +0 -32
- moai_adk/templates/.claude/skills/moai-cc-claude-md/SKILL.md +0 -298
- moai_adk/templates/.claude/skills/moai-cc-claude-md/templates/CLAUDE-template.md +0 -26
- moai_adk/templates/.claude/skills/moai-cc-commands/SKILL.md +0 -307
- moai_adk/templates/.claude/skills/moai-cc-commands/templates/command-template.md +0 -21
- moai_adk/templates/.claude/skills/moai-cc-hooks/SKILL.md +0 -252
- moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/pre-bash-check.sh +0 -19
- moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/preserve-permissions.sh +0 -19
- moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/validate-bash-command.py +0 -24
- moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/SKILL.md +0 -199
- moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/templates/settings-mcp-template.json +0 -39
- moai_adk/templates/.claude/skills/moai-cc-memory/SKILL.md +0 -316
- moai_adk/templates/.claude/skills/moai-cc-memory/templates/session-summary-template.md +0 -18
- moai_adk/templates/.claude/skills/moai-cc-settings/SKILL.md +0 -263
- moai_adk/templates/.claude/skills/moai-cc-settings/templates/settings-complete-template.json +0 -30
- moai_adk/templates/.claude/skills/moai-cc-skills/SKILL.md +0 -291
- moai_adk/templates/.claude/skills/moai-cc-skills/templates/SKILL-template.md +0 -15
- moai_adk/templates/.claude/skills/moai-domain-cli-tool/SKILL.md +0 -123
- moai_adk/templates/.claude/skills/moai-domain-cli-tool/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-domain-cli-tool/reference.md +0 -30
- moai_adk/templates/.claude/skills/moai-domain-data-science/SKILL.md +0 -123
- moai_adk/templates/.claude/skills/moai-domain-data-science/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-domain-data-science/reference.md +0 -30
- moai_adk/templates/.claude/skills/moai-domain-devops/SKILL.md +0 -124
- moai_adk/templates/.claude/skills/moai-domain-devops/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-domain-devops/reference.md +0 -31
- moai_adk/templates/.claude/skills/moai-domain-ml/SKILL.md +0 -123
- moai_adk/templates/.claude/skills/moai-domain-ml/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-domain-ml/reference.md +0 -30
- moai_adk/templates/.claude/skills/moai-domain-mobile-app/SKILL.md +0 -123
- moai_adk/templates/.claude/skills/moai-domain-mobile-app/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-domain-mobile-app/reference.md +0 -30
- moai_adk/templates/.claude/skills/moai-domain-security/SKILL.md +0 -123
- moai_adk/templates/.claude/skills/moai-domain-security/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-domain-security/reference.md +0 -30
- moai_adk/templates/.claude/skills/moai-domain-web-api/SKILL.md +0 -123
- moai_adk/templates/.claude/skills/moai-domain-web-api/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-domain-web-api/reference.md +0 -30
- moai_adk/templates/.claude/skills/moai-essentials-debug/SKILL.md +0 -303
- moai_adk/templates/.claude/skills/moai-essentials-debug/examples.md +0 -1064
- moai_adk/templates/.claude/skills/moai-essentials-debug/reference.md +0 -1047
- moai_adk/templates/.claude/skills/moai-essentials-perf/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-essentials-perf/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-essentials-perf/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-essentials-refactor/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-essentials-refactor/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-essentials-refactor/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-essentials-review/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-essentials-review/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-essentials-review/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-foundation-ears/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-foundation-ears/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-foundation-ears/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-foundation-git/SKILL.md +0 -122
- moai_adk/templates/.claude/skills/moai-foundation-git/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-foundation-git/reference.md +0 -29
- moai_adk/templates/.claude/skills/moai-foundation-langs/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-foundation-langs/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-foundation-langs/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-foundation-specs/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-foundation-specs/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-foundation-specs/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-foundation-tags/SKILL.md +0 -113
- moai_adk/templates/.claude/skills/moai-foundation-tags/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-foundation-tags/reference.md +0 -28
- moai_adk/templates/.claude/skills/moai-foundation-trust/SKILL.md +0 -307
- moai_adk/templates/.claude/skills/moai-foundation-trust/examples.md +0 -0
- moai_adk/templates/.claude/skills/moai-foundation-trust/reference.md +0 -1099
- moai_adk/templates/.claude/skills/moai-lang-c/SKILL.md +0 -124
- moai_adk/templates/.claude/skills/moai-lang-c/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-lang-c/reference.md +0 -31
- moai_adk/templates/.claude/skills/moai-lang-cpp/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-lang-cpp/reference.md +0 -31
- moai_adk/templates/.claude/skills/moai-lang-csharp/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-lang-csharp/reference.md +0 -30
- moai_adk/templates/.claude/skills/moai-lang-dart/SKILL.md +0 -123
- moai_adk/templates/.claude/skills/moai-lang-dart/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-lang-dart/reference.md +0 -30
- moai_adk/templates/.claude/skills/moai-lang-javascript/SKILL.md +0 -125
- moai_adk/templates/.claude/skills/moai-lang-javascript/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-lang-javascript/reference.md +0 -32
- moai_adk/templates/.claude/skills/moai-lang-php/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-lang-php/reference.md +0 -30
- moai_adk/templates/.claude/skills/moai-lang-r/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-lang-r/reference.md +0 -30
- moai_adk/templates/.claude/skills/moai-lang-ruby/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-lang-ruby/reference.md +0 -31
- moai_adk/templates/.claude/skills/moai-lang-shell/SKILL.md +0 -123
- moai_adk/templates/.claude/skills/moai-lang-shell/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-lang-shell/reference.md +0 -30
- moai_adk/templates/.claude/skills/moai-lang-sql/SKILL.md +0 -124
- moai_adk/templates/.claude/skills/moai-lang-sql/examples.md +0 -29
- moai_adk/templates/.claude/skills/moai-lang-sql/reference.md +0 -31
- moai_adk/templates/.claude/skills/moai-skill-factory/CHECKLIST.md +0 -482
- moai_adk/templates/.claude/skills/moai-skill-factory/EXAMPLES.md +0 -278
- moai_adk/templates/.claude/skills/moai-skill-factory/INTERACTIVE-DISCOVERY.md +0 -524
- moai_adk/templates/.claude/skills/moai-skill-factory/METADATA.md +0 -477
- moai_adk/templates/.claude/skills/moai-skill-factory/PARALLEL-ANALYSIS-REPORT.md +0 -429
- moai_adk/templates/.claude/skills/moai-skill-factory/PYTHON-VERSION-MATRIX.md +0 -391
- moai_adk/templates/.claude/skills/moai-skill-factory/SKILL-FACTORY-WORKFLOW.md +0 -431
- moai_adk/templates/.claude/skills/moai-skill-factory/SKILL-UPDATE-ADVISOR.md +0 -577
- moai_adk/templates/.claude/skills/moai-skill-factory/SKILL.md +0 -271
- moai_adk/templates/.claude/skills/moai-skill-factory/STEP-BY-STEP-GUIDE.md +0 -466
- moai_adk/templates/.claude/skills/moai-skill-factory/STRUCTURE.md +0 -583
- moai_adk/templates/.claude/skills/moai-skill-factory/WEB-RESEARCH.md +0 -526
- moai_adk/templates/.claude/skills/moai-skill-factory/reference.md +0 -465
- moai_adk/templates/.claude/skills/moai-skill-factory/scripts/generate-structure.sh +0 -328
- moai_adk/templates/.claude/skills/moai-skill-factory/scripts/validate-skill.sh +0 -312
- moai_adk/templates/.claude/skills/moai-skill-factory/templates/SKILL_TEMPLATE.md +0 -245
- moai_adk/templates/.claude/skills/moai-skill-factory/templates/examples-template.md +0 -285
- moai_adk/templates/.claude/skills/moai-skill-factory/templates/reference-template.md +0 -278
- moai_adk/templates/.claude/skills/moai-skill-factory/templates/scripts-template.sh +0 -303
- moai_adk/templates/.claude/skills/moai-spec-authoring/README.md +0 -137
- moai_adk/templates/.claude/skills/moai-spec-authoring/SKILL.md +0 -218
- moai_adk/templates/.claude/skills/moai-spec-authoring/examples/validate-spec.sh +0 -161
- moai_adk/templates/.claude/skills/moai-spec-authoring/examples.md +0 -541
- moai_adk/templates/.claude/skills/moai-spec-authoring/reference.md +0 -622
- moai_adk/templates/.github/ISSUE_TEMPLATE/spec.yml +0 -176
- moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +0 -69
- moai_adk/templates/.github/workflows/moai-gitflow.yml +0 -256
- moai_adk/templates/.moai/config.json +0 -96
- moai_adk/templates/.moai/memory/CLAUDE-AGENTS-GUIDE.md +0 -208
- moai_adk/templates/.moai/memory/CLAUDE-PRACTICES.md +0 -369
- moai_adk/templates/.moai/memory/CLAUDE-RULES.md +0 -539
- moai_adk/templates/.moai/memory/CONFIG-SCHEMA.md +0 -444
- moai_adk/templates/.moai/memory/DEVELOPMENT-GUIDE.md +0 -344
- moai_adk/templates/.moai/memory/GITFLOW-PROTECTION-POLICY.md +0 -220
- moai_adk/templates/.moai/memory/SKILLS-DESCRIPTION-POLICY.md +0 -218
- moai_adk/templates/.moai/memory/SPEC-METADATA.md +0 -356
- moai_adk/templates/.moai/memory/config-schema.md +0 -444
- moai_adk/templates/.moai/memory/gitflow-protection-policy.md +0 -220
- moai_adk/templates/.moai/memory/spec-metadata.md +0 -356
- moai_adk/templates/.moai/project/product.md +0 -161
- moai_adk/templates/.moai/project/structure.md +0 -156
- moai_adk/templates/.moai/project/tech.md +0 -227
- moai_adk/templates/__init__.py +0 -2
- moai_adk-0.8.0.dist-info/METADATA +0 -1722
- moai_adk-0.8.0.dist-info/RECORD +0 -282
- {moai_adk-0.8.0.dist-info → moai_adk-0.34.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,624 +1,977 @@
|
|
|
1
|
-
# Python
|
|
1
|
+
# Python Production-Ready Code Examples
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Complete FastAPI Application
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
+
```
|
|
8
40
|
|
|
9
|
-
###
|
|
41
|
+
### Main Application Entry
|
|
10
42
|
|
|
11
43
|
```python
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
```
|
|
26
87
|
|
|
88
|
+
### Configuration
|
|
27
89
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
)
|
|
32
101
|
|
|
102
|
+
# Application
|
|
103
|
+
app_name: str = "FastAPI App"
|
|
104
|
+
debug: bool = False
|
|
105
|
+
environment: str = "development"
|
|
33
106
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
39
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"
|
|
40
118
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"""Test async user fetching with mocked database."""
|
|
44
|
-
user_service.db_client.fetch_user.return_value = User(
|
|
45
|
-
id=1, name="Bob", email="bob@example.com"
|
|
46
|
-
)
|
|
119
|
+
# CORS
|
|
120
|
+
cors_origins: list[str] = ["http://localhost:3000"]
|
|
47
121
|
|
|
48
|
-
|
|
122
|
+
# Redis (optional)
|
|
123
|
+
redis_url: str | None = None
|
|
49
124
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
125
|
+
@lru_cache
|
|
126
|
+
def get_settings() -> Settings:
|
|
127
|
+
return Settings()
|
|
128
|
+
```
|
|
53
129
|
|
|
130
|
+
### Database Setup
|
|
54
131
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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,
|
|
65
162
|
)
|
|
66
163
|
|
|
67
|
-
|
|
164
|
+
async_session_factory = async_sessionmaker(
|
|
165
|
+
engine,
|
|
166
|
+
class_=AsyncSession,
|
|
167
|
+
expire_on_commit=False,
|
|
168
|
+
autoflush=False,
|
|
169
|
+
)
|
|
68
170
|
|
|
69
|
-
|
|
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
|
|
70
192
|
```
|
|
71
193
|
|
|
72
|
-
|
|
73
|
-
- ✅ Fixtures for dependency injection
|
|
74
|
-
- ✅ `@pytest.mark.asyncio` for async tests
|
|
75
|
-
- ✅ `@pytest.mark.parametrize` for data-driven tests
|
|
76
|
-
- ✅ AsyncMock for mocking async operations
|
|
77
|
-
- ✅ One assertion per test (clarity)
|
|
78
|
-
|
|
79
|
-
**Run Commands**:
|
|
80
|
-
```bash
|
|
81
|
-
pytest tests/test_user_service.py -v
|
|
82
|
-
pytest tests/test_user_service.py --cov=src.services --cov-report=term
|
|
83
|
-
```
|
|
194
|
+
### SQLAlchemy Models
|
|
84
195
|
|
|
85
|
-
|
|
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
|
|
86
201
|
|
|
87
|
-
|
|
202
|
+
from app.database import Base
|
|
88
203
|
|
|
89
|
-
|
|
204
|
+
class User(Base):
|
|
205
|
+
__tablename__ = "users"
|
|
90
206
|
|
|
91
|
-
|
|
92
|
-
[
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
)
|
|
96
221
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"E", # pycodestyle errors
|
|
100
|
-
"F", # pyflakes
|
|
101
|
-
"W", # pycodestyle warnings
|
|
102
|
-
"I", # isort (import sorting)
|
|
103
|
-
"N", # pep8-naming
|
|
104
|
-
"UP", # pyupgrade (use Python 3.13 features)
|
|
105
|
-
"B", # flake8-bugbear
|
|
106
|
-
"C4", # flake8-comprehensions
|
|
107
|
-
]
|
|
108
|
-
ignore = ["E501"] # Line length handled by formatter
|
|
109
|
-
|
|
110
|
-
[tool.ruff.lint.per-file-ignores]
|
|
111
|
-
"tests/**/*.py" = ["F401", "F811"] # Allow unused imports in tests
|
|
112
|
-
|
|
113
|
-
[tool.ruff.format]
|
|
114
|
-
quote-style = "double"
|
|
115
|
-
indent-style = "space"
|
|
222
|
+
def __repr__(self) -> str:
|
|
223
|
+
return f"<User(id={self.id}, email={self.email})>"
|
|
116
224
|
```
|
|
117
225
|
|
|
118
|
-
###
|
|
226
|
+
### Pydantic Schemas
|
|
119
227
|
|
|
120
228
|
```python
|
|
121
|
-
|
|
122
|
-
from
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
class Formatter:
|
|
126
|
-
"""Base formatter class."""
|
|
127
|
-
|
|
128
|
-
def format(self, text: str) -> str:
|
|
129
|
-
"""Format text with default behavior."""
|
|
130
|
-
return text.strip()
|
|
229
|
+
# app/schemas/user.py
|
|
230
|
+
from datetime import datetime
|
|
231
|
+
from pydantic import BaseModel, ConfigDict, EmailStr, Field
|
|
131
232
|
|
|
233
|
+
class UserBase(BaseModel):
|
|
234
|
+
email: EmailStr
|
|
235
|
+
name: str = Field(min_length=1, max_length=100)
|
|
132
236
|
|
|
133
|
-
class
|
|
134
|
-
|
|
237
|
+
class UserCreate(UserBase):
|
|
238
|
+
password: str = Field(min_length=8, max_length=100)
|
|
135
239
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return text.strip().upper()
|
|
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)
|
|
140
243
|
|
|
244
|
+
class UserResponse(UserBase):
|
|
245
|
+
model_config = ConfigDict(from_attributes=True)
|
|
141
246
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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"
|
|
146
267
|
```
|
|
147
268
|
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
# Lint and auto-fix issues
|
|
151
|
-
ruff check . --fix
|
|
152
|
-
|
|
153
|
-
# Format code (replaces black)
|
|
154
|
-
ruff format .
|
|
155
|
-
|
|
156
|
-
# Check specific rules
|
|
157
|
-
ruff check --select I . # Import sorting only
|
|
158
|
-
ruff check --select UP . # Python 3.13 upgrade suggestions
|
|
269
|
+
### Repository Pattern
|
|
159
270
|
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
162
347
|
```
|
|
163
348
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
## Example 3: mypy 1.8.0 Type Checking with Pydantic 2.7.0
|
|
167
|
-
|
|
168
|
-
### Source File: `src/models/product.py`
|
|
349
|
+
### Service Layer
|
|
169
350
|
|
|
170
351
|
```python
|
|
171
|
-
|
|
172
|
-
from
|
|
173
|
-
from
|
|
174
|
-
from
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
+
```
|
|
210
423
|
|
|
211
|
-
|
|
212
|
-
"""Add item to container."""
|
|
213
|
-
self.items.append(item)
|
|
424
|
+
### API Endpoints
|
|
214
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)
|
|
215
510
|
|
|
216
|
-
|
|
217
|
-
def
|
|
218
|
-
|
|
219
|
-
|
|
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)
|
|
220
540
|
```
|
|
221
541
|
|
|
222
|
-
###
|
|
542
|
+
### Dependencies
|
|
223
543
|
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
+
)
|
|
241
569
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
247
609
|
```
|
|
248
610
|
|
|
249
611
|
---
|
|
250
612
|
|
|
251
|
-
##
|
|
252
|
-
|
|
253
|
-
### API Server: `src/api/main.py`
|
|
613
|
+
## Complete pytest Test Suite
|
|
254
614
|
|
|
255
615
|
```python
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
from
|
|
260
|
-
from
|
|
261
|
-
import
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
app
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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()
|
|
281
657
|
|
|
282
|
-
|
|
283
|
-
async def
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
yield {"connection": "active"}
|
|
658
|
+
@pytest_asyncio.fixture
|
|
659
|
+
async def async_client(db_session):
|
|
660
|
+
async def override_get_db():
|
|
661
|
+
yield db_session
|
|
287
662
|
|
|
663
|
+
app.dependency_overrides[get_db] = override_get_db
|
|
288
664
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
665
|
+
async with AsyncClient(
|
|
666
|
+
transport=ASGITransport(app=app),
|
|
667
|
+
base_url="http://test",
|
|
668
|
+
) as client:
|
|
669
|
+
yield client
|
|
293
670
|
|
|
671
|
+
app.dependency_overrides.clear()
|
|
294
672
|
|
|
295
|
-
@
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
# Simulate async database operation
|
|
302
|
-
await asyncio.sleep(0.1)
|
|
303
|
-
|
|
304
|
-
# In production: insert into database
|
|
305
|
-
product_data = {
|
|
306
|
-
"id": 1,
|
|
307
|
-
"name": product.name,
|
|
308
|
-
"price": product.price,
|
|
309
|
-
"stock": product.stock,
|
|
673
|
+
@pytest.fixture
|
|
674
|
+
def user_data():
|
|
675
|
+
return {
|
|
676
|
+
"email": "test@example.com",
|
|
677
|
+
"name": "Test User",
|
|
678
|
+
"password": "password123",
|
|
310
679
|
}
|
|
680
|
+
```
|
|
311
681
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
async def get_product(
|
|
317
|
-
product_id: int,
|
|
318
|
-
db: Annotated[dict, Depends(get_db_session)]
|
|
319
|
-
):
|
|
320
|
-
"""Retrieve product by ID (async handler)."""
|
|
321
|
-
# Simulate async database lookup
|
|
322
|
-
await asyncio.sleep(0.1)
|
|
323
|
-
|
|
324
|
-
# In production: query from database
|
|
325
|
-
if product_id == 999:
|
|
326
|
-
raise HTTPException(status_code=404, detail="Product not found")
|
|
327
|
-
|
|
328
|
-
return ProductResponse(id=product_id, name="Sample Product", price=29.99, stock=100)
|
|
682
|
+
```python
|
|
683
|
+
# tests/test_users.py
|
|
684
|
+
import pytest
|
|
685
|
+
from httpx import AsyncClient
|
|
329
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)
|
|
330
690
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
""
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
)
|
|
338
|
-
```
|
|
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
|
|
339
697
|
|
|
340
|
-
|
|
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)
|
|
341
702
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
import pytest
|
|
345
|
-
from httpx import AsyncClient, ASGITransport
|
|
346
|
-
from src.api.main import app
|
|
703
|
+
# Second registration with same email
|
|
704
|
+
response = await async_client.post("/api/v1/users/register", json=user_data)
|
|
347
705
|
|
|
706
|
+
assert response.status_code == 400
|
|
707
|
+
assert "already registered" in response.json()["detail"]
|
|
348
708
|
|
|
349
709
|
@pytest.mark.asyncio
|
|
350
|
-
|
|
351
|
-
""
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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)
|
|
359
725
|
|
|
726
|
+
assert response.status_code == 422
|
|
360
727
|
|
|
361
728
|
@pytest.mark.asyncio
|
|
362
|
-
async def
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
"stock": 50,
|
|
372
|
-
})
|
|
373
|
-
assert response.status_code == 201
|
|
374
|
-
data = response.json()
|
|
375
|
-
assert data["name"] == "Test Product"
|
|
376
|
-
assert data["price"] == 19.99
|
|
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
|
+
)
|
|
377
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"
|
|
378
744
|
|
|
379
745
|
@pytest.mark.asyncio
|
|
380
|
-
async def
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
```
|
|
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"]
|
|
389
754
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
755
|
+
# Get current user
|
|
756
|
+
response = await async_client.get(
|
|
757
|
+
"/api/v1/users/me",
|
|
758
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
759
|
+
)
|
|
394
760
|
|
|
395
|
-
|
|
396
|
-
|
|
761
|
+
assert response.status_code == 200
|
|
762
|
+
data = response.json()
|
|
763
|
+
assert data["email"] == user_data["email"]
|
|
397
764
|
```
|
|
398
765
|
|
|
399
766
|
---
|
|
400
767
|
|
|
401
|
-
##
|
|
768
|
+
## Async Patterns Examples
|
|
402
769
|
|
|
403
|
-
###
|
|
770
|
+
### Task Groups (Python 3.11+)
|
|
404
771
|
|
|
405
772
|
```python
|
|
406
|
-
"""Data fetching service using Python 3.13 asyncio.TaskGroup."""
|
|
407
773
|
import asyncio
|
|
408
774
|
from typing import Any
|
|
409
|
-
import httpx
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
class DataFetcher:
|
|
413
|
-
"""Fetch data from multiple sources concurrently using TaskGroup."""
|
|
414
|
-
|
|
415
|
-
def __init__(self, base_url: str):
|
|
416
|
-
self.base_url = base_url
|
|
417
|
-
|
|
418
|
-
async def fetch_user(self, user_id: int) -> dict[str, Any]:
|
|
419
|
-
"""Fetch user data from API."""
|
|
420
|
-
await asyncio.sleep(0.1) # Simulate network delay
|
|
421
|
-
return {"id": user_id, "name": f"User{user_id}"}
|
|
422
|
-
|
|
423
|
-
async def fetch_posts(self, user_id: int) -> list[dict[str, Any]]:
|
|
424
|
-
"""Fetch user posts from API."""
|
|
425
|
-
await asyncio.sleep(0.15) # Simulate network delay
|
|
426
|
-
return [
|
|
427
|
-
{"id": 1, "user_id": user_id, "title": "Post 1"},
|
|
428
|
-
{"id": 2, "user_id": user_id, "title": "Post 2"},
|
|
429
|
-
]
|
|
430
|
-
|
|
431
|
-
async def fetch_comments(self, user_id: int) -> list[dict[str, Any]]:
|
|
432
|
-
"""Fetch user comments from API."""
|
|
433
|
-
await asyncio.sleep(0.12) # Simulate network delay
|
|
434
|
-
return [
|
|
435
|
-
{"id": 1, "user_id": user_id, "text": "Comment 1"},
|
|
436
|
-
]
|
|
437
|
-
|
|
438
|
-
async def fetch_user_profile(self, user_id: int) -> dict[str, Any]:
|
|
439
|
-
"""
|
|
440
|
-
Fetch complete user profile concurrently using TaskGroup.
|
|
441
|
-
|
|
442
|
-
TaskGroup advantages over asyncio.gather:
|
|
443
|
-
- Automatic exception propagation
|
|
444
|
-
- Structured concurrency (cancellation safety)
|
|
445
|
-
- Cleaner error handling
|
|
446
|
-
"""
|
|
447
|
-
async with asyncio.TaskGroup() as tg:
|
|
448
|
-
user_task = tg.create_task(self.fetch_user(user_id))
|
|
449
|
-
posts_task = tg.create_task(self.fetch_posts(user_id))
|
|
450
|
-
comments_task = tg.create_task(self.fetch_comments(user_id))
|
|
451
|
-
# All tasks run concurrently here
|
|
452
|
-
# If any task fails, TaskGroup cancels others and raises
|
|
453
|
-
|
|
454
|
-
# After TaskGroup exits, all tasks are complete
|
|
455
|
-
return {
|
|
456
|
-
"user": user_task.result(),
|
|
457
|
-
"posts": posts_task.result(),
|
|
458
|
-
"comments": comments_task.result(),
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
async def fetch_multiple_users(self, user_ids: list[int]) -> list[dict[str, Any]]:
|
|
462
|
-
"""Fetch multiple user profiles concurrently."""
|
|
463
|
-
async with asyncio.TaskGroup() as tg:
|
|
464
|
-
tasks = [
|
|
465
|
-
tg.create_task(self.fetch_user_profile(user_id))
|
|
466
|
-
for user_id in user_ids
|
|
467
|
-
]
|
|
468
|
-
|
|
469
|
-
return [task.result() for task in tasks]
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
# Context Variables for request tracking
|
|
473
|
-
from contextvars import ContextVar
|
|
474
|
-
|
|
475
|
-
request_id_var: ContextVar[str | None] = ContextVar("request_id", default=None)
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
async def process_request(request_id: str) -> dict[str, Any]:
|
|
479
|
-
"""Process request with context variable tracking."""
|
|
480
|
-
# Set context variable (inherited by all spawned tasks)
|
|
481
|
-
token = request_id_var.set(request_id)
|
|
482
775
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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]
|
|
486
783
|
|
|
487
|
-
|
|
488
|
-
current_request_id = request_id_var.get()
|
|
489
|
-
print(f"Request {current_request_id} completed")
|
|
784
|
+
return [task.result() for task in tasks]
|
|
490
785
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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 = []
|
|
494
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)
|
|
495
797
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
fetcher = DataFetcher(base_url="https://api.example.com")
|
|
798
|
+
async with asyncio.TaskGroup() as tg:
|
|
799
|
+
for uid in user_ids:
|
|
800
|
+
tg.create_task(safe_fetch(uid))
|
|
500
801
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
print(f"Profile: {profile}")
|
|
802
|
+
return results, errors
|
|
803
|
+
```
|
|
504
804
|
|
|
505
|
-
|
|
506
|
-
profiles = await fetcher.fetch_multiple_users([1, 2, 3])
|
|
507
|
-
print(f"Fetched {len(profiles)} profiles")
|
|
805
|
+
### Semaphore for Rate Limiting
|
|
508
806
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
807
|
+
```python
|
|
808
|
+
import asyncio
|
|
809
|
+
from contextlib import asynccontextmanager
|
|
512
810
|
|
|
811
|
+
class RateLimiter:
|
|
812
|
+
def __init__(self, max_concurrent: int = 10):
|
|
813
|
+
self._semaphore = asyncio.Semaphore(max_concurrent)
|
|
513
814
|
|
|
514
|
-
|
|
515
|
-
|
|
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()
|
|
516
827
|
```
|
|
517
828
|
|
|
518
|
-
###
|
|
829
|
+
### Async Generator Streaming
|
|
519
830
|
|
|
520
831
|
```python
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
+
```
|
|
524
860
|
|
|
861
|
+
---
|
|
525
862
|
|
|
526
|
-
|
|
527
|
-
async def test_fetch_user_profile():
|
|
528
|
-
"""Test concurrent user profile fetching."""
|
|
529
|
-
fetcher = DataFetcher(base_url="https://api.example.com")
|
|
530
|
-
profile = await fetcher.fetch_user_profile(user_id=1)
|
|
863
|
+
## Docker Production Dockerfile
|
|
531
864
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
assert profile["user"]["id"] == 1
|
|
865
|
+
```dockerfile
|
|
866
|
+
# Dockerfile
|
|
867
|
+
FROM python:3.13-slim AS builder
|
|
536
868
|
|
|
869
|
+
WORKDIR /app
|
|
537
870
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
profiles = await fetcher.fetch_multiple_users([1, 2, 3])
|
|
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/*
|
|
543
875
|
|
|
544
|
-
|
|
545
|
-
|
|
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
|
|
546
881
|
|
|
882
|
+
FROM python:3.13-slim AS runtime
|
|
547
883
|
|
|
548
|
-
|
|
549
|
-
async def test_taskgroup_error_propagation():
|
|
550
|
-
"""Test that TaskGroup properly propagates exceptions."""
|
|
551
|
-
fetcher = DataFetcher(base_url="https://api.example.com")
|
|
884
|
+
WORKDIR /app
|
|
552
885
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
886
|
+
# Create non-root user
|
|
887
|
+
RUN addgroup --system --gid 1001 appgroup && \
|
|
888
|
+
adduser --system --uid 1001 --gid 1001 appuser
|
|
556
889
|
|
|
557
|
-
|
|
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
|
|
558
893
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
```
|
|
894
|
+
# Copy application code
|
|
895
|
+
COPY --chown=appuser:appgroup . .
|
|
562
896
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
# Run the example
|
|
566
|
-
python -m src.services.data_fetcher
|
|
897
|
+
# Switch to non-root user
|
|
898
|
+
USER appuser
|
|
567
899
|
|
|
568
|
-
#
|
|
569
|
-
|
|
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"]
|
|
570
909
|
```
|
|
571
910
|
|
|
572
911
|
---
|
|
573
912
|
|
|
574
|
-
##
|
|
913
|
+
## pyproject.toml Complete Configuration
|
|
575
914
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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"
|
|
582
942
|
|
|
583
|
-
|
|
584
|
-
|
|
943
|
+
[tool.ruff]
|
|
944
|
+
line-length = 100
|
|
945
|
+
target-version = "py313"
|
|
585
946
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
947
|
+
[tool.ruff.lint]
|
|
948
|
+
select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"]
|
|
949
|
+
ignore = ["E501"]
|
|
589
950
|
|
|
590
|
-
|
|
951
|
+
[tool.ruff.lint.isort]
|
|
952
|
+
known-first-party = ["app"]
|
|
591
953
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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"
|
|
595
959
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
960
|
+
[tool.mypy]
|
|
961
|
+
python_version = "3.13"
|
|
962
|
+
strict = true
|
|
963
|
+
plugins = ["pydantic.mypy"]
|
|
599
964
|
|
|
600
|
-
|
|
601
|
-
|
|
965
|
+
[tool.coverage.run]
|
|
966
|
+
source = ["app"]
|
|
967
|
+
omit = ["tests/*"]
|
|
602
968
|
|
|
603
|
-
|
|
604
|
-
|
|
969
|
+
[build-system]
|
|
970
|
+
requires = ["poetry-core>=1.0.0"]
|
|
971
|
+
build-backend = "poetry.core.masonry.api"
|
|
605
972
|
```
|
|
606
973
|
|
|
607
|
-
### Production Dependencies Matrix
|
|
608
|
-
|
|
609
|
-
| Package | Version | Purpose |
|
|
610
|
-
|---------|---------|---------|
|
|
611
|
-
| pytest | 8.4.2 | Testing framework |
|
|
612
|
-
| pytest-asyncio | latest | Async test support |
|
|
613
|
-
| pytest-cov | latest | Coverage reporting |
|
|
614
|
-
| ruff | 0.13.1 | Linting & formatting |
|
|
615
|
-
| mypy | 1.8.0 | Static type checking |
|
|
616
|
-
| uv | 0.9.3 | Package management |
|
|
617
|
-
| FastAPI | 0.115.0 | Web framework |
|
|
618
|
-
| Pydantic | 2.7.0 | Data validation |
|
|
619
|
-
| httpx | latest | Async HTTP client |
|
|
620
|
-
| uvicorn | latest | ASGI server |
|
|
621
|
-
|
|
622
974
|
---
|
|
623
975
|
|
|
624
|
-
|
|
976
|
+
Last Updated: 2025-12-07
|
|
977
|
+
Version: 1.0.0
|