From 28224a52eed971ad4067d3b81e0ca891e94b1fef Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Mon, 20 Jul 2026 14:53:42 +0300 Subject: [PATCH 1/2] chore(release): prepare 0.5.0 Bump package versions and document the 0.5.0 changelog (OpenAPI docs UI, lifespan, TestClient, middleware, perf, and related work since 0.4.0). --- CHANGELOG.md | 52 ++++++++++++++++++++++++++++++++++++-------- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 6 ++--- docs/development.md | 4 ++-- oxyroute/__init__.py | 2 +- pyproject.toml | 2 +- src/state.rs | 2 +- uv.lock | 2 +- 9 files changed, 54 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c5546e..0ae1b1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)). @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 2642e17..c0e480c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1012,7 +1012,7 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "oxyroute" -version = "0.4.0" +version = "0.5.0" dependencies = [ "base64", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 9a336af..0dd9a57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 40b5cab..c5f443f 100644 --- a/README.md +++ b/README.md @@ -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` @@ -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 diff --git a/docs/development.md b/docs/development.md index 309ac62..f2ff917 100644 --- a/docs/development.md +++ b/docs/development.md @@ -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). diff --git a/oxyroute/__init__.py b/oxyroute/__init__.py index 247fc43..4abb2ce 100644 --- a/oxyroute/__init__.py +++ b/oxyroute/__init__.py @@ -38,4 +38,4 @@ "stream_jsonl", "stream_text", ] -__version__ = "0.4.0" +__version__ = "0.5.0" diff --git a/pyproject.toml b/pyproject.toml index 6703a44..6eabe0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/src/state.rs b/src/state.rs index 68d61f9..32828b9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -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 { diff --git a/uv.lock b/uv.lock index 7e7f533..ec23ca5 100644 --- a/uv.lock +++ b/uv.lock @@ -417,7 +417,7 @@ wheels = [ [[package]] name = "oxyroute" -version = "0.4.0" +version = "0.5.0" source = { editable = "." } [package.optional-dependencies] From c3d2003767e6ad149cd4eb48ace2ee0773e4aca8 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Mon, 20 Jul 2026 14:53:58 +0300 Subject: [PATCH 2/2] docs: align usage guide with v0.5.0 --- docs/usage.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 40b1814..3c784df 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -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. @@ -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.