25f6395
commit 25f6395
5 files changed
+427 -0
Showing 5 changed files+427-0
docs_site/src/content/docs/tutorials/body-fields.mddocs_site/src/content/docs/tutorials/body-multiple-params.mddocs_site/src/content/docs/tutorials/body-nested-models.mddocs_site/src/content/docs/tutorials/path-params-numeric-validations.mddocs_site/src/content/docs/tutorials/query-params-str-validations.md
▼docs_site/src/content/docs/tutorials/body-fields.md
+77-0
| @@ -0,0 +1,77 @@ | ||
1 | +--- | |
2 | +title: Body — Fields | |
3 | +description: Use Field() for Pydantic model field-level validation, defaults, and descriptions in JustAPI. | |
4 | +keywords: [JustAPI, Field, Pydantic, validation, field-level, model fields] | |
5 | +--- | |
6 | + | |
7 | +## Using Field() | |
8 | + | |
9 | +Use `Field()` to add metadata and constraints to Pydantic model fields: | |
10 | + | |
11 | +```python | |
12 | +from pydantic import BaseModel, Field | |
13 | +from typing import Optional | |
14 | +from justapi import JustAPIApp | |
15 | + | |
16 | +class Item(BaseModel): | |
17 | + name: str = Field(..., min_length=1, max_length=100) | |
18 | + price: float = Field(..., gt=0) | |
19 | + description: Optional[str] = Field(None, max_length=500) | |
20 | + tags: list[str] = Field(default_factory=list) | |
21 | + | |
22 | +app = JustAPIApp() | |
23 | + | |
24 | +@app.post("/items") | |
25 | +def create_item(item: Item): | |
26 | + return item.model_dump() | |
27 | +``` | |
28 | + | |
29 | +## Field Options | |
30 | + | |
31 | +| Option | Type | Description | | |
32 | +|--------|------|-------------| | |
33 | +| `default` | `Any` | Default value | | |
34 | +| `alias` | `str` | Alternative field name | | |
35 | +| `title` | `str` | Title in OpenAPI docs | | |
36 | +| `description` | `str` | Description in OpenAPI docs | | |
37 | +| `examples` | `list` | Example values | | |
38 | +| `min_length` | `int` | Minimum string length | | |
39 | +| `max_length` | `int` | Maximum string length | | |
40 | +| `gt` | `Number` | Greater than | | |
41 | +| `ge` | `Number` | Greater than or equal | | |
42 | +| `lt` | `Number` | Less than | | |
43 | +| `le` | `Number` | Less than or equal | | |
44 | +| `pattern` | `str` | Regex pattern | | |
45 | + | |
46 | +## Examples | |
47 | + | |
48 | +```python | |
49 | +class CreateUser(BaseModel): | |
50 | + username: str = Field( | |
51 | + ..., | |
52 | + min_length=3, | |
53 | + max_length=50, | |
54 | + pattern=r"^[a-zA-Z0-9_]+$", | |
55 | + title="Username", | |
56 | + description="Alphanumeric characters and underscores only", | |
57 | + examples=["alice_123"], | |
58 | + ) | |
59 | + email: str = Field( | |
60 | + ..., | |
61 | + pattern=r"^[\w.-]+@[\w.-]+\.\w+$", | |
62 | + title="Email address", | |
63 | + ) | |
64 | + age: int = Field( | |
65 | + ..., | |
66 | + ge=13, | |
67 | + le=120, | |
68 | + title="Age", | |
69 | + description="Must be between 13 and 120", | |
70 | + ) | |
71 | +``` | |
72 | + | |
73 | +## See Also | |
74 | + | |
75 | +- [Schema & Validation](/api-reference/schema-validation/) — validation reference | |
76 | +- [Body — Multiple Parameters](/tutorials/body-multiple-params/) — multiple body params | |
77 | +- [Body — Nested Models](/tutorials/body-nested-models/) — nested models | |
0 | 78 | |
▼docs_site/src/content/docs/tutorials/body-multiple-params.md
+91-0
| @@ -0,0 +1,91 @@ | ||
1 | +--- | |
2 | +title: Body — Multiple Parameters | |
3 | +description: Declare multiple body parameters and mix path, query, and body parameters in JustAPI. | |
4 | +keywords: [JustAPI, body parameters, multiple parameters, Body, mixed parameters] | |
5 | +--- | |
6 | + | |
7 | +## Mixing Parameter Sources | |
8 | + | |
9 | +JustAPI automatically determines where each parameter comes from: | |
10 | + | |
11 | +```python | |
12 | +from pydantic import BaseModel | |
13 | +from justapi import JustAPIApp | |
14 | + | |
15 | +class Item(BaseModel): | |
16 | + name: str | |
17 | + price: float | |
18 | + | |
19 | +app = JustAPIApp() | |
20 | + | |
21 | +@app.put("/items/{item_id}") | |
22 | +def update_item( | |
23 | + item_id: int, # path parameter | |
24 | + item: Item, # body parameter (Pydantic model) | |
25 | + q: str = None, # query parameter | |
26 | +): | |
27 | + result = {"item_id": item_id, **item.model_dump()} | |
28 | + if q: | |
29 | + result["q"] = q | |
30 | + return result | |
31 | +``` | |
32 | + | |
33 | +## Multiple Body Parameters | |
34 | + | |
35 | +For multiple Pydantic models in the body, use `Body(...)`: | |
36 | + | |
37 | +```python | |
38 | +from pydantic import BaseModel | |
39 | +from justapi import JustAPIApp, Body | |
40 | + | |
41 | +class User(BaseModel): | |
42 | + name: str | |
43 | + email: str | |
44 | + | |
45 | +class Item(BaseModel): | |
46 | + name: str | |
47 | + price: float | |
48 | + | |
49 | +app = JustAPIApp() | |
50 | + | |
51 | +@app.post("/users/{user_id}/items/") | |
52 | +def create_user_item( | |
53 | + user_id: int, # path | |
54 | + user: User, # body | |
55 | + item: Item = Body(...), # explicit body | |
56 | +): | |
57 | + return {"user": user.model_dump(), "item": item.model_dump()} | |
58 | +``` | |
59 | + | |
60 | +## Singular Values in Body | |
61 | + | |
62 | +Use `Body(...)` for non-model body values: | |
63 | + | |
64 | +```python | |
65 | +@app.post("/items/") | |
66 | +def create_item( | |
67 | + name: str = Body(...), | |
68 | + price: float = Body(...), | |
69 | + description: str = Body(None), | |
70 | +): | |
71 | + return {"name": name, "price": price, "description": description} | |
72 | +``` | |
73 | + | |
74 | +## Embedding Body | |
75 | + | |
76 | +By default, the body is read from the root of the JSON. To nest it: | |
77 | + | |
78 | +```python | |
79 | +@app.post("/items/") | |
80 | +def create_item( | |
81 | + item: Item = Body(..., embed=True), | |
82 | +): | |
83 | + # Client sends: {"item": {"name": "foo", "price": 42.0}} | |
84 | + return item.model_dump() | |
85 | +``` | |
86 | + | |
87 | +## See Also | |
88 | + | |
89 | +- [Request Body](/tutorials/request-body/) — basic body usage | |
90 | +- [Body — Fields](/tutorials/body-fields/) — field-level validation | |
91 | +- [Body — Nested Models](/tutorials/body-nested-models/) — nested Pydantic models | |
0 | 92 | |
▼docs_site/src/content/docs/tutorials/body-nested-models.md
+113-0
| @@ -0,0 +1,113 @@ | ||
1 | +--- | |
2 | +title: Body — Nested Models | |
3 | +description: Use nested Pydantic models for complex JSON structures in JustAPI. | |
4 | +keywords: [JustAPI, nested models, Pydantic, complex JSON, deeply nested] | |
5 | +--- | |
6 | + | |
7 | +## Basic Nested Models | |
8 | + | |
9 | +```python | |
10 | +from pydantic import BaseModel | |
11 | +from typing import Optional | |
12 | +from justapi import JustAPIApp | |
13 | + | |
14 | +class Address(BaseModel): | |
15 | + street: str | |
16 | + city: str | |
17 | + state: str | |
18 | + zip_code: str | |
19 | + | |
20 | +class User(BaseModel): | |
21 | + name: str | |
22 | + email: str | |
23 | + address: Address | |
24 | + secondary_address: Optional[Address] = None | |
25 | + | |
26 | +app = JustAPIApp() | |
27 | + | |
28 | +@app.post("/users") | |
29 | +def create_user(user: User): | |
30 | + return user.model_dump() | |
31 | +``` | |
32 | + | |
33 | +The client sends: | |
34 | + | |
35 | +```json | |
36 | +{ | |
37 | + "name": "Alice", | |
38 | + "email": "[email protected]", | |
39 | + "address": { | |
40 | + "street": "123 Main St", | |
41 | + "city": "Springfield", | |
42 | + "state": "IL", | |
43 | + "zip_code": "62701" | |
44 | + } | |
45 | +} | |
46 | +``` | |
47 | + | |
48 | +## Lists of Nested Models | |
49 | + | |
50 | +```python | |
51 | +class Tag(BaseModel): | |
52 | + name: str | |
53 | + color: str = "gray" | |
54 | + | |
55 | +class Item(BaseModel): | |
56 | + name: str | |
57 | + tags: list[Tag] = [] | |
58 | + | |
59 | +@app.post("/items") | |
60 | +def create_item(item: Item): | |
61 | + return item.model_dump() | |
62 | +``` | |
63 | + | |
64 | +```json | |
65 | +{ | |
66 | + "name": "Widget", | |
67 | + "tags": [ | |
68 | + {"name": "hardware", "color": "blue"}, | |
69 | + {"name": "sale", "color": "red"} | |
70 | + ] | |
71 | +} | |
72 | +``` | |
73 | + | |
74 | +## Deeply Nested Structures | |
75 | + | |
76 | +```python | |
77 | +class Comment(BaseModel): | |
78 | + author: str | |
79 | + text: str | |
80 | + replies: list["Comment"] = [] | |
81 | + | |
82 | +class Post(BaseModel): | |
83 | + title: str | |
84 | + content: str | |
85 | + comments: list[Comment] = [] | |
86 | + | |
87 | +@app.post("/posts") | |
88 | +def create_post(post: Post): | |
89 | + return post.model_dump() | |
90 | +``` | |
91 | + | |
92 | +## Validation at Every Level | |
93 | + | |
94 | +JustAPI validates the entire nested structure. Invalid data at any level returns a clear error: | |
95 | + | |
96 | +```json | |
97 | +{ | |
98 | + "detail": [ | |
99 | + { | |
100 | + "type": "string_type", | |
101 | + "loc": ["body", "address", "zip_code"], | |
102 | + "msg": "Input should be a valid string", | |
103 | + "input": 12345 | |
104 | + } | |
105 | + ] | |
106 | +} | |
107 | +``` | |
108 | + | |
109 | +## See Also | |
110 | + | |
111 | +- [Request Body](/tutorials/request-body/) — basic Pydantic usage | |
112 | +- [Body — Fields](/tutorials/body-fields/) — field-level validation | |
113 | +- [Extra Models](/tutorials/extra-models/) — multiple related models | |
0 | 114 | |
▼docs_site/src/content/docs/tutorials/path-params-numeric-validations.md
+68-0
| @@ -0,0 +1,68 @@ | ||
1 | +--- | |
2 | +title: Path Parameters and Numeric Validations | |
3 | +description: Add numeric validation constraints to path parameters — ge, le, gt, lt, multiple_of. | |
4 | +keywords: [JustAPI, path parameters, numeric validation, ge, le, gt, lt, Path] | |
5 | +--- | |
6 | + | |
7 | +## Using Path() for Validation | |
8 | + | |
9 | +Use `Path()` to add numeric constraints: | |
10 | + | |
11 | +```python | |
12 | +from justapi import JustAPIApp, Path | |
13 | + | |
14 | +app = JustAPIApp() | |
15 | + | |
16 | +@app.get("/items/{item_id}") | |
17 | +def get_item(item_id: int = Path(..., ge=1)): | |
18 | + return {"item_id": item_id} | |
19 | +``` | |
20 | + | |
21 | +The `ge=1` constraint means `item_id` must be >= 1. Sending `/items/0` returns a validation error. | |
22 | + | |
23 | +## Validation Options | |
24 | + | |
25 | +| Parameter | Type | Description | | |
26 | +|-----------|------|-------------| | |
27 | +| `ge` | `int/float` | Greater than or equal | | |
28 | +| `le` | `int/float` | Less than or equal | | |
29 | +| `gt` | `int/float` | Greater than | | |
30 | +| `lt` | `int/float` | Less than | | |
31 | +| `multiple_of` | `int/float` | Must be a multiple of this value | | |
32 | + | |
33 | +## Examples | |
34 | + | |
35 | +```python | |
36 | +@app.get("/users/{user_id}") | |
37 | +def get_user(user_id: int = Path(..., gt=0)): | |
38 | + """user_id must be positive""" | |
39 | + return {"user_id": user_id} | |
40 | + | |
41 | +@app.get("/products/{price}") | |
42 | +def get_product(price: float = Path(..., ge=0.01, le=9999.99)): | |
43 | + """price between 0.01 and 9999.99""" | |
44 | + return {"price": price} | |
45 | + | |
46 | +@app.get("/grid/{position}") | |
47 | +def get_position(position: int = Path(..., multiple_of=10)): | |
48 | + """position must be a multiple of 10""" | |
49 | + return {"position": position} | |
50 | +``` | |
51 | + | |
52 | +## Combining with Type Conversion | |
53 | + | |
54 | +```python | |
55 | +@app.get("/items/{item_id}") | |
56 | +def get_item( | |
57 | + item_id: int = Path(..., ge=1, description="Item ID, must be >= 1"), | |
58 | +): | |
59 | + return {"item_id": item_id} | |
60 | +``` | |
61 | + | |
62 | +The docs will show the constraint alongside the type. | |
63 | + | |
64 | +## See Also | |
65 | + | |
66 | +- [Path Parameters](/tutorials/path-params/) — basic path params | |
67 | +- [Query Parameters & String Validations](/tutorials/query-params-str-validations/) — string constraints | |
68 | +- [Schema & Validation](/api-reference/schema-validation/) — validation reference | |
0 | 69 | |
▼docs_site/src/content/docs/tutorials/query-params-str-validations.md
+78-0
| @@ -0,0 +1,78 @@ | ||
1 | +--- | |
2 | +title: Query Parameters and String Validations | |
3 | +description: Add string validation constraints to query parameters — min_length, max_length, regex patterns. | |
4 | +keywords: [JustAPI, query parameters, validation, min_length, max_length, regex, Query] | |
5 | +--- | |
6 | + | |
7 | +## Using Query() for Validation | |
8 | + | |
9 | +Use `Query()` to add constraints to query parameters: | |
10 | + | |
11 | +```python | |
12 | +from justapi import JustAPIApp, Query | |
13 | + | |
14 | +app = JustAPIApp() | |
15 | + | |
16 | +@app.get("/search") | |
17 | +def search( | |
18 | + q: str = Query(..., min_length=1, max_length=100), | |
19 | + tag: str = Query(None, max_length=50), | |
20 | +): | |
21 | + return {"q": q, "tag": tag} | |
22 | +``` | |
23 | + | |
24 | +## Validation Options | |
25 | + | |
26 | +| Parameter | Type | Description | | |
27 | +|-----------|------|-------------| | |
28 | +| `min_length` | `int` | Minimum string length | | |
29 | +| `max_length` | `int` | Maximum string length | | |
30 | +| `pattern` | `str` | Regex pattern the string must match | | |
31 | +| `title` | `str` | Title shown in the docs | | |
32 | +| `description` | `str` | Description shown in the docs | | |
33 | +| `deprecated` | `bool` | Mark as deprecated in the docs | | |
34 | +| `alias` | `str` | Alternative parameter name | | |
35 | + | |
36 | +## Regex Patterns | |
37 | + | |
38 | +```python | |
39 | +@app.get("/users") | |
40 | +def search_users( | |
41 | + email: str = Query(None, pattern=r"^[\w.-]+@[\w.-]+\.\w+$"), | |
42 | + phone: str = Query(None, pattern=r"^\+?\d{10,15}$"), | |
43 | +): | |
44 | + return {"email": email, "phone": phone} | |
45 | +``` | |
46 | + | |
47 | +:::tip | |
48 | +Use raw strings (`r"..."`) for regex patterns to avoid escaping issues. | |
49 | +::: | |
50 | + | |
51 | +## Aliasing | |
52 | + | |
53 | +Use `alias` when the parameter name differs from the Python variable name: | |
54 | + | |
55 | +```python | |
56 | +@app.get("/items") | |
57 | +def list_items(item_name: str = Query(..., alias="item-name")): | |
58 | + return {"item_name": item_name} | |
59 | +``` | |
60 | + | |
61 | +The client sends `?item-name=foo` but your handler receives `item_name`. | |
62 | + | |
63 | +## Deprecating Parameters | |
64 | + | |
65 | +```python | |
66 | +@app.get("/search") | |
67 | +def search(q: str = Query(..., deprecated=True)): | |
68 | + """Use the new search endpoint instead.""" | |
69 | + return {"q": q} | |
70 | +``` | |
71 | + | |
72 | +The parameter appears in the docs with a deprecation notice. | |
73 | + | |
74 | +## See Also | |
75 | + | |
76 | +- [Query Parameters](/tutorials/query-params/) — basic query params | |
77 | +- [Path Parameters & Numeric Validations](/tutorials/path-params-numeric-validations/) — numeric constraints | |
78 | +- [Body — Fields](/tutorials/body-fields/) — field-level validation | |
0 | 79 | |