litclaude-ai 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/LICENSE +21 -0
  3. package/README.md +369 -0
  4. package/README_ko-KR.md +374 -0
  5. package/RELEASE_CHECKLIST.md +165 -0
  6. package/bin/litclaude-ai.js +643 -0
  7. package/cover.png +0 -0
  8. package/docs/agents.md +67 -0
  9. package/docs/hooks.md +134 -0
  10. package/docs/lsp.md +40 -0
  11. package/docs/migration.md +209 -0
  12. package/docs/workflow-compatibility-audit.md +119 -0
  13. package/generate_cover.py +123 -0
  14. package/package.json +48 -0
  15. package/plugins/litclaude/.claude-plugin/plugin.json +25 -0
  16. package/plugins/litclaude/.lsp.json +13 -0
  17. package/plugins/litclaude/.mcp.json +9 -0
  18. package/plugins/litclaude/agents/boulder-executor.md +12 -0
  19. package/plugins/litclaude/agents/librarian-researcher.md +15 -0
  20. package/plugins/litclaude/agents/oracle-verifier.md +16 -0
  21. package/plugins/litclaude/agents/prometheus-planner.md +13 -0
  22. package/plugins/litclaude/agents/qa-runner.md +16 -0
  23. package/plugins/litclaude/agents/quality-reviewer.md +17 -0
  24. package/plugins/litclaude/bin/litclaude-hook.js +110 -0
  25. package/plugins/litclaude/bin/litclaude-hud.js +271 -0
  26. package/plugins/litclaude/bin/litclaude-lsp-doctor.js +15 -0
  27. package/plugins/litclaude/bin/litclaude-mcp.js +70 -0
  28. package/plugins/litclaude/commands/deep-interview.md +21 -0
  29. package/plugins/litclaude/commands/dynamic-workflow.md +36 -0
  30. package/plugins/litclaude/commands/lit-loop.md +40 -0
  31. package/plugins/litclaude/commands/lit-plan.md +35 -0
  32. package/plugins/litclaude/commands/litgoal.md +30 -0
  33. package/plugins/litclaude/commands/review-work.md +35 -0
  34. package/plugins/litclaude/commands/start-work.md +36 -0
  35. package/plugins/litclaude/hooks/hooks.json +54 -0
  36. package/plugins/litclaude/lib/context-pressure.mjs +25 -0
  37. package/plugins/litclaude/lib/hud-accent-palette.mjs +58 -0
  38. package/plugins/litclaude/lib/litgoal/cli.mjs +266 -0
  39. package/plugins/litclaude/lib/litgoal/ledger.mjs +16 -0
  40. package/plugins/litclaude/lib/litgoal/paths.mjs +7 -0
  41. package/plugins/litclaude/lib/litgoal/state.mjs +67 -0
  42. package/plugins/litclaude/lib/mutated-file-paths.mjs +63 -0
  43. package/plugins/litclaude/lib/start-work-continuation.mjs +99 -0
  44. package/plugins/litclaude/lib/workflow-check.mjs +83 -0
  45. package/plugins/litclaude/skills/ai-slop-remover/SKILL.md +142 -0
  46. package/plugins/litclaude/skills/comment-checker/SKILL.md +55 -0
  47. package/plugins/litclaude/skills/debugging/SKILL.md +70 -0
  48. package/plugins/litclaude/skills/debugging/references/methodology/00-setup.md +108 -0
  49. package/plugins/litclaude/skills/debugging/references/methodology/02-investigate.md +126 -0
  50. package/plugins/litclaude/skills/debugging/references/methodology/04-oracle-triple.md +106 -0
  51. package/plugins/litclaude/skills/debugging/references/methodology/05-escalate.md +69 -0
  52. package/plugins/litclaude/skills/debugging/references/methodology/06-fix.md +116 -0
  53. package/plugins/litclaude/skills/debugging/references/methodology/08-qa.md +94 -0
  54. package/plugins/litclaude/skills/debugging/references/methodology/09-cleanup.md +164 -0
  55. package/plugins/litclaude/skills/debugging/references/methodology/partial-runtime-evidence.md +228 -0
  56. package/plugins/litclaude/skills/debugging/references/runtimes/bundled-js-binary.md +415 -0
  57. package/plugins/litclaude/skills/debugging/references/runtimes/go.md +252 -0
  58. package/plugins/litclaude/skills/debugging/references/runtimes/native-binary.md +484 -0
  59. package/plugins/litclaude/skills/debugging/references/runtimes/node.md +260 -0
  60. package/plugins/litclaude/skills/debugging/references/runtimes/python.md +248 -0
  61. package/plugins/litclaude/skills/debugging/references/runtimes/rust.md +234 -0
  62. package/plugins/litclaude/skills/debugging/references/tools/ghidra.md +212 -0
  63. package/plugins/litclaude/skills/debugging/references/tools/playwright-cli.md +194 -0
  64. package/plugins/litclaude/skills/debugging/references/tools/pwndbg.md +263 -0
  65. package/plugins/litclaude/skills/debugging/references/tools/pwntools.md +265 -0
  66. package/plugins/litclaude/skills/deep-interview/SKILL.md +323 -0
  67. package/plugins/litclaude/skills/deep-interview/scripts/render_progress.py +193 -0
  68. package/plugins/litclaude/skills/frontend-ui-ux/SKILL.md +62 -0
  69. package/plugins/litclaude/skills/lit-loop/SKILL.md +144 -0
  70. package/plugins/litclaude/skills/lit-plan/SKILL.md +125 -0
  71. package/plugins/litclaude/skills/litgoal/SKILL.md +219 -0
  72. package/plugins/litclaude/skills/lsp/SKILL.md +63 -0
  73. package/plugins/litclaude/skills/programming/SKILL.md +106 -0
  74. package/plugins/litclaude/skills/programming/references/go/README.md +90 -0
  75. package/plugins/litclaude/skills/programming/references/go/backend-stack.md +641 -0
  76. package/plugins/litclaude/skills/programming/references/go/bootstrap.md +328 -0
  77. package/plugins/litclaude/skills/programming/references/go/bubbletea-v2.md +360 -0
  78. package/plugins/litclaude/skills/programming/references/go/cobra-stack.md +468 -0
  79. package/plugins/litclaude/skills/programming/references/go/concurrency.md +362 -0
  80. package/plugins/litclaude/skills/programming/references/go/data-modeling.md +329 -0
  81. package/plugins/litclaude/skills/programming/references/go/error-handling.md +359 -0
  82. package/plugins/litclaude/skills/programming/references/go/golangci-strict.md +236 -0
  83. package/plugins/litclaude/skills/programming/references/go/grpc-connect.md +375 -0
  84. package/plugins/litclaude/skills/programming/references/go/libraries.md +337 -0
  85. package/plugins/litclaude/skills/programming/references/go/one-liners.md +202 -0
  86. package/plugins/litclaude/skills/programming/references/go/sqlc-pgx.md +471 -0
  87. package/plugins/litclaude/skills/programming/references/go/testing.md +467 -0
  88. package/plugins/litclaude/skills/programming/references/go/type-patterns.md +298 -0
  89. package/plugins/litclaude/skills/programming/references/python/README.md +314 -0
  90. package/plugins/litclaude/skills/programming/references/python/async-anyio.md +442 -0
  91. package/plugins/litclaude/skills/programming/references/python/data-modeling.md +233 -0
  92. package/plugins/litclaude/skills/programming/references/python/data-processing.md +133 -0
  93. package/plugins/litclaude/skills/programming/references/python/error-handling.md +218 -0
  94. package/plugins/litclaude/skills/programming/references/python/fastapi-stack.md +316 -0
  95. package/plugins/litclaude/skills/programming/references/python/httpx2-optimization.md +360 -0
  96. package/plugins/litclaude/skills/programming/references/python/libraries.md +307 -0
  97. package/plugins/litclaude/skills/programming/references/python/one-liners.md +268 -0
  98. package/plugins/litclaude/skills/programming/references/python/orjson-stack.md +378 -0
  99. package/plugins/litclaude/skills/programming/references/python/pydantic-ai.md +285 -0
  100. package/plugins/litclaude/skills/programming/references/python/pyproject-strict.md +232 -0
  101. package/plugins/litclaude/skills/programming/references/python/textual-tui.md +201 -0
  102. package/plugins/litclaude/skills/programming/references/python/type-patterns.md +176 -0
  103. package/plugins/litclaude/skills/programming/references/rust/README.md +317 -0
  104. package/plugins/litclaude/skills/programming/references/rust/async-tokio.md +299 -0
  105. package/plugins/litclaude/skills/programming/references/rust/axum-stack.md +467 -0
  106. package/plugins/litclaude/skills/programming/references/rust/cargo-strict.md +317 -0
  107. package/plugins/litclaude/skills/programming/references/rust/clap-stack.md +409 -0
  108. package/plugins/litclaude/skills/programming/references/rust/concurrency.md +375 -0
  109. package/plugins/litclaude/skills/programming/references/rust/libraries.md +439 -0
  110. package/plugins/litclaude/skills/programming/references/rust/one-liners.md +291 -0
  111. package/plugins/litclaude/skills/programming/references/rust/proptest-insta.md +429 -0
  112. package/plugins/litclaude/skills/programming/references/rust/type-state.md +354 -0
  113. package/plugins/litclaude/skills/programming/references/rust/unsafe-discipline.md +250 -0
  114. package/plugins/litclaude/skills/programming/references/rust/zero-cost-safety.md +527 -0
  115. package/plugins/litclaude/skills/programming/references/rust-ub/README.md +289 -0
  116. package/plugins/litclaude/skills/programming/references/rust-ub/miri-sanitizers-loom.md +411 -0
  117. package/plugins/litclaude/skills/programming/references/rust-ub/ub-taxonomy.md +269 -0
  118. package/plugins/litclaude/skills/programming/references/typescript/README.md +195 -0
  119. package/plugins/litclaude/skills/programming/references/typescript/backend-hono.md +672 -0
  120. package/plugins/litclaude/skills/programming/references/typescript/bootstrap.md +199 -0
  121. package/plugins/litclaude/skills/programming/references/typescript/data-modeling.md +202 -0
  122. package/plugins/litclaude/skills/programming/references/typescript/error-handling.md +169 -0
  123. package/plugins/litclaude/skills/programming/references/typescript/tsconfig-strict.md +152 -0
  124. package/plugins/litclaude/skills/programming/references/typescript/type-patterns.md +196 -0
  125. package/plugins/litclaude/skills/programming/scripts/go/check-no-excuse-rules.sh +173 -0
  126. package/plugins/litclaude/skills/programming/scripts/go/new-project.py +138 -0
  127. package/plugins/litclaude/skills/programming/scripts/go/templates/.editorconfig +13 -0
  128. package/plugins/litclaude/skills/programming/scripts/go/templates/.golangci.yml +95 -0
  129. package/plugins/litclaude/skills/programming/scripts/go/templates/AGENTS.md.tmpl +24 -0
  130. package/plugins/litclaude/skills/programming/scripts/go/templates/README.md.tmpl +12 -0
  131. package/plugins/litclaude/skills/programming/scripts/go/templates/Taskfile.yml +40 -0
  132. package/plugins/litclaude/skills/programming/scripts/go/templates/ci.yml +37 -0
  133. package/plugins/litclaude/skills/programming/scripts/go/templates/config.go +24 -0
  134. package/plugins/litclaude/skills/programming/scripts/go/templates/gitignore +15 -0
  135. package/plugins/litclaude/skills/programming/scripts/go/templates/main.go.tmpl +22 -0
  136. package/plugins/litclaude/skills/programming/scripts/go/templates/run.go +15 -0
  137. package/plugins/litclaude/skills/programming/scripts/python/check-no-excuse-rules.py +687 -0
  138. package/plugins/litclaude/skills/programming/scripts/python/new-project.py +172 -0
  139. package/plugins/litclaude/skills/programming/scripts/python/new-script.py +116 -0
  140. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.py +296 -0
  141. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.sh +158 -0
  142. package/plugins/litclaude/skills/programming/scripts/rust/new-project.py +175 -0
  143. package/plugins/litclaude/skills/programming/scripts/typescript/check-no-excuse-rules.ts +282 -0
  144. package/plugins/litclaude/skills/programming/scripts/typescript/new-project.ts +177 -0
  145. package/plugins/litclaude/skills/refactor/SKILL.md +73 -0
  146. package/plugins/litclaude/skills/remove-ai-slops/SKILL.md +52 -0
  147. package/plugins/litclaude/skills/review-work/SKILL.md +331 -0
  148. package/plugins/litclaude/skills/rules/SKILL.md +66 -0
  149. package/plugins/litclaude/skills/start-work/SKILL.md +132 -0
  150. package/scripts/audit-plan-checkboxes.mjs +37 -0
  151. package/scripts/doctor.mjs +41 -0
  152. package/scripts/inspect-agent-tools.mjs +27 -0
  153. package/scripts/postinstall.mjs +50 -0
  154. package/scripts/qa-claude-plugin-smoke.sh +60 -0
  155. package/scripts/qa-portable-install.sh +136 -0
  156. package/scripts/validate-plugin.mjs +72 -0
