hive-skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +235 -0
  3. package/assets/LICENSE +32 -0
  4. package/assets/THIRD_PARTY_NOTICES.md +72 -0
  5. package/assets/manifest.json +1290 -0
  6. package/assets/skills/authored/code-review/composable/BUNDLE.md +192 -0
  7. package/assets/skills/authored/code-review/composable/INDEX.md +13 -0
  8. package/assets/skills/authored/code-review/composable/VERSION +1 -0
  9. package/assets/skills/authored/code-review/composable/mini/01-review-method.md +34 -0
  10. package/assets/skills/authored/code-review/composable/mini/02-security-review.md +20 -0
  11. package/assets/skills/authored/code-review/composable/mini/03-correctness-bugs.md +19 -0
  12. package/assets/skills/authored/code-review/composable/mini/04-concurrency.md +16 -0
  13. package/assets/skills/authored/code-review/composable/mini/05-error-handling.md +12 -0
  14. package/assets/skills/authored/code-review/composable/mini/06-performance.md +11 -0
  15. package/assets/skills/authored/code-review/composable/mini/07-api-design.md +20 -0
  16. package/assets/skills/authored/code-review/composable/mini/08-missing-tests.md +25 -0
  17. package/assets/skills/authored/code-review/composable/mini/09-refactoring-and-communication.md +32 -0
  18. package/assets/skills/authored/data-analysis/composable/BUNDLE.md +186 -0
  19. package/assets/skills/authored/data-analysis/composable/INDEX.md +13 -0
  20. package/assets/skills/authored/data-analysis/composable/VERSION +1 -0
  21. package/assets/skills/authored/data-analysis/composable/mini/01-data-quality-profiling.md +26 -0
  22. package/assets/skills/authored/data-analysis/composable/mini/02-aggregation-pitfalls.md +21 -0
  23. package/assets/skills/authored/data-analysis/composable/mini/03-time-series.md +19 -0
  24. package/assets/skills/authored/data-analysis/composable/mini/04-segmentation.md +23 -0
  25. package/assets/skills/authored/data-analysis/composable/mini/05-discount-pricing.md +17 -0
  26. package/assets/skills/authored/data-analysis/composable/mini/06-insight-generation.md +25 -0
  27. package/assets/skills/authored/data-analysis/composable/mini/07-executive-writing.md +26 -0
  28. package/assets/skills/authored/data-analysis/composable/mini/08-recommendations.md +20 -0
  29. package/assets/skills/authored/data-analysis/composable/mini/09-reproducibility-assumptions.md +20 -0
  30. package/assets/skills/authored/financial-analysis/composable/BUNDLE.md +1293 -0
  31. package/assets/skills/authored/financial-analysis/composable/INDEX.md +17 -0
  32. package/assets/skills/authored/financial-analysis/composable/VERSION +1 -0
  33. package/assets/skills/authored/financial-analysis/composable/mini/00-core.md +69 -0
  34. package/assets/skills/authored/financial-analysis/composable/mini/01-profitability-efficiency-ratios.md +173 -0
  35. package/assets/skills/authored/financial-analysis/composable/mini/02-liquidity-leverage-ratios.md +120 -0
  36. package/assets/skills/authored/financial-analysis/composable/mini/03-valuation-ratios-interpretation.md +107 -0
  37. package/assets/skills/authored/financial-analysis/composable/mini/04-dcf-projection-wacc.md +122 -0
  38. package/assets/skills/authored/financial-analysis/composable/mini/05-dcf-terminal-value-sensitivity.md +75 -0
  39. package/assets/skills/authored/financial-analysis/composable/mini/06-comparables-precedents.md +78 -0
  40. package/assets/skills/authored/financial-analysis/composable/mini/07-budget-variance-analysis.md +47 -0
  41. package/assets/skills/authored/financial-analysis/composable/mini/08-driver-based-forecasting-scenarios.md +111 -0
  42. package/assets/skills/authored/financial-analysis/composable/mini/09-rolling-forecasts-accuracy.md +114 -0
  43. package/assets/skills/authored/financial-analysis/composable/mini/10-reporting-exec-summary.md +58 -0
  44. package/assets/skills/authored/financial-analysis/composable/mini/11-industry-adaptations.md +130 -0
  45. package/assets/skills/authored/financial-analysis/composable/mini/12-toolkit-scripts-io.md +102 -0
  46. package/assets/skills/authored/python-api/composable/BUNDLE.md +389 -0
  47. package/assets/skills/authored/python-api/composable/INDEX.md +13 -0
  48. package/assets/skills/authored/python-api/composable/VERSION +1 -0
  49. package/assets/skills/authored/python-api/composable/mini/01-project-structure.md +44 -0
  50. package/assets/skills/authored/python-api/composable/mini/02-routing-and-app.md +38 -0
  51. package/assets/skills/authored/python-api/composable/mini/03-pydantic-models.md +45 -0
  52. package/assets/skills/authored/python-api/composable/mini/04-validation-and-errors.md +59 -0
  53. package/assets/skills/authored/python-api/composable/mini/05-dependencies-and-auth.md +60 -0
  54. package/assets/skills/authored/python-api/composable/mini/06-pagination-filtering.md +44 -0
  55. package/assets/skills/authored/python-api/composable/mini/07-async-performance.md +30 -0
  56. package/assets/skills/authored/python-api/composable/mini/08-testing.md +45 -0
  57. package/assets/skills/authored/python-api/composable/mini/09-middleware-observability.md +29 -0
  58. package/assets/skills/authored/tech-writing/composable/BUNDLE.md +220 -0
  59. package/assets/skills/authored/tech-writing/composable/INDEX.md +12 -0
  60. package/assets/skills/authored/tech-writing/composable/VERSION +1 -0
  61. package/assets/skills/authored/tech-writing/composable/mini/01-audience-analysis.md +26 -0
  62. package/assets/skills/authored/tech-writing/composable/mini/02-document-types.md +27 -0
  63. package/assets/skills/authored/tech-writing/composable/mini/03-readme-quickstart-changelog.md +22 -0
  64. package/assets/skills/authored/tech-writing/composable/mini/04-information-architecture.md +21 -0
  65. package/assets/skills/authored/tech-writing/composable/mini/05-style-clarity.md +29 -0
  66. package/assets/skills/authored/tech-writing/composable/mini/06-breaking-changes-migrations.md +33 -0
  67. package/assets/skills/authored/tech-writing/composable/mini/07-troubleshooting-security.md +30 -0
  68. package/assets/skills/authored/tech-writing/composable/mini/08-faq-voice-tone.md +32 -0
  69. package/assets/skills/converted/claude-api/composable/BUNDLE.md +14996 -0
  70. package/assets/skills/converted/claude-api/composable/INDEX.md +98 -0
  71. package/assets/skills/converted/claude-api/composable/VERSION +1 -0
  72. package/assets/skills/converted/claude-api/composable/mini/00-core.md +235 -0
  73. package/assets/skills/converted/claude-api/composable/mini/01-model-api-quick-reference.md +258 -0
  74. package/assets/skills/converted/claude-api/composable/mini/02-common-pitfalls.md +42 -0
  75. package/assets/skills/converted/claude-api/composable/mini/10-models.md +135 -0
  76. package/assets/skills/converted/claude-api/composable/mini/11-platform-availability.md +96 -0
  77. package/assets/skills/converted/claude-api/composable/mini/12-claude-platform-on-aws.md +59 -0
  78. package/assets/skills/converted/claude-api/composable/mini/13-live-sources.md +143 -0
  79. package/assets/skills/converted/claude-api/composable/mini/14-error-codes.md +256 -0
  80. package/assets/skills/converted/claude-api/composable/mini/15-prompt-caching.md +223 -0
  81. package/assets/skills/converted/claude-api/composable/mini/16-token-counting.md +56 -0
  82. package/assets/skills/converted/claude-api/composable/mini/17-tool-use-concepts.md +444 -0
  83. package/assets/skills/converted/claude-api/composable/mini/18-agent-design.md +101 -0
  84. package/assets/skills/converted/claude-api/composable/mini/19-anthropic-cli.md +246 -0
  85. package/assets/skills/converted/claude-api/composable/mini/20-model-migration.md +1301 -0
  86. package/assets/skills/converted/claude-api/composable/mini/30-managed-agents-overview.md +71 -0
  87. package/assets/skills/converted/claude-api/composable/mini/31-managed-agents-core.md +278 -0
  88. package/assets/skills/converted/claude-api/composable/mini/32-managed-agents-environments.md +219 -0
  89. package/assets/skills/converted/claude-api/composable/mini/33-managed-agents-tools.md +366 -0
  90. package/assets/skills/converted/claude-api/composable/mini/34-managed-agents-events.md +252 -0
  91. package/assets/skills/converted/claude-api/composable/mini/35-managed-agents-outcomes.md +106 -0
  92. package/assets/skills/converted/claude-api/composable/mini/36-managed-agents-multiagent.md +101 -0
  93. package/assets/skills/converted/claude-api/composable/mini/37-managed-agents-webhooks.md +123 -0
  94. package/assets/skills/converted/claude-api/composable/mini/38-managed-agents-memory.md +197 -0
  95. package/assets/skills/converted/claude-api/composable/mini/39-managed-agents-scheduled-deployments.md +146 -0
  96. package/assets/skills/converted/claude-api/composable/mini/40-managed-agents-client-patterns.md +211 -0
  97. package/assets/skills/converted/claude-api/composable/mini/41-managed-agents-onboarding.md +82 -0
  98. package/assets/skills/converted/claude-api/composable/mini/42-managed-agents-api-reference.md +441 -0
  99. package/assets/skills/converted/claude-api/composable/mini/43-managed-agents-self-hosted-sandboxes.md +174 -0
  100. package/assets/skills/converted/claude-api/composable/mini/50-python-readme.md +564 -0
  101. package/assets/skills/converted/claude-api/composable/mini/51-python-tool-use.md +590 -0
  102. package/assets/skills/converted/claude-api/composable/mini/52-python-streaming-batches-files.md +549 -0
  103. package/assets/skills/converted/claude-api/composable/mini/53-python-managed-agents.md +335 -0
  104. package/assets/skills/converted/claude-api/composable/mini/55-typescript-readme.md +404 -0
  105. package/assets/skills/converted/claude-api/composable/mini/56-typescript-tool-use.md +548 -0
  106. package/assets/skills/converted/claude-api/composable/mini/57-typescript-streaming-batches-files.md +384 -0
  107. package/assets/skills/converted/claude-api/composable/mini/58-typescript-managed-agents.md +358 -0
  108. package/assets/skills/converted/claude-api/composable/mini/60-csharp-readme.md +361 -0
  109. package/assets/skills/converted/claude-api/composable/mini/61-csharp-tool-use.md +163 -0
  110. package/assets/skills/converted/claude-api/composable/mini/62-csharp-streaming-batches-files.md +64 -0
  111. package/assets/skills/converted/claude-api/composable/mini/65-go-readme.md +185 -0
  112. package/assets/skills/converted/claude-api/composable/mini/66-go-tool-use.md +219 -0
  113. package/assets/skills/converted/claude-api/composable/mini/67-go-streaming-files.md +63 -0
  114. package/assets/skills/converted/claude-api/composable/mini/68-go-managed-agents.md +564 -0
  115. package/assets/skills/converted/claude-api/composable/mini/70-java-readme.md +237 -0
  116. package/assets/skills/converted/claude-api/composable/mini/71-java-tool-use.md +227 -0
  117. package/assets/skills/converted/claude-api/composable/mini/72-java-streaming-files.md +49 -0
  118. package/assets/skills/converted/claude-api/composable/mini/73-java-managed-agents.md +443 -0
  119. package/assets/skills/converted/claude-api/composable/mini/75-php-readme.md +173 -0
  120. package/assets/skills/converted/claude-api/composable/mini/76-php-tool-use.md +252 -0
  121. package/assets/skills/converted/claude-api/composable/mini/77-php-streaming-batches-files.md +53 -0
  122. package/assets/skills/converted/claude-api/composable/mini/78-php-managed-agents.md +443 -0
  123. package/assets/skills/converted/claude-api/composable/mini/80-ruby-readme.md +133 -0
  124. package/assets/skills/converted/claude-api/composable/mini/81-ruby-tool-use-streaming.md +56 -0
  125. package/assets/skills/converted/claude-api/composable/mini/82-ruby-managed-agents.md +394 -0
  126. package/assets/skills/converted/claude-api/composable/mini/85-curl-examples.md +255 -0
  127. package/assets/skills/converted/claude-api/composable/mini/86-curl-managed-agents.md +340 -0
  128. package/assets/skills/converted/claude-api/composable/presets/csharp.md +1151 -0
  129. package/assets/skills/converted/claude-api/composable/presets/curl.md +1154 -0
  130. package/assets/skills/converted/claude-api/composable/presets/go.md +1598 -0
  131. package/assets/skills/converted/claude-api/composable/presets/java.md +1523 -0
  132. package/assets/skills/converted/claude-api/composable/presets/php.md +1488 -0
  133. package/assets/skills/converted/claude-api/composable/presets/python.md +2605 -0
  134. package/assets/skills/converted/claude-api/composable/presets/ruby.md +1146 -0
  135. package/assets/skills/converted/claude-api/composable/presets/shared.md +6478 -0
  136. package/assets/skills/converted/claude-api/composable/presets/typescript.md +2261 -0
  137. package/assets/skills/converted/docx/composable/BUNDLE.md +617 -0
  138. package/assets/skills/converted/docx/composable/INDEX.md +12 -0
  139. package/assets/skills/converted/docx/composable/VERSION +1 -0
  140. package/assets/skills/converted/docx/composable/mini/00-core.md +26 -0
  141. package/assets/skills/converted/docx/composable/mini/01-reading-converting.md +36 -0
  142. package/assets/skills/converted/docx/composable/mini/02-creating-setup-layout.md +85 -0
  143. package/assets/skills/converted/docx/composable/mini/03-creating-elements.md +257 -0
  144. package/assets/skills/converted/docx/composable/mini/04-editing-existing.md +57 -0
  145. package/assets/skills/converted/docx/composable/mini/05-xml-reference.md +128 -0
  146. package/assets/skills/converted/internal-comms/composable/BUNDLE.md +220 -0
  147. package/assets/skills/converted/internal-comms/composable/INDEX.md +11 -0
  148. package/assets/skills/converted/internal-comms/composable/VERSION +1 -0
  149. package/assets/skills/converted/internal-comms/composable/mini/00-core.md +30 -0
  150. package/assets/skills/converted/internal-comms/composable/mini/01-3p-updates.md +49 -0
  151. package/assets/skills/converted/internal-comms/composable/mini/02-company-newsletter.md +67 -0
  152. package/assets/skills/converted/internal-comms/composable/mini/03-faq-answers.md +32 -0
  153. package/assets/skills/converted/internal-comms/composable/mini/04-general-comms.md +18 -0
  154. package/assets/skills/converted/mcp-builder/composable/BUNDLE.md +2460 -0
  155. package/assets/skills/converted/mcp-builder/composable/INDEX.md +23 -0
  156. package/assets/skills/converted/mcp-builder/composable/VERSION +1 -0
  157. package/assets/skills/converted/mcp-builder/composable/mini/00-core.md +43 -0
  158. package/assets/skills/converted/mcp-builder/composable/mini/01-workflow-research-planning.md +54 -0
  159. package/assets/skills/converted/mcp-builder/composable/mini/02-implementation-process.md +78 -0
  160. package/assets/skills/converted/mcp-builder/composable/mini/03-naming-conventions.md +33 -0
  161. package/assets/skills/converted/mcp-builder/composable/mini/04-response-formats.md +21 -0
  162. package/assets/skills/converted/mcp-builder/composable/mini/05-pagination.md +24 -0
  163. package/assets/skills/converted/mcp-builder/composable/mini/06-transport.md +47 -0
  164. package/assets/skills/converted/mcp-builder/composable/mini/07-annotations.md +20 -0
  165. package/assets/skills/converted/mcp-builder/composable/mini/08-security.md +35 -0
  166. package/assets/skills/converted/mcp-builder/composable/mini/09-error-handling.md +32 -0
  167. package/assets/skills/converted/mcp-builder/composable/mini/10-testing-and-documentation.md +21 -0
  168. package/assets/skills/converted/mcp-builder/composable/mini/11-python-implementation.md +432 -0
  169. package/assets/skills/converted/mcp-builder/composable/mini/12-python-advanced-and-checklist.md +233 -0
  170. package/assets/skills/converted/mcp-builder/composable/mini/13-node-implementation.md +717 -0
  171. package/assets/skills/converted/mcp-builder/composable/mini/14-node-advanced-and-checklist.md +200 -0
  172. package/assets/skills/converted/mcp-builder/composable/mini/15-evaluation-design.md +200 -0
  173. package/assets/skills/converted/mcp-builder/composable/mini/16-evaluation-running.md +198 -0
  174. package/assets/skills/converted/mcp-builder/composable/presets/node-server.md +1381 -0
  175. package/assets/skills/converted/mcp-builder/composable/presets/python-server.md +1129 -0
  176. package/assets/skills/converted/pdf/composable/BUNDLE.md +1284 -0
  177. package/assets/skills/converted/pdf/composable/INDEX.md +14 -0
  178. package/assets/skills/converted/pdf/composable/VERSION +1 -0
  179. package/assets/skills/converted/pdf/composable/mini/00-core.md +46 -0
  180. package/assets/skills/converted/pdf/composable/mini/01-creation.md +176 -0
  181. package/assets/skills/converted/pdf/composable/mini/02-extraction.md +178 -0
  182. package/assets/skills/converted/pdf/composable/mini/03-merge-split-manipulate.md +240 -0
  183. package/assets/skills/converted/pdf/composable/mini/04-images-ocr.md +168 -0
  184. package/assets/skills/converted/pdf/composable/mini/05-encryption.md +51 -0
  185. package/assets/skills/converted/pdf/composable/mini/06-forms.md +300 -0
  186. package/assets/skills/converted/pdf/composable/mini/07-libraries-reference.md +89 -0
  187. package/assets/skills/converted/pptx/composable/BUNDLE.md +911 -0
  188. package/assets/skills/converted/pptx/composable/INDEX.md +12 -0
  189. package/assets/skills/converted/pptx/composable/VERSION +1 -0
  190. package/assets/skills/converted/pptx/composable/mini/00-core.md +37 -0
  191. package/assets/skills/converted/pptx/composable/mini/01-reading.md +37 -0
  192. package/assets/skills/converted/pptx/composable/mini/02-creating-from-scratch.md +422 -0
  193. package/assets/skills/converted/pptx/composable/mini/03-editing-templates.md +207 -0
  194. package/assets/skills/converted/pptx/composable/mini/04-design.md +89 -0
  195. package/assets/skills/converted/pptx/composable/mini/05-qa-and-rendering.md +91 -0
  196. package/assets/skills/converted/pptx/composable/presets/creating.md +659 -0
  197. package/assets/skills/converted/pptx/composable/presets/editing.md +485 -0
  198. package/assets/skills/converted/skill-creator/composable/BUNDLE.md +1681 -0
  199. package/assets/skills/converted/skill-creator/composable/INDEX.md +21 -0
  200. package/assets/skills/converted/skill-creator/composable/VERSION +1 -0
  201. package/assets/skills/converted/skill-creator/composable/mini/00-core.md +59 -0
  202. package/assets/skills/converted/skill-creator/composable/mini/01-creating-a-skill.md +27 -0
  203. package/assets/skills/converted/skill-creator/composable/mini/02-skill-writing-guide.md +71 -0
  204. package/assets/skills/converted/skill-creator/composable/mini/03-test-cases.md +21 -0
  205. package/assets/skills/converted/skill-creator/composable/mini/04-running-and-evaluating.md +128 -0
  206. package/assets/skills/converted/skill-creator/composable/mini/05-improving-the-skill.md +30 -0
  207. package/assets/skills/converted/skill-creator/composable/mini/06-blind-comparison.md +5 -0
  208. package/assets/skills/converted/skill-creator/composable/mini/07-description-optimization.md +74 -0
  209. package/assets/skills/converted/skill-creator/composable/mini/08-packaging.md +11 -0
  210. package/assets/skills/converted/skill-creator/composable/mini/09-environment-specific.md +38 -0
  211. package/assets/skills/converted/skill-creator/composable/mini/10-scripts-and-references.md +34 -0
  212. package/assets/skills/converted/skill-creator/composable/mini/11-json-schemas.md +414 -0
  213. package/assets/skills/converted/skill-creator/composable/mini/12-agent-grader.md +225 -0
  214. package/assets/skills/converted/skill-creator/composable/mini/13-agent-comparator.md +204 -0
  215. package/assets/skills/converted/skill-creator/composable/mini/14-agent-analyzer.md +276 -0
  216. package/assets/skills/converted/skill-creator/composable/presets/authoring.md +198 -0
  217. package/assets/skills/converted/skill-creator/composable/presets/evaluating.md +1334 -0
  218. package/assets/skills/converted/skill-creator/composable/presets/optimizing.md +160 -0
  219. package/assets/skills/converted/skill-creator/composable/presets.json +5 -0
  220. package/assets/skills/meta/ccs-skill-creator/composable/BUNDLE.md +465 -0
  221. package/assets/skills/meta/ccs-skill-creator/composable/INDEX.md +8 -0
  222. package/assets/skills/meta/ccs-skill-creator/composable/VERSION +1 -0
  223. package/assets/skills/meta/ccs-skill-creator/composable/mini/00-core.md +46 -0
  224. package/assets/skills/meta/ccs-skill-creator/composable/mini/01-create-new-skill.md +167 -0
  225. package/assets/skills/meta/ccs-skill-creator/composable/mini/02-convert-existing-skill.md +120 -0
  226. package/assets/skills/meta/ccs-skill-creator/composable/mini/03-review-and-maintain.md +112 -0
  227. package/assets/skills/sources/anthropic/PROVENANCE.md +3 -0
  228. package/assets/skills/sources/anthropic/claude-api/LICENSE.txt +202 -0
  229. package/assets/skills/sources/anthropic/docx/LICENSE.txt +30 -0
  230. package/assets/skills/sources/anthropic/internal-comms/LICENSE.txt +202 -0
  231. package/assets/skills/sources/anthropic/mcp-builder/LICENSE.txt +202 -0
  232. package/assets/skills/sources/anthropic/pdf/LICENSE.txt +30 -0
  233. package/assets/skills/sources/anthropic/pptx/LICENSE.txt +30 -0
  234. package/assets/skills/sources/anthropic/skill-creator/LICENSE.txt +202 -0
  235. package/assets/skills/sources/financial-analyst/PROVENANCE.md +1 -0
  236. package/assets/tools/hive.py +728 -0
  237. package/dist/cli.js +7494 -0
  238. package/package.json +40 -0
