Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 43 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,35 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.5.0] - 2026-07-20

### Added

- First-class OpenAPI docs UI: `App(..., docs_ui="scalar"|"swagger")` and
`app.mount_docs(...)` serve CDN-backed Scalar / Swagger UI at `/docs` (or a custom
path) against `/openapi.json` ([#130](https://github.com/QueryaHub/OxyRoute/issues/130)).
`app.mount_docs(...)` serve CDN-backed Scalar / Swagger UI at `/docs` against
`/openapi.json` ([#130](https://github.com/QueryaHub/OxyRoute/issues/130)).
- OpenAPI enrichment for interactive explorers: matchit `:param` / `*rest` → `{param}` /
`{rest}` with path `parameters`; JWT `bearerAuth` security scheme when
`require_jwt=True`; operation `tags=` and `include_router(..., tags=[...])`;
`set_openapi_info` / constructor `openapi_description` / `openapi_contact` /
`openapi_servers`.
`{rest}` with path `parameters`; JWT `bearerAuth` when `require_jwt=True`; operation
`tags=` / `include_router(..., tags=[...])`; `set_openapi_info` and constructor
`openapi_description` / `openapi_contact` / `openapi_servers`.
- Granian-compatible lifespan: sync `__rsgi_init__(loop)` / `__rsgi_del__(loop)` run
`on_startup` / `on_shutdown` via `loop.run_until_complete`. Prefer overriding
`on_startup` / `on_shutdown` instead of async `__rsgi_init__`.
`on_startup` / `on_shutdown` instead of `async def __rsgi_init__`.
- `oxyroute.testing.TestClient` for in-process HTTP tests ([#102](https://github.com/QueryaHub/OxyRoute/issues/102)).
- Typed `Request` with lazy headers ([#101](https://github.com/QueryaHub/OxyRoute/issues/101)).
- Runtime `body_model` validation with HTTP 422 ([#100](https://github.com/QueryaHub/OxyRoute/issues/100)).
- Global exception handlers for sync and async routes
([#99](https://github.com/QueryaHub/OxyRoute/issues/99)).
- Optional request / response middleware chain
([#98](https://github.com/QueryaHub/OxyRoute/issues/98)).
- `StaticFiles` and `App.mount` ([#104](https://github.com/QueryaHub/OxyRoute/issues/104)).
- Generic streaming responses (non-SSE chunked generators)
([#103](https://github.com/QueryaHub/OxyRoute/issues/103)).
- Observability hooks: request id, access log, metrics
([#127](https://github.com/QueryaHub/OxyRoute/pull/127)).
- SQLx / Postgres pool helpers on `AppState` and dynamic query execution from Python
dependencies ([#116](https://github.com/QueryaHub/OxyRoute/issues/116),
[#118](https://github.com/QueryaHub/OxyRoute/issues/118)).
- Criterion microbenchmarks (`cargo bench --bench hot_path`) and expanded wrk scenarios
(`perf-test/bench_scenarios.sh`); optional `perf-smoke` workflow
([#110](https://github.com/QueryaHub/OxyRoute/issues/110)).
Expand All @@ -28,10 +44,28 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

- OpenAPI path keys use `{param}` form (breaking for consumers that asserted matchit
`:param` strings in `openapi_json()`).
- JWT hot path reuses prebuilt `DecodingKey` and `Validation` per route (no per-request
rebuild) ([#109](https://github.com/QueryaHub/OxyRoute/issues/109)).
- JWT hot path reuses prebuilt `DecodingKey` and `Validation` per route
([#109](https://github.com/QueryaHub/OxyRoute/issues/109)).
- CORS response merge skips the Python `response_header_pairs` call when the request has
no `Origin` header ([#108](https://github.com/QueryaHub/OxyRoute/issues/108)).
- OpenAPI document string is cached until the next registration change
([#129](https://github.com/QueryaHub/OxyRoute/pull/129)).
- Router / dispatch hot-path improvements: fewer path-param allocations, sync short-circuit
for trivial routes, direct `json_to_py`, cheaper str/bytes responses, env-flag caching
([#94](https://github.com/QueryaHub/OxyRoute/issues/94)–[#97](https://github.com/QueryaHub/OxyRoute/issues/97),
[#128](https://github.com/QueryaHub/OxyRoute/pull/128)).

### Migration

- Prefer `async def on_startup` / `on_shutdown` for worker lifecycle under Granian. Do not
override `__rsgi_init__` as `async def` (the coroutine is never awaited).
- Update any tooling that expected OpenAPI paths with matchit `:param` syntax to `{param}`.
- Interactive docs: `App(..., docs_ui="scalar")` (or `mount_docs`) instead of app-local HTML.

## [0.4.0] - 2026-05

RSGI-only line with native WebSockets, forms, CORS/CSRF/security headers, and related
hardening after the v0.3.0 ASGI removal. See `git log v0.3.0..v0.4.0` for the full list.

## [0.3.0] - 2026-04-27

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.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "oxyroute"
version = "0.4.0"
version = "0.5.0"
edition = "2021"
description = "RSGI web framework: Rust hot path, Python handlers"
license = "MIT"
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ High-performance web framework for **Granian RSGI**, tuned for high **single-wor
- **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)
- **Optional** `GET /openapi.json` with a minimal OpenAPI-style document
- **OpenAPI** `GET /openapi.json` plus optional Scalar/Swagger UI at `/docs`
- **Dependencies**: linear list of named factories (`Depends`, sync or async) passed as kwargs
- **Optional middleware layers** for pre-route decisions, CORS, CSRF, and browser security headers
- **Native RSGI WebSockets** via `@app.websocket(path)` and `oxyroute.WebSocket`
Expand Down Expand Up @@ -72,9 +72,9 @@ Run (from the repo, after `maturin develop` or an editable install):
granian --interface rsgi examples.rsgi_app:app
```

Per-worker setup (`__rsgi_init__`) is shown in [examples/rsgi_lifespan_app.py](examples/rsgi_lifespan_app.py) and [docs/rsgi.md](docs/rsgi.md#lifespan-optional).
Per-worker setup (`on_startup` / Granian-compatible `__rsgi_init__`) is shown in [examples/rsgi_lifespan_app.py](examples/rsgi_lifespan_app.py) and [docs/rsgi.md](docs/rsgi.md#lifespan-optional).

OxyRoute v0.3.0 supports **only** Granian RSGI; the legacy ASGI bridge (`uvicorn` / `granian --interface asgi`) was removed.
OxyRoute supports **only** Granian RSGI; the legacy ASGI bridge (`uvicorn` / `granian --interface asgi`) was removed in v0.3.0.

## Usage docs

Expand Down
4 changes: 2 additions & 2 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ The workflow at `.github/workflows/ci.yml` (job name: **ci**):

## Releasing to PyPI

Tag a release with a **`v`-prefixed** semver tag (example: **`v0.3.0`**). That triggers `.github/workflows/release-pypi.yml`, which builds an **sdist**, **manylinux** x86_64 wheels, **Windows** x64, and **macOS** arm64 + x86_64 wheels, then uploads to **PyPI** using a **project-scoped API token** stored in GitHub as **`PYPI_API_TOKEN`** (Secret or Environment variable) on the **`pypi`** environment. The publish step uses `secrets` first, then `vars` (so you can start with a variable and move the value to a **Secret** later).
Tag a release with a **`v`-prefixed** semver tag (example: **`v0.5.0`**). That triggers `.github/workflows/release-pypi.yml`, which builds an **sdist**, **manylinux** x86_64 wheels, **Windows** x64, and **macOS** arm64 + x86_64 wheels, then uploads to **PyPI** using a **project-scoped API token** stored in GitHub as **`PYPI_API_TOKEN`** (Secret or Environment variable) on the **`pypi`** environment. The publish step uses `secrets` first, then `vars` (so you can start with a variable and move the value to a **Secret** later).

**Before the first upload:**

1. Keep **`pyproject.toml`**, **`Cargo.toml`**, and **`oxyroute/__init__.py`** `__version__` in sync with the version you are releasing, and with the tag (e.g. `0.3.0` → tag `v0.3.0`).
1. Keep **`pyproject.toml`**, **`Cargo.toml`**, and **`oxyroute/__init__.py`** `__version__` in sync with the version you are releasing, and with the tag (e.g. `0.5.0` → tag `v0.5.0`).
2. On [PyPI](https://pypi.org), create a **scoped API token** for this project, then in GitHub → **Settings → Environments** create the **`pypi`** environment and add **`PYPI_API_TOKEN`** (strongly prefer an **Environment secret** over a **Variable**; tokens in Variables are visible to people with access to the environment).
3. Optional alternative to API tokens: [trusted publishing](https://docs.pypi.org/trusted-publishers/) (OIDC) — no long-lived token; then the workflow’s publish job should omit `with.password` and set `id-token: write` (see the PyPA action README).

Expand Down
7 changes: 2 additions & 5 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[← Documentation index](index.md)

This guide is the recommended end-to-end reference for using OxyRoute as an
application framework. It describes the current **v0.3.0** behavior: OxyRoute is
application framework. It describes the current **v0.5.0** behavior: OxyRoute is
**RSGI-only** and is intended to run behind **Granian** with `--interface rsgi`.
The removed ASGI bridge is not part of the supported runtime path.

Expand Down Expand Up @@ -565,12 +565,9 @@ Production checklist:
- Keep `OXYROUTE_DEBUG` unset in production.
- Use external storage for cross-worker state.

## Known limitations in v0.3.0
## Known limitations in v0.5.0

- Request bodies and multipart files are buffered in memory before parsing.
- There is one pre-route middleware hook; compose middleware manually or with
helpers such as `apply_cors(..., chain=...)`.
- There is no global exception-handler registry yet.
- WebSocket subprotocol negotiation is not exposed as a high-level API.
- Benchmark scripts are for local comparison and are not CI performance gates.

Expand Down
2 changes: 1 addition & 1 deletion oxyroute/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@
"stream_jsonl",
"stream_text",
]
__version__ = "0.4.0"
__version__ = "0.5.0"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "maturin"

[project]
name = "oxyroute"
version = "0.4.0"
version = "0.5.0"
description = "RSGI-first web framework: routing, JSON, and JWT in Rust (PyO3), Python handlers"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl AppState {
pub fn new() -> Self {
let openapi = serde_json::json!({
"openapi": "3.0.0",
"info": { "title": "OxyRoute", "version": "0.3.0" },
"info": { "title": "OxyRoute", "version": "0.5.0" },
"paths": {}
});
Self {
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

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

Loading