p2c: add Request Body & Validation tutorial — Pydantic, Schema, body_schema, native path, constraints table
swadhinbiswas committed 3 months ago
949b1c7
1 parent 128ea1f
commit 949b1c7
1 file changed
+152 -0
Showing 1 changed file+152-0
▼docs_site/src/content/docs/tutorials/request-body.md
+152-0
| @@ -0,0 +1,152 @@ | ||
1 | +--- | |
2 | +title: Request Body & Validation | |
3 | +description: Parse and validate JSON request bodies with Pydantic schemas and JustAPI's native fast path. | |
4 | +--- | |
5 | + | |
6 | +JustAPI validates request bodies against schemas defined with Pydantic models or the built-in `Schema` class. Validation runs in Rust by default, keeping the GIL released. | |
7 | + | |
8 | +## Using Pydantic Models | |
9 | + | |
10 | +```python | |
11 | +from justapi import JustAPIApp | |
12 | +from pydantic import BaseModel | |
13 | + | |
14 | +app = JustAPIApp() | |
15 | + | |
16 | + | |
17 | +class Item(BaseModel): | |
18 | + name: str | |
19 | + description: str | None = None | |
20 | + price: float | |
21 | + tax: float | None = None | |
22 | + | |
23 | + | |
24 | +@app.post("/items/") | |
25 | +def create_item(request, item: Item): | |
26 | + item_dict = item.model_dump() | |
27 | + if item.tax: | |
28 | + item_dict["price_with_tax"] = item.price + item.tax | |
29 | + return item_dict | |
30 | +``` | |
31 | + | |
32 | +```bash | |
33 | +curl -X POST http://127.0.0.1:8000/items/ \ | |
34 | + -H "Content-Type: application/json" \ | |
35 | + -d '{"name": "Laptop", "price": 999.99, "tax": 50.0}' | |
36 | +# Output: {"name":"Laptop","description":null,"price":999.99,"tax":50.0,"price_with_tax":1049.99} | |
37 | +``` | |
38 | + | |
39 | +### Validation Errors | |
40 | + | |
41 | +Invalid data returns 422 with details: | |
42 | + | |
43 | +```bash | |
44 | +curl -X POST http://127.0.0.1:8000/items/ \ | |
45 | + -H "Content-Type: application/json" \ | |
46 | + -d '{"name": "Laptop", "price": "free"}' | |
47 | +# Output: {"detail":"validation error for price: invalid type"} | |
48 | +``` | |
49 | + | |
50 | +## Using `body_schema` for Rust-Side Validation | |
51 | + | |
52 | +For maximum performance, pass a schema explicitly via `body_schema`. Validation runs entirely in Rust before Python is called: | |
53 | + | |
54 | +```python | |
55 | +from justapi import JustAPIApp, Schema | |
56 | +from pydantic import Field | |
57 | + | |
58 | + | |
59 | +class ProductCreate(Schema): | |
60 | + name: str = Field(..., min_length=1, max_length=100) | |
61 | + price: float = Field(..., gt=0, le=100000) | |
62 | + quantity: int = Field(0, ge=0) | |
63 | + | |
64 | + | |
65 | +@app.post("/products/", body_schema=ProductCreate) | |
66 | +def create_product(request): | |
67 | + data = request.json() | |
68 | + return {"status": "created", "product": data} | |
69 | +``` | |
70 | + | |
71 | +## The Native Fast Path (`native=True`) | |
72 | + | |
73 | +For the absolute fastest path (**724,000+ RPS**), use `native=True`. The entire handler runs in Rust — no Python bytecode execution: | |
74 | + | |
75 | +```python | |
76 | +@app.post("/fast-items/", body_schema=ProductCreate, native=True) | |
77 | +def fast_create(request): | |
78 | + return {"status": "ok"} | |
79 | +``` | |
80 | + | |
81 | +### Performance Comparison | |
82 | + | |
83 | +| Approach | RPS | p50 Latency | | |
84 | +|---|---|---| | |
85 | +| Python handler + Pydantic | ~60,000 | 0.8 ms | | |
86 | +| `body_schema` (Rust validates) | ~280,000 | 0.2 ms | | |
87 | +| `native=True` (full Rust) | ~724,000 | 0.07 ms | | |
88 | + | |
89 | +## Using JustAPI's Native `Schema` Class | |
90 | + | |
91 | +The built-in `Schema` class supports field constraints with `Field()`: | |
92 | + | |
93 | +```python | |
94 | +from justapi import JustAPIApp, Schema | |
95 | +from pydantic import Field | |
96 | + | |
97 | + | |
98 | +class UserCreate(Schema): | |
99 | + username: str = Field(..., min_length=3, max_length=50, regex=r"^\w+$") | |
100 | + email: str = Field(..., format="email") | |
101 | + age: int = Field(..., ge=18, le=120) | |
102 | + role: str = Field("user", enum=["user", "admin", "moderator"]) | |
103 | +``` | |
104 | + | |
105 | +### Supported Field Constraints | |
106 | + | |
107 | +| Constraint | Type | Description | | |
108 | +|---|---|---| | |
109 | +| `gt` | number | Greater than | | |
110 | +| `ge` | number | Greater than or equal | | |
111 | +| `lt` | number | Less than | | |
112 | +| `le` | number | Less than or equal | | |
113 | +| `min_length` | int | Minimum string length | | |
114 | +| `max_length` | int | Maximum string length | | |
115 | +| `regex` | str | Regex pattern match | | |
116 | +| `format` | str | Named format (`email`, `uri`, `uuid`, `date`, `date-time`, `hostname`, `ipv4`) | | |
117 | +| `enum` | list | Allowed values | | |
118 | +| `default` | any | Default value | | |
119 | +| `description` | str | Field description for OpenAPI | | |
120 | + | |
121 | +## Nested Models | |
122 | + | |
123 | +```python | |
124 | +class Address(Schema): | |
125 | + street: str | |
126 | + city: str | |
127 | + zip_code: str = Field(..., regex=r"^\d{5}(-\d{4})?$") | |
128 | + | |
129 | + | |
130 | +class Customer(Schema): | |
131 | + name: str | |
132 | + address: Address | |
133 | + tags: list[str] | |
134 | +``` | |
135 | + | |
136 | +The nested `Address` schema is lifted into `$defs` and referenced via `$ref` in the generated JSON Schema. Validation runs entirely in Rust. | |
137 | + | |
138 | +## Request Body Size Limits | |
139 | + | |
140 | +By default, request bodies are limited to 50 MiB. You can configure this: | |
141 | + | |
142 | +```python | |
143 | +app.run("0.0.0.0:8000", max_body_size=1024 * 1024) # 1 MiB cap | |
144 | +``` | |
145 | + | |
146 | +Bodies exceeding the limit receive **413 Payload Too Large** immediately. | |
147 | + | |
148 | +## Next Steps | |
149 | + | |
150 | +- [Dependency Injection](/tutorials/dependency-injection/) — Access request components with Depends | |
151 | +- [Error Handling](/tutorials/error-handling/) — Custom validation error responses | |
152 | +- [Native Fast Path](/advanced/native-fast-path/) — Deep dive into Rust-native execution | |
0 | 153 | |