aigroup-workflow 2.2.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (645) hide show
  1. package/.claude/commands/fix-build.md +10 -5
  2. package/.claude/commands/init-project.md +13 -8
  3. package/.claude/commands/plan.md +15 -8
  4. package/.claude/commands/review.md +12 -6
  5. package/.claude/commands/tdd.md +11 -5
  6. package/.claude/commands/workflow-start.md +20 -11
  7. package/.claude/settings.json +28 -0
  8. package/.codex/agents/architect.toml +207 -0
  9. package/.codex/agents/build-error-resolver.toml +110 -0
  10. package/.codex/agents/code-reviewer.toml +233 -0
  11. package/.codex/agents/doc-updater.toml +103 -0
  12. package/.codex/agents/e2e-runner.toml +103 -0
  13. package/.codex/agents/get-current-datetime.toml +23 -0
  14. package/.codex/agents/init-architect.toml +181 -0
  15. package/.codex/agents/planner.toml +208 -0
  16. package/.codex/agents/refactor-cleaner.toml +81 -0
  17. package/.codex/agents/rust-reviewer.toml +90 -0
  18. package/.codex/agents/security-reviewer.toml +104 -0
  19. package/.codex/agents/tdd-guide.toml +87 -0
  20. package/AGENTS.md +2 -2
  21. package/CLAUDE.md +23 -1
  22. package/LICENSE +20 -20
  23. package/README.md +333 -333
  24. package/agents/a11y-architect.md +141 -141
  25. package/agents/architect.md +211 -211
  26. package/agents/build-error-resolver.md +114 -114
  27. package/agents/chief-of-staff.md +151 -151
  28. package/agents/code-architect.md +71 -71
  29. package/agents/code-explorer.md +69 -69
  30. package/agents/code-reviewer.md +237 -237
  31. package/agents/code-simplifier.md +47 -47
  32. package/agents/comment-analyzer.md +45 -45
  33. package/agents/conversation-analyzer.md +52 -52
  34. package/agents/cpp-build-resolver.md +90 -90
  35. package/agents/cpp-reviewer.md +72 -72
  36. package/agents/csharp-reviewer.md +101 -101
  37. package/agents/dart-build-resolver.md +201 -201
  38. package/agents/database-reviewer.md +91 -91
  39. package/agents/doc-updater.md +107 -107
  40. package/agents/docs-lookup.md +68 -68
  41. package/agents/e2e-runner.md +107 -107
  42. package/agents/flutter-reviewer.md +243 -243
  43. package/agents/gan-evaluator.md +209 -209
  44. package/agents/gan-generator.md +131 -131
  45. package/agents/gan-planner.md +99 -99
  46. package/agents/get-current-datetime.md +26 -26
  47. package/agents/go-build-resolver.md +94 -94
  48. package/agents/go-reviewer.md +76 -76
  49. package/agents/harness-optimizer.md +35 -35
  50. package/agents/healthcare-reviewer.md +83 -83
  51. package/agents/java-build-resolver.md +153 -153
  52. package/agents/java-reviewer.md +92 -92
  53. package/agents/kotlin-build-resolver.md +118 -118
  54. package/agents/kotlin-reviewer.md +159 -159
  55. package/agents/loop-operator.md +36 -36
  56. package/agents/opensource-forker.md +198 -198
  57. package/agents/opensource-packager.md +249 -249
  58. package/agents/opensource-sanitizer.md +188 -188
  59. package/agents/performance-optimizer.md +446 -446
  60. package/agents/planner.md +212 -212
  61. package/agents/pr-test-analyzer.md +45 -45
  62. package/agents/python-reviewer.md +98 -98
  63. package/agents/pytorch-build-resolver.md +120 -120
  64. package/agents/refactor-cleaner.md +85 -85
  65. package/agents/rust-build-resolver.md +148 -148
  66. package/agents/rust-reviewer.md +94 -94
  67. package/agents/security-reviewer.md +108 -108
  68. package/agents/seo-specialist.md +59 -59
  69. package/agents/silent-failure-hunter.md +50 -50
  70. package/agents/tdd-guide.md +91 -91
  71. package/agents/type-design-analyzer.md +41 -41
  72. package/agents/typescript-reviewer.md +112 -112
  73. package/cli/commands/update.mjs +1 -1
  74. package/cli/utils/scaffold.mjs +53 -0
  75. package/docs/rules/agents.md +166 -50
  76. package/docs/rules/cpp/coding-style.md +44 -44
  77. package/docs/rules/cpp/hooks.md +39 -39
  78. package/docs/rules/cpp/patterns.md +51 -51
  79. package/docs/rules/cpp/security.md +51 -51
  80. package/docs/rules/cpp/testing.md +44 -44
  81. package/docs/rules/csharp/coding-style.md +72 -72
  82. package/docs/rules/csharp/hooks.md +25 -25
  83. package/docs/rules/csharp/patterns.md +50 -50
  84. package/docs/rules/csharp/security.md +58 -58
  85. package/docs/rules/csharp/testing.md +46 -46
  86. package/docs/rules/dart/coding-style.md +159 -159
  87. package/docs/rules/dart/hooks.md +66 -66
  88. package/docs/rules/dart/patterns.md +261 -261
  89. package/docs/rules/dart/security.md +135 -135
  90. package/docs/rules/dart/testing.md +215 -215
  91. package/docs/rules/golang/coding-style.md +32 -32
  92. package/docs/rules/golang/hooks.md +17 -17
  93. package/docs/rules/golang/patterns.md +45 -45
  94. package/docs/rules/golang/security.md +34 -34
  95. package/docs/rules/golang/testing.md +31 -31
  96. package/docs/rules/java/coding-style.md +114 -114
  97. package/docs/rules/java/hooks.md +18 -18
  98. package/docs/rules/java/patterns.md +146 -146
  99. package/docs/rules/java/security.md +100 -100
  100. package/docs/rules/java/testing.md +131 -131
  101. package/docs/rules/kotlin/coding-style.md +86 -86
  102. package/docs/rules/kotlin/hooks.md +17 -17
  103. package/docs/rules/kotlin/patterns.md +146 -146
  104. package/docs/rules/kotlin/security.md +82 -82
  105. package/docs/rules/kotlin/testing.md +128 -128
  106. package/docs/rules/perl/coding-style.md +46 -46
  107. package/docs/rules/perl/hooks.md +22 -22
  108. package/docs/rules/perl/patterns.md +76 -76
  109. package/docs/rules/perl/security.md +69 -69
  110. package/docs/rules/perl/testing.md +54 -54
  111. package/docs/rules/php/coding-style.md +40 -40
  112. package/docs/rules/php/hooks.md +24 -24
  113. package/docs/rules/php/patterns.md +33 -33
  114. package/docs/rules/php/security.md +37 -37
  115. package/docs/rules/php/testing.md +39 -39
  116. package/docs/rules/python/coding-style.md +42 -42
  117. package/docs/rules/python/hooks.md +19 -19
  118. package/docs/rules/python/patterns.md +39 -39
  119. package/docs/rules/python/security.md +30 -30
  120. package/docs/rules/python/testing.md +38 -38
  121. package/docs/rules/rust/coding-style.md +151 -151
  122. package/docs/rules/rust/hooks.md +16 -16
  123. package/docs/rules/rust/patterns.md +168 -168
  124. package/docs/rules/rust/security.md +141 -141
  125. package/docs/rules/rust/testing.md +154 -154
  126. package/docs/rules/swift/coding-style.md +47 -47
  127. package/docs/rules/swift/hooks.md +20 -20
  128. package/docs/rules/swift/patterns.md +66 -66
  129. package/docs/rules/swift/security.md +33 -33
  130. package/docs/rules/swift/testing.md +45 -45
  131. package/docs/rules/typescript/coding-style.md +199 -199
  132. package/docs/rules/typescript/hooks.md +22 -22
  133. package/docs/rules/typescript/patterns.md +52 -52
  134. package/docs/rules/typescript/security.md +28 -28
  135. package/docs/rules/typescript/testing.md +18 -18
  136. package/docs/rules/web/coding-style.md +96 -96
  137. package/docs/rules/web/design-quality.md +62 -62
  138. package/docs/rules/web/hooks.md +120 -120
  139. package/docs/rules/web/patterns.md +79 -79
  140. package/docs/rules/web/performance.md +64 -64
  141. package/docs/rules/web/security.md +57 -57
  142. package/docs/rules/web/testing.md +55 -55
  143. package/docs/templates/README.md +36 -36
  144. package/docs/templates/ai-project-final.md +124 -124
  145. package/docs/templates/ai-project.md +105 -105
  146. package/docs/templates/api.md +157 -157
  147. package/docs/templates/bug.md +62 -62
  148. package/docs/templates/code-review.md +87 -87
  149. package/docs/templates/generic.md +116 -116
  150. package/docs/templates/implementation-plan.md +1 -1
  151. package/docs/templates/meeting.md +68 -68
  152. package/docs/templates/prd.md +98 -98
  153. package/docs/templates/ui.md +134 -134
  154. package/docs/workflow-pipeline.md +11 -10
  155. package/package.json +40 -39
  156. package/scripts/hooks/checks/orchestration-artifacts.cjs +28 -23
  157. package/scripts/hooks/checks/workflow-state.cjs +4 -5
  158. package/scripts/orchestration/lib/orchestrator.cjs +344 -117
  159. package/scripts/orchestration/lib/validate.cjs +145 -0
  160. package/scripts/orchestration/session.cjs +88 -44
  161. package/skills/SUPERPOWERS-LICENSE +21 -21
  162. package/skills/ai-ml/fine-tuning-expert/SKILL.md +162 -162
  163. package/skills/ai-ml/fine-tuning-expert/references/dataset-preparation.md +540 -540
  164. package/skills/ai-ml/fine-tuning-expert/references/deployment-optimization.md +673 -673
  165. package/skills/ai-ml/fine-tuning-expert/references/evaluation-metrics.md +597 -597
  166. package/skills/ai-ml/fine-tuning-expert/references/hyperparameter-tuning.md +565 -565
  167. package/skills/ai-ml/fine-tuning-expert/references/lora-peft.md +347 -347
  168. package/skills/ai-ml/ml-pipeline/SKILL.md +159 -159
  169. package/skills/ai-ml/ml-pipeline/references/experiment-tracking.md +833 -833
  170. package/skills/ai-ml/ml-pipeline/references/feature-engineering.md +631 -631
  171. package/skills/ai-ml/ml-pipeline/references/model-validation.md +978 -978
  172. package/skills/ai-ml/ml-pipeline/references/pipeline-orchestration.md +907 -907
  173. package/skills/ai-ml/ml-pipeline/references/training-pipelines.md +782 -782
  174. package/skills/ai-ml/rag-architect/SKILL.md +194 -194
  175. package/skills/ai-ml/rag-architect/references/chunking-strategies.md +878 -878
  176. package/skills/ai-ml/rag-architect/references/embedding-models.md +561 -561
  177. package/skills/ai-ml/rag-architect/references/rag-evaluation.md +833 -833
  178. package/skills/ai-ml/rag-architect/references/retrieval-optimization.md +795 -795
  179. package/skills/ai-ml/rag-architect/references/vector-databases.md +589 -589
  180. package/skills/ai-ml/spark-engineer/SKILL.md +148 -148
  181. package/skills/ai-ml/spark-engineer/references/partitioning-caching.md +543 -543
  182. package/skills/ai-ml/spark-engineer/references/performance-tuning.md +544 -544
  183. package/skills/ai-ml/spark-engineer/references/rdd-operations.md +599 -599
  184. package/skills/ai-ml/spark-engineer/references/spark-sql-dataframes.md +474 -474
  185. package/skills/ai-ml/spark-engineer/references/streaming-patterns.md +786 -786
  186. package/skills/backend/api-designer/SKILL.md +217 -217
  187. package/skills/backend/api-designer/references/error-handling.md +541 -541
  188. package/skills/backend/api-designer/references/openapi.md +824 -824
  189. package/skills/backend/api-designer/references/pagination.md +494 -494
  190. package/skills/backend/api-designer/references/rest-patterns.md +335 -335
  191. package/skills/backend/api-designer/references/versioning.md +391 -391
  192. package/skills/backend/architecture-designer/SKILL.md +117 -117
  193. package/skills/backend/architecture-designer/references/adr-template.md +116 -116
  194. package/skills/backend/architecture-designer/references/architecture-patterns.md +111 -111
  195. package/skills/backend/architecture-designer/references/database-selection.md +102 -102
  196. package/skills/backend/architecture-designer/references/nfr-checklist.md +112 -112
  197. package/skills/backend/architecture-designer/references/system-design.md +100 -100
  198. package/skills/backend/code-documenter/SKILL.md +147 -147
  199. package/skills/backend/code-documenter/references/api-docs-fastapi-django.md +166 -166
  200. package/skills/backend/code-documenter/references/api-docs-nestjs-express.md +220 -220
  201. package/skills/backend/code-documenter/references/coverage-reports.md +125 -125
  202. package/skills/backend/code-documenter/references/documentation-systems.md +333 -333
  203. package/skills/backend/code-documenter/references/interactive-api-docs.md +531 -531
  204. package/skills/backend/code-documenter/references/python-docstrings.md +121 -121
  205. package/skills/backend/code-documenter/references/typescript-jsdoc.md +145 -145
  206. package/skills/backend/code-documenter/references/user-guides-tutorials.md +530 -530
  207. package/skills/backend/debugging-wizard/SKILL.md +105 -105
  208. package/skills/backend/debugging-wizard/references/common-patterns.md +132 -132
  209. package/skills/backend/debugging-wizard/references/debugging-tools.md +140 -140
  210. package/skills/backend/debugging-wizard/references/quick-fixes.md +177 -177
  211. package/skills/backend/debugging-wizard/references/strategies.md +142 -142
  212. package/skills/backend/debugging-wizard/references/systematic-debugging.md +367 -367
  213. package/skills/backend/feature-forge/SKILL.md +98 -98
  214. package/skills/backend/feature-forge/references/acceptance-criteria.md +104 -104
  215. package/skills/backend/feature-forge/references/ears-syntax.md +99 -99
  216. package/skills/backend/feature-forge/references/interview-questions.md +150 -150
  217. package/skills/backend/feature-forge/references/pre-discovery-subagents.md +54 -54
  218. package/skills/backend/feature-forge/references/specification-template.md +103 -103
  219. package/skills/backend/fullstack-guardian/SKILL.md +105 -105
  220. package/skills/backend/fullstack-guardian/references/api-design-standards.md +307 -307
  221. package/skills/backend/fullstack-guardian/references/architecture-decisions.md +350 -350
  222. package/skills/backend/fullstack-guardian/references/backend-patterns.md +237 -237
  223. package/skills/backend/fullstack-guardian/references/common-patterns.md +134 -134
  224. package/skills/backend/fullstack-guardian/references/deliverables-checklist.md +354 -354
  225. package/skills/backend/fullstack-guardian/references/design-template.md +91 -91
  226. package/skills/backend/fullstack-guardian/references/error-handling.md +135 -135
  227. package/skills/backend/fullstack-guardian/references/frontend-patterns.md +340 -340
  228. package/skills/backend/fullstack-guardian/references/integration-patterns.md +333 -333
  229. package/skills/backend/fullstack-guardian/references/security-checklist.md +106 -106
  230. package/skills/backend/graphql-architect/SKILL.md +146 -146
  231. package/skills/backend/graphql-architect/references/federation.md +418 -418
  232. package/skills/backend/graphql-architect/references/migration-from-rest.md +1141 -1141
  233. package/skills/backend/graphql-architect/references/resolvers.md +425 -425
  234. package/skills/backend/graphql-architect/references/schema-design.md +393 -393
  235. package/skills/backend/graphql-architect/references/security.md +569 -569
  236. package/skills/backend/graphql-architect/references/subscriptions.md +510 -510
  237. package/skills/backend/legacy-modernizer/SKILL.md +137 -137
  238. package/skills/backend/legacy-modernizer/references/legacy-testing.md +381 -381
  239. package/skills/backend/legacy-modernizer/references/migration-strategies.md +423 -423
  240. package/skills/backend/legacy-modernizer/references/refactoring-patterns.md +395 -395
  241. package/skills/backend/legacy-modernizer/references/strangler-fig-pattern.md +281 -281
  242. package/skills/backend/legacy-modernizer/references/system-assessment.md +487 -487
  243. package/skills/backend/microservices-architect/SKILL.md +164 -164
  244. package/skills/backend/microservices-architect/references/communication.md +499 -499
  245. package/skills/backend/microservices-architect/references/data.md +721 -721
  246. package/skills/backend/microservices-architect/references/decomposition.md +344 -344
  247. package/skills/backend/microservices-architect/references/observability.md +805 -805
  248. package/skills/backend/microservices-architect/references/patterns.md +603 -603
  249. package/skills/database/database-optimizer/SKILL.md +147 -147
  250. package/skills/database/database-optimizer/references/index-strategies.md +331 -331
  251. package/skills/database/database-optimizer/references/monitoring-analysis.md +501 -501
  252. package/skills/database/database-optimizer/references/mysql-tuning.md +452 -452
  253. package/skills/database/database-optimizer/references/postgresql-tuning.md +413 -413
  254. package/skills/database/database-optimizer/references/query-optimization.md +251 -251
  255. package/skills/database/postgres-pro/SKILL.md +152 -152
  256. package/skills/database/postgres-pro/references/extensions.md +404 -404
  257. package/skills/database/postgres-pro/references/jsonb.md +321 -321
  258. package/skills/database/postgres-pro/references/maintenance.md +481 -481
  259. package/skills/database/postgres-pro/references/performance.md +265 -265
  260. package/skills/database/postgres-pro/references/replication.md +446 -446
  261. package/skills/database/sql-pro/SKILL.md +129 -129
  262. package/skills/database/sql-pro/references/database-design.md +402 -402
  263. package/skills/database/sql-pro/references/dialect-differences.md +419 -419
  264. package/skills/database/sql-pro/references/optimization.md +384 -384
  265. package/skills/database/sql-pro/references/query-patterns.md +285 -285
  266. package/skills/database/sql-pro/references/window-functions.md +328 -328
  267. package/skills/dotnet/csharp-developer/SKILL.md +125 -125
  268. package/skills/dotnet/csharp-developer/references/aspnet-core.md +394 -394
  269. package/skills/dotnet/csharp-developer/references/blazor.md +553 -553
  270. package/skills/dotnet/csharp-developer/references/entity-framework.md +409 -409
  271. package/skills/dotnet/csharp-developer/references/modern-csharp.md +248 -248
  272. package/skills/dotnet/csharp-developer/references/performance.md +498 -498
  273. package/skills/dotnet/dotnet-core-expert/SKILL.md +138 -138
  274. package/skills/dotnet/dotnet-core-expert/references/authentication.md +546 -546
  275. package/skills/dotnet/dotnet-core-expert/references/clean-architecture.md +455 -455
  276. package/skills/dotnet/dotnet-core-expert/references/cloud-native.md +548 -548
  277. package/skills/dotnet/dotnet-core-expert/references/entity-framework.md +440 -440
  278. package/skills/dotnet/dotnet-core-expert/references/minimal-apis.md +319 -319
  279. package/skills/frontend/angular-architect/SKILL.md +152 -152
  280. package/skills/frontend/angular-architect/references/components.md +297 -297
  281. package/skills/frontend/angular-architect/references/ngrx.md +401 -401
  282. package/skills/frontend/angular-architect/references/routing.md +361 -361
  283. package/skills/frontend/angular-architect/references/rxjs.md +319 -319
  284. package/skills/frontend/angular-architect/references/testing.md +405 -405
  285. package/skills/frontend/design-commands/design.md +91 -91
  286. package/skills/frontend/design-commands/handoff.md +97 -97
  287. package/skills/frontend/design-commands/prototype.md +120 -120
  288. package/skills/frontend/design-commands/spec.md +160 -160
  289. package/skills/frontend/design-commands/style.md +78 -78
  290. package/skills/frontend/flutter-expert/SKILL.md +138 -138
  291. package/skills/frontend/flutter-expert/references/bloc-state.md +259 -259
  292. package/skills/frontend/flutter-expert/references/gorouter-navigation.md +119 -119
  293. package/skills/frontend/flutter-expert/references/performance.md +99 -99
  294. package/skills/frontend/flutter-expert/references/project-structure.md +118 -118
  295. package/skills/frontend/flutter-expert/references/riverpod-state.md +130 -130
  296. package/skills/frontend/flutter-expert/references/widget-patterns.md +123 -123
  297. package/skills/frontend/nextjs-developer/SKILL.md +143 -143
  298. package/skills/frontend/nextjs-developer/references/app-router.md +311 -311
  299. package/skills/frontend/nextjs-developer/references/data-fetching.md +482 -482
  300. package/skills/frontend/nextjs-developer/references/deployment.md +545 -545
  301. package/skills/frontend/nextjs-developer/references/server-actions.md +462 -462
  302. package/skills/frontend/nextjs-developer/references/server-components.md +384 -384
  303. package/skills/frontend/react-expert/SKILL.md +149 -149
  304. package/skills/frontend/react-expert/references/hooks-patterns.md +162 -162
  305. package/skills/frontend/react-expert/references/migration-class-to-modern.md +1119 -1119
  306. package/skills/frontend/react-expert/references/performance.md +168 -168
  307. package/skills/frontend/react-expert/references/react-19-features.md +174 -174
  308. package/skills/frontend/react-expert/references/server-components.md +143 -143
  309. package/skills/frontend/react-expert/references/state-management.md +171 -171
  310. package/skills/frontend/react-expert/references/testing-react.md +174 -174
  311. package/skills/frontend/react-native-expert/SKILL.md +185 -185
  312. package/skills/frontend/react-native-expert/references/expo-router.md +187 -187
  313. package/skills/frontend/react-native-expert/references/list-optimization.md +204 -204
  314. package/skills/frontend/react-native-expert/references/platform-handling.md +188 -188
  315. package/skills/frontend/react-native-expert/references/project-structure.md +171 -171
  316. package/skills/frontend/react-native-expert/references/storage-hooks.md +173 -173
  317. package/skills/frontend/senior-frontend/SKILL.md +477 -477
  318. package/skills/frontend/senior-frontend/references/frontend_best_practices.md +806 -806
  319. package/skills/frontend/senior-frontend/references/nextjs_optimization_guide.md +724 -724
  320. package/skills/frontend/senior-frontend/references/react_patterns.md +746 -746
  321. package/skills/frontend/senior-frontend/scripts/bundle_analyzer.py +407 -407
  322. package/skills/frontend/senior-frontend/scripts/component_generator.py +329 -329
  323. package/skills/frontend/senior-frontend/scripts/frontend_scaffolder.py +1005 -1005
  324. package/skills/frontend/ui-ux-pro-max/SKILL.md +386 -386
  325. package/skills/frontend/ui-ux-pro-max/data/charts.csv +26 -26
  326. package/skills/frontend/ui-ux-pro-max/data/colors.csv +97 -97
  327. package/skills/frontend/ui-ux-pro-max/data/icons.csv +101 -101
  328. package/skills/frontend/ui-ux-pro-max/data/landing.csv +31 -31
  329. package/skills/frontend/ui-ux-pro-max/data/products.csv +96 -96
  330. package/skills/frontend/ui-ux-pro-max/data/react-performance.csv +45 -45
  331. package/skills/frontend/ui-ux-pro-max/data/stacks/astro.csv +54 -54
  332. package/skills/frontend/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
  333. package/skills/frontend/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
  334. package/skills/frontend/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
  335. package/skills/frontend/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
  336. package/skills/frontend/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
  337. package/skills/frontend/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
  338. package/skills/frontend/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
  339. package/skills/frontend/ui-ux-pro-max/data/stacks/react.csv +54 -54
  340. package/skills/frontend/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
  341. package/skills/frontend/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
  342. package/skills/frontend/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
  343. package/skills/frontend/ui-ux-pro-max/data/stacks/vue.csv +50 -50
  344. package/skills/frontend/ui-ux-pro-max/data/styles.csv +68 -68
  345. package/skills/frontend/ui-ux-pro-max/data/typography.csv +57 -57
  346. package/skills/frontend/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
  347. package/skills/frontend/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
  348. package/skills/frontend/ui-ux-pro-max/data/web-interface.csv +31 -31
  349. package/skills/frontend/ui-ux-pro-max/scripts/core.py +253 -253
  350. package/skills/frontend/ui-ux-pro-max/scripts/design_system.py +1067 -1067
  351. package/skills/frontend/ui-ux-pro-max/scripts/search.py +114 -114
  352. package/skills/frontend/vue-expert/SKILL.md +98 -98
  353. package/skills/frontend/vue-expert/references/build-tooling.md +480 -480
  354. package/skills/frontend/vue-expert/references/components.md +448 -448
  355. package/skills/frontend/vue-expert/references/composition-api.md +299 -299
  356. package/skills/frontend/vue-expert/references/mobile-hybrid.md +636 -636
  357. package/skills/frontend/vue-expert/references/nuxt.md +669 -669
  358. package/skills/frontend/vue-expert/references/state-management.md +449 -449
  359. package/skills/frontend/vue-expert/references/typescript.md +584 -584
  360. package/skills/frontend/vue-expert-js/SKILL.md +167 -167
  361. package/skills/frontend/vue-expert-js/references/component-architecture.md +219 -219
  362. package/skills/frontend/vue-expert-js/references/composables-patterns.md +183 -183
  363. package/skills/frontend/vue-expert-js/references/jsdoc-typing.md +535 -535
  364. package/skills/frontend/vue-expert-js/references/state-management.md +249 -249
  365. package/skills/frontend/vue-expert-js/references/testing-patterns.md +237 -237
  366. package/skills/go-rust-cpp/cpp-pro/SKILL.md +115 -115
  367. package/skills/go-rust-cpp/cpp-pro/references/build-tooling.md +440 -440
  368. package/skills/go-rust-cpp/cpp-pro/references/concurrency.md +437 -437
  369. package/skills/go-rust-cpp/cpp-pro/references/memory-performance.md +397 -397
  370. package/skills/go-rust-cpp/cpp-pro/references/modern-cpp.md +304 -304
  371. package/skills/go-rust-cpp/cpp-pro/references/templates.md +357 -357
  372. package/skills/go-rust-cpp/golang-pro/SKILL.md +122 -122
  373. package/skills/go-rust-cpp/golang-pro/references/concurrency.md +329 -329
  374. package/skills/go-rust-cpp/golang-pro/references/generics.md +442 -442
  375. package/skills/go-rust-cpp/golang-pro/references/interfaces.md +432 -432
  376. package/skills/go-rust-cpp/golang-pro/references/project-structure.md +477 -477
  377. package/skills/go-rust-cpp/golang-pro/references/testing.md +451 -451
  378. package/skills/go-rust-cpp/rust-engineer/SKILL.md +167 -167
  379. package/skills/go-rust-cpp/rust-engineer/references/async.md +458 -458
  380. package/skills/go-rust-cpp/rust-engineer/references/error-handling.md +334 -334
  381. package/skills/go-rust-cpp/rust-engineer/references/ownership.md +278 -278
  382. package/skills/go-rust-cpp/rust-engineer/references/testing.md +470 -470
  383. package/skills/go-rust-cpp/rust-engineer/references/traits.md +413 -413
  384. package/skills/infra/cli-developer/SKILL.md +113 -113
  385. package/skills/infra/cli-developer/references/design-patterns.md +221 -221
  386. package/skills/infra/cli-developer/references/go-cli.md +540 -540
  387. package/skills/infra/cli-developer/references/node-cli.md +383 -383
  388. package/skills/infra/cli-developer/references/python-cli.md +422 -422
  389. package/skills/infra/cli-developer/references/ux-patterns.md +448 -448
  390. package/skills/infra/cloud-architect/SKILL.md +216 -216
  391. package/skills/infra/cloud-architect/references/aws.md +394 -394
  392. package/skills/infra/cloud-architect/references/azure.md +562 -562
  393. package/skills/infra/cloud-architect/references/cost.md +582 -582
  394. package/skills/infra/cloud-architect/references/gcp.md +633 -633
  395. package/skills/infra/cloud-architect/references/multi-cloud.md +483 -483
  396. package/skills/infra/devops-engineer/SKILL.md +144 -144
  397. package/skills/infra/devops-engineer/references/deployment-strategies.md +241 -241
  398. package/skills/infra/devops-engineer/references/docker-patterns.md +113 -113
  399. package/skills/infra/devops-engineer/references/github-actions.md +139 -139
  400. package/skills/infra/devops-engineer/references/incident-response.md +331 -331
  401. package/skills/infra/devops-engineer/references/kubernetes.md +154 -154
  402. package/skills/infra/devops-engineer/references/platform-engineering.md +417 -417
  403. package/skills/infra/devops-engineer/references/release-automation.md +527 -527
  404. package/skills/infra/devops-engineer/references/terraform-iac.md +141 -141
  405. package/skills/infra/kubernetes-specialist/SKILL.md +241 -241
  406. package/skills/infra/kubernetes-specialist/references/configuration.md +452 -452
  407. package/skills/infra/kubernetes-specialist/references/cost-optimization.md +458 -458
  408. package/skills/infra/kubernetes-specialist/references/custom-operators.md +563 -563
  409. package/skills/infra/kubernetes-specialist/references/gitops.md +530 -530
  410. package/skills/infra/kubernetes-specialist/references/helm-charts.md +912 -912
  411. package/skills/infra/kubernetes-specialist/references/multi-cluster.md +507 -507
  412. package/skills/infra/kubernetes-specialist/references/networking.md +447 -447
  413. package/skills/infra/kubernetes-specialist/references/service-mesh.md +459 -459
  414. package/skills/infra/kubernetes-specialist/references/storage.md +535 -535
  415. package/skills/infra/kubernetes-specialist/references/troubleshooting.md +414 -414
  416. package/skills/infra/kubernetes-specialist/references/workloads.md +377 -377
  417. package/skills/infra/mcp-developer/SKILL.md +143 -143
  418. package/skills/infra/mcp-developer/references/protocol.md +244 -244
  419. package/skills/infra/mcp-developer/references/python-sdk.md +367 -367
  420. package/skills/infra/mcp-developer/references/resources.md +554 -554
  421. package/skills/infra/mcp-developer/references/tools.md +480 -480
  422. package/skills/infra/mcp-developer/references/typescript-sdk.md +350 -350
  423. package/skills/infra/monitoring-expert/SKILL.md +176 -176
  424. package/skills/infra/monitoring-expert/references/alerting-rules.md +141 -141
  425. package/skills/infra/monitoring-expert/references/application-profiling.md +331 -331
  426. package/skills/infra/monitoring-expert/references/capacity-planning.md +344 -344
  427. package/skills/infra/monitoring-expert/references/dashboards.md +126 -126
  428. package/skills/infra/monitoring-expert/references/opentelemetry.md +123 -123
  429. package/skills/infra/monitoring-expert/references/performance-testing.md +269 -269
  430. package/skills/infra/monitoring-expert/references/prometheus-metrics.md +136 -136
  431. package/skills/infra/monitoring-expert/references/structured-logging.md +142 -142
  432. package/skills/infra/sre-engineer/SKILL.md +181 -181
  433. package/skills/infra/sre-engineer/references/automation-toil.md +492 -492
  434. package/skills/infra/sre-engineer/references/error-budget-policy.md +334 -334
  435. package/skills/infra/sre-engineer/references/incident-chaos.md +576 -576
  436. package/skills/infra/sre-engineer/references/monitoring-alerting.md +424 -424
  437. package/skills/infra/sre-engineer/references/slo-sli-management.md +238 -238
  438. package/skills/infra/terraform-engineer/SKILL.md +143 -143
  439. package/skills/infra/terraform-engineer/references/best-practices.md +583 -583
  440. package/skills/infra/terraform-engineer/references/module-patterns.md +297 -297
  441. package/skills/infra/terraform-engineer/references/providers.md +452 -452
  442. package/skills/infra/terraform-engineer/references/state-management.md +371 -371
  443. package/skills/infra/terraform-engineer/references/testing.md +486 -486
  444. package/skills/infra/websocket-engineer/SKILL.md +168 -168
  445. package/skills/infra/websocket-engineer/references/alternatives.md +391 -391
  446. package/skills/infra/websocket-engineer/references/patterns.md +400 -400
  447. package/skills/infra/websocket-engineer/references/protocol.md +195 -195
  448. package/skills/infra/websocket-engineer/references/scaling.md +333 -333
  449. package/skills/infra/websocket-engineer/references/security.md +474 -474
  450. package/skills/java/java-architect/SKILL.md +132 -132
  451. package/skills/java/java-architect/references/jpa-optimization.md +393 -393
  452. package/skills/java/java-architect/references/reactive-webflux.md +356 -356
  453. package/skills/java/java-architect/references/spring-boot-setup.md +269 -269
  454. package/skills/java/java-architect/references/spring-security.md +445 -445
  455. package/skills/java/java-architect/references/testing-patterns.md +500 -500
  456. package/skills/java/kotlin-specialist/SKILL.md +147 -147
  457. package/skills/java/kotlin-specialist/references/android-compose.md +419 -419
  458. package/skills/java/kotlin-specialist/references/coroutines-flow.md +276 -276
  459. package/skills/java/kotlin-specialist/references/dsl-idioms.md +421 -421
  460. package/skills/java/kotlin-specialist/references/ktor-server.md +426 -426
  461. package/skills/java/kotlin-specialist/references/multiplatform-kmp.md +380 -380
  462. package/skills/java/spring-boot-engineer/SKILL.md +195 -195
  463. package/skills/java/spring-boot-engineer/references/cloud.md +498 -498
  464. package/skills/java/spring-boot-engineer/references/data.md +381 -381
  465. package/skills/java/spring-boot-engineer/references/security.md +459 -459
  466. package/skills/java/spring-boot-engineer/references/testing.md +545 -545
  467. package/skills/java/spring-boot-engineer/references/web.md +295 -295
  468. package/skills/javascript/javascript-pro/SKILL.md +132 -132
  469. package/skills/javascript/javascript-pro/references/async-patterns.md +334 -334
  470. package/skills/javascript/javascript-pro/references/browser-apis.md +398 -398
  471. package/skills/javascript/javascript-pro/references/modern-syntax.md +272 -272
  472. package/skills/javascript/javascript-pro/references/modules.md +357 -357
  473. package/skills/javascript/javascript-pro/references/node-essentials.md +471 -471
  474. package/skills/javascript/nestjs-expert/SKILL.md +206 -206
  475. package/skills/javascript/nestjs-expert/references/authentication.md +166 -166
  476. package/skills/javascript/nestjs-expert/references/controllers-routing.md +111 -111
  477. package/skills/javascript/nestjs-expert/references/dtos-validation.md +153 -153
  478. package/skills/javascript/nestjs-expert/references/migration-from-express.md +1237 -1237
  479. package/skills/javascript/nestjs-expert/references/services-di.md +140 -140
  480. package/skills/javascript/nestjs-expert/references/testing-patterns.md +186 -186
  481. package/skills/javascript/typescript-pro/SKILL.md +145 -145
  482. package/skills/javascript/typescript-pro/references/advanced-types.md +259 -259
  483. package/skills/javascript/typescript-pro/references/configuration.md +445 -445
  484. package/skills/javascript/typescript-pro/references/patterns.md +484 -484
  485. package/skills/javascript/typescript-pro/references/type-guards.md +352 -352
  486. package/skills/javascript/typescript-pro/references/utility-types.md +329 -329
  487. package/skills/php/laravel-specialist/SKILL.md +262 -262
  488. package/skills/php/laravel-specialist/references/eloquent.md +351 -351
  489. package/skills/php/laravel-specialist/references/livewire.md +512 -512
  490. package/skills/php/laravel-specialist/references/queues.md +423 -423
  491. package/skills/php/laravel-specialist/references/routing.md +362 -362
  492. package/skills/php/laravel-specialist/references/testing.md +522 -522
  493. package/skills/php/php-pro/SKILL.md +206 -206
  494. package/skills/php/php-pro/references/async-patterns.md +412 -412
  495. package/skills/php/php-pro/references/laravel-patterns.md +377 -377
  496. package/skills/php/php-pro/references/modern-php-features.md +323 -323
  497. package/skills/php/php-pro/references/symfony-patterns.md +466 -466
  498. package/skills/php/php-pro/references/testing-quality.md +466 -466
  499. package/skills/product/competitive-analysis/SKILL.md +257 -257
  500. package/skills/product/meeting-notes/SKILL.md +266 -266
  501. package/skills/product/prd-template/SKILL.md +150 -150
  502. package/skills/product/stakeholder-update/SKILL.md +225 -225
  503. package/skills/product/user-research-synthesis/SKILL.md +235 -235
  504. package/skills/python/django-expert/SKILL.md +162 -162
  505. package/skills/python/django-expert/references/authentication.md +145 -145
  506. package/skills/python/django-expert/references/drf-serializers.md +148 -148
  507. package/skills/python/django-expert/references/models-orm.md +151 -151
  508. package/skills/python/django-expert/references/testing-django.md +204 -204
  509. package/skills/python/django-expert/references/viewsets-views.md +153 -153
  510. package/skills/python/fastapi-expert/SKILL.md +185 -185
  511. package/skills/python/fastapi-expert/references/async-sqlalchemy.md +146 -146
  512. package/skills/python/fastapi-expert/references/authentication.md +159 -159
  513. package/skills/python/fastapi-expert/references/endpoints-routing.md +142 -142
  514. package/skills/python/fastapi-expert/references/migration-from-django.md +996 -996
  515. package/skills/python/fastapi-expert/references/pydantic-v2.md +135 -135
  516. package/skills/python/fastapi-expert/references/testing-async.md +159 -159
  517. package/skills/python/pandas-pro/SKILL.md +178 -178
  518. package/skills/python/pandas-pro/references/aggregation-groupby.md +545 -545
  519. package/skills/python/pandas-pro/references/data-cleaning.md +500 -500
  520. package/skills/python/pandas-pro/references/dataframe-operations.md +420 -420
  521. package/skills/python/pandas-pro/references/merging-joining.md +596 -596
  522. package/skills/python/pandas-pro/references/performance-optimization.md +597 -597
  523. package/skills/python/python-pro/SKILL.md +177 -177
  524. package/skills/python/python-pro/references/async-patterns.md +356 -356
  525. package/skills/python/python-pro/references/packaging.md +460 -460
  526. package/skills/python/python-pro/references/standard-library.md +378 -378
  527. package/skills/python/python-pro/references/testing.md +404 -404
  528. package/skills/python/python-pro/references/type-system.md +290 -290
  529. package/skills/quality/chaos-engineer/SKILL.md +182 -182
  530. package/skills/quality/chaos-engineer/references/chaos-tools.md +511 -511
  531. package/skills/quality/chaos-engineer/references/experiment-design.md +229 -229
  532. package/skills/quality/chaos-engineer/references/game-days.md +434 -434
  533. package/skills/quality/chaos-engineer/references/infrastructure-chaos.md +348 -348
  534. package/skills/quality/chaos-engineer/references/kubernetes-chaos.md +432 -432
  535. package/skills/quality/code-reviewer/SKILL.md +119 -119
  536. package/skills/quality/code-reviewer/references/common-issues.md +142 -142
  537. package/skills/quality/code-reviewer/references/feedback-examples.md +144 -144
  538. package/skills/quality/code-reviewer/references/receiving-feedback.md +238 -238
  539. package/skills/quality/code-reviewer/references/report-template.md +109 -109
  540. package/skills/quality/code-reviewer/references/review-checklist.md +88 -88
  541. package/skills/quality/code-reviewer/references/spec-compliance-review.md +258 -258
  542. package/skills/quality/playwright-expert/SKILL.md +169 -169
  543. package/skills/quality/playwright-expert/references/api-mocking.md +140 -140
  544. package/skills/quality/playwright-expert/references/configuration.md +155 -155
  545. package/skills/quality/playwright-expert/references/debugging-flaky.md +150 -150
  546. package/skills/quality/playwright-expert/references/page-object-model.md +152 -152
  547. package/skills/quality/playwright-expert/references/selectors-locators.md +119 -119
  548. package/skills/quality/secure-code-guardian/SKILL.md +191 -191
  549. package/skills/quality/secure-code-guardian/references/authentication.md +136 -136
  550. package/skills/quality/secure-code-guardian/references/input-validation.md +146 -146
  551. package/skills/quality/secure-code-guardian/references/owasp-prevention.md +135 -135
  552. package/skills/quality/secure-code-guardian/references/security-headers.md +133 -133
  553. package/skills/quality/secure-code-guardian/references/xss-csrf.md +157 -157
  554. package/skills/quality/security-reviewer/SKILL.md +103 -103
  555. package/skills/quality/security-reviewer/references/infrastructure-security.md +268 -268
  556. package/skills/quality/security-reviewer/references/penetration-testing.md +268 -268
  557. package/skills/quality/security-reviewer/references/report-template.md +170 -170
  558. package/skills/quality/security-reviewer/references/sast-tools.md +117 -117
  559. package/skills/quality/security-reviewer/references/secret-scanning.md +125 -125
  560. package/skills/quality/security-reviewer/references/vulnerability-patterns.md +152 -152
  561. package/skills/quality/senior-qa/README.md +196 -196
  562. package/skills/quality/senior-qa/SKILL.md +399 -399
  563. package/skills/quality/senior-qa/references/qa_best_practices.md +964 -964
  564. package/skills/quality/senior-qa/references/test_automation_patterns.md +1009 -1009
  565. package/skills/quality/senior-qa/references/testing_strategies.md +649 -649
  566. package/skills/quality/senior-qa/scripts/coverage_analyzer.py +836 -836
  567. package/skills/quality/senior-qa/scripts/e2e_test_scaffolder.py +820 -820
  568. package/skills/quality/senior-qa/scripts/test_suite_generator.py +605 -605
  569. package/skills/quality/tdd-guide/HOW_TO_USE.md +313 -313
  570. package/skills/quality/tdd-guide/README.md +680 -680
  571. package/skills/quality/tdd-guide/SKILL.md +122 -122
  572. package/skills/quality/tdd-guide/assets/expected_output.json +77 -77
  573. package/skills/quality/tdd-guide/assets/sample_input_python.json +39 -39
  574. package/skills/quality/tdd-guide/assets/sample_input_typescript.json +36 -36
  575. package/skills/quality/tdd-guide/references/ci-integration.md +195 -195
  576. package/skills/quality/tdd-guide/references/framework-guide.md +206 -206
  577. package/skills/quality/tdd-guide/references/tdd-best-practices.md +128 -128
  578. package/skills/quality/tdd-guide/scripts/coverage_analyzer.py +434 -434
  579. package/skills/quality/tdd-guide/scripts/fixture_generator.py +440 -440
  580. package/skills/quality/tdd-guide/scripts/format_detector.py +384 -384
  581. package/skills/quality/tdd-guide/scripts/framework_adapter.py +428 -428
  582. package/skills/quality/tdd-guide/scripts/metrics_calculator.py +456 -456
  583. package/skills/quality/tdd-guide/scripts/output_formatter.py +354 -354
  584. package/skills/quality/tdd-guide/scripts/tdd_workflow.py +474 -474
  585. package/skills/quality/tdd-guide/scripts/test_generator.py +438 -438
  586. package/skills/quality/test-master/SKILL.md +94 -94
  587. package/skills/quality/test-master/references/automation-frameworks.md +294 -294
  588. package/skills/quality/test-master/references/e2e-testing.md +128 -128
  589. package/skills/quality/test-master/references/integration-testing.md +120 -120
  590. package/skills/quality/test-master/references/performance-testing.md +118 -118
  591. package/skills/quality/test-master/references/qa-methodology.md +247 -247
  592. package/skills/quality/test-master/references/security-testing.md +127 -127
  593. package/skills/quality/test-master/references/tdd-iron-laws.md +174 -174
  594. package/skills/quality/test-master/references/test-reports.md +104 -104
  595. package/skills/quality/test-master/references/testing-anti-patterns.md +231 -231
  596. package/skills/quality/test-master/references/unit-testing.md +113 -113
  597. package/skills/ruby/rails-expert/SKILL.md +154 -154
  598. package/skills/ruby/rails-expert/references/active-record.md +244 -244
  599. package/skills/ruby/rails-expert/references/api-development.md +401 -401
  600. package/skills/ruby/rails-expert/references/background-jobs.md +272 -272
  601. package/skills/ruby/rails-expert/references/hotwire-turbo.md +228 -228
  602. package/skills/ruby/rails-expert/references/rspec-testing.md +367 -367
  603. package/skills/swift/swift-expert/SKILL.md +163 -163
  604. package/skills/swift/swift-expert/references/async-concurrency.md +360 -360
  605. package/skills/swift/swift-expert/references/memory-performance.md +377 -377
  606. package/skills/swift/swift-expert/references/protocol-oriented.md +354 -354
  607. package/skills/swift/swift-expert/references/swiftui-patterns.md +291 -291
  608. package/skills/swift/swift-expert/references/testing-patterns.md +399 -399
  609. package/skills/workflow/brainstorming/SKILL.md +164 -164
  610. package/skills/workflow/brainstorming/scripts/frame-template.html +214 -214
  611. package/skills/workflow/brainstorming/scripts/helper.js +88 -88
  612. package/skills/workflow/brainstorming/scripts/server.cjs +354 -354
  613. package/skills/workflow/brainstorming/scripts/start-server.sh +148 -148
  614. package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -56
  615. package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -49
  616. package/skills/workflow/brainstorming/visual-companion.md +287 -287
  617. package/skills/workflow/documentation/SKILL.md +45 -45
  618. package/skills/workflow/entropy-management/SKILL.md +115 -115
  619. package/skills/workflow/executing-plans/SKILL.md +70 -70
  620. package/skills/workflow/finishing-a-development-branch/SKILL.md +200 -200
  621. package/skills/workflow/receiving-code-review/SKILL.md +213 -213
  622. package/skills/workflow/requesting-code-review/SKILL.md +105 -105
  623. package/skills/workflow/requesting-code-review/code-reviewer.md +146 -146
  624. package/skills/workflow/requirement-engineering/SKILL.md +111 -111
  625. package/skills/workflow/systematic-debugging/CREATION-LOG.md +119 -119
  626. package/skills/workflow/systematic-debugging/SKILL.md +296 -296
  627. package/skills/workflow/systematic-debugging/condition-based-waiting-example.ts +158 -158
  628. package/skills/workflow/systematic-debugging/condition-based-waiting.md +115 -115
  629. package/skills/workflow/systematic-debugging/defense-in-depth.md +122 -122
  630. package/skills/workflow/systematic-debugging/find-polluter.sh +63 -63
  631. package/skills/workflow/systematic-debugging/root-cause-tracing.md +169 -169
  632. package/skills/workflow/systematic-debugging/test-academic.md +14 -14
  633. package/skills/workflow/systematic-debugging/test-pressure-1.md +58 -58
  634. package/skills/workflow/systematic-debugging/test-pressure-2.md +68 -68
  635. package/skills/workflow/systematic-debugging/test-pressure-3.md +69 -69
  636. package/skills/workflow/using-git-worktrees/SKILL.md +218 -218
  637. package/skills/workflow/verification-before-completion/SKILL.md +139 -139
  638. package/skills/workflow/writing-plans/SKILL.md +151 -151
  639. package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -49
  640. package/skills/workflow/writing-skills/SKILL.md +655 -655
  641. package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -1150
  642. package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -189
  643. package/skills/workflow/writing-skills/persuasion-principles.md +187 -187
  644. package/skills/workflow/writing-skills/render-graphs.js +168 -168
  645. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -384
