p3n-q: add Plugins, Testing Client, UploadFile, Database API reference pages
swadhinbiswas committed 3 months ago
2e97a02
1 parent 6138bab
commit 2e97a02
4 files changed
+325 -0
Showing 4 changed files+325-0
▼docs_site/src/content/docs/api-reference/database.md
+95-0
| @@ -0,0 +1,95 @@ | ||
1 | +--- | |
2 | +title: Database API | |
3 | +description: Reference for the Rust-native database connection pool and query interface. | |
4 | +--- | |
5 | + | |
6 | +## `app.set_database()` | |
7 | + | |
8 | +Configure the database connection pool: | |
9 | + | |
10 | +```python | |
11 | +app.set_database( | |
12 | + url="postgres://user:pass@localhost:5432/mydb", | |
13 | + init_sql="CREATE TABLE IF NOT EXISTS ...", | |
14 | + max_connections=10, | |
15 | + request_acquire_timeout=3.0, | |
16 | + wal=False, | |
17 | + pragmas=None, | |
18 | +) | |
19 | +``` | |
20 | + | |
21 | +### Parameters | |
22 | + | |
23 | +| Parameter | Type | Default | Description | | |
24 | +|---|---|---|---| | |
25 | +| `url` | `str` | required | Database connection URL | | |
26 | +| `init_sql` | `str` | `None` | SQL to run on startup (for schema creation) | | |
27 | +| `max_connections` | `int` | `10` | Maximum pool connections | | |
28 | +| `request_acquire_timeout` | `float` | `3.0` | Seconds before pool saturation returns 503 | | |
29 | +| `wal` | `bool` | `False` | Enable SQLite WAL mode | | |
30 | +| `pragmas` | `list[str]` | `None` | Additional SQLite pragmas | | |
31 | + | |
32 | +## `app.db` Object | |
33 | + | |
34 | +### Methods | |
35 | + | |
36 | +| Method | Returns | Description | | |
37 | +|---|---|---| | |
38 | +| `app.db.query(sql, params)` | `list[dict]` | Execute SELECT and return rows | | |
39 | +| `app.db.execute(sql, params)` | `int` | Execute INSERT/UPDATE/DELETE, return affected rows | | |
40 | + | |
41 | +#### `query()` | |
42 | + | |
43 | +```python | |
44 | +rows = app.db.query( | |
45 | + "SELECT id, name, price FROM products WHERE price > $1 ORDER BY id", | |
46 | + [100.0], | |
47 | +) | |
48 | +# Returns: [{"id": 1, "name": "Widget", "price": 150.0}, ...] | |
49 | +``` | |
50 | + | |
51 | +| Parameter | Type | Default | Description | | |
52 | +|---|---|---|---| | |
53 | +| `sql` | `str` | required | SQL query with `$1`, `$2`, ... placeholders | | |
54 | +| `params` | `list` | `[]` | Query parameters | | |
55 | + | |
56 | +#### `execute()` | |
57 | + | |
58 | +```python | |
59 | +affected = app.db.execute( | |
60 | + "INSERT INTO products (name, price) VALUES ($1, $2)", | |
61 | + ["Widget", 150.0], | |
62 | +) | |
63 | +# Returns: 1 | |
64 | +``` | |
65 | + | |
66 | +| Parameter | Type | Default | Description | | |
67 | +|---|---|---|---| | |
68 | +| `sql` | `str` | required | SQL with `$1`, `$2`, ... placeholders | | |
69 | +| `params` | `list` | `[]` | Query parameters | | |
70 | + | |
71 | +## Connection URLs | |
72 | + | |
73 | +| Database | URL Format | Example | | |
74 | +|---|---|---| | |
75 | +| PostgreSQL | `postgres://user:pass@host:port/db` | `postgres://postgres@localhost/mydb` | | |
76 | +| MySQL | `mysql://user:pass@host:port/db` | `mysql://root@localhost/mydb` | | |
77 | +| SQLite | `sqlite:///path/to/db.db` | `sqlite:///app.db` | | |
78 | +| SQLite (memory) | `sqlite://:memory:` | `sqlite://:memory:` | | |
79 | + | |
80 | +## Pool Saturation | |
81 | + | |
82 | +When all connections are in use, new requests wait up to `request_acquire_timeout` seconds, then receive **503 Service Unavailable** with a `Retry-After: 1` header. | |
83 | + | |
84 | +## SQLite Defaults | |
85 | + | |
86 | +For SQLite connections, JustAPI automatically enables: | |
87 | +- `PRAGMA busy_timeout=5000` — Wait up to 5 seconds for busy connections | |
88 | +- `PRAGMA journal_mode=WAL` — Write-Ahead Logging for concurrent access | |
89 | +- `PRAGMA synchronous=NORMAL` — Balanced durability/performance | |
90 | + | |
91 | +## See Also | |
92 | + | |
93 | +- [Database Integration Tutorial](/tutorials/database-integration/) — Usage guide | |
94 | +- [JustAPIApp](/api-reference/justapiapp/) — `set_database()` method | |
95 | +- [Performance Tuning](/advanced/performance-tuning/) — Database optimization | |
0 | 96 | |
▼docs_site/src/content/docs/api-reference/plugins.md
+105-0
| @@ -0,0 +1,105 @@ | ||
1 | +--- | |
2 | +title: Plugin System API | |
3 | +description: Reference for extending JustAPI with Python and Rust plugins. | |
4 | +--- | |
5 | + | |
6 | +JustAPI's plugin system supports Python plugins (for flexibility) and Rust plugins (for maximum performance). | |
7 | + | |
8 | +## Python Plugin Interface | |
9 | + | |
10 | +A Python plugin is any class implementing one or more lifecycle hooks: | |
11 | + | |
12 | +```python | |
13 | +class MyPlugin: | |
14 | + def build(self, app): | |
15 | + """Called when plugin is registered with app.use().""" | |
16 | + pass | |
17 | + | |
18 | + async def on_startup(self): | |
19 | + """Called before server starts accepting requests.""" | |
20 | + pass | |
21 | + | |
22 | + async def on_shutdown(self): | |
23 | + """Called during server shutdown.""" | |
24 | + pass | |
25 | +``` | |
26 | + | |
27 | +### Hook Details | |
28 | + | |
29 | +| Method | Timing | Purpose | | |
30 | +|---|---|---| | |
31 | +| `build(self, app)` | On `app.use()` | Add routes, middleware, modify config | | |
32 | +| `on_startup(self)` | Before server starts | Connect to services, warm caches | | |
33 | +| `on_shutdown(self)` | During graceful shutdown | Close connections, save state | | |
34 | + | |
35 | +### Registration | |
36 | + | |
37 | +```python | |
38 | +from justapi import JustAPIApp | |
39 | + | |
40 | +class MyPlugin: | |
41 | + def build(self, app: JustAPIApp): | |
42 | + print("Plugin registered!") | |
43 | + | |
44 | + async def on_startup(self): | |
45 | + print("Server starting...") | |
46 | + | |
47 | + async def on_shutdown(self): | |
48 | + print("Server stopping...") | |
49 | + | |
50 | +app = JustAPIApp() | |
51 | +app.use(MyPlugin()) | |
52 | +``` | |
53 | + | |
54 | +## Rust Plugin Interface | |
55 | + | |
56 | +For performance-critical extensions, Rust plugins implement the `Plugin` trait: | |
57 | + | |
58 | +```rust | |
59 | +use justapi_core::plugin::Plugin; | |
60 | +use async_trait::async_trait; | |
61 | + | |
62 | +pub struct MyRustPlugin; | |
63 | + | |
64 | +#[async_trait] | |
65 | +impl Plugin for MyRustPlugin { | |
66 | + fn name(&self) -> &'static str { | |
67 | + "MyRustPlugin" | |
68 | + } | |
69 | + | |
70 | + async fn on_startup(&self) -> anyhow::Result<()> { | |
71 | + println!("Rust plugin started"); | |
72 | + Ok(()) | |
73 | + } | |
74 | + | |
75 | + async fn on_shutdown(&self) -> anyhow::Result<()> { | |
76 | + println!("Rust plugin stopped"); | |
77 | + Ok(()) | |
78 | + } | |
79 | +} | |
80 | +``` | |
81 | + | |
82 | +### Registration | |
83 | + | |
84 | +```rust | |
85 | +use justapi_core::plugin::PluginRegistration; | |
86 | +use std::sync::Arc; | |
87 | + | |
88 | +inventory::submit! { | |
89 | + PluginRegistration::new(|| Arc::new(MyRustPlugin)) | |
90 | +} | |
91 | +``` | |
92 | + | |
93 | +## Plugin Marketplace | |
94 | + | |
95 | +To publish a JustAPI Python plugin to the community: | |
96 | + | |
97 | +1. Publish to PyPI with the `justapi-plugin` keyword | |
98 | +2. Users install via `pip install justapi-plugin-yourname` | |
99 | +3. Usage: `from justapi_plugin_yourname import YourPlugin; app.use(YourPlugin())` | |
100 | + | |
101 | +## See Also | |
102 | + | |
103 | +- [Plugins Guide](/docs/plugins.md) — Detailed plugin development guide | |
104 | +- [JustAPIApp](/api-reference/justapiapp/) — `app.use()` method | |
105 | +- [Rust Core Deep Dive](/advanced/rust-core-deep-dive/) — Plugin trait internals | |
0 | 106 | |
▼docs_site/src/content/docs/api-reference/testing-client.md
+54-0
| @@ -0,0 +1,54 @@ | ||
1 | +--- | |
2 | +title: Testing Client API | |
3 | +description: Reference for the in-process HTTP test client. | |
4 | +--- | |
5 | + | |
6 | +## `JustAPITestClient` | |
7 | + | |
8 | +The test client sends HTTP requests to your application without starting a real server, using `tokio` duplex streams. | |
9 | + | |
10 | +```python | |
11 | +from justapi import JustAPITestClient | |
12 | + | |
13 | +client = JustAPITestClient(app) | |
14 | +``` | |
15 | + | |
16 | +### Methods | |
17 | + | |
18 | +```python | |
19 | +response = client.get("/items/42") | |
20 | +response = client.post("/items/", body={"name": "Item"}) | |
21 | +response = client.put("/items/42", body={"name": "Updated"}) | |
22 | +response = client.patch("/items/42", body={"name": "Patched"}) | |
23 | +response = client.delete("/items/42") | |
24 | +``` | |
25 | + | |
26 | +### Response Object | |
27 | + | |
28 | +| Attribute | Type | Description | | |
29 | +|---|---|---| | |
30 | +| `status` | `int` | HTTP status code | | |
31 | +| `body` | `bytes` | Raw response body | | |
32 | +| `json()` | `dict` | Parsed JSON body | | |
33 | + | |
34 | +### Example Test | |
35 | + | |
36 | +```python | |
37 | +from justapi import JustAPIApp, JustAPITestClient | |
38 | + | |
39 | +app = JustAPIApp() | |
40 | + | |
41 | +@app.get("/ping") | |
42 | +def ping(request): | |
43 | + return {"status": "ok"} | |
44 | + | |
45 | +client = JustAPITestClient(app) | |
46 | +response = client.get("/ping") | |
47 | +assert response.status == 200 | |
48 | +assert response.json() == {"status": "ok"} | |
49 | +``` | |
50 | + | |
51 | +## See Also | |
52 | + | |
53 | +- [Testing Guide](/contributing/testing-guide/) — Writing tests for JustAPI | |
54 | +- [JustAPIApp](/api-reference/justapiapp/) — App configuration | |
0 | 55 | |
▼docs_site/src/content/docs/api-reference/uploadfile.md
+71-0
| @@ -0,0 +1,71 @@ | ||
1 | +--- | |
2 | +title: UploadFile API | |
3 | +description: Reference for uploaded file representation. | |
4 | +--- | |
5 | + | |
6 | +## `UploadFile` Object | |
7 | + | |
8 | +Represents a file uploaded via `multipart/form-data`. Files are streamed to temporary files by the Rust `multer` parser. | |
9 | + | |
10 | +```python | |
11 | +from justapi import UploadFile, File | |
12 | +``` | |
13 | + | |
14 | +### Properties | |
15 | + | |
16 | +| Attribute | Type | Description | | |
17 | +|---|---|---| | |
18 | +| `filename` | `str` | Original filename from the client | | |
19 | +| `content_type` | `str` | MIME type (e.g., `image/png`) | | |
20 | +| `file` | `str` | Path to the temporary file on disk | | |
21 | +| `size` | `int` | File size in bytes | | |
22 | + | |
23 | +### Methods | |
24 | + | |
25 | +| Method | Returns | Description | | |
26 | +|---|---|---| | |
27 | +| `.read()` | `bytes` | Read file contents into memory | | |
28 | +| `.write(data)` | `int` | Write bytes to the temp file | | |
29 | +| `.seek(pos)` | `None` | Seek to position in the temp file | | |
30 | +| `.close()` | `None` | Close and clean up the temp file | | |
31 | + | |
32 | +### Usage | |
33 | + | |
34 | +```python | |
35 | +@app.post("/upload") | |
36 | +async def upload(request, file: UploadFile = File(...)): | |
37 | + content = await file.read() | |
38 | + return { | |
39 | + "name": file.filename, | |
40 | + "type": file.content_type, | |
41 | + "size": file.size, | |
42 | + "content": content.decode(), | |
43 | + } | |
44 | +``` | |
45 | + | |
46 | +## `File()` | |
47 | + | |
48 | +Declare a file parameter: | |
49 | + | |
50 | +```python | |
51 | +File( | |
52 | + default=..., # Use ... for required | |
53 | + description=None, # OpenAPI description | |
54 | +) | |
55 | +``` | |
56 | + | |
57 | +## `Form()` | |
58 | + | |
59 | +Declare a form field parameter: | |
60 | + | |
61 | +```python | |
62 | +Form( | |
63 | + default=..., | |
64 | + description=None, | |
65 | +) | |
66 | +``` | |
67 | + | |
68 | +## See Also | |
69 | + | |
70 | +- [File Uploads Tutorial](/tutorials/file-uploads/) — Usage guide | |
71 | +- [Dependency Injection API](/api-reference/dependency-injection/) — File and Form extractors | |
0 | 72 | |