π docs: refreshed index (verified numbers, new features), og-image, migration guide fix, all-llm.txt (LLM app-building guide)
swadhinbiswas committed 2 months ago
0436ae9
1 parent a4cfa86
commit 0436ae9
5 files changed
+569 -16
Showing 5 changed files+569-16
βΌall-llm.txt
+543-0
| @@ -0,0 +1,543 @@ | ||
1 | +# JustAPI β Complete Framework Reference for AI/LLM Agents | |
2 | + | |
3 | +> This file is the authoritative, self-contained guide to building production-grade | |
4 | +> applications with **JustAPI** (v2.0.9). It is written for LLM agents and developers | |
5 | +> who want to build complete applications without reading the entire source tree. | |
6 | +> | |
7 | +> JustAPI = Python web framework with a Rust core. **You write Python logic. | |
8 | +> Rust does everything else** β networking, TLS, routing, validation, | |
9 | +> serialization, SSE, database I/O, rate limiting. | |
10 | +> | |
11 | +> Version: 2.0.9 | License: MIT | Repo: https://github.com/swadhinbiswas/JustAPI | |
12 | + | |
13 | +--- | |
14 | + | |
15 | +## 1. Installation | |
16 | + | |
17 | +```bash | |
18 | +pip install justapi==2.0.9 # from PyPI (8 platforms incl. free-threaded 3.14t) | |
19 | +uv pip install justapi # uv users | |
20 | +uvx justapi create my_app # scaffold a full project | |
21 | +``` | |
22 | + | |
23 | +Requires Python β₯ 3.11 (abi3 wheel). The wheel bundles the entire Rust runtime β | |
24 | +no separate binary, no uvicorn, no gunicorn needed. | |
25 | + | |
26 | +**Verify:** | |
27 | +```python | |
28 | +import justapi | |
29 | +print(justapi.__version__) # "2.0.9" | |
30 | +``` | |
31 | + | |
32 | +--- | |
33 | + | |
34 | +## 2. Minimal App (the mental model) | |
35 | + | |
36 | +```python | |
37 | +from justapi import JustAPIApp | |
38 | + | |
39 | +app = JustAPIApp() | |
40 | + | |
41 | +@app.get("/") | |
42 | +def read_root(): | |
43 | + return {"Hello": "World"} | |
44 | + | |
45 | +app.run() # defaults to 127.0.0.1:8000 | |
46 | +``` | |
47 | + | |
48 | +That is a complete server: routing, JSON serialization, OpenAPI docs at `/docs`, | |
49 | +ReDoc at `/redoc`, all in Rust. `app.run()` blocks; bind explicitly with | |
50 | +`app.run("0.0.0.0:8080")`. | |
51 | + | |
52 | +**The core contract:** handlers return Python objects (dict, list, str, int, | |
53 | +Pydantic model, dataclass, Response). Rust serializes them to JSON. Exceptions | |
54 | +become HTTP errors. | |
55 | + | |
56 | +--- | |
57 | + | |
58 | +## 3. Routing β all methods | |
59 | + | |
60 | +```python | |
61 | +from justapi import JustAPIApp | |
62 | + | |
63 | +app = JustAPIApp() | |
64 | + | |
65 | +@app.get("/items/{item_id}") # path params: type-annotated β validated | |
66 | +def read_item(item_id: int, q: str | None = None): # query params auto-extracted | |
67 | + return {"item_id": item_id, "q": q} | |
68 | + | |
69 | +@app.post("/items", body_schema=ItemCreate) # body validated (Schema or Pydantic model) | |
70 | +def create_item(request): | |
71 | + data = request.json() # parsed + validated body | |
72 | + return {"created": True, **data} | |
73 | + | |
74 | +@app.put("/items/{item_id}") | |
75 | +@app.patch("/items/{item_id}") | |
76 | +@app.delete("/items/{item_id}") | |
77 | +@app.head("/health") | |
78 | +@app.options("/anything") | |
79 | +@app.trace("/trace") | |
80 | +def handler(request): ... | |
81 | + | |
82 | +# HTTP QUERY method (RFC 10008) | |
83 | +@app.query("/search") | |
84 | +def search(request): | |
85 | + return {"results": []} | |
86 | + | |
87 | +# WebSocket | |
88 | +@app.websocket("/ws") | |
89 | +async def ws_handler(websocket): | |
90 | + await websocket.accept() | |
91 | + await websocket.send_text("hello") | |
92 | + await websocket.close() | |
93 | + | |
94 | +# Server-Sent Events (Python generator) | |
95 | +@app.sse("/stream") | |
96 | +def stream(request): | |
97 | + for i in range(10): | |
98 | + yield {"n": i} | |
99 | +``` | |
100 | + | |
101 | +**Path/query params:** declare them as function arguments with type annotations. | |
102 | +`item_id: int` validates; `q: str | None = None` makes it optional; `request` | |
103 | +gives full access to method, headers, body, query, path params, client, auth. | |
104 | + | |
105 | +--- | |
106 | + | |
107 | +## 4. Request Body Validation (Pydantic bridge + native Schema) | |
108 | + | |
109 | +```python | |
110 | +from pydantic import BaseModel, Field | |
111 | +from justapi import JustAPIApp, Schema | |
112 | + | |
113 | +# Option A: Pydantic v2 models (FastAPI-compatible) | |
114 | +class ItemCreate(BaseModel): | |
115 | + name: str = Field(..., min_length=1, max_length=100) | |
116 | + price: float = Field(..., ge=0) | |
117 | + tags: list[str] = [] | |
118 | + | |
119 | +# Option B: native JustAPI Schema (validated in Rust β faster) | |
120 | +class ItemCreateRust(Schema): | |
121 | + name: str = Field(..., min_length=1) | |
122 | + price: float = Field(..., ge=0) | |
123 | + | |
124 | +@app.post("/items", body_schema=ItemCreate) # Pydantic v2 models work as body_schema | |
125 | +def create(request): | |
126 | + item = request.json() # validated dict (Rust engine validates) | |
127 | + return item | |
128 | + | |
129 | +@app.post("/items2", body_schema=ItemCreateRust) # native Schema β validated in Rust | |
130 | +def create2(request): | |
131 | + return request.json() | |
132 | +``` | |
133 | + | |
134 | +**IMPORTANT:** Pydantic models are validated via `body_schema=` (their JSON | |
135 | +Schema is compiled in Rust). Unlike FastAPI, Pydantic models are NOT injected | |
136 | +as handler arguments β read the validated body from `request.json()`. Native | |
137 | +`justapi.Schema` subclasses work the same way (fastest path). | |
138 | + | |
139 | +Validation errors β `422` with RFC 9457-style detail. Handlers can also use | |
140 | +`request["body"]` (parsed) or `request["body_raw"]` (bytes). | |
141 | + | |
142 | +--- | |
143 | + | |
144 | +## 5. Dependencies & DI | |
145 | + | |
146 | +```python | |
147 | +from justapi import Depends | |
148 | + | |
149 | +async def get_db(): | |
150 | + return app.db | |
151 | + | |
152 | +def get_current_user(auth=Depends(get_auth)): | |
153 | + ... | |
154 | + | |
155 | +@app.get("/users/me") | |
156 | +def read_me(user: dict = Depends(get_current_user)): | |
157 | + return user | |
158 | + | |
159 | +# Generator dependencies (yield) β cleanup runs after response | |
160 | +async def get_session(): | |
161 | + session = open_session() | |
162 | + try: | |
163 | + yield session | |
164 | + finally: | |
165 | + session.close() | |
166 | + | |
167 | +# Cached per-request | |
168 | +@app.get("/x") | |
169 | +def x(db=Depends(get_db, use_cache=True)): | |
170 | + ... | |
171 | +``` | |
172 | + | |
173 | +DI supports: sync/async functions, classes, generators (sync+async) with | |
174 | +`finally` cleanup, sub-dependencies, and `Security(dep, scopes=[...])` for | |
175 | +OAuth2 scopes. | |
176 | + | |
177 | +--- | |
178 | + | |
179 | +## 6. Database β synchronous + NATIVE ASYNC | |
180 | + | |
181 | +```python | |
182 | +app.set_database("postgres://user:pass@host/db") # or sqlite://file.db, mysql://... | |
183 | +app.connect_database() | |
184 | + | |
185 | +# In sync handlers (blocks the GIL worker, releases GIL during query): | |
186 | +def handler(request): | |
187 | + rows = app.db.query("SELECT * FROM users WHERE id = ?", [1]) | |
188 | + n = app.db.execute("UPDATE users SET name = ? WHERE id = ?", ["x", 1]) | |
189 | + | |
190 | +# NATIVE ASYNC (recommended for async handlers β NEVER blocks the loop): | |
191 | +@app.get("/users/{uid}") | |
192 | +async def get_user(uid: int): | |
193 | + row = await app.db.query_async("SELECT * FROM users WHERE id = ?", [uid]) | |
194 | + return row | |
195 | + | |
196 | +@app.post("/users") | |
197 | +async def create_user(request): | |
198 | + n = await app.db.execute_async("INSERT INTO users (name) VALUES (?)", ["alice"]) | |
199 | + return {"affected": n} | |
200 | + | |
201 | +# Transactions | |
202 | +app.db.transaction([ | |
203 | + ["UPDATE a SET x = 1", None], | |
204 | + ["UPDATE b SET y = 2", None], | |
205 | +]) | |
206 | +``` | |
207 | + | |
208 | +**Why query_async matters:** it runs on the DB's own multi-threaded tokio | |
209 | +runtime with the GIL released for the whole execution. The asyncio loop is never | |
210 | +blocked β measured **53Γ faster than the blocking path on slow queries** | |
211 | +(320 vs 6 RPS). Use `query_async`/`execute_async` in every async handler. | |
212 | + | |
213 | +Supported DBs: SQLite, PostgreSQL, MySQL, DuckDB (via sqlx). Pooled, with | |
214 | +migrations (`justapi create` scaffolds them), WAL pragmas for SQLite, busy | |
215 | +timeout, and health checks. | |
216 | + | |
217 | +--- | |
218 | + | |
219 | +## 7. Authentication & Security | |
220 | + | |
221 | +```python | |
222 | +from justapi import JustAPIApp, Depends, Security | |
223 | +from justapi.auth import JwtAuth, OAuth2PasswordBearer | |
224 | + | |
225 | +app = JustAPIApp() | |
226 | +app.set_jwt_auth(secret="super-secret", algorithm="HS256") # or RS256/ES256 | |
227 | + | |
228 | +# JWT-protected route | |
229 | +@app.get("/protected") | |
230 | +def protected(request): | |
231 | + claims = request.auth_claims # {"sub": ..., "role": ...} | |
232 | + return claims | |
233 | + | |
234 | +# OAuth2 password flow | |
235 | +oauth2 = OAuth2PasswordBearer(tokenUrl="/token") | |
236 | + | |
237 | +@app.post("/token") | |
238 | +def login(form: OAuth2PasswordRequestForm = Depends()): | |
239 | + ... # validate, issue JWT | |
240 | + | |
241 | +@app.get("/me") | |
242 | +def me(user=Security(get_current_user, scopes=["read"])): | |
243 | + return user | |
244 | + | |
245 | +# Rate limiting (GCRA, Redis-distributed; feature: redis-rate-limit) | |
246 | +from justapi import RateLimiter | |
247 | +limiter = await RateLimiter.new_redis("redis://localhost:6379/0") | |
248 | +result = await limiter.check_limit(key="user:123", limit=10, window_seconds=60) | |
249 | +if not result.allowed: | |
250 | + raise HTTPException(status_code=429, detail="rate limit exceeded") | |
251 | + | |
252 | +# CORS | |
253 | +app.add_cors(allow_origins=["https://example.com"], allow_methods=["GET", "POST"]) | |
254 | + | |
255 | +# Security headers: HSTS, CSP, X-Frame-Options, nosniff β built-in | |
256 | +``` | |
257 | + | |
258 | +--- | |
259 | + | |
260 | +## 8. Middleware | |
261 | + | |
262 | +```python | |
263 | +@app.middleware("http") | |
264 | +async def timing_middleware(request, call_next): | |
265 | + start = time.perf_counter() | |
266 | + response = await call_next(request) | |
267 | + response.headers["x-timing"] = str(time.perf_counter() - start) | |
268 | + return response | |
269 | + | |
270 | +# Built-in Rust middleware (near-zero overhead): | |
271 | +# CORS, SecurityHeaders, JwtAuth, RateLimiter, IpRateLimiter, | |
272 | +# CircuitBreaker (per-route), Compression (gzip/brotli/zstd), StaticDir | |
273 | +``` | |
274 | + | |
275 | +--- | |
276 | + | |
277 | +## 9. Background Tasks, Scheduler, Streaming | |
278 | + | |
279 | +```python | |
280 | +from justapi import BackgroundTasks | |
281 | + | |
282 | +@app.post("/send-email") | |
283 | +def send(request, background: BackgroundTasks): | |
284 | + background.add_task(send_email_async, "[email protected]") | |
285 | + return {"queued": True} | |
286 | + | |
287 | +# Cron scheduler (native Rust scheduler β GIL released, UTC) | |
288 | +app.schedule("0 */6 * * *", cleanup_job) # 5-field cron, UTC | |
289 | +app.every(3600, poll_upstream, url="https://...") # interval job | |
290 | + | |
291 | +# Streaming responses | |
292 | +@app.get("/stream") | |
293 | +def stream(request): | |
294 | + def gen(): | |
295 | + for i in range(100): | |
296 | + yield f"data: {i}\n\n" | |
297 | + return StreamingResponse(gen(), media_type="text/event-stream") | |
298 | + | |
299 | +# RUST-NATIVE SSE (zero Python per event β fastest path): | |
300 | +app.sse_native("/events", count=1000, interval_ms=0) # 1000 events, no delay | |
301 | + | |
302 | +# TokenStreamResponse for LLM token streaming with validation | |
303 | +from justapi import TokenStreamResponse | |
304 | +``` | |
305 | + | |
306 | +--- | |
307 | + | |
308 | +## 10. Advanced Features | |
309 | + | |
310 | +```python | |
311 | +from justapi import JustAPIApp, native_async | |
312 | + | |
313 | +# @native_async: marks handlers for the fastest dispatch; on free-threaded | |
314 | +# CPython 3.14t the dispatch runs in true parallel across cores. | |
315 | +@app.get("/users/{uid}") | |
316 | +@native_async | |
317 | +async def get_user(request, uid: int): | |
318 | + return await app.db.query_async("SELECT * FROM users WHERE id = ?", [uid]) | |
319 | + | |
320 | +# Class-based controllers | |
321 | +from justapi import Controller, controller | |
322 | + | |
323 | +@controller("/api") | |
324 | +class UsersController(Controller): | |
325 | + @route_get("/{uid}") | |
326 | + def get_user(self, uid: int): ... | |
327 | + | |
328 | +# APIRouter (sub-applications / multiple files) | |
329 | +from justapi import APIRouter | |
330 | +router = APIRouter(prefix="/v1") | |
331 | +@router.get("/items") | |
332 | +def items(): ... | |
333 | +app.include_router(router) | |
334 | + | |
335 | +# Adaptive batching (bursty workloads) | |
336 | +from justapi import adaptive_batch | |
337 | + | |
338 | +@app.post("/batch") | |
339 | +@adaptive_batch(max_size=32, window_ms=10) | |
340 | +def batch_handler(requests): ... | |
341 | + | |
342 | +# GraphQL / gRPC / JSON-RPC β all mountable | |
343 | +app.graphql(path="/graphql") | |
344 | +app.set_grpc_addr("0.0.0.0:50051") | |
345 | + | |
346 | +# HTTP/3 (QUIC) β only Python framework that ships it | |
347 | +app.enable_http3("cert.pem", "key.pem") | |
348 | +``` | |
349 | + | |
350 | +--- | |
351 | + | |
352 | +## 11. Error Handling | |
353 | + | |
354 | +```python | |
355 | +from justapi import HTTPException, RequestValidationError | |
356 | + | |
357 | +@app.get("/items/{item_id}") | |
358 | +def get(item_id: int): | |
359 | + if item_id < 0: | |
360 | + raise HTTPException(status_code=400, detail="negative id") | |
361 | + return {"id": item_id} | |
362 | + | |
363 | +# Custom exception handlers | |
364 | +@app.exception_handler(MyError) | |
365 | +def handle_my_error(request, exc): | |
366 | + return {"error": str(exc)}, 400 | |
367 | + | |
368 | +# Status codes: return (body, status) tuples or Response(status_code=...) | |
369 | +return {"done": True}, 201 | |
370 | +``` | |
371 | + | |
372 | +--- | |
373 | + | |
374 | +## 12. Testing | |
375 | + | |
376 | +```python | |
377 | +from justapi import JustAPIApp, JustAPITestClient | |
378 | + | |
379 | +app = JustAPIApp() | |
380 | +@app.get("/") | |
381 | +def root(): return {"ok": True} | |
382 | + | |
383 | +client = JustAPITestClient(app) | |
384 | +r = client.get("/") # synchronous, in-process, no server needed | |
385 | +assert r["status"] == 200 | |
386 | + | |
387 | +# Async tests | |
388 | +from justapi.testing import AsyncTestClient | |
389 | +import asyncio | |
390 | + | |
391 | +async def main(): | |
392 | + async with AsyncTestClient(app) as c: | |
393 | + r = await c.get("/") | |
394 | + assert r["status"] == 200 | |
395 | +asyncio.run(main()) | |
396 | + | |
397 | +# Database testing helpers: ManagedDb, db_client fixtures (transactional) | |
398 | +``` | |
399 | + | |
400 | +--- | |
401 | + | |
402 | +## 13. Deployment | |
403 | + | |
404 | +```bash | |
405 | +# Multi-worker prefork (saturate all cores) β 1.88x at 4 workers | |
406 | +justapi serve --workers 4 --scale | |
407 | + | |
408 | +# Docker (multi-stage, uv-based) | |
409 | +docker build -t my-app . | |
410 | +docker run -p 8000:8000 my-app | |
411 | + | |
412 | +# Kubernetes (Helm) | |
413 | +helm install my-api helm/justapi/ --set image.repository=your-registry/app | |
414 | + | |
415 | +# Direct | |
416 | +python main.py # app.run("0.0.0.0:8000") | |
417 | +``` | |
418 | + | |
419 | +Production checklist: TLS via rustls (`app.run` + reverse proxy or | |
420 | +`Server::with_tls`), health checks at `/live` `/ready`, OTel tracing + | |
421 | +Prometheus metrics, circuit breakers for downstream deps, structured logging. | |
422 | + | |
423 | +--- | |
424 | + | |
425 | +## 14. Performance Contract (verified numbers) | |
426 | + | |
427 | +| Metric | Value | | |
428 | +|---|---| | |
429 | +| Hello-world (native fast path) | **766k req/s** (p99 0.48ms, 12MB RSS) | | |
430 | +| JSON echo | **782k req/s** | | |
431 | +| Route lookup (500 routes) | 51 ns | | |
432 | +| Native CRUD SELECT | 181k req/s (Γ125 vs FastAPI) | | |
433 | +| Native async DB (slow query) | 320 RPS vs 6 RPS blocking (**53Γ**) | | |
434 | +| Multi-worker (4 workers) | 1.88Γ throughput | | |
435 | +| Free-threaded CPU-bound | 12.4Γ (GIL ceiling removed) | | |
436 | +| vs Granian | 2.4Γ hello-world, 5.4Γ JSON echo | | |
437 | + | |
438 | +**Honest limits:** Python-handler path ~60-120k req/s (GIL-bound on CPython); | |
439 | +light async handlers ~4-12k req/s (asyncio loop floor, same as every Python | |
440 | +framework); LLM/inference claims are NOT verified (no real GPU run). | |
441 | + | |
442 | +--- | |
443 | + | |
444 | +## 15. API Cheat-Sheet (quick lookup) | |
445 | + | |
446 | +```python | |
447 | +# App lifecycle | |
448 | +app = JustAPIApp(title="x", version="1.0") | |
449 | +app.run("0.0.0.0:8000") | |
450 | +app.set_database(url) / app.connect_database() / app.db | |
451 | +app.add_cors(...) / app.set_jwt_auth(...) / app.add_exception_handler(...) | |
452 | +app.sse_native(path, count, interval_ms) / app.enable_http3(cert, key) | |
453 | +app.graphql(path) / app.include_router(router) / app.middleware("http") | |
454 | +app.schedule(cron_expr, fn) / app.every(seconds, fn) | |
455 | +RateLimiter.new_redis(url) / limiter.check_limit(key, limit, window_seconds) | |
456 | + | |
457 | +# Routing decorators | |
458 | +@app.get/post/put/patch/delete/head/options/trace/query/sse/websocket(path, ...) | |
459 | +# kwargs: body_schema=, query_schema=, dependencies=[], middlewares=[], | |
460 | +# tags=[], summary=, description=, deprecated=, status_code=, | |
461 | +# responses={}, name=, include_in_schema=, native=True (Rust fast path) | |
462 | + | |
463 | +# Handler args (auto-injected by type/name) | |
464 | +request # full Request object | |
465 | +item_id: int # path param | |
466 | +q: str = None # query param | |
467 | +body: Model # validated body | |
468 | +db=Depends(...) # DI | |
469 | + | |
470 | +# DbPool methods | |
471 | +db.query(sql, params=None) -> list[dict] | |
472 | +db.execute(sql, params=None) -> int | |
473 | +db.query_async(sql, params=None) -> awaitable list[dict] | |
474 | +db.execute_async(sql, params=None) -> awaitable int | |
475 | +db.transaction([[sql, params], ...]) -> final rows | |
476 | + | |
477 | +# Exports | |
478 | +JustAPIApp, JustAPI, JustAPP, Depends, Security, APIRouter, Controller, | |
479 | +controller, route_get/post/put/patch/delete/query/sse/websocket, | |
480 | +native_async, adaptive_batch, Schema, pydantic_schema, BackgroundTasks, | |
481 | +Scheduler, TokenStreamResponse, ValidatedStreamResponse, StreamingResponse, | |
482 | +Response, HTMLResponse, JSONResponse, RedirectResponse, PlainTextResponse, | |
483 | +HTTPException, RequestValidationError, JustAPITestClient, Database, DbPool, | |
484 | +DbParam, RateLimiter, RateLimitResult, WebSocket, WebSocketState, Session, | |
485 | +Jinja2Templates, __version__ | |
486 | +``` | |
487 | + | |
488 | +--- | |
489 | + | |
490 | +## 16. Architecture Notes (why it's fast) | |
491 | + | |
492 | +1. **Everything except your code runs in Rust.** Kernel β tokio β TLS (rustls) | |
493 | + β HTTP (hyper) β router (matchit, 51ns) β middleware (Rust) β your Python | |
494 | + handler β Rust serializer (serde_json/simd-json) β socket. | |
495 | +2. **Native fast path:** routes with `native=True` + a Schema validate and | |
496 | + respond entirely in Rust β no Python at all (the 766k path). | |
497 | +3. **GIL pool:** one persistent Python worker thread; sync handlers dispatch | |
498 | + there (GIL released during I/O). Free-threaded CPython auto-detected for | |
499 | + parallel dispatch. | |
500 | +4. **Async handlers:** coroutines run on a single asyncio loop; completion is | |
501 | + callback-driven (no thread hop). Framework ops (DB via query_async, SSE via | |
502 | + sse_native) run on Rust runtimes β the loop never blocks. | |
503 | +5. **Multi-worker prefork:** `justapi serve --workers N` binds one socket, forks | |
504 | + N processes, auto-scales. Each worker gets its own GIL. | |
505 | +6. **Memory:** per-request arena + buffer pools; 12 MB RSS steady state. | |
506 | + | |
507 | +--- | |
508 | + | |
509 | +## 17. Production Do's and Don'ts | |
510 | + | |
511 | +**DO:** | |
512 | +- Use `query_async`/`execute_async` in async handlers (never `db.query` there) | |
513 | +- Use `@native_async` on async handlers doing DB/IO/SSE work | |
514 | +- Use `app.sse_native` for high-throughput event streams | |
515 | +- Set JWT auth + rate limits + CORS before exposing anything | |
516 | +- Use `--workers N` in production (N = cores) | |
517 | +- Keep Pydantic models for FastAPI-style migration; `Schema` for max speed | |
518 | +- Read BENCHMARKS.md before claiming performance numbers | |
519 | + | |
520 | +**DON'T:** | |
521 | +- Don't call blocking `db.query()` from an async handler (freezes the loop) | |
522 | +- Don't write your own JSON serialization (Rust does it faster) | |
523 | +- Don't run `--reload` in production | |
524 | +- Don't claim LLM/inference performance (unverified β no real GPU run) | |
525 | +- Don't skip exception handlers for user input (422s are automatic, 500s are not) | |
526 | + | |
527 | +--- | |
528 | + | |
529 | +## 18. Where to look for more | |
530 | + | |
531 | +| Need | Source | | |
532 | +|---|---| | |
533 | +| Full API docs | https://github.com/swadhinbiswas/JustAPI/tree/main/docs_site (161 pages) | | |
534 | +| Performance ledger | BENCHMARKS.md (append-only, honest) | | |
535 | +| Roadmap/status | PLAN.md (single source of truth, honesty-coded) | | |
536 | +| Architecture decisions | DECISIONS.md (ADR-001 β ADR-093) | | |
537 | +| Migration from FastAPI | docs_site: getting-started/migrating-from-fastapi | | |
538 | +| Release history | CHANGELOG.md | | |
539 | + | |
540 | +--- | |
541 | + | |
542 | +*This file is maintained to stay in sync with the codebase. If something here | |
543 | +disagrees with the source, the source wins β file an issue.* | |
0 | 544 | |
βΌdocs_site/astro.config.mjs
+1-1
| @@ -77,7 +77,7 @@ export default defineConfig({ | ||
77 | 77 | url: site, |
78 | 78 | downloadUrl: 'https://pypi.org/project/justapi/', |
79 | 79 | installUrl: 'https://pypi.org/project/justapi/', |
80 | - softwareVersion: '2.0.8', | |
80 | + softwareVersion: '2.0.9', | |
81 | 81 | offers: { |
82 | 82 | '@type': 'Offer', |
83 | 83 | price: '0', |
84 | 84 | |
βΌdocs_site/public/og-image.png
+0-0
Binary file not shown.
βΌdocs_site/src/content/docs/getting-started/migrating-from-fastapi.md
+8-6
| @@ -98,9 +98,10 @@ def verify_token(token: str = Header(...)): | ||
98 | 98 | def get_pet(pet_id: int, token: str = Depends(verify_token)): |
99 | 99 | return {"pet_id": pet_id} |
100 | 100 | |
101 | -@app.post("/pets/") | |
102 | -def create_pet(pet: Pet): | |
103 | - return {"name": pet.name} | |
101 | +@app.post("/pets/", body_schema=Pet) | |
102 | +def create_pet(request): | |
103 | + pet = request.json() # validated by Pydantic's JSON Schema (Rust engine) | |
104 | + return {"name": pet["name"]} | |
104 | 105 | ``` |
105 | 106 | |
106 | 107 | ### JustAPI (Same Logic) |
| @@ -123,9 +124,10 @@ def verify_token(token: str = Header(...)): | ||
123 | 124 | def get_pet(pet_id: int, token: str = Depends(verify_token)): |
124 | 125 | return {"pet_id": pet_id} |
125 | 126 | |
126 | -@app.post("/pets/") | |
127 | -def create_pet(pet: Pet): | |
128 | - return {"name": pet.name} | |
127 | +@app.post("/pets/", body_schema=Pet) | |
128 | +def create_pet(request): | |
129 | + pet = request.json() # validated by Pydantic's JSON Schema (Rust engine) | |
130 | + return {"name": pet["name"]} | |
129 | 131 | ``` |
130 | 132 | |
131 | 133 | ## Middleware Migration |
132 | 134 | |
βΌdocs_site/src/content/docs/index.mdx
+17-9
| @@ -21,15 +21,14 @@ import { Card, CardGrid } from '@astrojs/starlight/components'; | ||
21 | 21 | |
22 | 22 | ## Performance |
23 | 23 | |
24 | -| Framework | Requests/sec | p50 Latency | p99 Latency | | |
25 | -|---|--:|--:|--:| | |
26 | -| **JustAPI (native fast path)** | **701,234** | **0.07 ms** | **0.19 ms** | | |
27 | -| **JustAPI (Python handler)** | **60,297** | **1.03 ms** | **5.21 ms** | | |
28 | -| Robyn 0.88 | 39,103 | 1.78 ms | 11.47 ms | | |
29 | -| Granian | 69,195 | 0.72 ms | 3.87 ms | | |
30 | -| FastAPI + Uvicorn | 36,189 | 1.72 ms | 24.63 ms | | |
24 | +| Framework | Hello-world req/s | JSON echo | p99 latency | RSS | | |
25 | +|---|--:|--:|--:|--:| | |
26 | +| **JustAPI (native fast path)** | **766,000** | **782,000** | **0.48 ms** | **12 MB** | | |
27 | +| Granian (ASGI) | 314,000 | 145,000 | 0.74 ms | 37 MB | | |
28 | +| Robyn | 39,000 | β | β | β | | |
29 | +| FastAPI + Uvicorn | 36,000 | 33,000 | 24.63 ms | 29 MB | | |
31 | 30 | |
32 | -All measurements on the same hardware (i5-13600K, 20 threads), 100 concurrent connections, 30s duration. | |
31 | +All measurements on the same hardware, 100 concurrent connections, 30s duration. Full methodology in [BENCHMARKS.md](https://github.com/swadhinbiswas/JustAPI/blob/main/BENCHMARKS.md). | |
33 | 32 | |
34 | 33 | ## What It Does |
35 | 34 | |
| @@ -70,7 +69,16 @@ Open `/docs` for Swagger UI, `/redoc` for ReDoc, and the full OpenAPI schema β | ||
70 | 69 | JWT auth, rate limiting, circuit breakers, OpenTelemetry, health checks. |
71 | 70 | </Card> |
72 | 71 | <Card title="Multi-Protocol" icon="globe"> |
73 | - REST, GraphQL, gRPC, JSON-RPC 2.0 β all in one app. | |
72 | + REST, GraphQL, gRPC, JSON-RPC 2.0, WebSocket, SSE β all in one app. | |
73 | + </Card> | |
74 | + <Card title="Native Async DB" icon="rocket"> | |
75 | + `await app.db.query_async()` β SQL runs on Rust's tokio runtime, GIL released. 53Γ faster on slow queries. | |
76 | + </Card> | |
77 | + <Card title="Rust-Native SSE" icon="activity"> | |
78 | + `app.sse_native()` streams events with zero Python per event. | |
79 | + </Card> | |
80 | + <Card title="Free-Threathed 3.14t" icon="shield"> | |
81 | + CPU-bound Python scales with cores β 12.4Γ measured. | |
74 | 82 | </Card> |
75 | 83 | <Card title="Auto Docs" icon="document"> |
76 | 84 | Swagger UI, ReDoc, Scalar. Generated from your code. |
77 | 85 | |