@@ -1,997 +1,997 @@
1
- # Django to FastAPI Migration Guide
2
-
3
- ---
4
-
5
- ## When to Use This Guide
6
-
7
- **Migrate to FastAPI when:**
8
- - Need async/await for I/O-bound operations
9
- - Require WebSocket or Server-Sent Events
10
- - Want automatic OpenAPI/Swagger documentation
11
- - Need better performance for API-heavy workloads
12
- - Desire modern Python type hints and editor support
13
- - Building microservices from Django monolith
14
- - Require lower resource consumption
15
-
16
- **DO NOT migrate when:**
17
- - Heavy use of Django admin interface
18
- - Extensive Django ORM model inheritance
19
- - Complex form handling and validation
20
- - Server-side template rendering required
21
- - Team lacks async Python experience
22
- - Django ecosystem plugins are critical
23
- - Migration cost exceeds business value
24
-
25
- ---
26
-
27
- ## Concept Mapping: Django/DRF → FastAPI
28
-
29
- | Django/DRF Concept | FastAPI Equivalent | Notes |
30
- |-------------------|-------------------|-------|
31
- | `models.Model` | Pydantic `BaseModel` + SQLAlchemy | Separate schema from ORM |
32
- | `serializers.Serializer` | Pydantic `BaseModel` | Type-safe validation |
33
- | `ModelSerializer` | Multiple Pydantic models | Create/Read/Update schemas |
34
- | `ViewSet` | `APIRouter` + path operations | More explicit routing |
35
- | `GenericAPIView` | Dependency injection | Function-based approach |
36
- | `@api_view` decorator | `@router.get/post` | Built-in HTTP methods |
37
- | `urls.py` | `APIRouter` + `app.include_router` | Nested routers |
38
- | `settings.py` | `pydantic-settings` | Environment-based config |
39
- | `middleware` | Middleware + dependencies | More granular control |
40
- | `permissions` | Dependencies | Composable auth |
41
- | `authentication` | OAuth2 + JWT dependencies | Standards-based |
42
- | `pagination` | Query parameters + dependencies | Manual implementation |
43
- | `filters` | Query parameters | Type-safe filtering |
44
- | `Django ORM` | SQLAlchemy 2.0+ | Async support |
45
- | `select_related` | `selectinload` | Eager loading |
46
- | `prefetch_related` | `joinedload` | Join strategies |
47
- | `pytest-django` | `pytest + httpx` | Async test client |
48
- | `admin.py` | External (SQLAdmin, etc.) | Not built-in |
49
-
50
- ---
51
-
52
- ## Serializer → Pydantic V2 Migration
53
-
54
- ### Django REST Framework Serializer
55
-
56
- ```python
57
- # Django DRF
58
- from rest_framework import serializers
59
- from .models import User, Post
60
-
61
- class UserSerializer(serializers.ModelSerializer):
62
- post_count = serializers.SerializerMethodField()
63
-
64
- class Meta:
65
- model = User
66
- fields = ['id', 'username', 'email', 'created_at', 'post_count']
67
- read_only_fields = ['id', 'created_at']
68
- extra_kwargs = {
69
- 'email': {'write_only': True}
70
- }
71
-
72
- def get_post_count(self, obj):
73
- return obj.posts.count()
74
-
75
- def validate_username(self, value):
76
- if len(value) < 3:
77
- raise serializers.ValidationError("Username too short")
78
- return value
79
-
80
- class PostSerializer(serializers.ModelSerializer):
81
- author = UserSerializer(read_only=True)
82
- tags = serializers.ListField(child=serializers.CharField())
83
-
84
- class Meta:
85
- model = Post
86
- fields = ['id', 'title', 'content', 'author', 'tags', 'published']
87
-
88
- def create(self, validated_data):
89
- tags = validated_data.pop('tags', [])
90
- post = Post.objects.create(**validated_data)
91
- post.tags.set(tags)
92
- return post
93
- ```
94
-
95
- ### FastAPI Pydantic V2 Schemas
96
-
97
- ```python
98
- # FastAPI with Pydantic V2
99
- from pydantic import BaseModel, EmailStr, Field, field_validator, computed_field
100
- from datetime import datetime
101
- from typing import Annotated
102
-
103
- # Base schemas
104
- class UserBase(BaseModel):
105
- username: Annotated[str, Field(min_length=3, max_length=50)]
106
- email: EmailStr
107
-
108
- # Create schema (input)
109
- class UserCreate(UserBase):
110
- password: Annotated[str, Field(min_length=8)]
111
-
112
- @field_validator('username')
113
- @classmethod
114
- def validate_username(cls, v: str) -> str:
115
- if len(v) < 3:
116
- raise ValueError("Username too short")
117
- return v
118
-
119
- # Update schema (partial)
120
- class UserUpdate(BaseModel):
121
- username: Annotated[str | None, Field(min_length=3, max_length=50)] = None
122
- email: EmailStr | None = None
123
-
124
- # Read schema (output) - analogous to read_only_fields
125
- class UserRead(UserBase):
126
- id: int
127
- created_at: datetime
128
-
129
- model_config = {
130
- "from_attributes": True # Pydantic V2: replaces orm_mode
131
- }
132
-
133
- # Read schema with relations - analogous to SerializerMethodField
134
- class UserReadWithStats(UserRead):
135
- post_count: int
136
-
137
- @computed_field # Pydantic V2 computed fields
138
- @property
139
- def display_name(self) -> str:
140
- return f"@{self.username}"
141
-
142
- # Nested schemas
143
- class PostBase(BaseModel):
144
- title: Annotated[str, Field(max_length=200)]
145
- content: str
146
- tags: list[str] = []
147
- published: bool = False
148
-
149
- class PostCreate(PostBase):
150
- pass
151
-
152
- class PostRead(PostBase):
153
- id: int
154
- author: UserRead # Nested serialization
155
- created_at: datetime
156
-
157
- model_config = {"from_attributes": True}
158
-
159
- # Embedding vs side-loading
160
- class PostReadMinimal(BaseModel):
161
- """Minimal post representation (just ID)"""
162
- id: int
163
- title: str
164
- author_id: int # Side-loaded reference
165
-
166
- model_config = {"from_attributes": True}
167
- ```
168
-
169
- ---
170
-
171
- ## ViewSet → APIRouter Migration
172
-
173
- ### Django REST Framework ViewSet
174
-
175
- ```python
176
- # Django DRF ViewSet
177
- from rest_framework import viewsets, status
178
- from rest_framework.decorators import action
179
- from rest_framework.response import Response
180
- from rest_framework.permissions import IsAuthenticated
181
- from django.shortcuts import get_object_or_404
182
-
183
- class PostViewSet(viewsets.ModelViewSet):
184
- queryset = Post.objects.all()
185
- serializer_class = PostSerializer
186
- permission_classes = [IsAuthenticated]
187
-
188
- def get_queryset(self):
189
- queryset = super().get_queryset()
190
- if self.request.user.is_authenticated:
191
- return queryset.filter(author=self.request.user)
192
- return queryset.none()
193
-
194
- def perform_create(self, serializer):
195
- serializer.save(author=self.request.user)
196
-
197
- @action(detail=True, methods=['post'])
198
- def publish(self, request, pk=None):
199
- post = self.get_object()
200
- post.published = True
201
- post.save()
202
- return Response({'status': 'published'})
203
-
204
- @action(detail=False, methods=['get'])
205
- def recent(self, request):
206
- recent_posts = self.get_queryset().order_by('-created_at')[:10]
207
- serializer = self.get_serializer(recent_posts, many=True)
208
- return Response(serializer.data)
209
- ```
210
-
211
- ### FastAPI APIRouter with Dependencies
212
-
213
- ```python
214
- # FastAPI APIRouter
215
- from fastapi import APIRouter, Depends, HTTPException, status, Query
216
- from sqlalchemy.ext.asyncio import AsyncSession
217
- from sqlalchemy import select
218
- from typing import Annotated
219
-
220
- from .database import get_db
221
- from .auth import get_current_user
222
- from .models import Post as PostModel, User as UserModel
223
- from .schemas import PostRead, PostCreate, PostUpdate, UserRead
224
-
225
- router = APIRouter(prefix="/posts", tags=["posts"])
226
-
227
- # Dependency for database session
228
- DbSession = Annotated[AsyncSession, Depends(get_db)]
229
- CurrentUser = Annotated[UserModel, Depends(get_current_user)]
230
-
231
- # List posts (GET /posts)
232
- @router.get("/", response_model=list[PostRead])
233
- async def list_posts(
234
- db: DbSession,
235
- current_user: CurrentUser,
236
- skip: int = Query(0, ge=0),
237
- limit: int = Query(100, le=100),
238
- ):
239
- """Analogous to ViewSet.list()"""
240
- result = await db.execute(
241
- select(PostModel)
242
- .where(PostModel.author_id == current_user.id)
243
- .offset(skip)
244
- .limit(limit)
245
- )
246
- posts = result.scalars().all()
247
- return posts
248
-
249
- # Create post (POST /posts)
250
- @router.post("/", response_model=PostRead, status_code=status.HTTP_201_CREATED)
251
- async def create_post(
252
- post_data: PostCreate,
253
- db: DbSession,
254
- current_user: CurrentUser,
255
- ):
256
- """Analogous to ViewSet.create()"""
257
- post = PostModel(**post_data.model_dump(), author_id=current_user.id)
258
- db.add(post)
259
- await db.commit()
260
- await db.refresh(post)
261
- return post
262
-
263
- # Retrieve single post (GET /posts/{post_id})
264
- @router.get("/{post_id}", response_model=PostRead)
265
- async def get_post(
266
- post_id: int,
267
- db: DbSession,
268
- current_user: CurrentUser,
269
- ):
270
- """Analogous to ViewSet.retrieve()"""
271
- result = await db.execute(
272
- select(PostModel).where(
273
- PostModel.id == post_id,
274
- PostModel.author_id == current_user.id
275
- )
276
- )
277
- post = result.scalar_one_or_none()
278
- if not post:
279
- raise HTTPException(status_code=404, detail="Post not found")
280
- return post
281
-
282
- # Update post (PUT /posts/{post_id})
283
- @router.put("/{post_id}", response_model=PostRead)
284
- async def update_post(
285
- post_id: int,
286
- post_data: PostUpdate,
287
- db: DbSession,
288
- current_user: CurrentUser,
289
- ):
290
- """Analogous to ViewSet.update()"""
291
- result = await db.execute(
292
- select(PostModel).where(
293
- PostModel.id == post_id,
294
- PostModel.author_id == current_user.id
295
- )
296
- )
297
- post = result.scalar_one_or_none()
298
- if not post:
299
- raise HTTPException(status_code=404, detail="Post not found")
300
-
301
- # Update only provided fields
302
- for field, value in post_data.model_dump(exclude_unset=True).items():
303
- setattr(post, field, value)
304
-
305
- await db.commit()
306
- await db.refresh(post)
307
- return post
308
-
309
- # Delete post (DELETE /posts/{post_id})
310
- @router.delete("/{post_id}", status_code=status.HTTP_204_NO_CONTENT)
311
- async def delete_post(
312
- post_id: int,
313
- db: DbSession,
314
- current_user: CurrentUser,
315
- ):
316
- """Analogous to ViewSet.destroy()"""
317
- result = await db.execute(
318
- select(PostModel).where(
319
- PostModel.id == post_id,
320
- PostModel.author_id == current_user.id
321
- )
322
- )
323
- post = result.scalar_one_or_none()
324
- if not post:
325
- raise HTTPException(status_code=404, detail="Post not found")
326
-
327
- await db.delete(post)
328
- await db.commit()
329
-
330
- # Custom action: Publish (POST /posts/{post_id}/publish)
331
- @router.post("/{post_id}/publish", response_model=dict)
332
- async def publish_post(
333
- post_id: int,
334
- db: DbSession,
335
- current_user: CurrentUser,
336
- ):
337
- """Analogous to @action(detail=True)"""
338
- result = await db.execute(
339
- select(PostModel).where(
340
- PostModel.id == post_id,
341
- PostModel.author_id == current_user.id
342
- )
343
- )
344
- post = result.scalar_one_or_none()
345
- if not post:
346
- raise HTTPException(status_code=404, detail="Post not found")
347
-
348
- post.published = True
349
- await db.commit()
350
- return {"status": "published"}
351
-
352
- # Custom collection action: Recent posts (GET /posts/recent)
353
- @router.get("/actions/recent", response_model=list[PostRead])
354
- async def recent_posts(
355
- db: DbSession,
356
- current_user: CurrentUser,
357
- limit: int = Query(10, le=50),
358
- ):
359
- """Analogous to @action(detail=False)"""
360
- result = await db.execute(
361
- select(PostModel)
362
- .where(PostModel.author_id == current_user.id)
363
- .order_by(PostModel.created_at.desc())
364
- .limit(limit)
365
- )
366
- posts = result.scalars().all()
367
- return posts
368
- ```
369
-
370
- ---
371
-
372
- ## Django ORM → Async SQLAlchemy
373
-
374
- ### Django ORM Models
375
-
376
- ```python
377
- # Django models
378
- from django.db import models
379
-
380
- class User(models.Model):
381
- username = models.CharField(max_length=50, unique=True)
382
- email = models.EmailField(unique=True)
383
- created_at = models.DateTimeField(auto_now_add=True)
384
-
385
- class Meta:
386
- db_table = 'users'
387
- indexes = [
388
- models.Index(fields=['username']),
389
- ]
390
-
391
- class Post(models.Model):
392
- title = models.CharField(max_length=200)
393
- content = models.TextField()
394
- author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
395
- created_at = models.DateTimeField(auto_now_add=True)
396
- published = models.BooleanField(default=False)
397
-
398
- class Meta:
399
- db_table = 'posts'
400
- ordering = ['-created_at']
401
- ```
402
-
403
- ### SQLAlchemy 2.0 Async Models
404
-
405
- ```python
406
- # SQLAlchemy 2.0 models
407
- from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
408
- from sqlalchemy import String, Text, Boolean, ForeignKey, Index
409
- from datetime import datetime
410
- from typing import List
411
-
412
- class Base(DeclarativeBase):
413
- pass
414
-
415
- class User(Base):
416
- __tablename__ = 'users'
417
-
418
- # Primary key
419
- id: Mapped[int] = mapped_column(primary_key=True)
420
-
421
- # Columns with type hints
422
- username: Mapped[str] = mapped_column(String(50), unique=True, index=True)
423
- email: Mapped[str] = mapped_column(String(255), unique=True)
424
- created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
425
-
426
- # Relationships (analogous to related_name)
427
- posts: Mapped[List["Post"]] = relationship(back_populates="author")
428
-
429
- __table_args__ = (
430
- Index('ix_users_username', 'username'),
431
- )
432
-
433
- class Post(Base):
434
- __tablename__ = 'posts'
435
-
436
- id: Mapped[int] = mapped_column(primary_key=True)
437
- title: Mapped[str] = mapped_column(String(200))
438
- content: Mapped[str] = mapped_column(Text)
439
- author_id: Mapped[int] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'))
440
- created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
441
- published: Mapped[bool] = mapped_column(Boolean, default=False)
442
-
443
- # Relationship
444
- author: Mapped["User"] = relationship(back_populates="posts")
445
-
446
- __table_args__ = (
447
- Index('ix_posts_created_at', 'created_at'),
448
- )
449
- ```
450
-
451
- ### Query Patterns: Django ORM vs SQLAlchemy
452
-
453
- ```python
454
- # Django ORM queries
455
- from django.db.models import Count, Q
456
-
457
- # Simple filter
458
- posts = Post.objects.filter(published=True)
459
-
460
- # Select related (JOIN)
461
- posts = Post.objects.select_related('author').filter(published=True)
462
-
463
- # Prefetch related (separate query)
464
- users = User.objects.prefetch_related('posts').all()
465
-
466
- # Complex filtering
467
- posts = Post.objects.filter(
468
- Q(published=True) | Q(author__username='admin')
469
- ).order_by('-created_at')[:10]
470
-
471
- # Aggregation
472
- user_stats = User.objects.annotate(
473
- post_count=Count('posts')
474
- ).filter(post_count__gte=5)
475
- ```
476
-
477
- ```python
478
- # SQLAlchemy 2.0 async queries
479
- from sqlalchemy import select, func, or_
480
- from sqlalchemy.orm import selectinload, joinedload
481
-
482
- # Simple filter
483
- async def get_published_posts(db: AsyncSession):
484
- result = await db.execute(
485
- select(Post).where(Post.published == True)
486
- )
487
- return result.scalars().all()
488
-
489
- # Eager loading with JOIN (selectinload = separate query)
490
- async def get_posts_with_authors(db: AsyncSession):
491
- result = await db.execute(
492
- select(Post)
493
- .options(selectinload(Post.author))
494
- .where(Post.published == True)
495
- )
496
- return result.scalars().all()
497
-
498
- # Prefetch related (joinedload = single query with JOIN)
499
- async def get_users_with_posts(db: AsyncSession):
500
- result = await db.execute(
501
- select(User).options(joinedload(User.posts))
502
- )
503
- return result.unique().scalars().all()
504
-
505
- # Complex filtering
506
- async def get_complex_posts(db: AsyncSession):
507
- result = await db.execute(
508
- select(Post)
509
- .join(Post.author)
510
- .where(
511
- or_(
512
- Post.published == True,
513
- User.username == 'admin'
514
- )
515
- )
516
- .order_by(Post.created_at.desc())
517
- .limit(10)
518
- )
519
- return result.scalars().all()
520
-
521
- # Aggregation
522
- async def get_user_stats(db: AsyncSession):
523
- result = await db.execute(
524
- select(User, func.count(Post.id).label('post_count'))
525
- .join(Post)
526
- .group_by(User.id)
527
- .having(func.count(Post.id) >= 5)
528
- )
529
- return result.all()
530
- ```
531
-
532
- ---
533
-
534
- ## Authentication: SimpleJWT → FastAPI JWT
535
-
536
- ### Django SimpleJWT
537
-
538
- ```python
539
- # Django settings.py
540
- REST_FRAMEWORK = {
541
- 'DEFAULT_AUTHENTICATION_CLASSES': [
542
- 'rest_framework_simplejwt.authentication.JWTAuthentication',
543
- ],
544
- }
545
-
546
- # Views
547
- from rest_framework_simplejwt.views import TokenObtainPairView
548
-
549
- # Usage in ViewSet
550
- from rest_framework.permissions import IsAuthenticated
551
-
552
- class ProtectedViewSet(viewsets.ModelViewSet):
553
- permission_classes = [IsAuthenticated]
554
-
555
- def get_queryset(self):
556
- return Post.objects.filter(author=self.request.user)
557
- ```
558
-
559
- ### FastAPI JWT Authentication
560
-
561
- ```python
562
- # auth.py - FastAPI JWT implementation
563
- from fastapi import Depends, HTTPException, status
564
- from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
565
- from jose import JWTError, jwt
566
- from passlib.context import CryptContext
567
- from datetime import datetime, timedelta
568
- from pydantic import BaseModel
569
- from typing import Annotated
570
-
571
- # Configuration
572
- SECRET_KEY = "your-secret-key" # Use environment variable
573
- ALGORITHM = "HS256"
574
- ACCESS_TOKEN_EXPIRE_MINUTES = 30
575
-
576
- # Password hashing
577
- pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
578
-
579
- # OAuth2 scheme
580
- oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
581
-
582
- # Schemas
583
- class Token(BaseModel):
584
- access_token: str
585
- token_type: str
586
-
587
- class TokenData(BaseModel):
588
- username: str | None = None
589
-
590
- # Helper functions
591
- def verify_password(plain_password: str, hashed_password: str) -> bool:
592
- return pwd_context.verify(plain_password, hashed_password)
593
-
594
- def get_password_hash(password: str) -> str:
595
- return pwd_context.hash(password)
596
-
597
- def create_access_token(data: dict, expires_delta: timedelta | None = None):
598
- to_encode = data.copy()
599
- if expires_delta:
600
- expire = datetime.utcnow() + expires_delta
601
- else:
602
- expire = datetime.utcnow() + timedelta(minutes=15)
603
- to_encode.update({"exp": expire})
604
- encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
605
- return encoded_jwt
606
-
607
- # Dependency: Get current user from token
608
- async def get_current_user(
609
- token: Annotated[str, Depends(oauth2_scheme)],
610
- db: Annotated[AsyncSession, Depends(get_db)]
611
- ) -> UserModel:
612
- credentials_exception = HTTPException(
613
- status_code=status.HTTP_401_UNAUTHORIZED,
614
- detail="Could not validate credentials",
615
- headers={"WWW-Authenticate": "Bearer"},
616
- )
617
- try:
618
- payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
619
- username: str = payload.get("sub")
620
- if username is None:
621
- raise credentials_exception
622
- token_data = TokenData(username=username)
623
- except JWTError:
624
- raise credentials_exception
625
-
626
- result = await db.execute(
627
- select(UserModel).where(UserModel.username == token_data.username)
628
- )
629
- user = result.scalar_one_or_none()
630
- if user is None:
631
- raise credentials_exception
632
- return user
633
-
634
- # Login endpoint
635
- auth_router = APIRouter(prefix="/auth", tags=["auth"])
636
-
637
- @auth_router.post("/token", response_model=Token)
638
- async def login(
639
- form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
640
- db: Annotated[AsyncSession, Depends(get_db)]
641
- ):
642
- # Authenticate user
643
- result = await db.execute(
644
- select(UserModel).where(UserModel.username == form_data.username)
645
- )
646
- user = result.scalar_one_or_none()
647
-
648
- if not user or not verify_password(form_data.password, user.hashed_password):
649
- raise HTTPException(
650
- status_code=status.HTTP_401_UNAUTHORIZED,
651
- detail="Incorrect username or password",
652
- headers={"WWW-Authenticate": "Bearer"},
653
- )
654
-
655
- # Create access token
656
- access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
657
- access_token = create_access_token(
658
- data={"sub": user.username}, expires_delta=access_token_expires
659
- )
660
- return {"access_token": access_token, "token_type": "bearer"}
661
-
662
- # Protected endpoint usage
663
- @router.get("/protected")
664
- async def protected_route(current_user: Annotated[UserModel, Depends(get_current_user)]):
665
- return {"message": f"Hello {current_user.username}"}
666
- ```
667
-
668
- ---
669
-
670
- ## Testing Migration
671
-
672
- ### Django/DRF Tests
673
-
674
- ```python
675
- # Django pytest
676
- import pytest
677
- from rest_framework.test import APIClient
678
- from django.contrib.auth.models import User
679
-
680
- @pytest.fixture
681
- def api_client():
682
- return APIClient()
683
-
684
- @pytest.fixture
685
- def user(db):
686
- return User.objects.create_user(username='test', password='test123')
687
-
688
- @pytest.mark.django_db
689
- def test_create_post(api_client, user):
690
- api_client.force_authenticate(user=user)
691
- response = api_client.post('/api/posts/', {
692
- 'title': 'Test Post',
693
- 'content': 'Test content'
694
- })
695
- assert response.status_code == 201
696
- assert response.data['title'] == 'Test Post'
697
- ```
698
-
699
- ### FastAPI Tests
700
-
701
- ```python
702
- # FastAPI pytest with httpx
703
- import pytest
704
- from httpx import AsyncClient, ASGITransport
705
- from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
706
- from app.main import app
707
- from app.database import get_db, Base
708
- from app.models import User
709
-
710
- # Test database setup
711
- TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
712
-
713
- @pytest.fixture
714
- async def db_engine():
715
- engine = create_async_engine(TEST_DATABASE_URL, echo=False)
716
- async with engine.begin() as conn:
717
- await conn.run_sync(Base.metadata.create_all)
718
- yield engine
719
- await engine.dispose()
720
-
721
- @pytest.fixture
722
- async def db_session(db_engine):
723
- async_session = async_sessionmaker(
724
- db_engine, class_=AsyncSession, expire_on_commit=False
725
- )
726
- async with async_session() as session:
727
- yield session
728
-
729
- @pytest.fixture
730
- async def client(db_session):
731
- async def override_get_db():
732
- yield db_session
733
-
734
- app.dependency_overrides[get_db] = override_get_db
735
-
736
- async with AsyncClient(
737
- transport=ASGITransport(app=app),
738
- base_url="http://test"
739
- ) as ac:
740
- yield ac
741
-
742
- app.dependency_overrides.clear()
743
-
744
- @pytest.fixture
745
- async def auth_headers(client, db_session):
746
- # Create test user
747
- user = User(username="test", email="test@example.com")
748
- user.hashed_password = get_password_hash("test123")
749
- db_session.add(user)
750
- await db_session.commit()
751
-
752
- # Get token
753
- response = await client.post("/auth/token", data={
754
- "username": "test",
755
- "password": "test123"
756
- })
757
- token = response.json()["access_token"]
758
- return {"Authorization": f"Bearer {token}"}
759
-
760
- @pytest.mark.asyncio
761
- async def test_create_post(client, auth_headers):
762
- response = await client.post(
763
- "/posts/",
764
- json={"title": "Test Post", "content": "Test content"},
765
- headers=auth_headers
766
- )
767
- assert response.status_code == 201
768
- data = response.json()
769
- assert data["title"] == "Test Post"
770
-
771
- @pytest.mark.asyncio
772
- async def test_list_posts(client, auth_headers, db_session):
773
- # Create test data
774
- user = await db_session.execute(select(User).where(User.username == "test"))
775
- user = user.scalar_one()
776
-
777
- post = Post(title="Test", content="Content", author_id=user.id)
778
- db_session.add(post)
779
- await db_session.commit()
780
-
781
- # Test endpoint
782
- response = await client.get("/posts/", headers=auth_headers)
783
- assert response.status_code == 200
784
- assert len(response.json()) == 1
785
- ```
786
-
787
- ---
788
-
789
- ## Incremental Migration Strategy
790
-
791
- ### Phase 1: Parallel API (Strangler Pattern)
792
-
793
- Run Django and FastAPI side-by-side, migrating endpoints incrementally.
794
-
795
- ```python
796
- # Nginx routing config
797
- location /api/v2/ {
798
- proxy_pass http://fastapi:8000;
799
- }
800
-
801
- location /api/ {
802
- proxy_pass http://django:8001;
803
- }
804
- ```
805
-
806
- **Approach:**
807
- 1. Stand up FastAPI with shared database (read-only initially)
808
- 2. Migrate GET endpoints first (lowest risk)
809
- 3. Add write endpoints with dual-write to both systems
810
- 4. Validate data consistency
811
- 5. Switch traffic gradually (feature flags)
812
-
813
- ### Phase 2: Shared Database Migration
814
-
815
- ```python
816
- # FastAPI with existing Django database
817
- from sqlalchemy import MetaData
818
-
819
- # Reflect existing Django tables
820
- metadata = MetaData()
821
- metadata.reflect(bind=engine, only=['users', 'posts'])
822
-
823
- # Or define models matching Django schema
824
- class User(Base):
825
- __tablename__ = 'auth_user' # Django's user table
826
- # Map to Django's column names
827
- ```
828
-
829
- ### Phase 3: Database Schema Modernization
830
-
831
- After traffic migration, modernize schema:
832
- - Remove Django-specific fields (`content_type`, `permissions`)
833
- - Simplify table names (remove app prefixes)
834
- - Add database-level constraints
835
- - Optimize indexes for async queries
836
-
837
- ### Phase 4: Complete Cutover
838
-
839
- ```python
840
- # Decommission Django
841
- # 1. Archive Django admin usage
842
- # 2. Export management commands to FastAPI CLI
843
- # 3. Migrate background tasks to Celery/Dramatiq
844
- # 4. Remove Django dependency
845
- ```
846
-
847
- ---
848
-
849
- ## Common Pitfalls
850
-
851
- ### 1. Async/Await Mistakes
852
-
853
- **WRONG:**
854
- ```python
855
- # Blocking call in async function
856
- @router.get("/users")
857
- async def get_users(db: AsyncSession):
858
- users = db.execute(select(User)).scalars().all() # Missing await
859
- return users
860
- ```
861
-
862
- **CORRECT:**
863
- ```python
864
- @router.get("/users")
865
- async def get_users(db: AsyncSession):
866
- result = await db.execute(select(User)) # Await async operation
867
- users = result.scalars().all()
868
- return users
869
- ```
870
-
871
- ### 2. Missing `from_attributes` (orm_mode)
872
-
873
- **WRONG:**
874
- ```python
875
- class UserRead(BaseModel):
876
- id: int
877
- username: str
878
- # Missing config - won't work with SQLAlchemy models
879
- ```
880
-
881
- **CORRECT:**
882
- ```python
883
- class UserRead(BaseModel):
884
- id: int
885
- username: str
886
-
887
- model_config = {"from_attributes": True} # Pydantic V2
888
- ```
889
-
890
- ### 3. Session Management
891
-
892
- **WRONG:**
893
- ```python
894
- # Reusing session across requests
895
- db_session = async_sessionmaker(engine)()
896
-
897
- @router.get("/users")
898
- async def get_users():
899
- return await db_session.execute(select(User)) # Session leak
900
- ```
901
-
902
- **CORRECT:**
903
- ```python
904
- # Dependency injection per request
905
- async def get_db():
906
- async with async_sessionmaker(engine)() as session:
907
- yield session
908
- await session.commit()
909
-
910
- @router.get("/users")
911
- async def get_users(db: Annotated[AsyncSession, Depends(get_db)]):
912
- result = await db.execute(select(User))
913
- return result.scalars().all()
914
- ```
915
-
916
- ### 4. Relationship Loading
917
-
918
- **WRONG:**
919
- ```python
920
- # Lazy loading in async (causes errors)
921
- user = await db.get(User, user_id)
922
- posts = user.posts # Error: lazy loading not supported in async
923
- ```
924
-
925
- **CORRECT:**
926
- ```python
927
- # Eager loading with selectinload
928
- result = await db.execute(
929
- select(User).options(selectinload(User.posts)).where(User.id == user_id)
930
- )
931
- user = result.scalar_one()
932
- posts = user.posts # Already loaded
933
- ```
934
-
935
- ### 5. Transaction Handling
936
-
937
- **WRONG:**
938
- ```python
939
- # Auto-commit not configured
940
- @router.post("/users")
941
- async def create_user(user: UserCreate, db: AsyncSession):
942
- db_user = User(**user.dict())
943
- db.add(db_user)
944
- # Missing commit - changes lost
945
- return db_user
946
- ```
947
-
948
- **CORRECT:**
949
- ```python
950
- @router.post("/users")
951
- async def create_user(user: UserCreate, db: AsyncSession):
952
- db_user = User(**user.model_dump())
953
- db.add(db_user)
954
- await db.commit() # Explicit commit
955
- await db.refresh(db_user) # Refresh to get DB-generated fields
956
- return db_user
957
- ```
958
-
959
- ---
960
-
961
- ## Cross-Reference
962
-
963
- For comprehensive migration strategies and modernization patterns:
964
- - **Legacy Modernizer**: `/skills/legacy-modernizer/references/migration-strategies.md`
965
- - Strangler pattern implementation
966
- - Feature flag strategies
967
- - Rollback procedures
968
- - Data migration pipelines
969
-
970
- ---
971
-
972
- ## Migration Checklist
973
-
974
- **Pre-Migration:**
975
- - [ ] Async readiness assessment (I/O bound workload?)
976
- - [ ] Team async Python experience validated
977
- - [ ] Database compatibility verified (async drivers available)
978
- - [ ] Admin interface replacement identified
979
- - [ ] Migration timeline approved (6-12 months realistic)
980
-
981
- **During Migration:**
982
- - [ ] Parallel deployment configured
983
- - [ ] Monitoring and alerting set up
984
- - [ ] Load testing completed
985
- - [ ] Data consistency validation automated
986
- - [ ] Rollback procedure tested
987
-
988
- **Post-Migration:**
989
- - [ ] Django dependencies removed
990
- - [ ] Documentation updated
991
- - [ ] Team training completed
992
- - [ ] Performance gains measured
993
- - [ ] Cost savings validated
994
-
995
- ---
996
-
1
+ # Django to FastAPI Migration Guide
2
+
3
+ ---
4
+
5
+ ## When to Use This Guide
6
+
7
+ **Migrate to FastAPI when:**
8
+ - Need async/await for I/O-bound operations
9
+ - Require WebSocket or Server-Sent Events
10
+ - Want automatic OpenAPI/Swagger documentation
11
+ - Need better performance for API-heavy workloads
12
+ - Desire modern Python type hints and editor support
13
+ - Building microservices from Django monolith
14
+ - Require lower resource consumption
15
+
16
+ **DO NOT migrate when:**
17
+ - Heavy use of Django admin interface
18
+ - Extensive Django ORM model inheritance
19
+ - Complex form handling and validation
20
+ - Server-side template rendering required
21
+ - Team lacks async Python experience
22
+ - Django ecosystem plugins are critical
23
+ - Migration cost exceeds business value
24
+
25
+ ---
26
+
27
+ ## Concept Mapping: Django/DRF → FastAPI
28
+
29
+ | Django/DRF Concept | FastAPI Equivalent | Notes |
30
+ |-------------------|-------------------|-------|
31
+ | `models.Model` | Pydantic `BaseModel` + SQLAlchemy | Separate schema from ORM |
32
+ | `serializers.Serializer` | Pydantic `BaseModel` | Type-safe validation |
33
+ | `ModelSerializer` | Multiple Pydantic models | Create/Read/Update schemas |
34
+ | `ViewSet` | `APIRouter` + path operations | More explicit routing |
35
+ | `GenericAPIView` | Dependency injection | Function-based approach |
36
+ | `@api_view` decorator | `@router.get/post` | Built-in HTTP methods |
37
+ | `urls.py` | `APIRouter` + `app.include_router` | Nested routers |
38
+ | `settings.py` | `pydantic-settings` | Environment-based config |
39
+ | `middleware` | Middleware + dependencies | More granular control |
40
+ | `permissions` | Dependencies | Composable auth |
41
+ | `authentication` | OAuth2 + JWT dependencies | Standards-based |
42
+ | `pagination` | Query parameters + dependencies | Manual implementation |
43
+ | `filters` | Query parameters | Type-safe filtering |
44
+ | `Django ORM` | SQLAlchemy 2.0+ | Async support |
45
+ | `select_related` | `selectinload` | Eager loading |
46
+ | `prefetch_related` | `joinedload` | Join strategies |
47
+ | `pytest-django` | `pytest + httpx` | Async test client |
48
+ | `admin.py` | External (SQLAdmin, etc.) | Not built-in |
49
+
50
+ ---
51
+
52
+ ## Serializer → Pydantic V2 Migration
53
+
54
+ ### Django REST Framework Serializer
55
+
56
+ ```python
57
+ # Django DRF
58
+ from rest_framework import serializers
59
+ from .models import User, Post
60
+
61
+ class UserSerializer(serializers.ModelSerializer):
62
+ post_count = serializers.SerializerMethodField()
63
+
64
+ class Meta:
65
+ model = User
66
+ fields = ['id', 'username', 'email', 'created_at', 'post_count']
67
+ read_only_fields = ['id', 'created_at']
68
+ extra_kwargs = {
69
+ 'email': {'write_only': True}
70
+ }
71
+
72
+ def get_post_count(self, obj):
73
+ return obj.posts.count()
74
+
75
+ def validate_username(self, value):
76
+ if len(value) < 3:
77
+ raise serializers.ValidationError("Username too short")
78
+ return value
79
+
80
+ class PostSerializer(serializers.ModelSerializer):
81
+ author = UserSerializer(read_only=True)
82
+ tags = serializers.ListField(child=serializers.CharField())
83
+
84
+ class Meta:
85
+ model = Post
86
+ fields = ['id', 'title', 'content', 'author', 'tags', 'published']
87
+
88
+ def create(self, validated_data):
89
+ tags = validated_data.pop('tags', [])
90
+ post = Post.objects.create(**validated_data)
91
+ post.tags.set(tags)
92
+ return post
93
+ ```
94
+
95
+ ### FastAPI Pydantic V2 Schemas
96
+
97
+ ```python
98
+ # FastAPI with Pydantic V2
99
+ from pydantic import BaseModel, EmailStr, Field, field_validator, computed_field
100
+ from datetime import datetime
101
+ from typing import Annotated
102
+
103
+ # Base schemas
104
+ class UserBase(BaseModel):
105
+ username: Annotated[str, Field(min_length=3, max_length=50)]
106
+ email: EmailStr
107
+
108
+ # Create schema (input)
109
+ class UserCreate(UserBase):
110
+ password: Annotated[str, Field(min_length=8)]
111
+
112
+ @field_validator('username')
113
+ @classmethod
114
+ def validate_username(cls, v: str) -> str:
115
+ if len(v) < 3:
116
+ raise ValueError("Username too short")
117
+ return v
118
+
119
+ # Update schema (partial)
120
+ class UserUpdate(BaseModel):
121
+ username: Annotated[str | None, Field(min_length=3, max_length=50)] = None
122
+ email: EmailStr | None = None
123
+
124
+ # Read schema (output) - analogous to read_only_fields
125
+ class UserRead(UserBase):
126
+ id: int
127
+ created_at: datetime
128
+
129
+ model_config = {
130
+ "from_attributes": True # Pydantic V2: replaces orm_mode
131
+ }
132
+
133
+ # Read schema with relations - analogous to SerializerMethodField
134
+ class UserReadWithStats(UserRead):
135
+ post_count: int
136
+
137
+ @computed_field # Pydantic V2 computed fields
138
+ @property
139
+ def display_name(self) -> str:
140
+ return f"@{self.username}"
141
+
142
+ # Nested schemas
143
+ class PostBase(BaseModel):
144
+ title: Annotated[str, Field(max_length=200)]
145
+ content: str
146
+ tags: list[str] = []
147
+ published: bool = False
148
+
149
+ class PostCreate(PostBase):
150
+ pass
151
+
152
+ class PostRead(PostBase):
153
+ id: int
154
+ author: UserRead # Nested serialization
155
+ created_at: datetime
156
+
157
+ model_config = {"from_attributes": True}
158
+
159
+ # Embedding vs side-loading
160
+ class PostReadMinimal(BaseModel):
161
+ """Minimal post representation (just ID)"""
162
+ id: int
163
+ title: str
164
+ author_id: int # Side-loaded reference
165
+
166
+ model_config = {"from_attributes": True}
167
+ ```
168
+
169
+ ---
170
+
171
+ ## ViewSet → APIRouter Migration
172
+
173
+ ### Django REST Framework ViewSet
174
+
175
+ ```python
176
+ # Django DRF ViewSet
177
+ from rest_framework import viewsets, status
178
+ from rest_framework.decorators import action
179
+ from rest_framework.response import Response
180
+ from rest_framework.permissions import IsAuthenticated
181
+ from django.shortcuts import get_object_or_404
182
+
183
+ class PostViewSet(viewsets.ModelViewSet):
184
+ queryset = Post.objects.all()
185
+ serializer_class = PostSerializer
186
+ permission_classes = [IsAuthenticated]
187
+
188
+ def get_queryset(self):
189
+ queryset = super().get_queryset()
190
+ if self.request.user.is_authenticated:
191
+ return queryset.filter(author=self.request.user)
192
+ return queryset.none()
193
+
194
+ def perform_create(self, serializer):
195
+ serializer.save(author=self.request.user)
196
+
197
+ @action(detail=True, methods=['post'])
198
+ def publish(self, request, pk=None):
199
+ post = self.get_object()
200
+ post.published = True
201
+ post.save()
202
+ return Response({'status': 'published'})
203
+
204
+ @action(detail=False, methods=['get'])
205
+ def recent(self, request):
206
+ recent_posts = self.get_queryset().order_by('-created_at')[:10]
207
+ serializer = self.get_serializer(recent_posts, many=True)
208
+ return Response(serializer.data)
209
+ ```
210
+
211
+ ### FastAPI APIRouter with Dependencies
212
+
213
+ ```python
214
+ # FastAPI APIRouter
215
+ from fastapi import APIRouter, Depends, HTTPException, status, Query
216
+ from sqlalchemy.ext.asyncio import AsyncSession
217
+ from sqlalchemy import select
218
+ from typing import Annotated
219
+
220
+ from .database import get_db
221
+ from .auth import get_current_user
222
+ from .models import Post as PostModel, User as UserModel
223
+ from .schemas import PostRead, PostCreate, PostUpdate, UserRead
224
+
225
+ router = APIRouter(prefix="/posts", tags=["posts"])
226
+
227
+ # Dependency for database session
228
+ DbSession = Annotated[AsyncSession, Depends(get_db)]
229
+ CurrentUser = Annotated[UserModel, Depends(get_current_user)]
230
+
231
+ # List posts (GET /posts)
232
+ @router.get("/", response_model=list[PostRead])
233
+ async def list_posts(
234
+ db: DbSession,
235
+ current_user: CurrentUser,
236
+ skip: int = Query(0, ge=0),
237
+ limit: int = Query(100, le=100),
238
+ ):
239
+ """Analogous to ViewSet.list()"""
240
+ result = await db.execute(
241
+ select(PostModel)
242
+ .where(PostModel.author_id == current_user.id)
243
+ .offset(skip)
244
+ .limit(limit)
245
+ )
246
+ posts = result.scalars().all()
247
+ return posts
248
+
249
+ # Create post (POST /posts)
250
+ @router.post("/", response_model=PostRead, status_code=status.HTTP_201_CREATED)
251
+ async def create_post(
252
+ post_data: PostCreate,
253
+ db: DbSession,
254
+ current_user: CurrentUser,
255
+ ):
256
+ """Analogous to ViewSet.create()"""
257
+ post = PostModel(**post_data.model_dump(), author_id=current_user.id)
258
+ db.add(post)
259
+ await db.commit()
260
+ await db.refresh(post)
261
+ return post
262
+
263
+ # Retrieve single post (GET /posts/{post_id})
264
+ @router.get("/{post_id}", response_model=PostRead)
265
+ async def get_post(
266
+ post_id: int,
267
+ db: DbSession,
268
+ current_user: CurrentUser,
269
+ ):
270
+ """Analogous to ViewSet.retrieve()"""
271
+ result = await db.execute(
272
+ select(PostModel).where(
273
+ PostModel.id == post_id,
274
+ PostModel.author_id == current_user.id
275
+ )
276
+ )
277
+ post = result.scalar_one_or_none()
278
+ if not post:
279
+ raise HTTPException(status_code=404, detail="Post not found")
280
+ return post
281
+
282
+ # Update post (PUT /posts/{post_id})
283
+ @router.put("/{post_id}", response_model=PostRead)
284
+ async def update_post(
285
+ post_id: int,
286
+ post_data: PostUpdate,
287
+ db: DbSession,
288
+ current_user: CurrentUser,
289
+ ):
290
+ """Analogous to ViewSet.update()"""
291
+ result = await db.execute(
292
+ select(PostModel).where(
293
+ PostModel.id == post_id,
294
+ PostModel.author_id == current_user.id
295
+ )
296
+ )
297
+ post = result.scalar_one_or_none()
298
+ if not post:
299
+ raise HTTPException(status_code=404, detail="Post not found")
300
+
301
+ # Update only provided fields
302
+ for field, value in post_data.model_dump(exclude_unset=True).items():
303
+ setattr(post, field, value)
304
+
305
+ await db.commit()
306
+ await db.refresh(post)
307
+ return post
308
+
309
+ # Delete post (DELETE /posts/{post_id})
310
+ @router.delete("/{post_id}", status_code=status.HTTP_204_NO_CONTENT)
311
+ async def delete_post(
312
+ post_id: int,
313
+ db: DbSession,
314
+ current_user: CurrentUser,
315
+ ):
316
+ """Analogous to ViewSet.destroy()"""
317
+ result = await db.execute(
318
+ select(PostModel).where(
319
+ PostModel.id == post_id,
320
+ PostModel.author_id == current_user.id
321
+ )
322
+ )
323
+ post = result.scalar_one_or_none()
324
+ if not post:
325
+ raise HTTPException(status_code=404, detail="Post not found")
326
+
327
+ await db.delete(post)
328
+ await db.commit()
329
+
330
+ # Custom action: Publish (POST /posts/{post_id}/publish)
331
+ @router.post("/{post_id}/publish", response_model=dict)
332
+ async def publish_post(
333
+ post_id: int,
334
+ db: DbSession,
335
+ current_user: CurrentUser,
336
+ ):
337
+ """Analogous to @action(detail=True)"""
338
+ result = await db.execute(
339
+ select(PostModel).where(
340
+ PostModel.id == post_id,
341
+ PostModel.author_id == current_user.id
342
+ )
343
+ )
344
+ post = result.scalar_one_or_none()
345
+ if not post:
346
+ raise HTTPException(status_code=404, detail="Post not found")
347
+
348
+ post.published = True
349
+ await db.commit()
350
+ return {"status": "published"}
351
+
352
+ # Custom collection action: Recent posts (GET /posts/recent)
353
+ @router.get("/actions/recent", response_model=list[PostRead])
354
+ async def recent_posts(
355
+ db: DbSession,
356
+ current_user: CurrentUser,
357
+ limit: int = Query(10, le=50),
358
+ ):
359
+ """Analogous to @action(detail=False)"""
360
+ result = await db.execute(
361
+ select(PostModel)
362
+ .where(PostModel.author_id == current_user.id)
363
+ .order_by(PostModel.created_at.desc())
364
+ .limit(limit)
365
+ )
366
+ posts = result.scalars().all()
367
+ return posts
368
+ ```
369
+
370
+ ---
371
+
372
+ ## Django ORM → Async SQLAlchemy
373
+
374
+ ### Django ORM Models
375
+
376
+ ```python
377
+ # Django models
378
+ from django.db import models
379
+
380
+ class User(models.Model):
381
+ username = models.CharField(max_length=50, unique=True)
382
+ email = models.EmailField(unique=True)
383
+ created_at = models.DateTimeField(auto_now_add=True)
384
+
385
+ class Meta:
386
+ db_table = 'users'
387
+ indexes = [
388
+ models.Index(fields=['username']),
389
+ ]
390
+
391
+ class Post(models.Model):
392
+ title = models.CharField(max_length=200)
393
+ content = models.TextField()
394
+ author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
395
+ created_at = models.DateTimeField(auto_now_add=True)
396
+ published = models.BooleanField(default=False)
397
+
398
+ class Meta:
399
+ db_table = 'posts'
400
+ ordering = ['-created_at']
401
+ ```
402
+
403
+ ### SQLAlchemy 2.0 Async Models
404
+
405
+ ```python
406
+ # SQLAlchemy 2.0 models
407
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
408
+ from sqlalchemy import String, Text, Boolean, ForeignKey, Index
409
+ from datetime import datetime
410
+ from typing import List
411
+
412
+ class Base(DeclarativeBase):
413
+ pass
414
+
415
+ class User(Base):
416
+ __tablename__ = 'users'
417
+
418
+ # Primary key
419
+ id: Mapped[int] = mapped_column(primary_key=True)
420
+
421
+ # Columns with type hints
422
+ username: Mapped[str] = mapped_column(String(50), unique=True, index=True)
423
+ email: Mapped[str] = mapped_column(String(255), unique=True)
424
+ created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
425
+
426
+ # Relationships (analogous to related_name)
427
+ posts: Mapped[List["Post"]] = relationship(back_populates="author")
428
+
429
+ __table_args__ = (
430
+ Index('ix_users_username', 'username'),
431
+ )
432
+
433
+ class Post(Base):
434
+ __tablename__ = 'posts'
435
+
436
+ id: Mapped[int] = mapped_column(primary_key=True)
437
+ title: Mapped[str] = mapped_column(String(200))
438
+ content: Mapped[str] = mapped_column(Text)
439
+ author_id: Mapped[int] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'))
440
+ created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
441
+ published: Mapped[bool] = mapped_column(Boolean, default=False)
442
+
443
+ # Relationship
444
+ author: Mapped["User"] = relationship(back_populates="posts")
445
+
446
+ __table_args__ = (
447
+ Index('ix_posts_created_at', 'created_at'),
448
+ )
449
+ ```
450
+
451
+ ### Query Patterns: Django ORM vs SQLAlchemy
452
+
453
+ ```python
454
+ # Django ORM queries
455
+ from django.db.models import Count, Q
456
+
457
+ # Simple filter
458
+ posts = Post.objects.filter(published=True)
459
+
460
+ # Select related (JOIN)
461
+ posts = Post.objects.select_related('author').filter(published=True)
462
+
463
+ # Prefetch related (separate query)
464
+ users = User.objects.prefetch_related('posts').all()
465
+
466
+ # Complex filtering
467
+ posts = Post.objects.filter(
468
+ Q(published=True) | Q(author__username='admin')
469
+ ).order_by('-created_at')[:10]
470
+
471
+ # Aggregation
472
+ user_stats = User.objects.annotate(
473
+ post_count=Count('posts')
474
+ ).filter(post_count__gte=5)
475
+ ```
476
+
477
+ ```python
478
+ # SQLAlchemy 2.0 async queries
479
+ from sqlalchemy import select, func, or_
480
+ from sqlalchemy.orm import selectinload, joinedload
481
+
482
+ # Simple filter
483
+ async def get_published_posts(db: AsyncSession):
484
+ result = await db.execute(
485
+ select(Post).where(Post.published == True)
486
+ )
487
+ return result.scalars().all()
488
+
489
+ # Eager loading with JOIN (selectinload = separate query)
490
+ async def get_posts_with_authors(db: AsyncSession):
491
+ result = await db.execute(
492
+ select(Post)
493
+ .options(selectinload(Post.author))
494
+ .where(Post.published == True)
495
+ )
496
+ return result.scalars().all()
497
+
498
+ # Prefetch related (joinedload = single query with JOIN)
499
+ async def get_users_with_posts(db: AsyncSession):
500
+ result = await db.execute(
501
+ select(User).options(joinedload(User.posts))
502
+ )
503
+ return result.unique().scalars().all()
504
+
505
+ # Complex filtering
506
+ async def get_complex_posts(db: AsyncSession):
507
+ result = await db.execute(
508
+ select(Post)
509
+ .join(Post.author)
510
+ .where(
511
+ or_(
512
+ Post.published == True,
513
+ User.username == 'admin'
514
+ )
515
+ )
516
+ .order_by(Post.created_at.desc())
517
+ .limit(10)
518
+ )
519
+ return result.scalars().all()
520
+
521
+ # Aggregation
522
+ async def get_user_stats(db: AsyncSession):
523
+ result = await db.execute(
524
+ select(User, func.count(Post.id).label('post_count'))
525
+ .join(Post)
526
+ .group_by(User.id)
527
+ .having(func.count(Post.id) >= 5)
528
+ )
529
+ return result.all()
530
+ ```
531
+
532
+ ---
533
+
534
+ ## Authentication: SimpleJWT → FastAPI JWT
535
+
536
+ ### Django SimpleJWT
537
+
538
+ ```python
539
+ # Django settings.py
540
+ REST_FRAMEWORK = {
541
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
542
+ 'rest_framework_simplejwt.authentication.JWTAuthentication',
543
+ ],
544
+ }
545
+
546
+ # Views
547
+ from rest_framework_simplejwt.views import TokenObtainPairView
548
+
549
+ # Usage in ViewSet
550
+ from rest_framework.permissions import IsAuthenticated
551
+
552
+ class ProtectedViewSet(viewsets.ModelViewSet):
553
+ permission_classes = [IsAuthenticated]
554
+
555
+ def get_queryset(self):
556
+ return Post.objects.filter(author=self.request.user)
557
+ ```
558
+
559
+ ### FastAPI JWT Authentication
560
+
561
+ ```python
562
+ # auth.py - FastAPI JWT implementation
563
+ from fastapi import Depends, HTTPException, status
564
+ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
565
+ from jose import JWTError, jwt
566
+ from passlib.context import CryptContext
567
+ from datetime import datetime, timedelta
568
+ from pydantic import BaseModel
569
+ from typing import Annotated
570
+
571
+ # Configuration
572
+ SECRET_KEY = "your-secret-key" # Use environment variable
573
+ ALGORITHM = "HS256"
574
+ ACCESS_TOKEN_EXPIRE_MINUTES = 30
575
+
576
+ # Password hashing
577
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
578
+
579
+ # OAuth2 scheme
580
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
581
+
582
+ # Schemas
583
+ class Token(BaseModel):
584
+ access_token: str
585
+ token_type: str
586
+
587
+ class TokenData(BaseModel):
588
+ username: str | None = None
589
+
590
+ # Helper functions
591
+ def verify_password(plain_password: str, hashed_password: str) -> bool:
592
+ return pwd_context.verify(plain_password, hashed_password)
593
+
594
+ def get_password_hash(password: str) -> str:
595
+ return pwd_context.hash(password)
596
+
597
+ def create_access_token(data: dict, expires_delta: timedelta | None = None):
598
+ to_encode = data.copy()
599
+ if expires_delta:
600
+ expire = datetime.utcnow() + expires_delta
601
+ else:
602
+ expire = datetime.utcnow() + timedelta(minutes=15)
603
+ to_encode.update({"exp": expire})
604
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
605
+ return encoded_jwt
606
+
607
+ # Dependency: Get current user from token
608
+ async def get_current_user(
609
+ token: Annotated[str, Depends(oauth2_scheme)],
610
+ db: Annotated[AsyncSession, Depends(get_db)]
611
+ ) -> UserModel:
612
+ credentials_exception = HTTPException(
613
+ status_code=status.HTTP_401_UNAUTHORIZED,
614
+ detail="Could not validate credentials",
615
+ headers={"WWW-Authenticate": "Bearer"},
616
+ )
617
+ try:
618
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
619
+ username: str = payload.get("sub")
620
+ if username is None:
621
+ raise credentials_exception
622
+ token_data = TokenData(username=username)
623
+ except JWTError:
624
+ raise credentials_exception
625
+
626
+ result = await db.execute(
627
+ select(UserModel).where(UserModel.username == token_data.username)
628
+ )
629
+ user = result.scalar_one_or_none()
630
+ if user is None:
631
+ raise credentials_exception
632
+ return user
633
+
634
+ # Login endpoint
635
+ auth_router = APIRouter(prefix="/auth", tags=["auth"])
636
+
637
+ @auth_router.post("/token", response_model=Token)
638
+ async def login(
639
+ form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
640
+ db: Annotated[AsyncSession, Depends(get_db)]
641
+ ):
642
+ # Authenticate user
643
+ result = await db.execute(
644
+ select(UserModel).where(UserModel.username == form_data.username)
645
+ )
646
+ user = result.scalar_one_or_none()
647
+
648
+ if not user or not verify_password(form_data.password, user.hashed_password):
649
+ raise HTTPException(
650
+ status_code=status.HTTP_401_UNAUTHORIZED,
651
+ detail="Incorrect username or password",
652
+ headers={"WWW-Authenticate": "Bearer"},
653
+ )
654
+
655
+ # Create access token
656
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
657
+ access_token = create_access_token(
658
+ data={"sub": user.username}, expires_delta=access_token_expires
659
+ )
660
+ return {"access_token": access_token, "token_type": "bearer"}
661
+
662
+ # Protected endpoint usage
663
+ @router.get("/protected")
664
+ async def protected_route(current_user: Annotated[UserModel, Depends(get_current_user)]):
665
+ return {"message": f"Hello {current_user.username}"}
666
+ ```
667
+
668
+ ---
669
+
670
+ ## Testing Migration
671
+
672
+ ### Django/DRF Tests
673
+
674
+ ```python
675
+ # Django pytest
676
+ import pytest
677
+ from rest_framework.test import APIClient
678
+ from django.contrib.auth.models import User
679
+
680
+ @pytest.fixture
681
+ def api_client():
682
+ return APIClient()
683
+
684
+ @pytest.fixture
685
+ def user(db):
686
+ return User.objects.create_user(username='test', password='test123')
687
+
688
+ @pytest.mark.django_db
689
+ def test_create_post(api_client, user):
690
+ api_client.force_authenticate(user=user)
691
+ response = api_client.post('/api/posts/', {
692
+ 'title': 'Test Post',
693
+ 'content': 'Test content'
694
+ })
695
+ assert response.status_code == 201
696
+ assert response.data['title'] == 'Test Post'
697
+ ```
698
+
699
+ ### FastAPI Tests
700
+
701
+ ```python
702
+ # FastAPI pytest with httpx
703
+ import pytest
704
+ from httpx import AsyncClient, ASGITransport
705
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
706
+ from app.main import app
707
+ from app.database import get_db, Base
708
+ from app.models import User
709
+
710
+ # Test database setup
711
+ TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
712
+
713
+ @pytest.fixture
714
+ async def db_engine():
715
+ engine = create_async_engine(TEST_DATABASE_URL, echo=False)
716
+ async with engine.begin() as conn:
717
+ await conn.run_sync(Base.metadata.create_all)
718
+ yield engine
719
+ await engine.dispose()
720
+
721
+ @pytest.fixture
722
+ async def db_session(db_engine):
723
+ async_session = async_sessionmaker(
724
+ db_engine, class_=AsyncSession, expire_on_commit=False
725
+ )
726
+ async with async_session() as session:
727
+ yield session
728
+
729
+ @pytest.fixture
730
+ async def client(db_session):
731
+ async def override_get_db():
732
+ yield db_session
733
+
734
+ app.dependency_overrides[get_db] = override_get_db
735
+
736
+ async with AsyncClient(
737
+ transport=ASGITransport(app=app),
738
+ base_url="http://test"
739
+ ) as ac:
740
+ yield ac
741
+
742
+ app.dependency_overrides.clear()
743
+
744
+ @pytest.fixture
745
+ async def auth_headers(client, db_session):
746
+ # Create test user
747
+ user = User(username="test", email="test@example.com")
748
+ user.hashed_password = get_password_hash("test123")
749
+ db_session.add(user)
750
+ await db_session.commit()
751
+
752
+ # Get token
753
+ response = await client.post("/auth/token", data={
754
+ "username": "test",
755
+ "password": "test123"
756
+ })
757
+ token = response.json()["access_token"]
758
+ return {"Authorization": f"Bearer {token}"}
759
+
760
+ @pytest.mark.asyncio
761
+ async def test_create_post(client, auth_headers):
762
+ response = await client.post(
763
+ "/posts/",
764
+ json={"title": "Test Post", "content": "Test content"},
765
+ headers=auth_headers
766
+ )
767
+ assert response.status_code == 201
768
+ data = response.json()
769
+ assert data["title"] == "Test Post"
770
+
771
+ @pytest.mark.asyncio
772
+ async def test_list_posts(client, auth_headers, db_session):
773
+ # Create test data
774
+ user = await db_session.execute(select(User).where(User.username == "test"))
775
+ user = user.scalar_one()
776
+
777
+ post = Post(title="Test", content="Content", author_id=user.id)
778
+ db_session.add(post)
779
+ await db_session.commit()
780
+
781
+ # Test endpoint
782
+ response = await client.get("/posts/", headers=auth_headers)
783
+ assert response.status_code == 200
784
+ assert len(response.json()) == 1
785
+ ```
786
+
787
+ ---
788
+
789
+ ## Incremental Migration Strategy
790
+
791
+ ### Phase 1: Parallel API (Strangler Pattern)
792
+
793
+ Run Django and FastAPI side-by-side, migrating endpoints incrementally.
794
+
795
+ ```python
796
+ # Nginx routing config
797
+ location /api/v2/ {
798
+ proxy_pass http://fastapi:8000;
799
+ }
800
+
801
+ location /api/ {
802
+ proxy_pass http://django:8001;
803
+ }
804
+ ```
805
+
806
+ **Approach:**
807
+ 1. Stand up FastAPI with shared database (read-only initially)
808
+ 2. Migrate GET endpoints first (lowest risk)
809
+ 3. Add write endpoints with dual-write to both systems
810
+ 4. Validate data consistency
811
+ 5. Switch traffic gradually (feature flags)
812
+
813
+ ### Phase 2: Shared Database Migration
814
+
815
+ ```python
816
+ # FastAPI with existing Django database
817
+ from sqlalchemy import MetaData
818
+
819
+ # Reflect existing Django tables
820
+ metadata = MetaData()
821
+ metadata.reflect(bind=engine, only=['users', 'posts'])
822
+
823
+ # Or define models matching Django schema
824
+ class User(Base):
825
+ __tablename__ = 'auth_user' # Django's user table
826
+ # Map to Django's column names
827
+ ```
828
+
829
+ ### Phase 3: Database Schema Modernization
830
+
831
+ After traffic migration, modernize schema:
832
+ - Remove Django-specific fields (`content_type`, `permissions`)
833
+ - Simplify table names (remove app prefixes)
834
+ - Add database-level constraints
835
+ - Optimize indexes for async queries
836
+
837
+ ### Phase 4: Complete Cutover
838
+
839
+ ```python
840
+ # Decommission Django
841
+ # 1. Archive Django admin usage
842
+ # 2. Export management commands to FastAPI CLI
843
+ # 3. Migrate background tasks to Celery/Dramatiq
844
+ # 4. Remove Django dependency
845
+ ```
846
+
847
+ ---
848
+
849
+ ## Common Pitfalls
850
+
851
+ ### 1. Async/Await Mistakes
852
+
853
+ **WRONG:**
854
+ ```python
855
+ # Blocking call in async function
856
+ @router.get("/users")
857
+ async def get_users(db: AsyncSession):
858
+ users = db.execute(select(User)).scalars().all() # Missing await
859
+ return users
860
+ ```
861
+
862
+ **CORRECT:**
863
+ ```python
864
+ @router.get("/users")
865
+ async def get_users(db: AsyncSession):
866
+ result = await db.execute(select(User)) # Await async operation
867
+ users = result.scalars().all()
868
+ return users
869
+ ```
870
+
871
+ ### 2. Missing `from_attributes` (orm_mode)
872
+
873
+ **WRONG:**
874
+ ```python
875
+ class UserRead(BaseModel):
876
+ id: int
877
+ username: str
878
+ # Missing config - won't work with SQLAlchemy models
879
+ ```
880
+
881
+ **CORRECT:**
882
+ ```python
883
+ class UserRead(BaseModel):
884
+ id: int
885
+ username: str
886
+
887
+ model_config = {"from_attributes": True} # Pydantic V2
888
+ ```
889
+
890
+ ### 3. Session Management
891
+
892
+ **WRONG:**
893
+ ```python
894
+ # Reusing session across requests
895
+ db_session = async_sessionmaker(engine)()
896
+
897
+ @router.get("/users")
898
+ async def get_users():
899
+ return await db_session.execute(select(User)) # Session leak
900
+ ```
901
+
902
+ **CORRECT:**
903
+ ```python
904
+ # Dependency injection per request
905
+ async def get_db():
906
+ async with async_sessionmaker(engine)() as session:
907
+ yield session
908
+ await session.commit()
909
+
910
+ @router.get("/users")
911
+ async def get_users(db: Annotated[AsyncSession, Depends(get_db)]):
912
+ result = await db.execute(select(User))
913
+ return result.scalars().all()
914
+ ```
915
+
916
+ ### 4. Relationship Loading
917
+
918
+ **WRONG:**
919
+ ```python
920
+ # Lazy loading in async (causes errors)
921
+ user = await db.get(User, user_id)
922
+ posts = user.posts # Error: lazy loading not supported in async
923
+ ```
924
+
925
+ **CORRECT:**
926
+ ```python
927
+ # Eager loading with selectinload
928
+ result = await db.execute(
929
+ select(User).options(selectinload(User.posts)).where(User.id == user_id)
930
+ )
931
+ user = result.scalar_one()
932
+ posts = user.posts # Already loaded
933
+ ```
934
+
935
+ ### 5. Transaction Handling
936
+
937
+ **WRONG:**
938
+ ```python
939
+ # Auto-commit not configured
940
+ @router.post("/users")
941
+ async def create_user(user: UserCreate, db: AsyncSession):
942
+ db_user = User(**user.dict())
943
+ db.add(db_user)
944
+ # Missing commit - changes lost
945
+ return db_user
946
+ ```
947
+
948
+ **CORRECT:**
949
+ ```python
950
+ @router.post("/users")
951
+ async def create_user(user: UserCreate, db: AsyncSession):
952
+ db_user = User(**user.model_dump())
953
+ db.add(db_user)
954
+ await db.commit() # Explicit commit
955
+ await db.refresh(db_user) # Refresh to get DB-generated fields
956
+ return db_user
957
+ ```
958
+
959
+ ---
960
+
961
+ ## Cross-Reference
962
+
963
+ For comprehensive migration strategies and modernization patterns:
964
+ - **Legacy Modernizer**: `/skills/legacy-modernizer/references/migration-strategies.md`
965
+ - Strangler pattern implementation
966
+ - Feature flag strategies
967
+ - Rollback procedures
968
+ - Data migration pipelines
969
+
970
+ ---
971
+
972
+ ## Migration Checklist
973
+
974
+ **Pre-Migration:**
975
+ - [ ] Async readiness assessment (I/O bound workload?)
976
+ - [ ] Team async Python experience validated
977
+ - [ ] Database compatibility verified (async drivers available)
978
+ - [ ] Admin interface replacement identified
979
+ - [ ] Migration timeline approved (6-12 months realistic)
980
+
981
+ **During Migration:**
982
+ - [ ] Parallel deployment configured
983
+ - [ ] Monitoring and alerting set up
984
+ - [ ] Load testing completed
985
+ - [ ] Data consistency validation automated
986
+ - [ ] Rollback procedure tested
987
+
988
+ **Post-Migration:**
989
+ - [ ] Django dependencies removed
990
+ - [ ] Documentation updated
991
+ - [ ] Team training completed
992
+ - [ ] Performance gains measured
993
+ - [ ] Cost savings validated
994
+
995
+ ---
996
+
997
997
  **Key Takeaway:** Migrate incrementally. Start with read-heavy endpoints, validate thoroughly, then gradually move write operations. Always maintain rollback capability.