Skip to content

Commit aa81acf

Browse files
Merge pull request #93 from QueryaHub/chore-readme-positioning-main-pr
docs: update README positioning and 0.3.0 changelog heading
2 parents 92824fe + bd83903 commit aa81acf

64 files changed

Lines changed: 3509 additions & 728 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Changelog
2+
3+
All notable changes to OxyRoute are documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
6+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
## [0.3.0] - 2026-04-27
11+
12+
### Added
13+
14+
- Native RSGI WebSocket support: `@app.websocket(path)` and `oxyroute.WebSocket` are
15+
exported from the package and dispatched directly inside the Rust extension. No ASGI
16+
shim is involved; the helper class wraps Granian's `RSGIWebsocketProtocol` and exposes
17+
`accept`, `receive` / `receive_text` / `receive_bytes`, `send_text` / `send_bytes` /
18+
`send_json`, and `close`. Path matching uses the same `matchit` syntax as HTTP routes
19+
and lives in its own router. Unknown paths produce a polite `close(1000)`; handler
20+
errors trigger `close(1011)`.
21+
22+
### Removed (breaking)
23+
24+
- The optional ASGI 3.0 compatibility bridge (`oxyroute.asgi`) was removed. `App` is no
25+
longer an ASGI callable; `App.__call__`, `App._asgi3`, the `asgi_to_rsgi` helper and the
26+
`WebSocket` helper class are gone. Run OxyRoute exclusively under
27+
``granian --interface rsgi``.
28+
- The ASGI-based `@app.websocket(path)` decorator and the
29+
`App._handle_asgi_websocket` plumbing were removed alongside the bridge. Native RSGI
30+
WebSocket support is reintroduced in this release (see *Added*).
31+
32+
### Migration
33+
34+
- If you ran `uvicorn` or `granian --interface asgi` against an OxyRoute app, switch to
35+
`granian --interface rsgi`. RSGI is now the only supported transport.
36+
- For unit tests that drove the app via `httpx.ASGITransport(app=app)`, import the
37+
test-only shim from the test tree:
38+
39+
```python
40+
from tests._rsgi_test_transport import asgi_test_app
41+
transport = httpx.ASGITransport(app=asgi_test_app(app))
42+
```
43+
44+
The shim is **only** built for the test suite; production code must not import it.
45+
46+
## [0.2.0] - 2026-04
47+
48+
Initial public release. See `git log v0.2.0` for details.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "oxyroute"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
edition = "2021"
55
description = "RSGI web framework: Rust hot path, Python handlers"
66
license = "MIT"

README.md

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
# OxyRoute
22

