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
102 changes: 102 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,113 @@ The site is served from `https://j03-dev.github.io/oxapy/`. Two ways to publish:
- Use relative links, and mind the depth prefix: from `tutorial/`, `guides/`, `advanced/`, or `api/` pages, links to a sibling category need `../` (e.g. `../guides/routing`); from `intro.md` use `./guides/routing`. The `onBrokenLinks: 'throw'` build is the safety net — never ship a doc change without running `npx docusaurus build`.
- API pages (`docs/docs/api/*`) document the Python surface; guides (`docs/docs/guides/*`) teach usage with examples; `docs/docs/tutorial/notes-api.md` walks through a complete production-style app (SQLAlchemy + serializers + JWT + async mode).

## Project Structure

```
Comment thread
j03-dev marked this conversation as resolved.
oxapy/
├── Cargo.toml # Rust crate config (pyo3 cdylib + rlib)
├── pyproject.toml # Python packaging (maturin)
├── build.sh # Build helper script
├── src/
│ ├── lib.rs # HttpServer, server loop, request dispatch, PyModule registration
│ ├── middleware.rs # Middleware chain builder (sequence-based wrapping)
│ ├── request.rs # Request struct, RequestBuilder, cookie/JSON/form parsing
│ ├── response.rs # Response struct, Redirect, FileStreaming, header manipulation
│ ├── routing.rs # Route, Router, HTTP method decorators (get/post/etc), matchit
│ ├── status.rs # Status enum (all HTTP status codes)
│ ├── into_response.rs # convert_to_response: normalizes handler returns to Response
│ ├── exceptions.rs # BadRequest/Unauthorized/Forbidden/NotFound/Conflict/InternalError
│ ├── cors.rs # CORS config and header injection
│ ├── jwt.rs # JWT encode/decode (jsonwebtoken crate)
│ ├── json.rs # JSON serialization (wraps orjson)
│ ├── multipart.rs # Multipart form/file parsing (multer crate)
│ ├── templating.rs # Tera template engine + render() function
│ └── serializer/
│ ├── mod.rs # Serializer class (DRF-style: validate, create, save, update)
│ └── fields.rs # Field types (Char, Email, Integer, Boolean, Number, UUID, etc.)
├── oxapy/
│ ├── __init__.py # Python re-exports + Oxapy (hot-reload), Session, CsrfProtect, static_file
│ ├── __init__.pyi # Auto-generated type stubs
│ ├── jwt/__init__.pyi # JWT stubs
│ ├── exceptions/__init__.pyi # Exception stubs
│ ├── serializer/__init__.pyi # Serializer stubs
│ └── templating/__init__.pyi # Template stubs
└── tests/
├── conftest.py # Test server fixture (Oxapy on port 9999, auth middleware demo)
├── app.py # Minimal async Oxapy example
├── test_http_server.py # Integration tests (ping, echo, forms, uploads, auth, redirects)
├── test_session.py # JWT encode/decode tests
├── test_response.py # Response/Redirect unit tests
├── test_cors.py # CORS config tests
├── test_serializer.py # Serializer tests
├── test_exceptions.py # Exception tests
└── utils.py # Multipart test helper
```

## Architecture

### Request Lifecycle

```
Client → TcpListener → RequestBuilder → Request::process()
→ OPTIONS + CORS configured? → return preflight response
→ iterate routers → router.find(method, uri) via matchit
→ if match: create ProcessRequest → mpsc channel
→ process_requests loop:
→ build middleware chain (sequence-based wrapping)
→ call_python_handler: middleware chain → Python handler
→ convert_to_response (normalize return type)
→ wrapper(request, response) [if HttpServer.wrap() configured]
→ response.apply_cors(headers)
→ send via oneshot channel → hyper Response → Client
```

### Middleware System

Middleware is **sequence-based** and wraps handlers. Each middleware receives a `next` keyword argument:

```python
def my_middleware(request, next, **kwargs):
# runs BEFORE handler
result = next(request, **kwargs) # calls next layer (or handler)
# result is the handler's return value
return result
```

- `Router.middleware(fn)` registers middleware; it applies to routes registered **after** it
- Chain is built recursively: last middleware wraps the handler, second-to-last wraps that, etc.
- Multiple `Router` instances with different middleware can be attached to the same server

### Template System (Tera)

- Templates are loaded via `Template.load(glob_pattern)` (Tera syntax)
- Custom functions must be registered **before** `load()`: `template.register_function("name", fn)`
- `render(request, "template.html", context)` auto-injects:
- `session` dict (if `Session` middleware is active and `request.session` exists)
- `csrf_token` string (if `CsrfProtect` middleware is active and `request.csrf_token` exists)
- `csrf_token` string (if `CsrfProtect` middleware is active and `request.csrf_token` exists)
Comment thread
j03-dev marked this conversation as resolved.

### Python Modules (oxapy/__init__.py)

Pure Python features implemented in `oxapy/__init__.py`:
- **`Session(secret, max_age)`** — Signed cookie middleware (HMAC-SHA256)
- **`CsrfProtect(secret, ...)`** — CSRF protection middleware + `csrf_input()` template helper
- **`secure_join(base, *paths)`** — Path traversal protection
- **`static_file(path, directory)`** — Static file serving route
- **`send_file(path)`** — File response helper

Rust-implemented features exposed via PyO3:
- `HttpServer`, `Router`, `Route`, `Request`, `Response`, `Status`, `Cors`, `Redirect`, `FileStreaming`
- HTTP method decorators: `get`, `post`, `put`, `patch`, `delete`, `head`, `options`
- `templating.Template`, `templating.render`
- `jwt.Jwt` (encode/decode), `exceptions.*`, `serializer.Serializer`

## Code Style Guidelines

### General Project Structure