@@ -0,0 +1,389 @@
1
+ <!-- GENERATED by tools/hive.py compile. Do not hand-edit. -->
2
+ # python-api: Compiled Skill Bundle (v1.0.0)
3
+
4
+ All composable mini-skills for `python-api` concatenated in index order, frontmatter stripped. Regenerate with `tools/hive.py compile <skill-dir>`. Do not edit by hand.
5
+
6
+ ---
7
+ <!-- module: 01-project-structure.md -->
8
+
9
+ # Project Structure & Hygiene
10
+
11
+ Organize by feature, not by layer, once past a handful of endpoints:
12
+
13
+ ```
14
+ app/
15
+ main.py # create_app(), mount routers, middleware
16
+ core/config.py # Settings (pydantic-settings)
17
+ core/security.py # hashing, JWT helpers
18
+ db/session.py # engine, SessionLocal, get_db dependency
19
+ api/deps.py # shared dependencies
20
+ api/v1/users.py # APIRouter per resource
21
+ models/ # SQLAlchemy ORM models
22
+ schemas/ # Pydantic request/response models
23
+ services/ # business logic, no framework imports
24
+ tests/
25
+ ```
26
+
27
+ Keep business logic out of route handlers. A handler parses input, calls a service function, and shapes the response. Services stay framework-agnostic and unit-testable, which is what makes the codebase maintainable and lets you test logic without spinning up HTTP.
28
+
29
+ Use `pydantic-settings` so config is typed and validated at startup, failing fast on missing vars:
30
+
31
+ ```python
32
+ from pydantic_settings import BaseSettings, SettingsConfigDict
33
+
34
+ class Settings(BaseSettings):
35
+ model_config = SettingsConfigDict(env_file=".env")
36
+ database_url: str
37
+ jwt_secret: str
38
+ jwt_expire_minutes: int = 30
39
+
40
+ settings = Settings() # raises on missing required vars
41
+ ```
42
+
43
+ Hygiene checklist:
44
+ - Pin dependencies (`requirements.txt` with hashes, or `pyproject.toml` + lockfile).
45
+ - Never commit `.env` or secrets; load them from the environment.
46
+ - Run `ruff` (lint/format) and `mypy` (types) in CI.
47
+ - Keep one responsibility per module; avoid mutable global state shared across requests.
48
+ - Version the API under a path prefix (`/api/v1`) so you can evolve it without breaking clients.
49
+
50
+ Layer-first layouts (`controllers/`, `services/`, `models/` each holding every feature) force you to touch many directories per change and scale badly as the app grows; feature-aware grouping keeps related code together.
51
+
52
+ The payoff of this structure: routes stay thin, services are pure and testable, schemas are reusable, and configuration errors surface at boot rather than at request time.
53
+
54
+ ---
55
+ <!-- module: 02-routing-and-app.md -->
56
+
57
+ # App Creation & Routing
58
+
59
+ Prefer an application factory plus one `APIRouter` per resource, versioned under a path prefix:
60
+
61
+ ```python
62
+ from fastapi import FastAPI, APIRouter, Depends
63
+
64
+ def create_app() -> FastAPI:
65
+ app = FastAPI(title="My API", version="1.0.0")
66
+ app.include_router(users.router, prefix="/api/v1")
67
+ return app
68
+
69
+ router = APIRouter(prefix="/users", tags=["users"])
70
+
71
+ @router.post("", response_model=UserOut, status_code=201)
72
+ def create_user(payload: UserCreate, db: Session = Depends(get_db)):
73
+ ...
74
+ ```
75
+
76
+ **Always set `response_model`.** It filters output so you never leak internal fields (like a password hash), documents the schema in OpenAPI, and validates what you return.
77
+
78
+ **Status code discipline:**
79
+ - `201` for creation, `204` for deletes with no body, `200` for normal reads/updates.
80
+ - `400` malformed request, `401` unauthenticated, `403` authenticated-but-forbidden, `404` not found, `409` conflict (e.g. duplicate email), `422` validation, `429` rate limit, `500` unexpected.
81
+
82
+ **Resource naming:** use plural nouns for collections (`/users`, `/users/{id}`), not verbs. Nest sub-resources sparingly (`/users/{id}/orders`). Use path params for identity, query params for filtering/pagination.
83
+
84
+ Group related routes on one router with a shared `prefix` and `tags` (tags drive OpenAPI grouping). Mount routers in the factory rather than decorating a global `app`, so tests can build a fresh app instance and swap dependencies.
85
+
86
+ Keep handlers thin: parse the validated `payload`, call a service, return an object the `response_model` can serialize. Push branching, DB work, and business rules into services. A handler that is more than ~15 lines is usually doing too much: the logic belongs in a framework-agnostic service you can unit-test and reuse from a CLI or worker.
87
+
88
+ Prefer building the app in a factory over decorating a module-level global `app`. The factory lets tests construct a fresh instance and override dependencies per test, and keeps import-time side effects out of your modules.
89
+
90
+ ---
91
+ <!-- module: 03-pydantic-models.md -->
92
+
93
+ # Pydantic Models: Request vs Response
94
+
95
+ Separate input and output models. Never accept your ORM model directly, and never expose it raw.
96
+
97
+ ```python
98
+ from pydantic import BaseModel, EmailStr, Field
99
+
100
+ class UserCreate(BaseModel):
101
+ email: EmailStr
102
+ password: str = Field(min_length=8, max_length=128)
103
+ full_name: str = Field(min_length=1, max_length=200)
104
+
105
+ class UserOut(BaseModel):
106
+ model_config = {"from_attributes": True} # read from ORM objects
107
+ id: int
108
+ email: EmailStr
109
+ full_name: str
110
+ ```
111
+
112
+ Key patterns:
113
+ - `from_attributes=True` (Pydantic v2) lets `response_model` read ORM instances directly, so you can `return user` and get a filtered `UserOut`.
114
+ - Use rich types (`EmailStr`, `HttpUrl`, `conint`, `Field(gt=0)`) so validation is declarative and lives in the schema.
115
+ - **Constrain everything:** string lengths, numeric bounds, list sizes. Unbounded input is a DoS and data-quality risk.
116
+ - The input/output split is also your security boundary: a field that only exists on `UserCreate` (password) can never accidentally appear in `UserOut`.
117
+
118
+ **Partial updates (PATCH):** make every field `Optional` with a default, then apply only what the client sent:
119
+
120
+ ```python
121
+ class UserUpdate(BaseModel):
122
+ email: EmailStr | None = None
123
+ full_name: str | None = Field(default=None, min_length=1, max_length=200)
124
+
125
+ data = payload.model_dump(exclude_unset=True) # only fields the client provided
126
+ for k, v in data.items():
127
+ setattr(user, k, v)
128
+ ```
129
+
130
+ `exclude_unset=True` distinguishes "field omitted" from "field set to null", which is essential for correct PATCH semantics. For nested structures, define nested models rather than accepting free-form `dict`, so each level is validated.
131
+
132
+ Why the strict split matters in practice: your ORM model is a database concern that changes for storage reasons; your response model is an API contract clients depend on. Coupling them means a schema migration can silently change your public API or leak a newly-added internal column. Keeping `UserCreate`, `UserUpdate`, and `UserOut` distinct from the ORM `User` gives you three independent contracts you can evolve safely.
133
+
134
+ ---
135
+ <!-- module: 04-validation-and-errors.md -->
136
+
137
+ # Validation & Error Semantics
138
+
139
+ FastAPI auto-returns `422` with a structured body for schema violations, so don't fight it. Add cross-field checks with validators.
140
+
141
+ **Field validator** (single field):
142
+ ```python
143
+ from pydantic import field_validator
144
+
145
+ @field_validator("password")
146
+ @classmethod
147
+ def strength(cls, v: str) -> str:
148
+ if v.isalnum():
149
+ raise ValueError("password needs a symbol")
150
+ return v
151
+ ```
152
+
153
+ **Model validator** (cross-field):
154
+ ```python
155
+ from pydantic import model_validator
156
+
157
+ class DateRange(BaseModel):
158
+ start: date
159
+ end: date
160
+
161
+ @model_validator(mode="after")
162
+ def check_order(self):
163
+ if self.start > self.end:
164
+ raise ValueError("start must be <= end")
165
+ return self
166
+ ```
167
+
168
+ **Business errors** use `HTTPException` with the right status and a clear detail:
169
+
170
+ ```python
171
+ user = db.get(User, user_id)
172
+ if user is None:
173
+ raise HTTPException(status_code=404, detail="User not found")
174
+ ```
175
+
176
+ **Status discipline:** `400` malformed, `401` unauthenticated, `403` forbidden, `404` missing, `409` conflict, `422` validation, `429` rate-limited, `500` unexpected.
177
+
178
+ **Consistent error shape + safe fallback.** Add a handler so uncaught errors are logged server-side and clients never see a stack trace:
179
+
180
+ ```python
181
+ @app.exception_handler(Exception)
182
+ async def unhandled(request, exc):
183
+ logger.exception("unhandled error")
184
+ return JSONResponse(status_code=500, content={"detail": "Internal error"})
185
+ ```
186
+
187
+ Catch integrity errors at the boundary and translate them (`IntegrityError` → `409`) rather than letting them 500. Never swallow exceptions silently: either handle them meaningfully or let them propagate to the logging handler. Keep `detail` messages user-facing and free of internal identifiers, SQL, or secrets.
188
+
189
+ Prefer letting FastAPI/Pydantic reject malformed input with its automatic `422` over hand-writing validation in the handler, since declarative constraints on the schema are less error-prone and self-document in OpenAPI. Reserve `HTTPException` for business-rule failures the schema can't express (resource not found, permission denied, duplicate). Return the same error envelope everywhere so clients can parse failures uniformly.
190
+
191
+ ---
192
+ <!-- module: 05-dependencies-and-auth.md -->
193
+
194
+ # Dependencies, Authentication & Authorization
195
+
196
+ Dependencies are FastAPI's core reuse mechanism: DB sessions, auth, pagination. Use a generator dependency for resources needing teardown:
197
+
198
+ ```python
199
+ def get_db():
200
+ db = SessionLocal()
201
+ try:
202
+ yield db
203
+ finally:
204
+ db.close()
205
+ ```
206
+
207
+ `Depends` results are cached within a request, so `get_current_user` runs once even if several dependencies need it. Compose dependencies to build up context.
208
+
209
+ Scope the session to the request (one per request via the generator), and manage transactions explicitly: commit once at the end of a successful unit of work and roll back on error, so a partial multi-step mutation never persists. Don't commit after each step, and never share a session across requests in a module-level global: it isn't thread-safe and leaks state between callers. The `finally` block closes it on every path.
210
+
211
+ **Auth: OAuth2 password flow + JWT bearer tokens.** Hash passwords with bcrypt/argon2 via `passlib`: never plaintext, never a fast hash like SHA-256.
212
+
213
+ ```python
214
+ from passlib.context import CryptContext
215
+ from jose import jwt, JWTError
216
+
217
+ pwd = CryptContext(schemes=["bcrypt"], deprecated="auto")
218
+
219
+ def make_token(sub: str) -> str:
220
+ exp = datetime.utcnow() + timedelta(minutes=settings.jwt_expire_minutes)
221
+ return jwt.encode({"sub": sub, "exp": exp}, settings.jwt_secret, algorithm="HS256")
222
+ ```
223
+
224
+ Current-user dependency decodes, validates, and loads the user:
225
+
226
+ ```python
227
+ oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
228
+
229
+ def get_current_user(token: str = Depends(oauth2), db: Session = Depends(get_db)) -> User:
230
+ exc = HTTPException(401, "Invalid credentials", {"WWW-Authenticate": "Bearer"})
231
+ try:
232
+ payload = jwt.decode(token, settings.jwt_secret, algorithms=["HS256"])
233
+ except JWTError:
234
+ raise exc
235
+ user = db.get(User, int(payload["sub"]))
236
+ if user is None:
237
+ raise exc
238
+ return user
239
+ ```
240
+
241
+ Rules:
242
+ - **Always pass `algorithms=[...]` on decode**: omitting it enables `alg=none` token forgery.
243
+ - Keep tokens short-lived; pair with refresh tokens.
244
+ - **Authentication ≠ authorization.** Enforce ownership/roles in handlers or dependencies. Write a factory `require_role("admin")` returning a dependency that checks `current_user` and raises `403`; don't rely on authentication alone. The classic bug is an endpoint that verifies the caller is logged in but never checks the requested object belongs to them.
245
+
246
+ Dependency injection is also what makes the app testable: in tests you override `get_db` with a test session and `get_current_user` with a fake user, exercising real routes without real auth or a real database.
247
+
248
+ ---
249
+ <!-- module: 06-pagination-filtering.md -->
250
+
251
+ # Pagination, Filtering & Sorting
252
+
253
+ Never return an unbounded collection. Offer limit/offset for simple cases, keyset (cursor) for large or fast-changing data.
254
+
255
+ **Limit/offset:**
256
+ ```python
257
+ class Page(BaseModel):
258
+ limit: int = Field(20, ge=1, le=100)
259
+ offset: int = Field(0, ge=0)
260
+
261
+ @router.get("", response_model=PageOut[UserOut])
262
+ def list_users(p: Page = Depends(), db: Session = Depends(get_db)):
263
+ q = select(User).order_by(User.id)
264
+ total = db.scalar(select(func.count()).select_from(User))
265
+ rows = db.scalars(q.limit(p.limit).offset(p.offset)).all()
266
+ return {"items": rows, "total": total, "limit": p.limit, "offset": p.offset}
267
+ ```
268
+
269
+ Note the `le=100` cap: clients can't request unbounded pages.
270
+
271
+ **Keyset pagination** avoids the deep-offset performance cliff (offset scans and discards skipped rows): `WHERE id > :cursor ORDER BY id LIMIT :n`, returning the last id as the next cursor. Use it for large tables and infinite scroll.
272
+
273
+ **Deterministic ordering is mandatory.** Always apply an `ORDER BY` with a unique tiebreaker (e.g. `ORDER BY created_at, id`). Without it, rows can shift between pages and clients see duplicates or gaps.
274
+
275
+ **Safe filtering & sorting** (the security-critical part):
276
+ - Expose an explicit allowlist of filter fields as typed query params. Never build SQL from raw client strings and never `eval` a filter expression.
277
+ - Map sort keys through a whitelist dict to columns:
278
+
279
+ ```python
280
+ SORTABLE = {"email": User.email, "created": User.created_at}
281
+ col = SORTABLE.get(sort_key)
282
+ if col is None:
283
+ raise HTTPException(400, "invalid sort field")
284
+ q = q.order_by(col.desc() if descending else col.asc())
285
+ ```
286
+
287
+ This prevents SQL injection and stops clients sorting by or filtering on private columns. Always use parameterized queries / the ORM, never string interpolation.
288
+
289
+ **Response metadata.** Return enough for the client to navigate: the items plus `limit`/`offset` (or a `next_cursor`), and a `total` only when it's cheap: a `COUNT(*)` over a huge filtered table can cost as much as the page query itself, so for large datasets prefer cursor pagination with a `has_more` flag and omit the total. Cap the maximum page size server-side (`le=100`) regardless of what the client asks for, so no single request can pull the whole table.
290
+
291
+ ---
292
+ <!-- module: 07-async-performance.md -->
293
+
294
+ # Async, Performance & the Event Loop
295
+
296
+ FastAPI runs `async def` handlers on the event loop and plain `def` handlers in a threadpool. Understanding this split is essential for performance.
297
+
298
+ **The cardinal sin:** calling blocking I/O inside an `async def` handler (a sync DB driver, `requests`, `time.sleep`, or any CPU-heavy loop). It stalls the entire event loop, so every concurrent request stalls with it. Fixes:
299
+ - Make the handler plain `def` (FastAPI runs it in a threadpool), **or**
300
+ - Use async libraries: `httpx.AsyncClient` instead of `requests`, async SQLAlchemy / async drivers, `asyncio.sleep` instead of `time.sleep`.
301
+
302
+ Don't mix them: a `def` handler calling async code, or an `async def` handler calling blocking code, are both wrong.
303
+
304
+ **Database performance:**
305
+ - Use connection pooling (configure pool size to match worker count).
306
+ - Prevent N+1 queries with eager loading (`selectinload`, `joinedload`) when you access relationships in a loop.
307
+ - Push filtering/aggregation into SQL rather than loading rows and processing in Python.
308
+
309
+ **Outbound calls:**
310
+ - Reuse a single HTTP client with connection pooling; don't create a client per request.
311
+ - **Set timeouts on every outbound call**: a hung dependency without a timeout ties up a worker indefinitely.
312
+
313
+ **Caching:** add caching for hot read paths, using HTTP `ETag`/`Cache-Control` headers for client/proxy caching, or Redis for shared server-side caching. Cache computed or expensive-to-fetch data with a sensible TTL and clear invalidation rules.
314
+
315
+ **General:** avoid mutable default arguments and shared global mutable state across requests (each request should get its own state). Profile before optimizing; most latency is I/O (DB and downstream calls), not Python CPU.
316
+
317
+ **Production concurrency model:** run multiple worker processes (`uvicorn`/`gunicorn`, sized roughly to cores for CPU-bound apps, higher for I/O-bound) behind a process manager. Each worker has its own event loop; scaling out processes is how you use multiple cores, since one Python process is GIL-bound for CPU work. Offload genuinely heavy or long-running work (report generation, image processing, third-party fan-out) to a background task queue (Celery, RQ, `arq`) and return `202 Accepted` with a status URL, rather than blocking a request worker for seconds.
318
+
319
+ ---
320
+ <!-- module: 08-testing.md -->
321
+
322
+ # Testing with pytest + TestClient
323
+
324
+ Test through the API with `TestClient` (sync) or `httpx.AsyncClient` (async). Override dependencies to inject a test DB and fake auth: this is the payoff of dependency injection.
325
+
326
+ ```python
327
+ from fastapi.testclient import TestClient
328
+
329
+ @pytest.fixture
330
+ def client():
331
+ app.dependency_overrides[get_db] = lambda: test_session
332
+ with TestClient(app) as c:
333
+ yield c
334
+ app.dependency_overrides.clear()
335
+
336
+ def test_create_user(client):
337
+ r = client.post("/api/v1/users", json={"email": "a@b.com",
338
+ "password": "s3cret!!", "full_name": "A"})
339
+ assert r.status_code == 201
340
+ assert "password" not in r.json() # sensitive field must not leak
341
+
342
+ def test_rejects_short_password(client):
343
+ r = client.post("/api/v1/users", json={"email": "a@b.com",
344
+ "password": "x", "full_name": "A"})
345
+ assert r.status_code == 422
346
+ ```
347
+
348
+ **Test the contract, not the implementation:**
349
+ - Status codes for success and each error path.
350
+ - Response shape, and that sensitive fields (password hashes, internal ids) are absent.
351
+ - Auth enforced: unauthenticated returns `401`, wrong-user/role returns `403`.
352
+ - Validation rejects bad input (`422`); pagination bounds hold (`limit` cap respected).
353
+
354
+ **Isolation & data:**
355
+ - Use a fresh transactional DB per test: create schema then wrap each test in a rollback, or use a disposable SQLite / testcontainers Postgres.
356
+ - `pytest.fixture` for setup/teardown; `parametrize` for edge cases.
357
+ - Build test data with factories (`factory_boy` or plain builder functions), not copy-pasted dicts.
358
+
359
+ Keep tests fast and isolated. Mock only external services (payment gateways, third-party APIs); never mock your own code, or you test the mock instead of the behavior. A test that passes against a mock of your service proves nothing about the service.
360
+
361
+ Cover the full contract deliberately: a happy-path test per endpoint, plus one test per error branch (404, 409, 422), an auth test (401 unauthenticated, 403 wrong role/owner), and a pagination test asserting the `limit` cap is enforced. Prefer many small focused tests over a few sprawling ones: when a focused test fails, its name tells you what broke.
362
+
363
+ ---
364
+ <!-- module: 09-middleware-observability.md -->
365
+
366
+ # Middleware, CORS, Observability & Rate Limiting
367
+
368
+ Cross-cutting concerns belong in middleware and app-level config, not scattered through individual handlers.
369
+
370
+ **CORS.** If a browser front-end on another origin calls the API, configure `CORSMiddleware` with an explicit allowlist of origins. Never combine `allow_origins=["*"]` with `allow_credentials=True`: it is both insecure and rejected by browsers. List the exact front-end origins, methods, and headers you actually need.
371
+
372
+ ```python
373
+ from fastapi.middleware.cors import CORSMiddleware
374
+ app.add_middleware(
375
+ CORSMiddleware,
376
+ allow_origins=settings.cors_origins,
377
+ allow_credentials=True,
378
+ allow_methods=["*"],
379
+ allow_headers=["*"],
380
+ )
381
+ ```
382
+
383
+ **Request logging & correlation.** Add middleware that assigns each request a correlation id (from an incoming `X-Request-ID` header or a freshly generated UUID), binds it to the logger, and logs method, path, status code, and duration. Structured JSON logs make a request traceable across services. Never log request bodies that may carry secrets or PII.
384
+
385
+ **Rate limiting.** Protect auth and expensive endpoints with rate limits (a library like `slowapi`, or an upstream API gateway). Limit by client identity or IP, return `429` with a `Retry-After` header when a caller exceeds the limit, and apply stricter limits to login/token endpoints to blunt credential-stuffing attacks.
386
+
387
+ **Health & readiness.** Expose a lightweight `/health` (the process is up) and a `/ready` (dependencies are reachable) endpoint for orchestrators and load balancers. Keep `/health` dependency-free so a slow database doesn't make the instance look dead and get killed. Emit basic metrics (request count, latency, error rate) for monitoring dashboards and alerts.
388
+
389
+ **Timeouts everywhere.** Set request and outbound-call timeouts so a slow client or hung downstream can't hold a worker indefinitely. Combined with rate limiting, this is your first line of defense against both accidental overload and abuse.
@@ -0,0 +1,13 @@
1
+ # Python REST API (FastAPI): Mini-Skill Index
2
+
3
+ Loading policy: read this menu, then load 00-core (if present) plus the minis relevant to your task. If most of this skill is relevant, load BUNDLE.md (or a matching presets/*.md) in one read instead.
4
+
5
+ - `01-project-structure.md` - Layout, settings, hygiene. Load when scaffolding a project or organizing modules/config.
6
+ - `02-routing-and-app.md` - App factory, APIRouter, status codes, resource naming. Load when defining endpoints or wiring routers.
7
+ - `03-pydantic-models.md` - Request/response models, from_attributes, PATCH. Load when designing schemas or shaping I/O.
8
+ - `04-validation-and-errors.md` - Field/model validators, HTTPException, status-code discipline, handlers. Load when handling bad input or errors.
9
+ - `05-dependencies-and-auth.md` - Depends, DB session, OAuth2/JWT, password hashing, roles. Load when adding auth or injection.
10
+ - `06-pagination-filtering.md` - Limit/offset, keyset cursors, safe filtering/sorting. Load when listing collections.
11
+ - `07-async-performance.md` - Event loop, blocking I/O, N+1, caching, timeouts. Load when tuning or writing async handlers.
12
+ - `08-testing.md` - TestClient, dependency overrides, fixtures, contract tests. Load when writing API tests.
13
+ - `09-middleware-observability.md` - CORS, request logging, rate limiting, health checks, timeouts. Load when wiring cross-cutting concerns or hardening for production.
@@ -0,0 +1,44 @@
1
+ # Project Structure & Hygiene
2
+
3
+ Organize by feature, not by layer, once past a handful of endpoints:
4
+
5
+ ```
6
+ app/
7
+ main.py # create_app(), mount routers, middleware
8
+ core/config.py # Settings (pydantic-settings)
9
+ core/security.py # hashing, JWT helpers
10
+ db/session.py # engine, SessionLocal, get_db dependency
11
+ api/deps.py # shared dependencies
12
+ api/v1/users.py # APIRouter per resource
13
+ models/ # SQLAlchemy ORM models
14
+ schemas/ # Pydantic request/response models
15
+ services/ # business logic, no framework imports
16
+ tests/
17
+ ```
18
+
19
+ Keep business logic out of route handlers. A handler parses input, calls a service function, and shapes the response. Services stay framework-agnostic and unit-testable, which is what makes the codebase maintainable and lets you test logic without spinning up HTTP.
20
+
21
+ Use `pydantic-settings` so config is typed and validated at startup, failing fast on missing vars:
22
+
23
+ ```python
24
+ from pydantic_settings import BaseSettings, SettingsConfigDict
25
+
26
+ class Settings(BaseSettings):
27
+ model_config = SettingsConfigDict(env_file=".env")
28
+ database_url: str
29
+ jwt_secret: str
30
+ jwt_expire_minutes: int = 30
31
+
32
+ settings = Settings() # raises on missing required vars
33
+ ```
34
+
35
+ Hygiene checklist:
36
+ - Pin dependencies (`requirements.txt` with hashes, or `pyproject.toml` + lockfile).
37
+ - Never commit `.env` or secrets; load them from the environment.
38
+ - Run `ruff` (lint/format) and `mypy` (types) in CI.
39
+ - Keep one responsibility per module; avoid mutable global state shared across requests.
40
+ - Version the API under a path prefix (`/api/v1`) so you can evolve it without breaking clients.
41
+
42
+ Layer-first layouts (`controllers/`, `services/`, `models/` each holding every feature) force you to touch many directories per change and scale badly as the app grows; feature-aware grouping keeps related code together.
43
+
44
+ The payoff of this structure: routes stay thin, services are pure and testable, schemas are reusable, and configuration errors surface at boot rather than at request time.
@@ -0,0 +1,38 @@
1
+ ---
2
+ pairs-with:
3
+ - 03-pydantic-models.md
4
+ - 04-validation-and-errors.md
5
+ ---
6
+
7
+ # App Creation & Routing
8
+
9
+ Prefer an application factory plus one `APIRouter` per resource, versioned under a path prefix:
10
+
11
+ ```python
12
+ from fastapi import FastAPI, APIRouter, Depends
13
+
14
+ def create_app() -> FastAPI:
15
+ app = FastAPI(title="My API", version="1.0.0")
16
+ app.include_router(users.router, prefix="/api/v1")
17
+ return app
18
+
19
+ router = APIRouter(prefix="/users", tags=["users"])
20
+
21
+ @router.post("", response_model=UserOut, status_code=201)
22
+ def create_user(payload: UserCreate, db: Session = Depends(get_db)):
23
+ ...
24
+ ```
25
+
26
+ **Always set `response_model`.** It filters output so you never leak internal fields (like a password hash), documents the schema in OpenAPI, and validates what you return.
27
+
28
+ **Status code discipline:**
29
+ - `201` for creation, `204` for deletes with no body, `200` for normal reads/updates.
30
+ - `400` malformed request, `401` unauthenticated, `403` authenticated-but-forbidden, `404` not found, `409` conflict (e.g. duplicate email), `422` validation, `429` rate limit, `500` unexpected.
31
+
32
+ **Resource naming:** use plural nouns for collections (`/users`, `/users/{id}`), not verbs. Nest sub-resources sparingly (`/users/{id}/orders`). Use path params for identity, query params for filtering/pagination.
33
+
34
+ Group related routes on one router with a shared `prefix` and `tags` (tags drive OpenAPI grouping). Mount routers in the factory rather than decorating a global `app`, so tests can build a fresh app instance and swap dependencies.
35
+
36
+ Keep handlers thin: parse the validated `payload`, call a service, return an object the `response_model` can serialize. Push branching, DB work, and business rules into services. A handler that is more than ~15 lines is usually doing too much: the logic belongs in a framework-agnostic service you can unit-test and reuse from a CLI or worker.
37
+
38
+ Prefer building the app in a factory over decorating a module-level global `app`. The factory lets tests construct a fresh instance and override dependencies per test, and keeps import-time side effects out of your modules.
@@ -0,0 +1,45 @@
1
+ ---
2
+ pairs-with:
3
+ - 04-validation-and-errors.md
4
+ ---
5
+
6
+ # Pydantic Models: Request vs Response
7
+
8
+ Separate input and output models. Never accept your ORM model directly, and never expose it raw.
9
+
10
+ ```python
11
+ from pydantic import BaseModel, EmailStr, Field
12
+
13
+ class UserCreate(BaseModel):
14
+ email: EmailStr
15
+ password: str = Field(min_length=8, max_length=128)
16
+ full_name: str = Field(min_length=1, max_length=200)
17
+
18
+ class UserOut(BaseModel):
19
+ model_config = {"from_attributes": True} # read from ORM objects
20
+ id: int
21
+ email: EmailStr
22
+ full_name: str
23
+ ```
24
+
25
+ Key patterns:
26
+ - `from_attributes=True` (Pydantic v2) lets `response_model` read ORM instances directly, so you can `return user` and get a filtered `UserOut`.
27
+ - Use rich types (`EmailStr`, `HttpUrl`, `conint`, `Field(gt=0)`) so validation is declarative and lives in the schema.
28
+ - **Constrain everything:** string lengths, numeric bounds, list sizes. Unbounded input is a DoS and data-quality risk.
29
+ - The input/output split is also your security boundary: a field that only exists on `UserCreate` (password) can never accidentally appear in `UserOut`.
30
+
31
+ **Partial updates (PATCH):** make every field `Optional` with a default, then apply only what the client sent:
32
+
33
+ ```python
34
+ class UserUpdate(BaseModel):
35
+ email: EmailStr | None = None
36
+ full_name: str | None = Field(default=None, min_length=1, max_length=200)
37
+
38
+ data = payload.model_dump(exclude_unset=True) # only fields the client provided
39
+ for k, v in data.items():
40
+ setattr(user, k, v)
41
+ ```
42
+
43
+ `exclude_unset=True` distinguishes "field omitted" from "field set to null", which is essential for correct PATCH semantics. For nested structures, define nested models rather than accepting free-form `dict`, so each level is validated.
44
+
45
+ Why the strict split matters in practice: your ORM model is a database concern that changes for storage reasons; your response model is an API contract clients depend on. Coupling them means a schema migration can silently change your public API or leak a newly-added internal column. Keeping `UserCreate`, `UserUpdate`, and `UserOut` distinct from the ORM `User` gives you three independent contracts you can evolve safely.
@@ -0,0 +1,59 @@
1
+ ---
2
+ pairs-with:
3
+ - 03-pydantic-models.md
4
+ - code-review/05-error-handling.md
5
+ ---
6
+
7
+ # Validation & Error Semantics
8
+
9
+ FastAPI auto-returns `422` with a structured body for schema violations, so don't fight it. Add cross-field checks with validators.
10
+
11
+ **Field validator** (single field):
12
+ ```python
13
+ from pydantic import field_validator
14
+
15
+ @field_validator("password")
16
+ @classmethod
17
+ def strength(cls, v: str) -> str:
18
+ if v.isalnum():
19
+ raise ValueError("password needs a symbol")
20
+ return v
21
+ ```
22
+
23
+ **Model validator** (cross-field):
24
+ ```python
25
+ from pydantic import model_validator
26
+
27
+ class DateRange(BaseModel):
28
+ start: date
29
+ end: date
30
+
31
+ @model_validator(mode="after")
32
+ def check_order(self):
33
+ if self.start > self.end:
34
+ raise ValueError("start must be <= end")
35
+ return self
36
+ ```
37
+
38
+ **Business errors** use `HTTPException` with the right status and a clear detail:
39
+
40
+ ```python
41
+ user = db.get(User, user_id)
42
+ if user is None:
43
+ raise HTTPException(status_code=404, detail="User not found")
44
+ ```
45
+
46
+ **Status discipline:** `400` malformed, `401` unauthenticated, `403` forbidden, `404` missing, `409` conflict, `422` validation, `429` rate-limited, `500` unexpected.
47
+
48
+ **Consistent error shape + safe fallback.** Add a handler so uncaught errors are logged server-side and clients never see a stack trace:
49
+
50
+ ```python
51
+ @app.exception_handler(Exception)
52
+ async def unhandled(request, exc):
53
+ logger.exception("unhandled error")
54
+ return JSONResponse(status_code=500, content={"detail": "Internal error"})
55
+ ```
56
+
57
+ Catch integrity errors at the boundary and translate them (`IntegrityError` → `409`) rather than letting them 500. Never swallow exceptions silently: either handle them meaningfully or let them propagate to the logging handler. Keep `detail` messages user-facing and free of internal identifiers, SQL, or secrets.
58
+
59
+ Prefer letting FastAPI/Pydantic reject malformed input with its automatic `422` over hand-writing validation in the handler, since declarative constraints on the schema are less error-prone and self-document in OpenAPI. Reserve `HTTPException` for business-rule failures the schema can't express (resource not found, permission denied, duplicate). Return the same error envelope everywhere so clients can parse failures uniformly.
@@ -0,0 +1,60 @@
1
+ ---
2
+ requires:
3
+ - code-review/02-security-review.md
4
+ pairs-with:
5
+ - 08-testing.md
6
+ ---
7
+
8
+ # Dependencies, Authentication & Authorization
9
+
10
+ Dependencies are FastAPI's core reuse mechanism: DB sessions, auth, pagination. Use a generator dependency for resources needing teardown:
11
+
12
+ ```python
13
+ def get_db():
14
+ db = SessionLocal()
15
+ try:
16
+ yield db
17
+ finally:
18
+ db.close()
19
+ ```
20
+
21
+ `Depends` results are cached within a request, so `get_current_user` runs once even if several dependencies need it. Compose dependencies to build up context.
22
+
23
+ Scope the session to the request (one per request via the generator), and manage transactions explicitly: commit once at the end of a successful unit of work and roll back on error, so a partial multi-step mutation never persists. Don't commit after each step, and never share a session across requests in a module-level global: it isn't thread-safe and leaks state between callers. The `finally` block closes it on every path.
24
+
25
+ **Auth: OAuth2 password flow + JWT bearer tokens.** Hash passwords with bcrypt/argon2 via `passlib`: never plaintext, never a fast hash like SHA-256.
26
+
27
+ ```python
28
+ from passlib.context import CryptContext
29
+ from jose import jwt, JWTError
30
+
31
+ pwd = CryptContext(schemes=["bcrypt"], deprecated="auto")
32
+
33
+ def make_token(sub: str) -> str:
34
+ exp = datetime.utcnow() + timedelta(minutes=settings.jwt_expire_minutes)
35
+ return jwt.encode({"sub": sub, "exp": exp}, settings.jwt_secret, algorithm="HS256")
36
+ ```
37
+
38
+ Current-user dependency decodes, validates, and loads the user:
39
+
40
+ ```python
41
+ oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
42
+
43
+ def get_current_user(token: str = Depends(oauth2), db: Session = Depends(get_db)) -> User:
44
+ exc = HTTPException(401, "Invalid credentials", {"WWW-Authenticate": "Bearer"})
45
+ try:
46
+ payload = jwt.decode(token, settings.jwt_secret, algorithms=["HS256"])
47
+ except JWTError:
48
+ raise exc
49
+ user = db.get(User, int(payload["sub"]))
50
+ if user is None:
51
+ raise exc
52
+ return user
53
+ ```
54
+
55
+ Rules:
56
+ - **Always pass `algorithms=[...]` on decode**: omitting it enables `alg=none` token forgery.
57
+ - Keep tokens short-lived; pair with refresh tokens.
58
+ - **Authentication ≠ authorization.** Enforce ownership/roles in handlers or dependencies. Write a factory `require_role("admin")` returning a dependency that checks `current_user` and raises `403`; don't rely on authentication alone. The classic bug is an endpoint that verifies the caller is logged in but never checks the requested object belongs to them.
59
+
60
+ Dependency injection is also what makes the app testable: in tests you override `get_db` with a test session and `get_current_user` with a fake user, exercising real routes without real auth or a real database.