@jaimevalasek/aioson 1.3.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 (288) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/CODE_OF_CONDUCT.md +12 -0
  3. package/CONTRIBUTING.md +13 -0
  4. package/LICENSE +21 -0
  5. package/README.md +254 -0
  6. package/bin/aioson.js +4 -0
  7. package/docs/en/cli-reference.md +398 -0
  8. package/docs/en/i18n.md +52 -0
  9. package/docs/en/json-schemas.md +41 -0
  10. package/docs/en/mcp.md +56 -0
  11. package/docs/en/parallel.md +82 -0
  12. package/docs/en/qa-browser.md +339 -0
  13. package/docs/en/release-flow.md +22 -0
  14. package/docs/en/release-notes-template.md +41 -0
  15. package/docs/en/release.md +28 -0
  16. package/docs/en/schemas/agent-prompt.schema.json +17 -0
  17. package/docs/en/schemas/agents.schema.json +32 -0
  18. package/docs/en/schemas/context-validate.schema.json +36 -0
  19. package/docs/en/schemas/doctor.schema.json +89 -0
  20. package/docs/en/schemas/error.schema.json +24 -0
  21. package/docs/en/schemas/i18n-add.schema.json +15 -0
  22. package/docs/en/schemas/index.json +116 -0
  23. package/docs/en/schemas/info.schema.json +39 -0
  24. package/docs/en/schemas/init.schema.json +48 -0
  25. package/docs/en/schemas/install.schema.json +60 -0
  26. package/docs/en/schemas/locale-apply.schema.json +30 -0
  27. package/docs/en/schemas/mcp-doctor.schema.json +95 -0
  28. package/docs/en/schemas/mcp-init.schema.json +122 -0
  29. package/docs/en/schemas/package-test.schema.json +24 -0
  30. package/docs/en/schemas/parallel-assign.schema.json +57 -0
  31. package/docs/en/schemas/parallel-doctor.schema.json +86 -0
  32. package/docs/en/schemas/parallel-init.schema.json +53 -0
  33. package/docs/en/schemas/parallel-status.schema.json +94 -0
  34. package/docs/en/schemas/setup-context.schema.json +39 -0
  35. package/docs/en/schemas/smoke.schema.json +23 -0
  36. package/docs/en/schemas/update.schema.json +48 -0
  37. package/docs/en/schemas/workflow-plan.schema.json +30 -0
  38. package/docs/en/web3.md +54 -0
  39. package/docs/pt/README.md +46 -0
  40. package/docs/pt/advisor-spec.md +335 -0
  41. package/docs/pt/agentes.md +453 -0
  42. package/docs/pt/cenarios.md +1230 -0
  43. package/docs/pt/clientes-ai.md +224 -0
  44. package/docs/pt/comandos-cli.md +511 -0
  45. package/docs/pt/genome-3.0-spec.md +296 -0
  46. package/docs/pt/guia-engineer.md +226 -0
  47. package/docs/pt/inicio-rapido.md +138 -0
  48. package/docs/pt/profiler-system.md +214 -0
  49. package/docs/pt/runtime-observability.md +72 -0
  50. package/docs/pt/squad-genoma.md +777 -0
  51. package/docs/pt/web3.md +797 -0
  52. package/docs/testing/genome-2.0-manual-regression.md +23 -0
  53. package/docs/testing/genome-2.0-matrix.md +36 -0
  54. package/docs/testing/genome-2.0-rollout.md +184 -0
  55. package/package.json +50 -0
  56. package/src/agents.js +56 -0
  57. package/src/cli.js +497 -0
  58. package/src/commands/agents.js +142 -0
  59. package/src/commands/cloud.js +1767 -0
  60. package/src/commands/config.js +90 -0
  61. package/src/commands/context-validate.js +91 -0
  62. package/src/commands/doctor.js +123 -0
  63. package/src/commands/genome-doctor.js +41 -0
  64. package/src/commands/genome-migrate.js +49 -0
  65. package/src/commands/i18n-add.js +56 -0
  66. package/src/commands/info.js +41 -0
  67. package/src/commands/init.js +75 -0
  68. package/src/commands/install.js +68 -0
  69. package/src/commands/locale-apply.js +51 -0
  70. package/src/commands/locale-diff.js +126 -0
  71. package/src/commands/mcp-doctor.js +406 -0
  72. package/src/commands/mcp-init.js +379 -0
  73. package/src/commands/package-e2e.js +273 -0
  74. package/src/commands/parallel-assign.js +403 -0
  75. package/src/commands/parallel-doctor.js +437 -0
  76. package/src/commands/parallel-init.js +249 -0
  77. package/src/commands/parallel-status.js +290 -0
  78. package/src/commands/qa-doctor.js +185 -0
  79. package/src/commands/qa-init.js +161 -0
  80. package/src/commands/qa-report.js +58 -0
  81. package/src/commands/qa-run.js +873 -0
  82. package/src/commands/qa-scan.js +337 -0
  83. package/src/commands/runtime.js +948 -0
  84. package/src/commands/scan-project.js +1107 -0
  85. package/src/commands/setup-context.js +650 -0
  86. package/src/commands/smoke.js +426 -0
  87. package/src/commands/squad-doctor.js +358 -0
  88. package/src/commands/squad-export.js +46 -0
  89. package/src/commands/squad-pipeline.js +97 -0
  90. package/src/commands/squad-repair-genomes.js +39 -0
  91. package/src/commands/squad-status.js +424 -0
  92. package/src/commands/squad-validate.js +230 -0
  93. package/src/commands/test-agents.js +194 -0
  94. package/src/commands/update.js +55 -0
  95. package/src/commands/workflow-next.js +594 -0
  96. package/src/commands/workflow-plan.js +108 -0
  97. package/src/constants.js +314 -0
  98. package/src/context-parse-reason.js +22 -0
  99. package/src/context-writer.js +150 -0
  100. package/src/context.js +217 -0
  101. package/src/detector.js +261 -0
  102. package/src/doctor.js +289 -0
  103. package/src/execution-gateway.js +461 -0
  104. package/src/genome-files.js +198 -0
  105. package/src/genome-format.js +442 -0
  106. package/src/genome-schema.js +215 -0
  107. package/src/genomes/bindings.js +281 -0
  108. package/src/genomes.js +467 -0
  109. package/src/i18n/index.js +103 -0
  110. package/src/i18n/messages/en.js +784 -0
  111. package/src/i18n/messages/es.js +718 -0
  112. package/src/i18n/messages/fr.js +725 -0
  113. package/src/i18n/messages/pt-BR.js +818 -0
  114. package/src/i18n/scaffold.js +64 -0
  115. package/src/installer.js +232 -0
  116. package/src/lib/genomes/compat.js +206 -0
  117. package/src/lib/genomes/migrate.js +90 -0
  118. package/src/lib/squads/genome-repair.js +49 -0
  119. package/src/locales.js +84 -0
  120. package/src/onboarding.js +305 -0
  121. package/src/parser.js +53 -0
  122. package/src/prompt-tool.js +20 -0
  123. package/src/qa-html-report.js +472 -0
  124. package/src/runtime-store.js +1527 -0
  125. package/src/squads/apply-genome.js +21 -0
  126. package/src/squads/genome-binding-service.js +154 -0
  127. package/src/updater.js +32 -0
  128. package/src/utils.js +46 -0
  129. package/src/version.js +50 -0
  130. package/template/.aioson/advisors/.gitkeep +1 -0
  131. package/template/.aioson/agents/analyst.md +225 -0
  132. package/template/.aioson/agents/architect.md +221 -0
  133. package/template/.aioson/agents/dev.md +201 -0
  134. package/template/.aioson/agents/discovery-design-doc.md +196 -0
  135. package/template/.aioson/agents/genoma.md +300 -0
  136. package/template/.aioson/agents/orchestrator.md +107 -0
  137. package/template/.aioson/agents/pm.md +89 -0
  138. package/template/.aioson/agents/product.md +361 -0
  139. package/template/.aioson/agents/profiler-enricher.md +266 -0
  140. package/template/.aioson/agents/profiler-forge.md +188 -0
  141. package/template/.aioson/agents/profiler-researcher.md +245 -0
  142. package/template/.aioson/agents/qa.md +344 -0
  143. package/template/.aioson/agents/setup.md +381 -0
  144. package/template/.aioson/agents/squad.md +837 -0
  145. package/template/.aioson/agents/ux-ui.md +416 -0
  146. package/template/.aioson/config.md +56 -0
  147. package/template/.aioson/context/.gitkeep +0 -0
  148. package/template/.aioson/context/parallel/.gitkeep +0 -0
  149. package/template/.aioson/context/spec.md.template +37 -0
  150. package/template/.aioson/genomas/.gitkeep +0 -0
  151. package/template/.aioson/locales/en/agents/analyst.md +214 -0
  152. package/template/.aioson/locales/en/agents/architect.md +210 -0
  153. package/template/.aioson/locales/en/agents/dev.md +187 -0
  154. package/template/.aioson/locales/en/agents/discovery-design-doc.md +27 -0
  155. package/template/.aioson/locales/en/agents/genoma.md +212 -0
  156. package/template/.aioson/locales/en/agents/orchestrator.md +105 -0
  157. package/template/.aioson/locales/en/agents/pm.md +77 -0
  158. package/template/.aioson/locales/en/agents/product.md +310 -0
  159. package/template/.aioson/locales/en/agents/profiler-enricher.md +5 -0
  160. package/template/.aioson/locales/en/agents/profiler-forge.md +5 -0
  161. package/template/.aioson/locales/en/agents/profiler-researcher.md +5 -0
  162. package/template/.aioson/locales/en/agents/qa.md +214 -0
  163. package/template/.aioson/locales/en/agents/setup.md +342 -0
  164. package/template/.aioson/locales/en/agents/squad.md +247 -0
  165. package/template/.aioson/locales/en/agents/ux-ui.md +320 -0
  166. package/template/.aioson/locales/es/agents/analyst.md +203 -0
  167. package/template/.aioson/locales/es/agents/architect.md +208 -0
  168. package/template/.aioson/locales/es/agents/dev.md +183 -0
  169. package/template/.aioson/locales/es/agents/discovery-design-doc.md +19 -0
  170. package/template/.aioson/locales/es/agents/genoma.md +102 -0
  171. package/template/.aioson/locales/es/agents/orchestrator.md +108 -0
  172. package/template/.aioson/locales/es/agents/pm.md +81 -0
  173. package/template/.aioson/locales/es/agents/product.md +310 -0
  174. package/template/.aioson/locales/es/agents/profiler-enricher.md +5 -0
  175. package/template/.aioson/locales/es/agents/profiler-forge.md +5 -0
  176. package/template/.aioson/locales/es/agents/profiler-researcher.md +5 -0
  177. package/template/.aioson/locales/es/agents/qa.md +163 -0
  178. package/template/.aioson/locales/es/agents/setup.md +347 -0
  179. package/template/.aioson/locales/es/agents/squad.md +247 -0
  180. package/template/.aioson/locales/es/agents/ux-ui.md +201 -0
  181. package/template/.aioson/locales/fr/agents/analyst.md +203 -0
  182. package/template/.aioson/locales/fr/agents/architect.md +208 -0
  183. package/template/.aioson/locales/fr/agents/dev.md +183 -0
  184. package/template/.aioson/locales/fr/agents/discovery-design-doc.md +19 -0
  185. package/template/.aioson/locales/fr/agents/genoma.md +102 -0
  186. package/template/.aioson/locales/fr/agents/orchestrator.md +108 -0
  187. package/template/.aioson/locales/fr/agents/pm.md +81 -0
  188. package/template/.aioson/locales/fr/agents/product.md +310 -0
  189. package/template/.aioson/locales/fr/agents/profiler-enricher.md +5 -0
  190. package/template/.aioson/locales/fr/agents/profiler-forge.md +5 -0
  191. package/template/.aioson/locales/fr/agents/profiler-researcher.md +5 -0
  192. package/template/.aioson/locales/fr/agents/qa.md +163 -0
  193. package/template/.aioson/locales/fr/agents/setup.md +347 -0
  194. package/template/.aioson/locales/fr/agents/squad.md +247 -0
  195. package/template/.aioson/locales/fr/agents/ux-ui.md +201 -0
  196. package/template/.aioson/locales/pt-BR/agents/analyst.md +217 -0
  197. package/template/.aioson/locales/pt-BR/agents/architect.md +213 -0
  198. package/template/.aioson/locales/pt-BR/agents/dev.md +198 -0
  199. package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +198 -0
  200. package/template/.aioson/locales/pt-BR/agents/genoma.md +297 -0
  201. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +108 -0
  202. package/template/.aioson/locales/pt-BR/agents/pm.md +81 -0
  203. package/template/.aioson/locales/pt-BR/agents/product.md +316 -0
  204. package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +5 -0
  205. package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +5 -0
  206. package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +5 -0
  207. package/template/.aioson/locales/pt-BR/agents/qa.md +217 -0
  208. package/template/.aioson/locales/pt-BR/agents/setup.md +371 -0
  209. package/template/.aioson/locales/pt-BR/agents/squad.md +772 -0
  210. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +322 -0
  211. package/template/.aioson/mcp/servers.md +24 -0
  212. package/template/.aioson/profiler-reports/.gitkeep +1 -0
  213. package/template/.aioson/schemas/content-blueprint.schema.json +30 -0
  214. package/template/.aioson/schemas/genome-meta.schema.json +150 -0
  215. package/template/.aioson/schemas/genome.schema.json +115 -0
  216. package/template/.aioson/schemas/readiness.schema.json +27 -0
  217. package/template/.aioson/schemas/squad-blueprint.schema.json +172 -0
  218. package/template/.aioson/schemas/squad-manifest.schema.json +276 -0
  219. package/template/.aioson/skills/dynamic/README.md +30 -0
  220. package/template/.aioson/skills/dynamic/cardano-docs.md +16 -0
  221. package/template/.aioson/skills/dynamic/ethereum-docs.md +17 -0
  222. package/template/.aioson/skills/dynamic/flux-ui-docs.md +13 -0
  223. package/template/.aioson/skills/dynamic/laravel-docs.md +41 -0
  224. package/template/.aioson/skills/dynamic/npm-packages.md +16 -0
  225. package/template/.aioson/skills/dynamic/solana-docs.md +16 -0
  226. package/template/.aioson/skills/references/premium-command-center-ui/master-application-prompt.md +79 -0
  227. package/template/.aioson/skills/references/premium-command-center-ui/operational-ux-playbook.md +253 -0
  228. package/template/.aioson/skills/references/premium-command-center-ui/quality-validation-checklist.md +82 -0
  229. package/template/.aioson/skills/references/premium-command-center-ui/visual-system-and-component-patterns.md +270 -0
  230. package/template/.aioson/skills/static/django-patterns.md +342 -0
  231. package/template/.aioson/skills/static/fastapi-patterns.md +344 -0
  232. package/template/.aioson/skills/static/filament-patterns.md +267 -0
  233. package/template/.aioson/skills/static/flux-ui-components.md +262 -0
  234. package/template/.aioson/skills/static/git-conventions.md +227 -0
  235. package/template/.aioson/skills/static/interface-design.md +372 -0
  236. package/template/.aioson/skills/static/jetstream-setup.md +200 -0
  237. package/template/.aioson/skills/static/laravel-conventions.md +491 -0
  238. package/template/.aioson/skills/static/nextjs-patterns.md +321 -0
  239. package/template/.aioson/skills/static/node-express-patterns.md +317 -0
  240. package/template/.aioson/skills/static/node-typescript-patterns.md +282 -0
  241. package/template/.aioson/skills/static/premium-command-center-ui.md +190 -0
  242. package/template/.aioson/skills/static/rails-conventions.md +307 -0
  243. package/template/.aioson/skills/static/react-motion-patterns.md +577 -0
  244. package/template/.aioson/skills/static/static-html-patterns.md +1935 -0
  245. package/template/.aioson/skills/static/tall-stack-patterns.md +286 -0
  246. package/template/.aioson/skills/static/ui-ux-modern.md +75 -0
  247. package/template/.aioson/skills/static/web3-cardano-patterns.md +337 -0
  248. package/template/.aioson/skills/static/web3-ethereum-patterns.md +310 -0
  249. package/template/.aioson/skills/static/web3-security-checklist.md +284 -0
  250. package/template/.aioson/skills/static/web3-solana-patterns.md +324 -0
  251. package/template/.aioson/squads/.artisan/.gitkeep +0 -0
  252. package/template/.aioson/squads/.gitkeep +0 -0
  253. package/template/.aioson/squads/memory.md +5 -0
  254. package/template/.aioson/tasks/squad-analyze.md +83 -0
  255. package/template/.aioson/tasks/squad-create.md +99 -0
  256. package/template/.aioson/tasks/squad-design.md +100 -0
  257. package/template/.aioson/tasks/squad-export.md +20 -0
  258. package/template/.aioson/tasks/squad-extend.md +68 -0
  259. package/template/.aioson/tasks/squad-pipeline.md +122 -0
  260. package/template/.aioson/tasks/squad-repair.md +85 -0
  261. package/template/.aioson/tasks/squad-validate.md +58 -0
  262. package/template/.aioson/templates/squads/content-basic/template.json +21 -0
  263. package/template/.aioson/templates/squads/media-channel/template.json +24 -0
  264. package/template/.aioson/templates/squads/research-analysis/template.json +22 -0
  265. package/template/.aioson/templates/squads/software-delivery/template.json +21 -0
  266. package/template/.claude/commands/aioson/analyst.md +5 -0
  267. package/template/.claude/commands/aioson/architect.md +5 -0
  268. package/template/.claude/commands/aioson/dev.md +5 -0
  269. package/template/.claude/commands/aioson/orchestrator.md +5 -0
  270. package/template/.claude/commands/aioson/pm.md +5 -0
  271. package/template/.claude/commands/aioson/qa.md +5 -0
  272. package/template/.claude/commands/aioson/setup.md +5 -0
  273. package/template/.claude/commands/aioson/ux-ui.md +5 -0
  274. package/template/.gemini/GEMINI.md +10 -0
  275. package/template/.gemini/commands/aios-analyst.toml +4 -0
  276. package/template/.gemini/commands/aios-architect.toml +7 -0
  277. package/template/.gemini/commands/aios-dev.toml +8 -0
  278. package/template/.gemini/commands/aios-discovery-design-doc.toml +4 -0
  279. package/template/.gemini/commands/aios-orchestrator.toml +8 -0
  280. package/template/.gemini/commands/aios-pm.toml +8 -0
  281. package/template/.gemini/commands/aios-product.toml +4 -0
  282. package/template/.gemini/commands/aios-qa.toml +6 -0
  283. package/template/.gemini/commands/aios-setup.toml +3 -0
  284. package/template/.gemini/commands/aios-ux-ui.toml +8 -0
  285. package/template/AGENTS.md +67 -0
  286. package/template/CLAUDE.md +31 -0
  287. package/template/OPENCODE.md +24 -0
  288. package/template/aioson-models.json +40 -0
