p3c-f: add Routing, APIRouter, Request, Responses API reference pages
swadhinbiswas committed 3 months ago
29908fb
1 parent ddf0c91
commit 29908fb
4 files changed
+464 -0
Showing 4 changed files+464-0
▼docs_site/src/content/docs/api-reference/apirouter.md
+92-0
| @@ -0,0 +1,92 @@ | ||
1 | +--- | |
2 | +title: APIRouter | |
3 | +description: Modular router for grouping related routes in separate files. | |
4 | +--- | |
5 | + | |
6 | +`APIRouter` lets you organize routes into modular groups, then include them in the main application. | |
7 | + | |
8 | +## Constructor | |
9 | + | |
10 | +```python | |
11 | +from justapi import APIRouter | |
12 | + | |
13 | +router = APIRouter( | |
14 | + prefix="/api/v1", | |
15 | + tags=["API v1"], | |
16 | +) | |
17 | +``` | |
18 | + | |
19 | +### Parameters | |
20 | + | |
21 | +| Parameter | Type | Default | Description | | |
22 | +|---|---|---|---| | |
23 | +| `prefix` | `str` | `""` | URL prefix applied to all routes | | |
24 | +| `tags` | `list[str]` | `None` | OpenAPI tags for all routes | | |
25 | + | |
26 | +## Methods | |
27 | + | |
28 | +`APIRouter` supports the same route decorators as `JustAPIApp`: | |
29 | + | |
30 | +| Method | Signature | | |
31 | +|---|---| | |
32 | +| `get(path, dependencies, tags)` | Same as `JustAPIApp.get()` | | |
33 | +| `post(path, body_schema, schema, dependencies, tags, native)` | Same as `JustAPIApp.post()` | | |
34 | +| `put(path, body_schema, schema, dependencies, tags, native)` | Same as `JustAPIApp.put()` | | |
35 | +| `patch(path, body_schema, schema, dependencies, tags, native)` | Same as `JustAPIApp.patch()` | | |
36 | +| `delete(path, dependencies, tags)` | Same as `JustAPIApp.delete()` | | |
37 | +| `websocket(path)` | Same as `JustAPIApp.websocket()` | | |
38 | + | |
39 | +## Including Routers | |
40 | + | |
41 | +```python | |
42 | +from justapi import JustAPIApp | |
43 | +from app.routers.users import router as users_router | |
44 | +from app.routers.products import router as products_router | |
45 | + | |
46 | +app = JustAPIApp() | |
47 | +app.include_router(users_router, prefix="/api/v1") | |
48 | +``` | |
49 | + | |
50 | +### `include_router()` Parameters | |
51 | + | |
52 | +| Parameter | Type | Default | Description | | |
53 | +|---|---|---|---| | |
54 | +| `router` | `APIRouter` | required | The router to include | | |
55 | +| `prefix` | `str` | `""` | Additional prefix applied on top of the router's own prefix | | |
56 | + | |
57 | +## URL Construction | |
58 | + | |
59 | +The final URL for each route is: `include_router_prefix` + `router.prefix` + `route_path` | |
60 | + | |
61 | +```python | |
62 | +router = APIRouter(prefix="/users") | |
63 | + | |
64 | +@router.get("/{user_id}") | |
65 | +def get_user(request, user_id: int): ... | |
66 | + | |
67 | +app.include_router(router, prefix="/api/v1") | |
68 | + | |
69 | +# Final URL: /api/v1/users/{user_id} | |
70 | +``` | |
71 | + | |
72 | +## Router-Level Dependencies | |
73 | + | |
74 | +```python | |
75 | +from justapi import APIRouter, Depends, Header, HTTPException | |
76 | + | |
77 | +def require_token(authorization: str = Header(...)): | |
78 | + if not authorization.startswith("Bearer "): | |
79 | + raise HTTPException(401) | |
80 | + | |
81 | +admin_router = APIRouter(prefix="/admin", tags=["Admin"]) | |
82 | + | |
83 | +@admin_router.get("/users", dependencies=[Depends(require_token)]) | |
84 | +def list_users(request): | |
85 | + return [{"id": 1}] | |
86 | +``` | |
87 | + | |
88 | +## See Also | |
89 | + | |
90 | +- [Routing API](/api-reference/routing/) — Route decorator reference | |
91 | +- [JustAPIApp](/api-reference/justapiapp/) — Main application reference | |
92 | +- [Routing & Sub-routers Tutorial](/tutorials/routing-subrouters/) — Step-by-step guide | |
0 | 93 | |
▼docs_site/src/content/docs/api-reference/request.md
+124-0
| @@ -0,0 +1,124 @@ | ||
1 | +--- | |
2 | +title: Request Object | |
3 | +description: Access incoming HTTP request data through the Request object. | |
4 | +--- | |
5 | + | |
6 | +The `Request` object provides zero-copy access to the incoming HTTP request. It's passed as the first parameter to every route handler. | |
7 | + | |
8 | +## Request Properties | |
9 | + | |
10 | +```python | |
11 | +@app.get("/items/{item_id}") | |
12 | +def handler(request, item_id: int): | |
13 | + method = request.method # str: GET, POST, etc. | |
14 | + path = request.path # str: /items/42 | |
15 | + url = request.url # URL object | |
16 | + headers = request.headers # Headers object | |
17 | + query_params = request.query_params # QueryParams object | |
18 | + state = request.state # State object (per-request storage) | |
19 | +``` | |
20 | + | |
21 | +### `request.method` | |
22 | + | |
23 | +The HTTP method as a string: `"GET"`, `"POST"`, `"PUT"`, `"PATCH"`, `"DELETE"`. | |
24 | + | |
25 | +### `request.path` | |
26 | + | |
27 | +The URL path: `"/items/42"`. | |
28 | + | |
29 | +### `request.url` | |
30 | + | |
31 | +A `URL` object with: | |
32 | + | |
33 | +| Attribute | Type | Description | | |
34 | +|---|---|---| | |
35 | +| `scheme` | `str` | `"http"` or `"https"` | | |
36 | +| `host` | `str` | Hostname | | |
37 | +| `port` | `int` | Port number | | |
38 | +| `path` | `str` | URL path | | |
39 | + | |
40 | +### `request.headers` | |
41 | + | |
42 | +A `Headers` object providing dict-like access to HTTP headers. All header names are lowercased. | |
43 | + | |
44 | +```python | |
45 | +@app.get("/") | |
46 | +def handler(request): | |
47 | + user_agent = request.headers.get("user-agent") | |
48 | + content_type = request.headers.get("content-type") | |
49 | + auth = request.headers.get("authorization") | |
50 | + return {"ua": user_agent} | |
51 | +``` | |
52 | + | |
53 | +| Method | Description | | |
54 | +|---|---| | |
55 | +| `.get(key)` | Get header value (case-insensitive) | | |
56 | +| `.items()` | Iterate over `(key, value)` pairs | | |
57 | + | |
58 | +### `request.query_params` | |
59 | + | |
60 | +A `QueryParams` object providing dict-like access to query string parameters. | |
61 | + | |
62 | +```python | |
63 | +@app.get("/search") | |
64 | +def handler(request): | |
65 | + q = request.query_params.get("q") | |
66 | + page = request.query_params.get("page", "1") | |
67 | + return {"query": q, "page": int(page)} | |
68 | +``` | |
69 | + | |
70 | +| Method | Description | | |
71 | +|---|---| | |
72 | +| `.get(key, default)` | Get query parameter | | |
73 | +| `.items()` | Iterate over `(key, value)` pairs | | |
74 | + | |
75 | +### `request.state` | |
76 | + | |
77 | +A `State` object for per-request storage. Useful for passing data between middleware and handlers: | |
78 | + | |
79 | +```python | |
80 | +@app.middleware("http") | |
81 | +def set_user(request, call_next): | |
82 | + request.state.user = {"id": 42, "role": "admin"} | |
83 | + return call_next(request) | |
84 | + | |
85 | +@app.get("/profile") | |
86 | +def profile(request): | |
87 | + user = request.state.user | |
88 | + return {"user_id": user["id"]} | |
89 | +``` | |
90 | + | |
91 | +## Request Body Methods | |
92 | + | |
93 | +```python | |
94 | +@app.post("/data") | |
95 | +def handler(request): | |
96 | + # Parse JSON body | |
97 | + data = request.json() # dict | list | |
98 | + | |
99 | + # Raw bytes | |
100 | + body = request.body() # bytes | |
101 | + | |
102 | + # Parse form data | |
103 | + form = request.form() # dict | |
104 | +``` | |
105 | + | |
106 | +| Method | Returns | Description | | |
107 | +|---|---|---| | |
108 | +| `request.json()` | `dict` or `list` | Parse body as JSON | | |
109 | +| `request.body()` | `bytes` | Raw request body bytes | | |
110 | +| `request.form()` | `dict` | Parse as URL-encoded form | | |
111 | + | |
112 | +## Zero-Copy Architecture | |
113 | + | |
114 | +The `Request` object uses PyO3's buffer protocol to provide **zero-copy** access to request data: | |
115 | + | |
116 | +- Headers and query parameters are parsed in Rust and exposed as read-only memory views | |
117 | +- No data is copied between Rust and Python | |
118 | +- Body reading triggers a one-time copy from the network buffer | |
119 | + | |
120 | +## See Also | |
121 | + | |
122 | +- [Response Classes](/api-reference/responses/) — Returning responses | |
123 | +- [Dependency Injection](/api-reference/dependency-injection/) — Extracting request components with Depends | |
124 | +- [Zero-GIL Architecture](/advanced/zero-gil-architecture/) — How the GIL is released during request processing | |
0 | 125 | |
▼docs_site/src/content/docs/api-reference/responses.md
+130-0
| @@ -0,0 +1,130 @@ | ||
1 | +--- | |
2 | +title: Response Classes | |
3 | +description: Return responses with different content types, status codes, and streaming capabilities. | |
4 | +--- | |
5 | + | |
6 | +JustAPI provides several response classes optimized for zero-copy data transfer from Rust to Python. | |
7 | + | |
8 | +## `Response` | |
9 | + | |
10 | +Base response class: | |
11 | + | |
12 | +```python | |
13 | +from justapi.responses import Response | |
14 | + | |
15 | +return Response( | |
16 | + content="Hello", | |
17 | + status_code=200, | |
18 | + headers={"X-Custom": "value"}, | |
19 | +) | |
20 | +``` | |
21 | + | |
22 | +| Parameter | Type | Default | Description | | |
23 | +|---|---|---|---| | |
24 | +| `content` | `str` | `""` | Response body text | | |
25 | +| `status_code` | `int` | `200` | HTTP status code | | |
26 | +| `headers` | `dict` | `None` | Additional headers | | |
27 | + | |
28 | +## `JSONResponse` | |
29 | + | |
30 | +Automatically serializes a Python object to JSON using Rust's `serde_json`: | |
31 | + | |
32 | +```python | |
33 | +from justapi.responses import JSONResponse | |
34 | + | |
35 | +return JSONResponse({"message": "Hello", "items": [1, 2, 3]}) | |
36 | +``` | |
37 | + | |
38 | +| Parameter | Type | Default | Description | | |
39 | +|---|---|---|---| | |
40 | +| `content` | `dict` or `list` | required | Python object to serialize | | |
41 | + | |
42 | +## `HTMLResponse` | |
43 | + | |
44 | +Sets `Content-Type: text/html`: | |
45 | + | |
46 | +```python | |
47 | +from justapi.responses import HTMLResponse | |
48 | + | |
49 | +return HTMLResponse("<h1>Hello</h1>") | |
50 | +``` | |
51 | + | |
52 | +## `PlainTextResponse` | |
53 | + | |
54 | +Sets `Content-Type: text/plain`: | |
55 | + | |
56 | +```python | |
57 | +from justapi.responses import PlainTextResponse | |
58 | + | |
59 | +return PlainTextResponse("Hello, World!") | |
60 | +``` | |
61 | + | |
62 | +## `RedirectResponse` | |
63 | + | |
64 | +Returns an HTTP redirect: | |
65 | + | |
66 | +```python | |
67 | +from justapi.responses import RedirectResponse | |
68 | + | |
69 | +return RedirectResponse( | |
70 | + url="https://example.com", | |
71 | + status_code=307, # Temporary redirect | |
72 | +) | |
73 | +``` | |
74 | + | |
75 | +| Parameter | Type | Default | Description | | |
76 | +|---|---|---|---| | |
77 | +| `url` | `str` | required | Redirect target URL | | |
78 | +| `status_code` | `int` | `307` | Redirect status (307, 308, 301, 302) | | |
79 | + | |
80 | +## `StreamingResponse` | |
81 | + | |
82 | +Streams content using a Python generator: | |
83 | + | |
84 | +```python | |
85 | +from justapi.responses import StreamingResponse | |
86 | +import asyncio | |
87 | + | |
88 | + | |
89 | +@app.get("/stream") | |
90 | +async def stream(request): | |
91 | + async def generate(): | |
92 | + for i in range(100): | |
93 | + yield f"data: {i}\n\n" | |
94 | + await asyncio.sleep(0.1) | |
95 | + | |
96 | + return StreamingResponse(generate(), media_type="text/plain") | |
97 | +``` | |
98 | + | |
99 | +| Parameter | Type | Default | Description | | |
100 | +|---|---|---|---| | |
101 | +| `generator` | async generator | required | Content generator | | |
102 | +| `media_type` | `str` | `"text/plain"` | Content-Type header | | |
103 | + | |
104 | +## Dict Return (Shortcut) | |
105 | + | |
106 | +When a route handler returns a plain dict, it's automatically converted to `JSONResponse`: | |
107 | + | |
108 | +```python | |
109 | +@app.get("/") | |
110 | +def handler(request): | |
111 | + return {"message": "Hello"} # Equivalent to JSONResponse(...) | |
112 | +``` | |
113 | + | |
114 | +## Setting Status Codes | |
115 | + | |
116 | +Use tuples or the `status` key in the response dict for non-200 status codes: | |
117 | + | |
118 | +```python | |
119 | +@app.post("/items") | |
120 | +def create_item(request): | |
121 | + return {"status": 201, "body": {"id": 1, "name": "Item"}} | |
122 | +``` | |
123 | + | |
124 | +The `status` key sets the HTTP status code, while `body` contains the response body. | |
125 | + | |
126 | +## See Also | |
127 | + | |
128 | +- [Request Object](/api-reference/request/) — Accessing request data | |
129 | +- [Streaming Output](/advanced/streaming-output/) — Validated streaming with `@app.stream_json` | |
130 | +- [Error Handling](/tutorials/error-handling/) — Error response patterns | |
0 | 131 | |
▼docs_site/src/content/docs/api-reference/routing.md
+118-0
| @@ -0,0 +1,118 @@ | ||
1 | +--- | |
2 | +title: Routing API | |
3 | +description: Complete reference for HTTP method decorators and route registration. | |
4 | +--- | |
5 | + | |
6 | +## Method Decorators | |
7 | + | |
8 | +Each HTTP method decorator registers a route handler in the radix-trie router. | |
9 | + | |
10 | +### `@app.get()` | |
11 | + | |
12 | +```python | |
13 | +@app.get( | |
14 | + path: str, | |
15 | + dependencies: list | None = None, | |
16 | + tags: list[str] | None = None, | |
17 | +) | |
18 | +``` | |
19 | + | |
20 | +### `@app.post()` | |
21 | + | |
22 | +```python | |
23 | +@app.post( | |
24 | + path: str, | |
25 | + body_schema: type[Schema] | None = None, | |
26 | + schema: type[Schema] | None = None, | |
27 | + dependencies: list | None = None, | |
28 | + tags: list[str] | None = None, | |
29 | + native: bool = False, | |
30 | +) | |
31 | +``` | |
32 | + | |
33 | +### `@app.put()` | |
34 | + | |
35 | +```python | |
36 | +@app.put( | |
37 | + path: str, | |
38 | + body_schema: type[Schema] | None = None, | |
39 | + schema: type[Schema] | None = None, | |
40 | + dependencies: list | None = None, | |
41 | + tags: list[str] | None = None, | |
42 | + native: bool = False, | |
43 | +) | |
44 | +``` | |
45 | + | |
46 | +### `@app.patch()` | |
47 | + | |
48 | +```python | |
49 | +@app.patch( | |
50 | + path: str, | |
51 | + body_schema: type[Schema] | None = None, | |
52 | + schema: type[Schema] | None = None, | |
53 | + dependencies: list | None = None, | |
54 | + tags: list[str] | None = None, | |
55 | + native: bool = False, | |
56 | +) | |
57 | +``` | |
58 | + | |
59 | +### `@app.delete()` | |
60 | + | |
61 | +```python | |
62 | +@app.delete( | |
63 | + path: str, | |
64 | + dependencies: list | None = None, | |
65 | + tags: list[str] | None = None, | |
66 | +) | |
67 | +``` | |
68 | + | |
69 | +## Route Parameters | |
70 | + | |
71 | +| Parameter | Applies To | Type | Description | | |
72 | +|---|---|---|---| | |
73 | +| `path` | All | `str` | URL pattern with `{param}` placeholders | | |
74 | +| `body_schema` | POST, PUT, PATCH | `Schema` subclass | Validate body against this schema in Rust | | |
75 | +| `schema` | POST, PUT, PATCH | `Schema` subclass | Alias for `body_schema` | | |
76 | +| `dependencies` | All | `list[Depends]` | Route-level dependency injection | | |
77 | +| `tags` | All | `list[str]` | OpenAPI operation tags | | |
78 | +| `native` | POST, PUT, PATCH | `bool` | Execute entirely in Rust (724k+ RPS) | | |
79 | + | |
80 | +## Path Parameter Types | |
81 | + | |
82 | +| Type | Example | Valid Input | | |
83 | +|---|---|---| | |
84 | +| `str` | `/hello/{name}` | Any string | | |
85 | +| `int` | `/items/{id}` | `42` | | |
86 | +| `float` | `/price/{val}` | `19.99` | | |
87 | +| `bool` | `/flag/{val}` | `true`, `1`, `yes` | | |
88 | +| `UUID` | `/users/{uid}` | `550e8400-...` | | |
89 | +| `datetime` | `/events/{ts}` | `2026-07-24T12:00:00Z` | | |
90 | + | |
91 | +## Native Fast Path | |
92 | + | |
93 | +When `native=True`, the handler executes entirely in Rust: | |
94 | + | |
95 | +- Request body is validated against the schema in Rust | |
96 | +- The handler runs as a Rust callback | |
97 | +- The response is serialized in Rust | |
98 | +- Zero GIL acquisition for the entire request lifecycle | |
99 | + | |
100 | +```python | |
101 | +@app.post("/fast-items", body_schema=ItemSchema, native=True) | |
102 | +def create_item(request): | |
103 | + return {"status": "ok"} | |
104 | +``` | |
105 | + | |
106 | +## Route Resolution | |
107 | + | |
108 | +Routes are resolved in O(1) time using a radix-trie (matchit). The route-lookup cache memoizes repeated lookups for the same path. | |
109 | + | |
110 | +## OpenAPI Generation | |
111 | + | |
112 | +All registered routes automatically generate OpenAPI 3.1 documentation available at `/openapi.json`, `/docs` (Swagger UI), `/redoc` (ReDoc), and `/scalar` (Scalar UI). | |
113 | + | |
114 | +## See Also | |
115 | + | |
116 | +- [APIRouter](/api-reference/apirouter/) — Modular route grouping | |
117 | +- [JustAPIApp](/api-reference/justapiapp/) — App configuration reference | |
118 | +- [Native Fast Path](/advanced/native-fast-path/) — Deep dive on Rust-native execution | |
0 | 119 | |