Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
fd88f8d
fix(security): prevent path traversal and symlink escape in StaticFiles
ZhuchkaTriplesix Aug 26, 2026
cc3390d
Merge pull request #165 from QueryaHub/issue-144-static-traversal
ZhuchkaTriplesix Aug 26, 2026
8786c09
ci: add native Rust cargo tests and automated security audit workflow
ZhuchkaTriplesix Aug 26, 2026
c8425e2
ci: update security-audit to run cargo audit in non-blocking reportin…
ZhuchkaTriplesix Aug 26, 2026
44d44f9
style: fix Ruff B904 in static.py with raise from None
ZhuchkaTriplesix Aug 26, 2026
5f7319b
Merge branch 'dev' into feat-ci-enhancements
ZhuchkaTriplesix Aug 26, 2026
a1b581c
style: apply ruff formatting across tests and docs
ZhuchkaTriplesix Aug 26, 2026
50beb73
Merge branch 'dev' into feat-ci-enhancements
ZhuchkaTriplesix Aug 26, 2026
3cae5f9
Merge pull request #166 from QueryaHub/feat-ci-enhancements
ZhuchkaTriplesix Aug 26, 2026
d1d7150
fix(typing): add PEP 561 py.typed marker and _oxyroute.pyi type stubs
ZhuchkaTriplesix Aug 26, 2026
f4f78f6
style: fix isort import formatting in test_typing.py
ZhuchkaTriplesix Aug 26, 2026
4b754de
Merge pull request #167 from QueryaHub/issue-150-py-typed-stubs
ZhuchkaTriplesix Aug 26, 2026
47559d5
fix(params): schema-aware path parameter coercion to prevent unintend…
ZhuchkaTriplesix Aug 26, 2026
d0a0dbc
style: cargo fmt in params.rs
ZhuchkaTriplesix Aug 26, 2026
e1df38f
Merge pull request #168 from QueryaHub/issue-142-path-param-coercion
ZhuchkaTriplesix Aug 26, 2026
4c5f3c8
fix(build): set panic = "abort" in Cargo.toml to prevent FFI stack un…
ZhuchkaTriplesix Aug 26, 2026
3597ae4
Merge pull request #169 from QueryaHub/issue-156-panic-abort
ZhuchkaTriplesix Aug 26, 2026
47c86b1
feat(dx): auto-infer Pydantic body_model and support flexible paramet…
ZhuchkaTriplesix Aug 26, 2026
a7ecd53
Merge pull request #170 from QueryaHub/issue-151-152-pydantic-dx
ZhuchkaTriplesix Aug 26, 2026
edd1c26
perf(call): adopt PEP 590 Vectorcall protocol to eliminate PyDict kwa…
ZhuchkaTriplesix Aug 26, 2026
41d4cdd
perf(cache): pack RouteEntry layout into single 64B cacheline for hot…
ZhuchkaTriplesix Aug 26, 2026
97f859b
Merge pull request #171 from QueryaHub/issue-159-vectorcall
ZhuchkaTriplesix Aug 26, 2026
ee95ef8
fix(app): use callable() instead of hasattr(__call__) for handler ins…
ZhuchkaTriplesix Aug 26, 2026
b305b3d
Merge pull request #172 from QueryaHub/issue-147-pack-route-entry
ZhuchkaTriplesix Aug 26, 2026
f28e18c
fix(dispatch): restore abi3-py310 compliant call0 and kwargs calling
ZhuchkaTriplesix Aug 27, 2026
0fe8cc6
fix(dispatch): fix db query execution and pool reference in RSGI disp…
ZhuchkaTriplesix Aug 27, 2026
b2cd52a
fix(dispatch): filter kwargs passed to dependency factories by declar…
ZhuchkaTriplesix Aug 27, 2026
a1d5350
fix(dispatch): correct route entry destructure and kw filtering
ZhuchkaTriplesix Aug 27, 2026
0395bb0
fix(clippy): use is_none_or instead of map_or(true, ...)
ZhuchkaTriplesix Aug 27, 2026
6995ba0
Merge pull request #173 from QueryaHub/fix-abi3-vectorcall
ZhuchkaTriplesix Aug 27, 2026
6894213
perf(cache): unify dependency arrays into contiguous Arc<[DependencyE…
ZhuchkaTriplesix Aug 27, 2026
f97dbae
fix(dispatch): use dependencies for has_dep_kwargs check
ZhuchkaTriplesix Aug 27, 2026
ca0724b
Merge pull request #174 from QueryaHub/perf-unify-dependencies-148
ZhuchkaTriplesix Aug 27, 2026
fbb7049
perf(routing): single-pass bitmask / unified radix lookup for 405 Met…
ZhuchkaTriplesix Aug 27, 2026
d5764d8
fix(bench): initialize all_paths in sample_compiled_routers
ZhuchkaTriplesix Aug 27, 2026
f7a9e9c
style: format sample_compiled_routers in lib.rs
ZhuchkaTriplesix Aug 27, 2026
02cd128
fix(test): support direct router insertions and update methods_matchi…
ZhuchkaTriplesix Aug 27, 2026
38bdff6
fix(clippy): allow type_complexity on test helper match_route
ZhuchkaTriplesix Aug 27, 2026
1417de8
Merge pull request #175 from QueryaHub/perf-single-pass-routing-139
ZhuchkaTriplesix Aug 27, 2026
a8f4a54
perf(params): zero-allocation path parameter extraction from URL slic…
ZhuchkaTriplesix Aug 27, 2026
90caf70
style: format dispatch.rs and state.rs per cargo fmt
ZhuchkaTriplesix Aug 27, 2026
b804b38
Merge pull request #176 from QueryaHub/perf-zero-alloc-params-140
ZhuchkaTriplesix Aug 27, 2026
5345138
perf(memory): implement thread-local buffer pool for request bodies (…
ZhuchkaTriplesix Aug 27, 2026
4a3a789
chore(deps): upgrade oxyjwt to >=0.7.0 and drop pyjwt / cryptography
ZhuchkaTriplesix Aug 27, 2026
ac7452a
Merge pull request #177 from QueryaHub/perf-buffer-pool-160
ZhuchkaTriplesix Aug 27, 2026
d9823fd
Merge pull request #178 from QueryaHub/chore-upgrade-oxyjwt-0-7
ZhuchkaTriplesix Aug 27, 2026
708281d
perf(cache): consolidate HotSnapshot atomic refcounts into single Arc…
ZhuchkaTriplesix Aug 27, 2026
7962011
fix(state): ensure GIL is held when rebuilding snapshot with Python o…
ZhuchkaTriplesix Aug 27, 2026
7af51f7
Merge pull request #179 from QueryaHub/perf-consolidate-snapshot-149
ZhuchkaTriplesix Aug 27, 2026
bf0eea7
fix(observability): ensure access_log_hook executes on unhandled exce…
ZhuchkaTriplesix Sep 12, 2026
747272e
Merge pull request #180 from QueryaHub/issue-157-access-log-try-finally
ZhuchkaTriplesix Sep 12, 2026
858b8a5
feat(openapi): align default OpenAPI version to 3.1.0 for Pydantic v2…
ZhuchkaTriplesix Sep 12, 2026
87e4cbc
Merge pull request #181 from QueryaHub/issue-154-openapi-3-1-0
ZhuchkaTriplesix Sep 12, 2026
98e377d
feat(openapi): auto-document 401 Unauthorized and 422 Validation Erro…
ZhuchkaTriplesix Sep 12, 2026
e934927
Merge pull request #182 from QueryaHub/issue-153-openapi-error-responses
ZhuchkaTriplesix Sep 12, 2026
72b71d5
feat(openapi): support query and header parameter schema declarations
ZhuchkaTriplesix Sep 12, 2026
c9e12e7
Merge pull request #183 from QueryaHub/issue-155-openapi-query-header…
ZhuchkaTriplesix Sep 12, 2026
7dfd311
perf(response): pre-baked static response headers to eliminate string…
ZhuchkaTriplesix Sep 12, 2026
08d6370
feat(resilience): adaptive concurrency limiting and load shedding to …
ZhuchkaTriplesix Sep 12, 2026
a792615
docs(security): document DBQuery parameter binding and injection prev…
ZhuchkaTriplesix Sep 12, 2026
31e593d
feat(websocket): serialize concurrent send operations to prevent fram…
ZhuchkaTriplesix Sep 12, 2026
f7d6865
feat(websocket): notify active WebSocket connections with 1001 Going …
ZhuchkaTriplesix Sep 12, 2026
4b0d644
feat(di): DAG dependency resolution with cycle detection at route reg…
ZhuchkaTriplesix Sep 12, 2026
d85c46e
style: ruff format oxyroute tests examples (#190)
ZhuchkaTriplesix Sep 12, 2026
3bd5e1f
style: cargo fmt src/lib.rs (#191)
ZhuchkaTriplesix Sep 12, 2026
afe64f9
feat(rate-limit): native Rust in-memory token bucket rate limiter on …
ZhuchkaTriplesix Sep 12, 2026
1c9ad47
perf(db): streaming and chunked row decoding for DBQuery results (#14…
ZhuchkaTriplesix Sep 12, 2026
fa17bd2
perf(json): evaluate SIMD-accelerated JSON deserialization via simd-j…
ZhuchkaTriplesix Sep 12, 2026
d70b029
fix(rate-limit): import pyo3 prelude for PyAnyMethods in rate_limit.r…
ZhuchkaTriplesix Sep 12, 2026
7738ce0
fix(rate-limit): extract client IP via Bound::get_item instead of ext…
ZhuchkaTriplesix Sep 12, 2026
a7fca81
test(rate-limit): remove unused Any import in test_rate_limit.py (#197)
ZhuchkaTriplesix Sep 12, 2026
673f91d
fix(clippy): replace redundant closure with PyValueError::new_err in …
ZhuchkaTriplesix Sep 12, 2026
ec58faf
chore(release): bump version to 0.6.0, update CHANGELOG and README (#…
ZhuchkaTriplesix Sep 12, 2026
9720bfb
chore: sync Cargo.lock and uv.lock with version 0.6.0 (#201)
ZhuchkaTriplesix Sep 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
run: |
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
- name: Native Rust tests
run: |
cargo test --all-targets

test:
strategy:
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: security-audit

on:
push:
branches: [main, master, dev]
pull_request:
branches: [main, master, dev]
schedule:
- cron: "0 0 * * 0" # Weekly on Sundays

jobs:
cargo-audit:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install and run cargo-audit
run: |
cargo install cargo-audit
cargo audit || true
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,51 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.6.0] - 2026-09-12

### Added

- **Native Rate Limiting**: In-memory sharded Token Bucket rate limiter in Rust on route decorators
(`@app.get(..., rate_limit="100/minute")`) and `APIRouter`. Supports key strategies for client IP
(with `X-Forwarded-For` / `X-Real-IP` support), request headers (`header:<name>`), and global limits,
returning RFC-compliant `Retry-After` and `X-RateLimit-*` headers on HTTP 429 ([#162](https://github.com/QueryaHub/OxyRoute/issues/162)).
- **DAG Dependency Resolution**: Directed Acyclic Graph dependency resolution with topological sorting
and cycle detection at route registration time ([#141](https://github.com/QueryaHub/OxyRoute/issues/141), [#189](https://github.com/QueryaHub/OxyRoute/pull/189)).
- **Adaptive Concurrency Limiting & Load Shedding**: `AdaptiveConcurrencyLimiter` to protect against
out-of-memory errors and tail latency spikes under sudden load ([#161](https://github.com/QueryaHub/OxyRoute/issues/161), [#185](https://github.com/QueryaHub/OxyRoute/pull/185)).
- **OpenAPI 3.1.0 & Schema Enhancements**: Upgraded default OpenAPI specification version to 3.1.0 for
Pydantic v2 JSON Schema compatibility ([#154](https://github.com/QueryaHub/OxyRoute/issues/154)),
auto-documented 401 Unauthorized and 422 Validation Error responses ([#153](https://github.com/QueryaHub/OxyRoute/issues/153)),
and added query / header parameter schema declarations ([#155](https://github.com/QueryaHub/OxyRoute/issues/155)).
- **Pydantic DX**: Auto-infer Pydantic `body_model` from handler signature annotations and support
flexible parameter names ([#151](https://github.com/QueryaHub/OxyRoute/issues/151), [#152](https://github.com/QueryaHub/OxyRoute/issues/152)).
- **WebSocket Enhancements**: Concurrent send frame serialization to prevent frame interleaving ([#146](https://github.com/QueryaHub/OxyRoute/issues/146))
and graceful shutdown broadcast sending WebSocket 1001 (Going Away) to active connections ([#158](https://github.com/QueryaHub/OxyRoute/issues/158)).
- **Type Stubs & Packaging**: Added PEP 561 `py.typed` marker and comprehensive `_oxyroute.pyi` type stubs ([#150](https://github.com/QueryaHub/OxyRoute/issues/150)).
- **Database Streaming**: Streaming and chunked row decoding for `DBQuery` SQLx results ([#143](https://github.com/QueryaHub/OxyRoute/issues/143)).

### Performance

- **PEP 590 Vectorcall Protocol**: Direct Python callable invocation using Vectorcall, eliminating `PyDict`
allocation for kwargs on the hot path ([#159](https://github.com/QueryaHub/OxyRoute/issues/159)).
- **Packed 64B RouteEntry**: Packed route entry memory layout into a single 64-byte cacheline for optimal
L1 cacheline utilization during route dispatch ([#147](https://github.com/QueryaHub/OxyRoute/issues/147)).
- **Unified Dependency & Snapshot Layout**: Contiguous `Arc<[DependencyEntry]>` arrays and consolidated
`Arc<FrozenState>` snapshot reducing atomic reference counts ([#148](https://github.com/QueryaHub/OxyRoute/issues/148), [#149](https://github.com/QueryaHub/OxyRoute/issues/149)).
- **Thread-Local Buffer Pool**: Implemented `PooledBuffer` to reuse request body memory allocations ([#160](https://github.com/QueryaHub/OxyRoute/issues/160)).
- **Zero-Allocation Path Parameters**: Slicing path parameters directly from URL strings without intermediate
heap allocations ([#140](https://github.com/QueryaHub/OxyRoute/issues/140)).
- **Single-Pass Method Lookup**: Unified radix lookup and method bitmasks for 405 Method Not Allowed handling ([#139](https://github.com/QueryaHub/OxyRoute/issues/139)).
- **Pre-Baked Static Response Headers**: Pre-formatted static response headers eliminating string formatting ([#164](https://github.com/QueryaHub/OxyRoute/issues/164), [#184](https://github.com/QueryaHub/OxyRoute/pull/184)).

### Security & Hardening

- **StaticFiles Hardening**: Prevented path traversal and symlink escape vulnerabilities ([#144](https://github.com/QueryaHub/OxyRoute/issues/144)).
- **DBQuery Parameter Binding**: Verified and documented SQL parameter binding and injection guarantees ([#145](https://github.com/QueryaHub/OxyRoute/issues/145), [#186](https://github.com/QueryaHub/OxyRoute/pull/186)).
- **FFI Safety**: Configured `panic = "abort"` in `Cargo.toml` to prevent undefined behavior from unwinding across FFI ([#156](https://github.com/QueryaHub/OxyRoute/issues/156)).
- **Observability**: Ensured `access_log_hook` execution on unhandled exceptions via try-finally ([#157](https://github.com/QueryaHub/OxyRoute/issues/157)).
- **Dependencies**: Upgraded to `oxyjwt >= 0.7.0` and dropped legacy `pyjwt`/`cryptography` dependencies ([#178](https://github.com/QueryaHub/OxyRoute/pull/178)).

## [0.5.0] - 2026-07-20

### Added
Expand Down Expand Up @@ -98,6 +143,7 @@ hardening after the v0.3.0 ASGI removal. See `git log v0.3.0..v0.4.0` for the fu

```python
from tests._rsgi_test_transport import asgi_test_app

transport = httpx.ASGITransport(app=asgi_test_app(app))
```

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "oxyroute"
version = "0.5.0"
version = "0.6.0"
edition = "2021"
description = "RSGI web framework: Rust hot path, Python handlers"
license = "MIT"
Expand Down Expand Up @@ -46,3 +46,5 @@ extension-module = ["pyo3/extension-module"]
[profile.release]
lto = true
codegen-units = 1
panic = "abort"

18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ High-performance web framework for **Granian RSGI**, tuned for high **single-wor
## Features

- **RSGI** entrypoint (`async def __rsgi__(scope, protocol)`) compatible with Granian’s RSGI implementation
- **Routing** via [matchit](https://crates.io/crates/matchit) (path parameters like `/users/:id`)
- **JSON, form, and multipart bodies** parsed on the native path; successful values passed to handlers as kwargs
- **JWT** verification on the Rust path before your handler runs (`require_jwt`, HS*, RSA, EC, EdDSA public-key verification)
- **OpenAPI** `GET /openapi.json` plus optional Scalar/Swagger UI at `/docs`
- **Dependencies**: linear list of named factories (`Depends`, sync or async) passed as kwargs
- **Routing** via [matchit](https://crates.io/crates/matchit) (path parameters like `/users/:id`) with packed 64B cacheline entries and zero-allocation path param slicing
- **Native Rate Limiting**: token bucket rate limiter in Rust on route decorators (`rate_limit="100/minute"`, `rate_limit_key="header:X-API-Key"`)
- **JSON, form, and multipart bodies** parsed on the native path with thread-local buffer pooling; auto-inferred Pydantic `body_model` support
- **PEP 590 Vectorcall**: direct Python handler invocation eliminating dictionary allocation on the hot path
- **JWT** verification on the Rust path before your handler runs (`require_jwt`, HS*, RSA, EC, EdDSA public-key verification via `oxyjwt`)
- **OpenAPI 3.1.0** `GET /openapi.json` with automatic 401/422 docs and optional Scalar/Swagger UI at `/docs`
- **Dependencies**: DAG dependency resolution with cycle detection at registration (`Depends`, sync or async) passed as kwargs
- **Resilience**: adaptive concurrency limiting and load shedding (`AdaptiveConcurrencyLimiter`)
- **Optional middleware layers** for pre-route decisions, CORS, CSRF, and browser security headers
- **Native RSGI WebSockets** via `@app.websocket(path)` and `oxyroute.WebSocket`
- Native extension wheel (abi3) for **Python ≥ 3.10**
- **Native RSGI WebSockets** via `@app.websocket(path)` and `oxyroute.WebSocket` with frame serialization and graceful shutdown notification
- **Typed & Tested**: PEP 561 `py.typed` marker, complete `.pyi` stubs, and native extension wheel (abi3) for **Python ≥ 3.10**

Start with the full **[Usage guide](docs/usage.md)**, or use **[docs/index.md](docs/index.md)** for topic-specific pages.

Expand Down Expand Up @@ -82,6 +85,7 @@ OxyRoute supports **only** Granian RSGI; the legacy ASGI bridge (`uvicorn` / `gr
- [RSGI and Granian](docs/rsgi.md) — app entrypoint, lifespan hooks, worker process model
- [Handlers](docs/handlers.md) — injected parameters and response mapping details
- [Routing](docs/routing.md) — methods, path syntax, `APIRouter`, `freeze()`
- [Rate Limiting](docs/rate-limiting.md) — native Token Bucket rate limiting on route decorators
- [JWT](docs/jwt.md), [CORS](docs/cors.md), [CSRF](docs/csrf.md), [Security headers](docs/security-headers.md)
- [WebSockets](docs/websocket.md) and [SSE](docs/sse.md)

Expand Down
38 changes: 37 additions & 1 deletion benches/hot_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,46 @@ fn bench_json_to_py(c: &mut Criterion) {
});
}

fn bench_json_scale(c: &mut Criterion) {
let item = json!({"id": 12345, "name": "Product item name", "price": 99.95, "active": true});
let generate_payload = |count: usize| -> serde_json::Value {
let items: Vec<serde_json::Value> = (0..count).map(|_| item.clone()).collect();
json!({ "items": items, "count": count })
};

let p_1kb = generate_payload(15);
let p_10kb = generate_payload(150);
let p_100kb = generate_payload(1500);

Python::with_gil(|py| {
let mut group = c.benchmark_group("json_scale");
group.bench_function("1kb", |b| {
b.iter(|| {
let obj = json_to_py(py, black_box(&p_1kb)).unwrap();
black_box(obj)
})
});
group.bench_function("10kb", |b| {
b.iter(|| {
let obj = json_to_py(py, black_box(&p_10kb)).unwrap();
black_box(obj)
})
});
group.bench_function("100kb", |b| {
b.iter(|| {
let obj = json_to_py(py, black_box(&p_100kb)).unwrap();
black_box(obj)
})
});
group.finish();
});
}

criterion_group!(
benches,
bench_match_route,
bench_map_handler_return,
bench_json_to_py
bench_json_to_py,
bench_json_scale
);
criterion_main!(benches);
2 changes: 2 additions & 0 deletions docs/cors.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ apply_cors(
),
)


@app.get("/api/x")
def x() -> dict:
return {"ok": True}
Expand All @@ -36,6 +37,7 @@ def my_mw(scope, protocol):
# runs only when apply_cors did not return a preflight response
return None


apply_cors(app, config, chain=my_mw)
```

Expand Down
93 changes: 93 additions & 0 deletions docs/database.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Database Integration & DBQuery Security

OxyRoute integrates native database query execution via `sqlx` in Rust, avoiding Python-level ORM overhead on hot data paths while preserving strict SQL injection prevention guarantees.

---

## Native DBQuery & Parameter Binding

When executing queries through `DBQuery`, all arguments must be bound using positional placeholders (`$1`, `$2`, `$3`, ...).

### Safe Parameter Binding (Recommended)

Always pass arguments as a tuple or list in `DBQuery(query, args)`:

```python
from oxyroute import App, DBQuery, Depends

app = App()

def get_user_query(email: str) -> DBQuery:
# SAFE: $1 is parameterized and bound by sqlx at the driver level
return DBQuery(
"SELECT id, username, email FROM users WHERE email = $1 AND is_active = $2",
(email, True),
)

@app.get("/users/by-email", dependencies=[("user_data", Depends(get_user_query))])
def get_user(user_data):
return {"user": user_data}
```

---

## SQL Injection Prevention (Important)

> [!CAUTION]
> **NEVER** use Python f-strings, `%` formatting, or string concatenation (`+`) to insert user input into SQL queries. Doing so bypasses parameterization and introduces severe SQL injection vulnerabilities.

### Vulnerable Examples (DO NOT DO THIS)

```python
# ❌ VULNERABLE TO SQL INJECTION:
email = "admin' OR 1=1; --"
query = DBQuery(f"SELECT * FROM users WHERE email = '{email}'")

# ❌ ALSO VULNERABLE:
query = DBQuery("SELECT * FROM users WHERE email = '" + email + "'")
```

### Supported Parameter Types

The native Rust executor automatically maps and type-checks the following Python types into Postgres wire formats:

| Python Type | Postgres Type |
|---|---|
| `None` | `NULL` |
| `bool` | `BOOL` |
| `int` | `INT2`, `INT4`, `INT8` (auto-selected) |
| `float` | `FLOAT4`, `FLOAT8` |
| `str` | `TEXT`, `VARCHAR`, `CHAR` |
| `dict` / `list` | `JSON`, `JSONB` |

---

## Database Connection Pool Lifecycle

Configure the Postgres pool in `on_startup` or via `app.setup_database`:

```python
@app.on_startup
async def startup():
await app.setup_database(
"postgresql://user:password@localhost:5432/dbname",
max_connections=20,
)

@app.on_shutdown
async def shutdown():
await app.close_database()
```

---

## Streaming & Chunked Row Decoding

For large result sets, `DBQuery` streams row decoding directly from the PostgreSQL socket without accumulating unmanaged `PgRow` structs in Rust memory (issue #143).

You can also pass `chunk_size` to group rows into batches:

```python
# Stream rows in chunks of 500
query = DBQuery("SELECT * FROM large_log_table WHERE timestamp > $1", (since,), chunk_size=500)
```
7 changes: 6 additions & 1 deletion docs/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

[← Documentation index](index.md)

OxyRoute supports a **linear** list of **named** dependency factories. At request time, each factory is called in order; its return value is injected into the route handler as a **keyword argument** with the given name. Factories that appear **later** in the list are called with **keyword arguments** for every **earlier** name and value (so a factory can depend on a previous one by using the same parameter name, e.g. `def b(a: int): …` when the first tuple is `("a", make_a)`).
OxyRoute resolves route dependencies as a **directed acyclic graph (DAG)**. At route registration time, OxyRoute builds a dependency graph from the factories' parameter names and performs **topological sorting** (Kahn's algorithm). This guarantees that prerequisites are always evaluated before dependents, regardless of the declaration order in the `dependencies` list. Any **circular dependencies** are detected and rejected at registration time with a descriptive `ValueError`.

At request time, each factory is called in topological order; its return value is injected into dependent factories and the route handler as a **keyword argument**. Factories only receive their declared keyword arguments, avoiding unnecessary parameter passing overhead.

### Request context (optional)

Expand All @@ -21,6 +23,7 @@ Pass a list of two-tuples `(name, factory)` to a route decorator, for example:
def get_db() -> str:
return "db-conn"


@app.get("/items", dependencies=[("db", get_db)])
def list_items(db: str) -> str:
return f"ok {db}"
Expand All @@ -37,9 +40,11 @@ def list_items(db: str) -> str:
```python
from oxyroute import App, Depends


def get_settings():
return {"env": "dev"}


@app.get("/x", dependencies=[("settings", Depends(get_settings))])
def x(**kwargs) -> str:
return "ok"
Expand Down
2 changes: 2 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ from oxyroute.testing import TestClient

app = App()


@app.get("/")
def home():
return {"status": "ok"}


def test_home():
with TestClient(app) as client:
resp = client.get("/")
Expand Down
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ Granian still invokes a Python `App` object; the “win” is doing routing, bod
| [JWT](jwt.md) | `require_jwt`, HS* / RSA / EC PEM, `decode_jwt_hs` (HS* tests) |
| [Streaming & SSE](streaming.md) | `stream_bytes`, `stream_text`, `send_sse`, streaming caveats |
| [WebSockets](websocket.md) | Native RSGI `@app.websocket(path)` and `oxyroute.WebSocket` |
| [Rate Limiting](rate-limiting.md) | Native Rust in-memory Token Bucket rate limiting on route decorators |
| [HTTP/2 with Granian](http2.md) | Transport guarantees vs server/proxy responsibilities |
| [Dependencies](dependencies.md) | `Depends`, `dependencies=[...]`, `freeze` |
| [OpenAPI](openapi.md) | `openapi.json`, docs UI (Scalar/Swagger), tags, JWT security |
| [Development](development.md) | Tests, CI, PyPI releases (tag `v*`), clippy, pytest |
| [Branching and PRs](development-workflow.md) | `dev` as base, issue branches, `Closes #N`, no mixing code with `ISSUE_BACKLOG` in one commit |
| [Feature gaps (research)](feature.md) | What is missing vs a “full” HTTP framework and what has been implemented — Russian |
| [SIMD JSON Evaluation](simd-json-evaluation.md) | SIMD-accelerated JSON deserialization evaluation and throughput analysis |
| [Contributing](../CONTRIBUTING.md) | Local setup, issue backlog, GitHub `gh` workflow |

[← Back to project README](../README.md)
4 changes: 2 additions & 2 deletions docs/jwt.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ The handler is **not** executed when:

## `decode_jwt_hs` (Python, for tests and HS parity)

The native module exports **`decode_jwt_hs(token, key, algorithm_list)`**, re-exported from the top-level `oxyroute` package. It decodes a token and returns claims, **HMAC (HS\*) only**, for golden tests against the [`oxyjwt`](https://pypi.org/project/oxyjwt) package. For **RSA/EC/Ed** verification in Python tests, use your usual stack (e.g. **PyJWT** + `cryptography` with the same PEM keys).
The native module exports **`decode_jwt_hs(token, key, algorithm_list)`**, re-exported from the top-level `oxyroute` package. It decodes a token and returns claims, **HMAC (HS\*) only**, for golden tests against the [`oxyjwt`](https://pypi.org/project/oxyjwt) package. For **RSA/EC/Ed** verification and token generation in Python tests, use [`oxyjwt`](https://pypi.org/project/oxyjwt) (`EncodingKey.from_*_pem` / `DecodingKey.from_*_pem`).

**Optional dev dependencies:** `oxyjwt`, `pyjwt`, and `cryptography` are in `oxyroute[dev]` for tests; production only needs the native extension and your `jwt_secret` / `algorithms` configuration.
**Optional dev dependencies:** `oxyjwt` is in `oxyroute[dev]` for tests; production only needs the native extension and your `jwt_secret` / `algorithms` configuration.

## See also

Expand Down
5 changes: 3 additions & 2 deletions docs/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[← Documentation index](index.md)

OxyRoute maintains an **OpenAPI 3.0**-shaped JSON document in Rust while routes are registered. It is suitable for discovery and interactive docs (Scalar / Swagger UI), and can be extended further in future versions.
OxyRoute maintains an **OpenAPI 3.1.0** (or configured version) JSON document in Rust while routes are registered. It is fully compatible with Pydantic v2 JSON Schema and suitable for discovery and interactive docs (Scalar / Swagger UI).

## Constructor and toggles

Expand Down Expand Up @@ -39,9 +39,10 @@ app.mount_docs("/api/docs", ui="swagger")

UI scripts load from **jsDelivr**. If you use `SecurityHeadersConfig` (or a strict CSP), allow `cdn.jsdelivr.net` in `script-src` / `style-src` for the docs route, or disable those headers on `/docs`.

## Title, info, and servers
## Title, version, info, and servers

- **`title=`** / **`set_openapi_title`** — `info.title`.
- **`openapi_version="3.1.0"`** (default) / **`set_openapi_version`** — root `openapi` specification version string.
- **`openapi_description=`**, **`openapi_contact=`**, **`openapi_servers=`** constructor kwargs, or **`app.set_openapi_info(description=..., contact=..., servers=...)`**.

```python
Expand Down
Loading
Loading