@@ -0,0 +1,268 @@
1
+ # One-liner Scripts (PEP 723 + uv)
2
+
3
+ Self-contained Python scripts with declared dependencies, run with no environment setup. The combination eliminates the historical reason to write small tools in Go or Bash.
4
+
5
+ **Rule: EVERY `.py` script — even throwaway — MUST use PEP 723 inline metadata with the usage comment block.** No venv, no requirements.txt, no setup.py. The script IS the environment spec.
6
+
7
+ ## The two patterns
8
+
9
+ ### Pattern 1: inline `uv run` invocation
10
+
11
+ ```bash
12
+ uv run --with httpx2 --with rich python -c "
13
+ import httpx2
14
+ from rich import print
15
+ print(httpx2.get('https://api.github.com').json())
16
+ "
17
+ ```
18
+
19
+ Use for terminal one-shots that you don't want to save. `--with PKG` may be repeated.
20
+
21
+ ### Pattern 2: PEP 723 script with shebang (THE CANONICAL PATTERN)
22
+
23
+ A regular `.py` file with metadata in a comment block. uv reads the metadata, materialises a disposable venv (cached), and runs the script.
24
+
25
+ ```python
26
+ #!/usr/bin/env -S uv run --script
27
+ # /// script
28
+ # requires-python = ">=3.13"
29
+ # dependencies = [
30
+ # "httpx2[http2,brotli,zstd]",
31
+ # "rich",
32
+ # ]
33
+ # ///
34
+
35
+ # ─── How to run ───
36
+ # 1. Install uv (if not installed):
37
+ # curl -LsSf https://astral.sh/uv/install.sh | sh
38
+ # 2. Run directly (no venv, no pip install needed):
39
+ # uv run my_script.py
40
+ # 3. Or make executable and run:
41
+ # chmod +x my_script.py && ./my_script.py
42
+ # ──────────────────
43
+
44
+ from __future__ import annotations
45
+
46
+ import httpx2
47
+ from rich import print as rprint
48
+
49
+
50
+ def main() -> None:
51
+ with httpx2.Client(http2=True, follow_redirects=True) as client:
52
+ resp = client.get("https://api.github.com")
53
+ resp.raise_for_status()
54
+ rprint(resp.json())
55
+
56
+
57
+ if __name__ == "__main__":
58
+ main()
59
+ ```
60
+
61
+ ### Mandatory elements
62
+
63
+ Every PEP 723 script MUST include these, in order:
64
+
65
+ 1. **Shebang**: `#!/usr/bin/env -S uv run --script`
66
+ 2. **PEP 723 metadata block**: `# /// script` ... `# ///` with `requires-python` and `dependencies`
67
+ 3. **Usage comment block**: How to install uv + how to run the script. Copy the template above verbatim.
68
+ 4. **`from __future__ import annotations`**: Always first import.
69
+ 5. **`if __name__ == "__main__": main()`**: Entry point guard.
70
+
71
+ ### The usage comment block (NON-NEGOTIABLE)
72
+
73
+ ```python
74
+ # ─── How to run ───
75
+ # 1. Install uv (if not installed):
76
+ # curl -LsSf https://astral.sh/uv/install.sh | sh
77
+ # 2. Run directly (no venv, no pip install needed):
78
+ # uv run <SCRIPT_NAME>.py [ARGS]
79
+ # 3. Or make executable and run:
80
+ # chmod +x <SCRIPT_NAME>.py && ./<SCRIPT_NAME>.py
81
+ # ──────────────────
82
+ ```
83
+
84
+ Replace `<SCRIPT_NAME>` with the actual filename. Add argument descriptions if the script takes CLI args. This block goes immediately after the `# ///` closing line, before any imports.
85
+
86
+ **Why mandatory**: Anyone who receives this script — colleague, CI, future you — must know how to run it without reading docs. The comment IS the docs.
87
+
88
+ ## Template generator
89
+
90
+ Use `scripts/new-script.py` to scaffold a new PEP 723 script with all boilerplate pre-filled:
91
+
92
+ ```bash
93
+ # Generate to temp directory (default)
94
+ uv run scripts/new-script.py my_tool
95
+
96
+ # Generate to specific path
97
+ uv run scripts/new-script.py my_tool --output ./scripts/my_tool.py
98
+
99
+ # With extra dependencies
100
+ uv run scripts/new-script.py my_tool --deps "polars" "duckdb" "rich"
101
+ ```
102
+
103
+ ## Common dependency sets
104
+
105
+ | Use case | Dependencies line |
106
+ |---|---|
107
+ | API client | `"httpx2[http2,brotli,zstd]"` |
108
+ | Data processing | `"polars"`, `"duckdb"` |
109
+ | CLI tool | `"typer"`, `"rich"` |
110
+ | Web scraping | `"httpx2[http2,brotli,zstd]"`, `"selectolax"` |
111
+ | File watcher | `"watchfiles"` |
112
+ | JSON pretty | `"rich"` |
113
+ | AI / LLM | `"pydantic-ai"`, `"httpx2[http2,brotli,zstd]"` |
114
+
115
+ ## Real-world examples
116
+
117
+ ### Fetch + print JSON
118
+
119
+ ```python
120
+ #!/usr/bin/env -S uv run --script
121
+ # /// script
122
+ # requires-python = ">=3.13"
123
+ # dependencies = [
124
+ # "httpx2[http2,brotli,zstd]",
125
+ # "rich",
126
+ # ]
127
+ # ///
128
+
129
+ # ─── How to run ───
130
+ # 1. Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh
131
+ # 2. Run: uv run fetch_json.py https://api.github.com/repos/pydantic/httpx2
132
+ # ──────────────────
133
+
134
+ from __future__ import annotations
135
+
136
+ import sys
137
+
138
+ import httpx2
139
+ from rich import print as rprint
140
+
141
+
142
+ def main() -> None:
143
+ url = sys.argv[1] if len(sys.argv) > 1 else "https://api.github.com"
144
+ with httpx2.Client(http2=True, follow_redirects=True) as client:
145
+ resp = client.get(url)
146
+ resp.raise_for_status()
147
+ rprint(resp.json())
148
+
149
+
150
+ if __name__ == "__main__":
151
+ main()
152
+ ```
153
+
154
+ ### CSV → Parquet conversion
155
+
156
+ ```python
157
+ #!/usr/bin/env -S uv run --script
158
+ # /// script
159
+ # requires-python = ">=3.13"
160
+ # dependencies = [
161
+ # "polars",
162
+ # "typer",
163
+ # "rich",
164
+ # ]
165
+ # ///
166
+
167
+ # ─── How to run ───
168
+ # 1. Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh
169
+ # 2. Run: uv run csv2parquet.py input.csv output.parquet
170
+ # ──────────────────
171
+
172
+ from __future__ import annotations
173
+
174
+ from pathlib import Path
175
+
176
+ import polars as pl
177
+ import typer
178
+ from rich import print as rprint
179
+
180
+
181
+ def main(input_path: Path, output_path: Path | None = None) -> None:
182
+ """Convert CSV to Parquet."""
183
+ out = output_path or input_path.with_suffix(".parquet")
184
+ df = pl.read_csv(input_path)
185
+ df.write_parquet(out)
186
+ rprint(f"[green]✓[/green] {input_path} → {out} ({len(df)} rows)")
187
+
188
+
189
+ if __name__ == "__main__":
190
+ typer.run(main)
191
+ ```
192
+
193
+ ### Quick benchmark
194
+
195
+ ```python
196
+ #!/usr/bin/env -S uv run --script
197
+ # /// script
198
+ # requires-python = ">=3.13"
199
+ # dependencies = [
200
+ # "httpx2[http2,brotli,zstd]",
201
+ # "rich",
202
+ # "anyio",
203
+ # ]
204
+ # ///
205
+
206
+ # ─── How to run ───
207
+ # 1. Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh
208
+ # 2. Run: uv run bench.py https://api.example.com/health 50
209
+ # ──────────────────
210
+
211
+ from __future__ import annotations
212
+
213
+ import socket
214
+ import sys
215
+ import time
216
+
217
+ import anyio
218
+ import httpx2
219
+ from rich import print as rprint
220
+
221
+
222
+ async def main() -> None:
223
+ url = sys.argv[1] if len(sys.argv) > 1 else "https://api.github.com"
224
+ n = int(sys.argv[2]) if len(sys.argv) > 2 else 20
225
+
226
+ limits = httpx2.Limits(max_connections=200, max_keepalive_connections=40, keepalive_expiry=30.0)
227
+ timeout = httpx2.Timeout(connect=5.0, read=30.0, write=10.0, pool=10.0)
228
+ transport = httpx2.AsyncHTTPTransport(
229
+ http2=True, retries=3, limits=limits,
230
+ socket_options=[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)],
231
+ )
232
+
233
+ async with httpx2.AsyncClient(transport=transport, timeout=timeout, follow_redirects=True) as client:
234
+ # warmup
235
+ for _ in range(3):
236
+ await client.get(url)
237
+
238
+ start = time.perf_counter()
239
+ for _ in range(n):
240
+ r = await client.get(url)
241
+ assert r.status_code == 200
242
+ elapsed = time.perf_counter() - start
243
+
244
+ avg_ms = (elapsed / n) * 1000
245
+ rprint(f"[bold]{url}[/bold]: {avg_ms:.1f}ms avg over {n} requests ({elapsed:.2f}s total, {r.http_version})")
246
+
247
+
248
+ if __name__ == "__main__":
249
+ anyio.run(main)
250
+ ```
251
+
252
+ ## Anti-patterns
253
+
254
+ | ❌ Don't | ✅ Do |
255
+ |---|---|
256
+ | `pip install httpx2 && python script.py` | `uv run script.py` |
257
+ | `requirements.txt` alongside script | PEP 723 inline metadata |
258
+ | `python -m venv .venv && ...` | `uv run --script` handles it |
259
+ | Script without usage comment | Always include the "How to run" block |
260
+ | `import asyncio; asyncio.run(main())` | `import anyio; anyio.run(main)` |
261
+ | Bare `httpx2.AsyncClient()` | Full production defaults (see `references/httpx2-optimization.md`) |
262
+
263
+ ## Sources
264
+
265
+ - PEP 723 - Inline script metadata: <https://peps.python.org/pep-0723/>
266
+ - uv `run --script` docs: <https://docs.astral.sh/uv/guides/scripts/>
267
+ - Original article: <https://www.cottongeeks.com/articles/2025-06-24-fun-with-uv-and-pep-723>
268
+ - Simon Willison on one-shot Python tools: <https://simonwillison.net/2024/Dec/19/one-shot-python-tools/>
@@ -0,0 +1,378 @@
1
+ # orjson — When to Use, How to Integrate
2
+
3
+ `orjson` is the fastest JSON library on PyPI — written in Rust, 6–11× faster than stdlib `json` on serialization, 1.5–4× faster on deserialization. It also supports types the stdlib refuses to serialize: `datetime`, `date`, `UUID`, `numpy` arrays, `dataclass`, Pydantic models (via a small bridge).
4
+
5
+ This document covers the production patterns. **Not every project needs orjson.** The decision tree is in §1.
6
+
7
+ ---
8
+
9
+ ## 1. Decision tree — should you adopt orjson?
10
+
11
+ ```
12
+ Are you serializing/deserializing JSON in a hot path?
13
+ ├─ NO → stdlib `json` is fine. Stop here.
14
+ └─ YES ↓
15
+
16
+ Is the project FastAPI?
17
+ ├─ YES ↓
18
+
19
+ │ Is your response body fully described by a Pydantic v2 model?
20
+ │ ├─ YES → Use FastAPI's default JSON response (uses Pydantic's
21
+ │ │ Rust-backed serializer; orjson saves nothing in this path).
22
+ │ │ Adopt orjson only for *non-Pydantic* responses below.
23
+ │ └─ NO → Use `ORJSONResponse` for endpoints that return dicts,
24
+ │ lists, or arbitrary structures.
25
+
26
+ └─ NOT FastAPI ↓
27
+
28
+ Are you serializing Pydantic v2 models repeatedly?
29
+ ├─ YES → Use `model.model_dump_json()` directly — backed by pydantic-core
30
+ │ (Rust), within ~10% of orjson on the same payload, and respects
31
+ │ every Pydantic feature (computed fields, aliases, validators).
32
+ └─ NO ↓
33
+
34
+ Are you serializing dicts / lists / dataclasses / datetime / UUID?
35
+ ├─ YES → orjson is the right answer.
36
+ └─ NO → stdlib `json`.
37
+ ```
38
+
39
+ **The crucial 2026 fact**: with Pydantic v2's `model_dump_json()`, **Pydantic-shaped responses no longer need orjson**. Adopt orjson where you are still going through `dict` / `list` / `dataclass`.
40
+
41
+ ---
42
+
43
+ ## 2. Install
44
+
45
+ ```toml
46
+ # pyproject.toml
47
+ dependencies = [
48
+ "orjson>=3.10",
49
+ ]
50
+ ```
51
+
52
+ orjson wheels are published for every major CPython version and platform (macOS, Linux glibc/musl, Windows, ARM64). No compilation step on install.
53
+
54
+ ---
55
+
56
+ ## 3. Basic usage
57
+
58
+ ```python
59
+ import orjson
60
+
61
+ # Serialization — returns bytes, not str
62
+ raw: bytes = orjson.dumps({"hello": "world", "ts": datetime.now(UTC)})
63
+
64
+ # Deserialization
65
+ data = orjson.loads(raw)
66
+ ```
67
+
68
+ Two things to internalize:
69
+
70
+ 1. **`orjson.dumps` returns `bytes`**, not `str`. Stdlib `json.dumps` returns `str`. This is by design — most JSON destinations (sockets, files in binary mode, HTTP bodies) want bytes anyway, and skipping the encode/decode round trip is part of the speedup.
71
+ 2. **No `indent` arg.** orjson supports `OPT_INDENT_2` (and only 2-space indent) via flags. If you need other indentation, use stdlib `json`.
72
+
73
+ ---
74
+
75
+ ## 4. The option flags you actually use
76
+
77
+ ```python
78
+ import orjson
79
+
80
+ orjson.dumps(
81
+ payload,
82
+ option=(
83
+ orjson.OPT_NAIVE_UTC # treat naive datetimes as UTC (recommended)
84
+ | orjson.OPT_UTC_Z # render UTC as "...Z" instead of "+00:00"
85
+ | orjson.OPT_SERIALIZE_NUMPY # serialize numpy arrays natively
86
+ | orjson.OPT_SERIALIZE_DATACLASS # serialize @dataclass instances
87
+ | orjson.OPT_NON_STR_KEYS # allow int / UUID / datetime dict keys
88
+ # | orjson.OPT_SORT_KEYS # only when you need deterministic output
89
+ # | orjson.OPT_INDENT_2 # only for human-readable output (slower)
90
+ ),
91
+ )
92
+ ```
93
+
94
+ Each flag is opt-in for a reason — orjson defaults to spec-strict JSON.
95
+
96
+ The flag combination above is a sensible "production default" for application code. The `OPT_NAIVE_UTC | OPT_UTC_Z` pair is especially important: it produces RFC 3339 timestamps that every parser on earth accepts.
97
+
98
+ ---
99
+
100
+ ## 5. orjson + FastAPI
101
+
102
+ ### 5.1 The legacy pattern: `ORJSONResponse`
103
+
104
+ ```python
105
+ from fastapi import FastAPI
106
+ from fastapi.responses import ORJSONResponse
107
+
108
+ app = FastAPI(default_response_class=ORJSONResponse)
109
+
110
+ @app.get("/items")
111
+ async def get_items() -> dict[str, list[dict[str, int]]]:
112
+ return {"items": [{"id": i, "qty": i * 2} for i in range(1000)]}
113
+ ```
114
+
115
+ `default_response_class=ORJSONResponse` swaps the global JSON encoder for orjson. **This affects only the response body serialization**, not request parsing — for request parsing, FastAPI still uses Pydantic.
116
+
117
+ ### 5.2 The 2026 reality — Pydantic v2 vs orjson
118
+
119
+ With FastAPI 0.100+ on Pydantic v2:
120
+
121
+ - If your response is annotated with a Pydantic model, FastAPI calls `model_dump_json()` directly. **orjson is bypassed** even with `default_response_class=ORJSONResponse`, because the Pydantic serializer is already Rust-backed.
122
+ - If your response is a raw `dict` / `list` / Python object, `ORJSONResponse` does kick in and saves real time.
123
+
124
+ The benchmark in `tiangolo/fastapi#11728` (Apr 2024) showed `model_dump_json()` is ~10–15% faster than `ORJSONResponse + model_dump()` for Pydantic-shaped responses. The shape of the data matters; on mixed-shape APIs, keep `ORJSONResponse` as the default and trust Pydantic's path for typed responses.
125
+
126
+ ### 5.3 Recommended setup
127
+
128
+ ```python
129
+ from fastapi import FastAPI
130
+ from fastapi.responses import ORJSONResponse
131
+
132
+ app = FastAPI(
133
+ default_response_class=ORJSONResponse, # benefits dict/list returns
134
+ # Pydantic-typed returns automatically use pydantic-core serialization
135
+ )
136
+ ```
137
+
138
+ **Do NOT** wrap Pydantic models manually:
139
+
140
+ ```python
141
+ # BAD — defeats Pydantic's optimized path
142
+ @app.get("/users/{id}", response_class=ORJSONResponse)
143
+ async def get_user(id: int) -> ORJSONResponse:
144
+ user = await fetch_user(id)
145
+ return ORJSONResponse(content=user.model_dump()) # extra dict trip
146
+
147
+ # GOOD — let FastAPI serialize the model
148
+ @app.get("/users/{id}")
149
+ async def get_user(id: int) -> User:
150
+ return await fetch_user(id)
151
+ ```
152
+
153
+ ### 5.4 Streaming responses
154
+
155
+ `ORJSONResponse` does not stream — it buffers the whole response. For SSE, NDJSON, or chunked JSON, use `StreamingResponse` and call `orjson.dumps` per chunk:
156
+
157
+ ```python
158
+ from fastapi.responses import StreamingResponse
159
+ import orjson
160
+
161
+ async def ndjson_stream():
162
+ async for row in fetch_rows():
163
+ yield orjson.dumps(row) + b"\n"
164
+
165
+ @app.get("/export")
166
+ async def export():
167
+ return StreamingResponse(ndjson_stream(), media_type="application/x-ndjson")
168
+ ```
169
+
170
+ This is where orjson shines — per-chunk serialization in a tight loop, zero buffering.
171
+
172
+ ---
173
+
174
+ ## 6. orjson + Pydantic v2 (no FastAPI)
175
+
176
+ When you have a Pydantic model and want orjson's output for non-FastAPI contexts:
177
+
178
+ ```python
179
+ from pydantic import BaseModel
180
+ import orjson
181
+
182
+ class User(BaseModel):
183
+ id: int
184
+ email: str
185
+ created: datetime
186
+
187
+ user = User(id=1, email="a@b.com", created=datetime.now(UTC))
188
+
189
+ # Option A — Pydantic's built-in Rust serializer (USE THIS by default)
190
+ raw: bytes = user.model_dump_json().encode()
191
+ # 2026: ~1.2× faster than orjson on the same payload, supports
192
+ # every Pydantic feature (aliases, computed fields, json_schema_extra, etc.)
193
+
194
+ # Option B — orjson bridge for cases Pydantic does not cover
195
+ raw: bytes = orjson.dumps(
196
+ user,
197
+ default=lambda obj: obj.model_dump() if isinstance(obj, BaseModel) else None,
198
+ )
199
+ # Useful when serializing nested non-Pydantic structures that contain
200
+ # BaseModels — e.g. a list of dicts that each may contain a BaseModel.
201
+ ```
202
+
203
+ For routine "serialize one Pydantic model to JSON", `model_dump_json()` wins on speed AND feature parity. Reach for orjson only at the *container* level (a dict of mixed types).
204
+
205
+ ### Custom `default=` callback — the universal extension point
206
+
207
+ ```python
208
+ import orjson
209
+ from decimal import Decimal
210
+ from pydantic import BaseModel
211
+
212
+ def _default(obj):
213
+ if isinstance(obj, BaseModel):
214
+ return obj.model_dump()
215
+ if isinstance(obj, Decimal):
216
+ return str(obj)
217
+ if isinstance(obj, set):
218
+ return list(obj)
219
+ raise TypeError(f"orjson: cannot serialize {type(obj).__name__}")
220
+
221
+ orjson.dumps(payload, default=_default, option=orjson.OPT_NAIVE_UTC | orjson.OPT_UTC_Z)
222
+ ```
223
+
224
+ The `default=` callback runs once per unrecognized type, then orjson caches the path. Performance impact on subsequent calls is negligible.
225
+
226
+ ---
227
+
228
+ ## 7. Caching, queues, logging — the prime orjson use cases
229
+
230
+ These are where orjson pays off most clearly because there is no Pydantic in the loop:
231
+
232
+ ### Redis cache
233
+
234
+ ```python
235
+ import orjson
236
+ import redis.asyncio as redis
237
+
238
+ r = redis.from_url("redis://localhost")
239
+
240
+ async def set_cache(key: str, value: dict) -> None:
241
+ await r.set(key, orjson.dumps(value), ex=3600)
242
+
243
+ async def get_cache(key: str) -> dict | None:
244
+ raw = await r.get(key)
245
+ return orjson.loads(raw) if raw else None
246
+ ```
247
+
248
+ `orjson` over stdlib `json` here saves ~5–10× on the serialize step for typical cache payloads. Multiply by request rate.
249
+
250
+ ### Task queue payloads (Celery, RQ, dramatiq)
251
+
252
+ ```python
253
+ # Celery custom serializer
254
+ from kombu.serialization import register
255
+ import orjson
256
+
257
+ def _orjson_dumps(obj):
258
+ return orjson.dumps(obj, option=orjson.OPT_NAIVE_UTC | orjson.OPT_UTC_Z).decode()
259
+
260
+ def _orjson_loads(s):
261
+ return orjson.loads(s)
262
+
263
+ register("orjson", _orjson_dumps, _orjson_loads,
264
+ content_type="application/x-orjson",
265
+ content_encoding="utf-8")
266
+ ```
267
+
268
+ Same speedup, applied to every task payload encode/decode.
269
+
270
+ ### Structured logging (structlog, custom slog)
271
+
272
+ ```python
273
+ import structlog
274
+ import orjson
275
+
276
+ structlog.configure(
277
+ processors=[
278
+ structlog.processors.TimeStamper(fmt="iso"),
279
+ structlog.processors.add_log_level,
280
+ structlog.processors.JSONRenderer(serializer=orjson.dumps),
281
+ ],
282
+ )
283
+ ```
284
+
285
+ structlog's `JSONRenderer` accepts any callable; orjson is the obvious default. Logging hot paths benefit dramatically — every log line at info level becomes ~5× cheaper to render.
286
+
287
+ ---
288
+
289
+ ## 8. Gotchas
290
+
291
+ ### `orjson.dumps` returns bytes, not str
292
+
293
+ ```python
294
+ # BAD — concatenating bytes and str
295
+ log.info("payload: " + orjson.dumps(data)) # TypeError
296
+
297
+ # GOOD
298
+ log.info("payload: %s", orjson.dumps(data).decode())
299
+ # or
300
+ log.info("payload: %s", orjson.dumps(data)) # let the formatter handle it
301
+ ```
302
+
303
+ ### No `cls=` argument for custom encoders
304
+
305
+ orjson uses `default=` only. If you have a custom `JSONEncoder` subclass from stdlib `json`, port its `default()` method to a `default=` callable.
306
+
307
+ ### Subclasses of `dict` / `list` are NOT serialized as their parent
308
+
309
+ ```python
310
+ class StrictDict(dict): ...
311
+ d = StrictDict({"k": "v"})
312
+
313
+ import json
314
+ json.dumps(d) # OK — stdlib walks subclasses
315
+ orjson.dumps(d) # TypeError — orjson is strict by design
316
+ orjson.dumps(d, option=orjson.OPT_PASSTHROUGH_SUBCLASS) # then route via default=
317
+ ```
318
+
319
+ Set `OPT_PASSTHROUGH_SUBCLASS` and handle the subclass in `default=`. The design discourages accidental subclass usage that breaks elsewhere.
320
+
321
+ ### `int` overflow
322
+
323
+ orjson refuses to encode integers larger than 2⁵³ - 1 by default (the IEEE-754 double-precision safe-integer limit — what JavaScript can round-trip). For larger ints, opt in:
324
+
325
+ ```python
326
+ orjson.dumps(huge_int, option=orjson.OPT_STRICT_INTEGER) # error
327
+ orjson.dumps(huge_int) # default — int is encoded as JSON number
328
+ # JavaScript clients lose precision past 2^53; consider sending as string
329
+ ```
330
+
331
+ This is more spec-strict than stdlib `json`, which silently emits ints of any size.
332
+
333
+ ### Timezone-naive datetimes
334
+
335
+ By default, orjson treats naive `datetime` as the system local timezone — almost never what you want. **Always set `OPT_NAIVE_UTC`** to treat naive datetimes as UTC, or use timezone-aware datetimes (which is the better long-term habit).
336
+
337
+ ---
338
+
339
+ ## 9. Benchmark — should I actually adopt this?
340
+
341
+ The numbers below are 2024–2026 averages from `tiangolo/fastapi#11728` and orjson's own benchmark suite, on Python 3.13, modern x86_64:
342
+
343
+ | Payload | stdlib `json` | `orjson` | `model_dump_json()` (Pydantic v2) |
344
+ |---|---|---|---|
345
+ | Small dict (100 fields) | 1.0× | **8×** | n/a |
346
+ | List of 10k dicts | 1.0× | **11×** | n/a |
347
+ | Pydantic model with 20 fields | 1.0× (after `model_dump()`) | 5× (with `default=` bridge) | **6×** |
348
+ | Datetime-heavy payload | 1.0× (after manual ISO conv) | **9×** | 6× |
349
+ | numpy array (1M floats) | impossible without manual conv | **20×** vs json+tolist | n/a |
350
+
351
+ The takeaways:
352
+
353
+ - For raw dict/list/datetime, **orjson is dramatically faster**.
354
+ - For Pydantic models, **`model_dump_json()` is already faster than orjson+bridge**.
355
+ - For numpy, orjson is the only sane choice.
356
+
357
+ In production, the actual measured win on a FastAPI app with mixed payloads is typically 5–15% reduction in p99 latency. Worth the one-line `default_response_class=ORJSONResponse` switch.
358
+
359
+ ---
360
+
361
+ ## 10. When NOT to adopt orjson
362
+
363
+ - The codebase is small, JSON is not a bottleneck, and you have no measured perf concern.
364
+ - You depend on stdlib `json`'s `cls=` arg or its lax tolerance for non-spec input (NaN, Infinity, comments).
365
+ - You need pretty-printed JSON with custom indent — orjson only supports 2-space indent via the flag.
366
+ - You need pure-Python portability (e.g., MicroPython, no-wheel platforms) — orjson is a compiled Rust extension.
367
+
368
+ If the choice is "add a dependency that does 5–10× the speed on serialization for free", the answer is almost always yes. The "almost" is in the bullets above.
369
+
370
+ ---
371
+
372
+ ## Sources
373
+
374
+ - orjson: https://github.com/ijl/orjson
375
+ - Pydantic v2 `model_dump_json`: https://docs.pydantic.dev/latest/concepts/serialization/#modelmodel_dump_json
376
+ - FastAPI `ORJSONResponse`: https://fastapi.tiangolo.com/advanced/custom-response/#use-orjsonresponse
377
+ - "FastAPI + orjson vs Pydantic v2" benchmark: https://github.com/fastapi/fastapi/discussions/11728
378
+ - structlog JSON rendering: https://www.structlog.org/en/stable/api.html#structlog.processors.JSONRenderer