3-
**RSGI-first** web toolkit: HTTP routing, JSON handling, and HS\* JWT validation on a **Rust** hot path ([PyO3](https://pyo3.rs/) + [Maturin](https://www.maturin.rs/)), with your business logic in ordinary **Python** handlers. Pair it with **[Granian](https://github.com/emmett-framework/granian)** using `--interface rsgi` for the intended stack.
3+
High-performance web framework for **Granian RSGI**, tuned for high **single-worker** throughput: routing, JSON/form parsing, JWT checks, response mapping, and native WebSockets run on a **Rust** hot path ([PyO3](https://pyo3.rs/) + [Maturin](https://www.maturin.rs/)), while business logic stays in plain **Python** handlers.
44

55
[![CI](https://github.com/QueryaHub/OxyRoute/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/QueryaHub/OxyRoute/actions)
66

77
## Features
88

99
- **RSGI** entrypoint (`async def __rsgi__(scope, protocol)`) compatible with Granian’s RSGI implementation
1010
- **Routing** via [matchit](https://crates.io/crates/matchit) (path parameters like `/users/:id`)
11-
- **JSON bodies** parsed in Rust; successful values passed to handlers as kwargs
12-
- **JWT (HMAC)** verification on the Rust path before your handler runs (`require_jwt`, HS256/384/512)
11+
- **JSON, form, and multipart bodies** parsed on the native path; successful values passed to handlers as kwargs
12+
- **JWT** verification on the Rust path before your handler runs (`require_jwt`, HS*, RSA, EC, EdDSA public-key verification)
1313
- **Optional** `GET /openapi.json` with a minimal OpenAPI-style document
1414
- **Dependencies**: linear list of named factories (`Depends`, sync or async) passed as kwargs
15-
- **Optional ASGI 3** bridge: `async def __call__(scope, receive, send)` for servers that speak ASGI (see [docs/asgi.md](docs/asgi.md))
15+
- **Optional middleware layers** for pre-route decisions, CORS, CSRF, and browser security headers
16+
- **Native RSGI WebSockets** via `@app.websocket(path)` and `oxyroute.WebSocket`
1617
- Native extension wheel (abi3) for **Python ≥ 3.10**
1718

18-
Full documentation: **[docs/index.md](docs/index.md)**
19+
Start with the full **[Usage guide](docs/usage.md)**, or use **[docs/index.md](docs/index.md)** for topic-specific pages.
1920

2021
## Requirements
2122

@@ -61,8 +62,8 @@ def root() -> str:
6162

6263

6364
@app.get("/hello/:name")
64-
def hello_name(**kwargs) -> str:
65-
return f"Hello, {kwargs.get('name', '')}"
65+
def hello_name(name: str) -> dict:
66+
return {"message": f"Hello, {name}"}
6667
```
6768

6869
Run (from the repo, after `maturin develop` or an editable install):
@@ -73,11 +74,24 @@ granian --interface rsgi examples.rsgi_app:app
7374

7475
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).
7576

76-
ASGI and other servers are covered in [docs/asgi.md](docs/asgi.md).
77+
OxyRoute v0.3.0 supports **only** Granian RSGI; the legacy ASGI bridge (`uvicorn` / `granian --interface asgi`) was removed.
78+
79+
## Usage docs
80+
81+
- [Usage guide](docs/usage.md) — install, run, routing, bodies, responses, middleware, CORS, CSRF, JWT, WebSockets, deployment notes, limitations
82+
- [RSGI and Granian](docs/rsgi.md) — app entrypoint, lifespan hooks, worker process model
83+
- [Handlers](docs/handlers.md) — injected parameters and response mapping details
84+
- [Routing](docs/routing.md) — methods, path syntax, `APIRouter`, `freeze()`
85+
- [JWT](docs/jwt.md), [CORS](docs/cors.md), [CSRF](docs/csrf.md), [Security headers](docs/security-headers.md)
86+
- [WebSockets](docs/websocket.md) and [SSE](docs/sse.md)
87+
88+
## Production notes
89+
90+
OxyRoute is designed for Granian RSGI deployments. For public traffic, place it behind a normal production boundary (TLS, request-size limits, timeouts, logging, process supervision) and keep `OXYROUTE_DEBUG` disabled. Request bodies and multipart files are currently buffered in memory before parsing, so enforce limits both at the edge and with `OXYROUTE_MAX_BODY_BYTES`.
7791

7892
## Project layout
7993

80-
- `oxyroute/` — Python package (`App`, `Depends`, optional ASGI bridge)
94+
- `oxyroute/` — Python package (`App`, `Depends`)
8195
- `src/` — Rust extension (`_oxyroute`, routing, dispatch, JWT helpers)
8296
- `docs/` — detailed English documentation
8397
- `tests/` — pytest suite (run from a temp directory or an installed wheel so the source tree does not shadow the package; see [docs/development.md](docs/development.md))

docs/asgi.md

Lines changed: 0 additions & 30 deletions
This file was deleted.

docs/dependencies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ OxyRoute supports a **linear** list of **named** dependency factories. At reques
66

77
### Request context (optional)
88

9-
If a factory’s signature includes a parameter named `request`, the extension passes a **dict** (once per request, shared) with string keys: `method`, `path`, `query_string`, and `headers` (a flat `str``str` map, when the underlying RSGI scope exposes headers—see the ASGI bridge in `oxyroute.asgi`). Factories that do **not** declare `request` are still called with **no** extra arguments when they have no prior dependencies, preserving older behavior.
9+
If a factory’s signature includes a parameter named `request`, the extension passes a **dict** (once per request, shared) with string keys: `method`, `path`, `query_string`, and `headers` (a flat `str``str` map, when the underlying RSGI scope exposes headers). Factories that do **not** declare `request` are still called with **no** extra arguments when they have no prior dependencies, preserving older behavior.
1010

1111
## Declaring on a route
1212

docs/development.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pip install "oxyroute[dev]" # in your dev env, from source after maturin develo
4242

4343
## Granian RSGI (end-to-end)
4444

45-
`tests/test_granian_e2e.py` starts a real **Granian** subprocess with `--interface rsgi`, sends HTTP requests with **httpx**, then stops the server. It is part of the normal **pytest** run when `granian` is installed (`oxyroute[dev]` includes it). The same file runs in **CI** on every matrix combination (Linux, macOS, Windows), so the native RSGI path is exercised against a real server, not only the ASGI in-process transport.
45+
`tests/test_granian_e2e.py` starts a real **Granian** subprocess with `--interface rsgi`, sends HTTP requests with **httpx**, then stops the server. It is part of the normal **pytest** run when `granian` is installed (`oxyroute[dev]` includes it). The same file runs in **CI** on every matrix combination (Linux, macOS, Windows), so the native RSGI path is exercised against a real server, not only the in-process httpx test transport.
4646

4747
## Continuous integration
4848

@@ -53,11 +53,11 @@ The workflow at `.github/workflows/ci.yml` (job name: **ci**):
5353

5454
## Releasing to PyPI
5555

56-
Tag a release with a **`v`-prefixed** semver tag (example: **`v0.2.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).
56+
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).
5757

5858
**Before the first upload:**
5959

60-
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.2.0` → tag `v0.2.0`).
60+
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`).
6161
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).
6262
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).
6363

0 commit comments

Comments
 (0)