@@ -0,0 +1,342 @@
1
+ # Django Conventions
2
+
3
+ > Django's batteries are included for a reason. Use them — don't reinvent the ORM, admin, or auth system.
4
+
5
+ ---
6
+
7
+ ## Project structure
8
+
9
+ ```
10
+ myproject/
11
+ ├── manage.py
12
+ ├── config/
13
+ │ ├── settings/
14
+ │ │ ├── base.py
15
+ │ │ ├── development.py
16
+ │ │ └── production.py
17
+ │ ├── urls.py
18
+ │ └── wsgi.py
19
+ ├── apps/
20
+ │ ├── users/
21
+ │ │ ├── models.py
22
+ │ │ ├── views.py
23
+ │ │ ├── serializers.py
24
+ │ │ ├── urls.py
25
+ │ │ ├── services.py
26
+ │ │ └── tests/
27
+ │ └── core/
28
+ │ └── models.py # abstract base models (TimestampMixin, etc.)
29
+ ├── requirements/
30
+ │ ├── base.txt
31
+ │ ├── development.txt
32
+ │ └── production.txt
33
+ └── templates/
34
+ ```
35
+
36
+ **Convention:** keep apps small and domain-focused. One app = one bounded context.
37
+
38
+ ---
39
+
40
+ ## Models
41
+
42
+ ```python
43
+ # core/models.py — reusable base
44
+ from django.db import models
45
+
46
+ class TimestampMixin(models.Model):
47
+ created_at = models.DateTimeField(auto_now_add=True)
48
+ updated_at = models.DateTimeField(auto_now=True)
49
+
50
+ class Meta:
51
+ abstract = True
52
+
53
+ # users/models.py
54
+ class UserProfile(TimestampMixin):
55
+ user = models.OneToOneField(
56
+ settings.AUTH_USER_MODEL,
57
+ on_delete=models.CASCADE,
58
+ related_name='profile'
59
+ )
60
+ bio = models.TextField(blank=True)
61
+
62
+ class Meta:
63
+ db_table = 'user_profiles'
64
+
65
+ def __str__(self):
66
+ return f'Profile({self.user.email})'
67
+ ```
68
+
69
+ **Rules:**
70
+ - Always define `__str__`
71
+ - Always set `db_table` in `Meta` (avoid auto-generated names)
72
+ - Use `TimestampMixin` for all models that need audit fields
73
+ - `blank=True` for optional string fields; `null=True` only for non-string nullable fields
74
+
75
+ ---
76
+
77
+ ## Views — use class-based views for CRUD, function-based for one-offs
78
+
79
+ ```python
80
+ # WRONG — fat view with business logic
81
+ def create_order(request):
82
+ if request.method == 'POST':
83
+ # 30 lines of business logic here
84
+ pass
85
+
86
+ # RIGHT — view delegates to service
87
+ from django.views import View
88
+ from .services import OrderService
89
+
90
+ class OrderCreateView(LoginRequiredMixin, View):
91
+ def post(self, request):
92
+ result = OrderService.create(user=request.user, data=request.POST)
93
+ if result.ok:
94
+ return redirect('orders:detail', pk=result.order.id)
95
+ return render(request, 'orders/create.html', {'errors': result.errors})
96
+ ```
97
+
98
+ **Rules:**
99
+ - Views handle HTTP only: parse request → call service → format response
100
+ - Business logic goes in `services.py`
101
+ - Always use `LoginRequiredMixin` for authenticated views
102
+ - Use `get_object_or_404` instead of manual `try/except`
103
+
104
+ ---
105
+
106
+ ## Services — business logic layer
107
+
108
+ ```python
109
+ # orders/services.py
110
+ from dataclasses import dataclass
111
+ from typing import Optional
112
+ from .models import Order
113
+
114
+ @dataclass
115
+ class OrderResult:
116
+ ok: bool
117
+ order: Optional[Order] = None
118
+ errors: Optional[dict] = None
119
+
120
+ class OrderService:
121
+ @staticmethod
122
+ def create(user, data) -> OrderResult:
123
+ # validate
124
+ if not data.get('items'):
125
+ return OrderResult(ok=False, errors={'items': 'At least one item required'})
126
+
127
+ # execute
128
+ order = Order.objects.create(
129
+ user=user,
130
+ total=sum(item['price'] for item in data['items'])
131
+ )
132
+ return OrderResult(ok=True, order=order)
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Django REST Framework (DRF)
138
+
139
+ ```python
140
+ # serializers.py
141
+ from rest_framework import serializers
142
+ from .models import Order
143
+
144
+ class OrderSerializer(serializers.ModelSerializer):
145
+ class Meta:
146
+ model = Order
147
+ fields = ['id', 'status', 'total', 'created_at']
148
+ read_only_fields = ['id', 'created_at']
149
+
150
+ def validate_total(self, value):
151
+ if value <= 0:
152
+ raise serializers.ValidationError('Total must be positive')
153
+ return value
154
+
155
+ # views.py (DRF)
156
+ from rest_framework import generics, permissions
157
+ from .serializers import OrderSerializer
158
+
159
+ class OrderListCreateView(generics.ListCreateAPIView):
160
+ serializer_class = OrderSerializer
161
+ permission_classes = [permissions.IsAuthenticated]
162
+
163
+ def get_queryset(self):
164
+ return Order.objects.filter(user=self.request.user).select_related('user')
165
+
166
+ def perform_create(self, serializer):
167
+ serializer.save(user=self.request.user)
168
+ ```
169
+
170
+ **Rules:**
171
+ - Always define `read_only_fields` to prevent mass assignment
172
+ - Use `select_related` / `prefetch_related` in `get_queryset` to prevent N+1
173
+ - `perform_create` / `perform_update` for attaching request context (user, etc.)
174
+
175
+ ---
176
+
177
+ ## URL routing
178
+
179
+ ```python
180
+ # apps/orders/urls.py
181
+ from django.urls import path
182
+ from . import views
183
+
184
+ app_name = 'orders'
185
+
186
+ urlpatterns = [
187
+ path('', views.OrderListCreateView.as_view(), name='list'),
188
+ path('<int:pk>/', views.OrderDetailView.as_view(), name='detail'),
189
+ ]
190
+
191
+ # config/urls.py
192
+ from django.urls import path, include
193
+
194
+ urlpatterns = [
195
+ path('admin/', admin.site.urls),
196
+ path('api/orders/', include('apps.orders.urls')),
197
+ ]
198
+ ```
199
+
200
+ **Always use `app_name`** in each app's `urls.py` for namespaced reversals.
201
+
202
+ ---
203
+
204
+ ## Authentication
205
+
206
+ - **Built-in auth:** `django.contrib.auth` for session-based apps
207
+ - **JWT (API):** `djangorestframework-simplejwt`
208
+ - **Social auth:** `django-allauth`
209
+ - **Permission check:** always use `@login_required` / `LoginRequiredMixin` or DRF `permission_classes`
210
+
211
+ ```python
212
+ # DRF JWT setup
213
+ REST_FRAMEWORK = {
214
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
215
+ 'rest_framework_simplejwt.authentication.JWTAuthentication',
216
+ ],
217
+ 'DEFAULT_PERMISSION_CLASSES': [
218
+ 'rest_framework.permissions.IsAuthenticated',
219
+ ],
220
+ }
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Database
226
+
227
+ ```python
228
+ # Migrations — never edit after applied to production
229
+ python manage.py makemigrations
230
+ python manage.py migrate
231
+
232
+ # Always write reversible migrations (define both operations)
233
+ # Squash if > 50 unapplied migrations in dev
234
+
235
+ # Query optimization
236
+ # BAD — N+1
237
+ orders = Order.objects.all()
238
+ for o in orders:
239
+ print(o.user.email) # query per iteration
240
+
241
+ # GOOD
242
+ orders = Order.objects.select_related('user').all()
243
+
244
+ # Use .only() for large models when you need few fields
245
+ orders = Order.objects.only('id', 'status', 'total')
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Settings split
251
+
252
+ ```python
253
+ # config/settings/base.py — shared
254
+ INSTALLED_APPS = [
255
+ 'django.contrib.admin',
256
+ 'django.contrib.auth',
257
+ 'rest_framework',
258
+ 'apps.users',
259
+ 'apps.orders',
260
+ ]
261
+
262
+ # config/settings/development.py
263
+ from .base import *
264
+ DEBUG = True
265
+ DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3'}}
266
+
267
+ # config/settings/production.py
268
+ from .base import *
269
+ DEBUG = False
270
+ DATABASES = {'default': dj_database_url.config(conn_max_age=600)}
271
+ SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
272
+ ```
273
+
274
+ **Never commit secrets.** Use `python-decouple` or `django-environ`.
275
+
276
+ ---
277
+
278
+ ## Admin
279
+
280
+ ```python
281
+ # orders/admin.py
282
+ from django.contrib import admin
283
+ from .models import Order
284
+
285
+ @admin.register(Order)
286
+ class OrderAdmin(admin.ModelAdmin):
287
+ list_display = ['id', 'user', 'status', 'total', 'created_at']
288
+ list_filter = ['status']
289
+ search_fields = ['user__email']
290
+ readonly_fields = ['created_at', 'updated_at']
291
+ ordering = ['-created_at']
292
+ ```
293
+
294
+ Always register models with `@admin.register` (not `admin.site.register`).
295
+
296
+ ---
297
+
298
+ ## Tests (pytest-django)
299
+
300
+ ```python
301
+ # conftest.py
302
+ import pytest
303
+ from django.contrib.auth import get_user_model
304
+
305
+ @pytest.fixture
306
+ def user(db):
307
+ return get_user_model().objects.create_user(
308
+ email='test@example.com', password='testpass123'
309
+ )
310
+
311
+ @pytest.fixture
312
+ def api_client(user):
313
+ from rest_framework.test import APIClient
314
+ client = APIClient()
315
+ client.force_authenticate(user=user)
316
+ return client
317
+
318
+ # orders/tests/test_views.py
319
+ import pytest
320
+
321
+ @pytest.mark.django_db
322
+ def test_create_order_authenticated(api_client):
323
+ response = api_client.post('/api/orders/', {'items': [{'price': 10}]})
324
+ assert response.status_code == 201
325
+
326
+ @pytest.mark.django_db
327
+ def test_create_order_unauthenticated(client):
328
+ response = client.post('/api/orders/', {})
329
+ assert response.status_code == 401
330
+ ```
331
+
332
+ **Use `pytest-django` over `unittest`** — fixtures are composable and tests are shorter.
333
+
334
+ ---
335
+
336
+ ## Hard rules
337
+
338
+ - Never put business logic in views, models, or serializers — use services
339
+ - Never use raw SQL unless `ORM` genuinely can't express it
340
+ - Always use `select_related`/`prefetch_related` for related objects in list views
341
+ - Always split settings by environment (base / dev / prod)
342
+ - Always use `get_object_or_404` in views — never `Model.objects.get()` bare
@@ -0,0 +1,344 @@
1
+ # FastAPI Conventions
2
+
3
+ > Async by default, typed by contract. If it's not typed, it's not FastAPI.
4
+
5
+ ---
6
+
7
+ ## Project structure
8
+
9
+ ```
10
+ myproject/
11
+ ├── main.py # app entrypoint
12
+ ├── app/
13
+ │ ├── api/
14
+ │ │ ├── v1/
15
+ │ │ │ ├── router.py # aggregates all v1 routes
16
+ │ │ │ ├── users.py
17
+ │ │ │ └── orders.py
18
+ │ ├── core/
19
+ │ │ ├── config.py # settings via pydantic-settings
20
+ │ │ ├── database.py # async engine + session
21
+ │ │ └── security.py # password hashing, JWT
22
+ │ ├── models/
23
+ │ │ └── user.py # SQLAlchemy ORM models
24
+ │ ├── schemas/
25
+ │ │ └── user.py # Pydantic request/response models
26
+ │ ├── services/
27
+ │ │ └── user_service.py # business logic
28
+ │ └── repositories/
29
+ │ └── user_repo.py # DB queries (optional for SMALL+)
30
+ ├── tests/
31
+ │ ├── conftest.py
32
+ │ └── test_users.py
33
+ ├── alembic/ # migrations
34
+ └── requirements.txt
35
+ ```
36
+
37
+ ---
38
+
39
+ ## App bootstrap
40
+
41
+ ```python
42
+ # main.py
43
+ from fastapi import FastAPI
44
+ from app.api.v1.router import router as v1_router
45
+ from app.core.config import settings
46
+
47
+ app = FastAPI(
48
+ title=settings.PROJECT_NAME,
49
+ version="1.0.0",
50
+ docs_url="/docs" if settings.DEBUG else None,
51
+ )
52
+
53
+ app.include_router(v1_router, prefix="/api/v1")
54
+
55
+ @app.get("/health")
56
+ async def health():
57
+ return {"status": "ok"}
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Settings (pydantic-settings)
63
+
64
+ ```python
65
+ # app/core/config.py
66
+ from pydantic_settings import BaseSettings
67
+
68
+ class Settings(BaseSettings):
69
+ PROJECT_NAME: str = "My App"
70
+ DEBUG: bool = False
71
+ DATABASE_URL: str
72
+ SECRET_KEY: str
73
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
74
+
75
+ class Config:
76
+ env_file = ".env"
77
+
78
+ settings = Settings()
79
+ ```
80
+
81
+ **Never hardcode secrets.** Always read from environment via `pydantic-settings`.
82
+
83
+ ---
84
+
85
+ ## Database (SQLAlchemy async)
86
+
87
+ ```python
88
+ # app/core/database.py
89
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
90
+ from app.core.config import settings
91
+
92
+ engine = create_async_engine(settings.DATABASE_URL, echo=settings.DEBUG)
93
+ AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
94
+
95
+ async def get_db() -> AsyncSession:
96
+ async with AsyncSessionLocal() as session:
97
+ yield session
98
+ ```
99
+
100
+ ```python
101
+ # app/models/user.py
102
+ from sqlalchemy import Column, Integer, String, DateTime, func
103
+ from sqlalchemy.orm import DeclarativeBase
104
+
105
+ class Base(DeclarativeBase):
106
+ pass
107
+
108
+ class User(Base):
109
+ __tablename__ = "users"
110
+
111
+ id = Column(Integer, primary_key=True, index=True)
112
+ email = Column(String, unique=True, nullable=False, index=True)
113
+ hashed_password = Column(String, nullable=False)
114
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Schemas (Pydantic)
120
+
121
+ ```python
122
+ # app/schemas/user.py
123
+ from pydantic import BaseModel, EmailStr
124
+ from datetime import datetime
125
+
126
+ class UserCreate(BaseModel):
127
+ email: EmailStr
128
+ password: str
129
+
130
+ class UserResponse(BaseModel):
131
+ id: int
132
+ email: EmailStr
133
+ created_at: datetime
134
+
135
+ model_config = {"from_attributes": True} # pydantic v2
136
+
137
+ class UserLogin(BaseModel):
138
+ email: EmailStr
139
+ password: str
140
+ ```
141
+
142
+ **Rules:**
143
+ - Separate schemas for input (`UserCreate`) and output (`UserResponse`)
144
+ - Use `EmailStr` for email fields — free format validation
145
+ - `model_config = {"from_attributes": True}` to convert from ORM models
146
+
147
+ ---
148
+
149
+ ## Routers — thin, delegate to services
150
+
151
+ ```python
152
+ # app/api/v1/users.py
153
+ from fastapi import APIRouter, Depends, HTTPException, status
154
+ from sqlalchemy.ext.asyncio import AsyncSession
155
+ from app.core.database import get_db
156
+ from app.schemas.user import UserCreate, UserResponse
157
+ from app.services.user_service import UserService
158
+
159
+ router = APIRouter(prefix="/users", tags=["users"])
160
+
161
+ @router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
162
+ async def create_user(payload: UserCreate, db: AsyncSession = Depends(get_db)):
163
+ service = UserService(db)
164
+ user = await service.create(payload)
165
+ if not user:
166
+ raise HTTPException(status_code=409, detail="Email already registered")
167
+ return user
168
+
169
+ @router.get("/{user_id}", response_model=UserResponse)
170
+ async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
171
+ service = UserService(db)
172
+ user = await service.get_by_id(user_id)
173
+ if not user:
174
+ raise HTTPException(status_code=404, detail="User not found")
175
+ return user
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Services — business logic
181
+
182
+ ```python
183
+ # app/services/user_service.py
184
+ from sqlalchemy.ext.asyncio import AsyncSession
185
+ from sqlalchemy import select
186
+ from app.models.user import User
187
+ from app.schemas.user import UserCreate
188
+ from app.core.security import hash_password
189
+
190
+ class UserService:
191
+ def __init__(self, db: AsyncSession):
192
+ self.db = db
193
+
194
+ async def create(self, payload: UserCreate) -> User | None:
195
+ existing = await self.db.execute(select(User).where(User.email == payload.email))
196
+ if existing.scalar_one_or_none():
197
+ return None
198
+
199
+ user = User(
200
+ email=payload.email,
201
+ hashed_password=hash_password(payload.password)
202
+ )
203
+ self.db.add(user)
204
+ await self.db.commit()
205
+ await self.db.refresh(user)
206
+ return user
207
+
208
+ async def get_by_id(self, user_id: int) -> User | None:
209
+ result = await self.db.execute(select(User).where(User.id == user_id))
210
+ return result.scalar_one_or_none()
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Authentication (JWT)
216
+
217
+ ```python
218
+ # app/core/security.py
219
+ from passlib.context import CryptContext
220
+ from jose import jwt
221
+ from datetime import datetime, timedelta
222
+ from app.core.config import settings
223
+
224
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
225
+
226
+ def hash_password(password: str) -> str:
227
+ return pwd_context.hash(password)
228
+
229
+ def verify_password(plain: str, hashed: str) -> bool:
230
+ return pwd_context.verify(plain, hashed)
231
+
232
+ def create_access_token(subject: str) -> str:
233
+ expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
234
+ return jwt.encode({"sub": subject, "exp": expire}, settings.SECRET_KEY, algorithm="HS256")
235
+
236
+ # Dependency for protected routes
237
+ from fastapi import Security
238
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
239
+
240
+ security = HTTPBearer()
241
+
242
+ async def get_current_user(
243
+ credentials: HTTPAuthorizationCredentials = Security(security),
244
+ db: AsyncSession = Depends(get_db)
245
+ ):
246
+ try:
247
+ payload = jwt.decode(credentials.credentials, settings.SECRET_KEY, algorithms=["HS256"])
248
+ user_id = int(payload["sub"])
249
+ except Exception:
250
+ raise HTTPException(status_code=401, detail="Invalid token")
251
+ user = await UserService(db).get_by_id(user_id)
252
+ if not user:
253
+ raise HTTPException(status_code=401, detail="User not found")
254
+ return user
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Dependency injection pattern
260
+
261
+ ```python
262
+ # Reuse db dependency across all routes
263
+ @router.get("/me", response_model=UserResponse)
264
+ async def get_me(current_user: User = Depends(get_current_user)):
265
+ return current_user
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Migrations (Alembic)
271
+
272
+ ```bash
273
+ alembic init alembic
274
+ # Set sqlalchemy.url in alembic.ini or use env.py to read from config
275
+ alembic revision --autogenerate -m "create users table"
276
+ alembic upgrade head
277
+ ```
278
+
279
+ ```python
280
+ # alembic/env.py — connect to async engine
281
+ from app.models.user import Base
282
+ target_metadata = Base.metadata
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Tests (pytest + httpx)
288
+
289
+ ```python
290
+ # tests/conftest.py
291
+ import pytest
292
+ from httpx import AsyncClient, ASGITransport
293
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
294
+ from app.core.database import get_db
295
+ from app.models.user import Base
296
+ from main import app
297
+
298
+ TEST_DB_URL = "sqlite+aiosqlite:///./test.db"
299
+
300
+ @pytest.fixture
301
+ async def db():
302
+ engine = create_async_engine(TEST_DB_URL)
303
+ async with engine.begin() as conn:
304
+ await conn.run_sync(Base.metadata.create_all)
305
+ session_factory = async_sessionmaker(engine, expire_on_commit=False)
306
+ async with session_factory() as session:
307
+ yield session
308
+ async with engine.begin() as conn:
309
+ await conn.run_sync(Base.metadata.drop_all)
310
+
311
+ @pytest.fixture
312
+ async def client(db):
313
+ app.dependency_overrides[get_db] = lambda: db
314
+ async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
315
+ yield c
316
+ app.dependency_overrides.clear()
317
+
318
+ # tests/test_users.py
319
+ import pytest
320
+
321
+ @pytest.mark.anyio
322
+ async def test_create_user(client):
323
+ response = await client.post("/api/v1/users/", json={"email": "a@test.com", "password": "pass123"})
324
+ assert response.status_code == 201
325
+ assert response.json()["email"] == "a@test.com"
326
+
327
+ @pytest.mark.anyio
328
+ async def test_create_user_duplicate(client):
329
+ await client.post("/api/v1/users/", json={"email": "a@test.com", "password": "pass123"})
330
+ response = await client.post("/api/v1/users/", json={"email": "a@test.com", "password": "pass123"})
331
+ assert response.status_code == 409
332
+ ```
333
+
334
+ ---
335
+
336
+ ## Hard rules
337
+
338
+ - All route handlers must be `async def` — never mix sync I/O in async routes
339
+ - Business logic goes in `services/`, not in route handlers
340
+ - Always use Pydantic schemas for both input validation and response serialization
341
+ - Always use `response_model` in route decorators — never return raw dicts
342
+ - Use `Depends()` for all cross-cutting concerns (auth, db, rate limiting)
343
+ - Use `select_related` equivalent (`selectinload`/`joinedload`) to avoid N+1
344
+ - Never catch bare `Exception` — catch specific error types