- **Rust source**: `src/` directory with modular `.rs` files
- **Python source**: `oxapy/__init__.py` for pure-Python features, Rust for core server
- **Python tests**: `tests/` directory
- **Docs site**: `docs/` directory (Docusaurus; markdown in `docs/docs/`)

Expand Down
49 changes: 16 additions & 33 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,32 +91,25 @@ Roadmap based on feature gap analysis against Flask, FastAPI, Django, Litestar,
- [ ] Add `response.delete_cookie(name, path, domain)`
- [ ] Type-safe API instead of manual `insert_header("set-cookie", "...")`

### 13. CSRF Protection
- [ ] Add `CsrfMiddleware` that generates and validates CSRF tokens
- [ ] Support synchronizer token pattern (double submit cookie)
- [ ] Auto-exempt safe methods (GET, HEAD, OPTIONS)
- [ ] Configurable exempt routes/patterns
- [ ] Integrate with session middleware

### 14. Per-Status Error Handlers
### 13. Per-Status Error Handlers
- [ ] Add `@app.errorhandler(404)` decorator
- [ ] Add `@app.exception_handler(ExceptionType)` decorator
- [ ] Override default exception-to-status mapping
- [ ] Support custom error pages (HTML) and error responses (JSON)

### 15. URL Reverse Routing (`url_for`)
### 14. URL Reverse Routing (`url_for`)
- [ ] Register route names alongside path patterns
- [ ] Add `url_for(route_name, **params)` function
- [ ] Generate correct URLs with path parameters filled in
- [ ] Useful for templates, redirects, and emails

### 16. OAuth2 / Security Utilities
### 15. OAuth2 / Security Utilities
- [ ] Add `OAuth2PasswordBearer(tokenUrl="/token")` dependency
- [ ] Add `HTTPBasic` dependency for HTTP Basic auth
- [ ] Add `APIKeyHeader` / `APIKeyQuery` dependencies
- [ ] Support OAuth2 scopes

### 17. Content Negotiation
### 16. Content Negotiation
- [ ] Inspect `Accept` header to determine response format
- [ ] Support multiple serializers per route (JSON, XML, MessagePack)
- [ ] Default to JSON, fallback based on client preference
Expand All @@ -125,32 +118,22 @@ Roadmap based on feature gap analysis against Flask, FastAPI, Django, Litestar,

## Nice-to-Have (P2)

### 18. Class-Based Views / Controllers
- [ ] Add `Controller` class that groups related route handlers
- [ ] Support shared middleware, pre/post hooks per controller
- [ ] Auto-register routes from controller methods

### 19. Blueprint / Module System
- [ ] Add `Blueprint` class for splitting routes across files
- [ ] Support `app.register_blueprint(bp, prefix="/api/v1")`
- [ ] Auto-merge middleware and static files from blueprints

### 20. CLI Runner
### 17. CLI Runner
- [ ] Add `oxapy run app:main` command
- [ ] Auto-detect uvicorn-like reload in dev
- [ ] Support `--host`, `--port`, `--reload` flags

### 21. Rate Limiting
### 18. Rate Limiting
- [ ] Add `RateLimitMiddleware` with configurable limits
- [ ] Support per-IP and per-route limits
- [ ] Use in-memory store or pluggable backend (Redis)

### 22. Settings / Environment Configuration
### 19. Settings / Environment Configuration
- [ ] Add `Settings` base class (pydantic-settings style)
- [ ] Load from `.env` files and environment variables
- [ ] Type validation at startup

### 23. i18n / Localization
### 20. i18n / Localization
- [ ] Add `gettext`-style translation function
- [ ] Support locale detection from `Accept-Language` header
- [ ] Date/number formatting per locale
Expand All @@ -160,22 +143,22 @@ Roadmap based on feature gap analysis against Flask, FastAPI, Django, Litestar,
## Bug Fixes & Quality

### Stubs
- [ ] Fix `Session()` return type in `__init__.pyi` — should be `Callable`, not `Response`
- [ ] Remove `catcher` from `__init__.py.__all__` (doesn't exist)
- [ ] Remove `"from typing_extensions import Self"` from stub `__all__`
- [ ] Fix docstrings: `app_data()`, `attach()`, `wrap()` say `Returns: None` but return `self`
- [x] Fix `Session()` return type in `__init__.pyi` — should be `Callable`, not `Response`
- [x] Remove `catcher` from `__init__.py.__all__` (doesn't exist)
- [x] Remove `"from typing_extensions import Self"` from stub `__all__`
- [x] Fix docstrings: `app_data()`, `attach()`, `wrap()` say `Returns: None` but return `self`

### Security
- [ ] Change `SameSite=Lax` to `SameSite=Strict` on session cookies (or make configurable)
- [x] Change `SameSite=Lax` to `SameSite=Strict` on session cookies (or make configurable)
- [ ] Add `Origin` / `Referer` header check in session middleware for state-changing methods
- [ ] Replace `unwrap()` in `insert_header` / `append_header` with proper error handling
- [x] Replace `unwrap()` in `insert_header` / `append_header` with proper error handling

### Performance
- [ ] Cache `Regex::new` in `parse_params_value` (slug parsing) — currently recompiles every call
- [x] Cache `Regex::new` in `parse_params_value` (slug parsing) — currently recompiles every call
- [ ] Evaluate middleware chain `py.eval()` overhead — consider alternatives

### Safety
- [ ] Replace `unsafe { std::mem::transmute }` in `request.rs:286` with safe alternative (e.g., `ouroboros` or restructure lifetime)
- [x] Replace `unsafe { std::mem::transmute }`

### Cleanup
- [ ] Remove `#![allow(unused_variables, non_snake_case)]` crate-level attribute — fix individually
Expand Down
Loading