diff --git a/src/catcher.rs b/src/catcher.rs
index f94898d..e7cc05c 100644
--- a/src/catcher.rs
+++ b/src/catcher.rs
@@ -11,13 +11,13 @@ use pyo3::prelude::*;
/// handler (callable): The handler function that will be called when this status occurs.
///
/// Example:
-/// ```python
-/// from oxapy import catcher, Status
-///
-/// @catcher(Status.NOT_FOUND)
-/// def handle_not_found(request, response):
-/// return Response("
Custom 404 Page ", content_type="text/html")
-/// ```
+/// ```python
+/// from oxapy import catcher, Status
+///
+/// @catcher(Status.NOT_FOUND)
+/// def handle_not_found(request, response):
+/// return Response("Custom 404 Page ", content_type="text/html")
+/// ```
#[pyclass]
pub struct Catcher {
pub status: Status,
@@ -61,16 +61,16 @@ impl CatcherBuilder {
/// CatcherBuilder: A builder that creates a Catcher when called with a handler function.
///
/// Example:
-/// ```python
-/// from oxapy import catcher, Status, Response
-///
-/// @catcher(Status.NOT_FOUND)
-/// def handle_404(request, response):
-/// return Response("Page Not Found ", content_type="text/html")
-///
-/// # Add the catcher to your server
-/// app.catchers([handle_404])
-/// ```
+/// ```python
+/// from oxapy import catcher, Status, Response
+///
+/// @catcher(Status.NOT_FOUND)
+/// def handle_404(request, response):
+/// return Response("Page Not Found ", content_type="text/html")
+///
+/// # Add the catcher to your server
+/// app.catchers([handle_404])
+/// ```
#[pyfunction]
pub fn catcher(status: Status) -> CatcherBuilder {
CatcherBuilder { status }
diff --git a/src/cors.rs b/src/cors.rs
index 3a166ad..db33175 100644
--- a/src/cors.rs
+++ b/src/cors.rs
@@ -13,19 +13,19 @@ use pyo3::prelude::*;
/// Cors: A new CORS configuration with default settings.
///
/// Example:
-/// ```python
-/// from oxapy import HttpServer, Cors
-///
-/// app = HttpServer(("127.0.0.1", 8000))
-///
-/// # Set up CORS with custom configuration
-/// cors = Cors()
-/// cors.origins = ["https://example.com", "https://app.example.com"]
-/// cors.methods = ["GET", "POST", "OPTIONS"]
-/// cors.headers = ["Content-Type", "Authorization"]
-///
-/// app.cors(cors)
-/// ```
+/// ```python
+/// from oxapy import HttpServer, Cors
+///
+/// app = HttpServer(("127.0.0.1", 8000))
+///
+/// # Set up CORS with custom configuration
+/// cors = Cors()
+/// cors.origins = ["https://example.com", "https://app.example.com"]
+/// cors.methods = ["GET", "POST", "OPTIONS"]
+/// cors.headers = ["Content-Type", "Authorization"]
+///
+/// app.cors(cors)
+/// ```
#[derive(Clone, Debug)]
#[pyclass]
pub struct Cors {
@@ -66,14 +66,14 @@ impl Cors {
/// Cors: A new CORS configuration with default values.
///
/// Example:
- /// ```python
- /// # Create CORS with default configuration (allows all origins)
- /// cors = Cors()
- ///
- /// # Customize CORS settings
- /// cors.origins = ["https://example.com"]
- /// cors.allow_credentials = False
- /// ```
+ /// ```python
+ /// # Create CORS with default configuration (allows all origins)
+ /// cors = Cors()
+ ///
+ /// # Customize CORS settings
+ /// cors.origins = ["https://example.com"]
+ /// cors.allow_credentials = False
+ /// ```
#[new]
fn new() -> Self {
Self::default()
diff --git a/src/handling/response_handler.rs b/src/handling/response_handler.rs
index f9a06e4..280d35c 100644
--- a/src/handling/response_handler.rs
+++ b/src/handling/response_handler.rs
@@ -99,14 +99,13 @@ fn process_response(
let route = route.value;
let kwargs = prepare_route_params(params, py)?;
-
- kwargs.set_item("request", request.clone())?;
+ let request = request.clone();
let result = if !router.middlewares.is_empty() {
let chain = MiddlewareChain::new(router.middlewares.clone());
- chain.execute(py, &route.handler.clone(), kwargs.clone())?
+ chain.execute(py, &route.handler.clone(), (request,), kwargs.clone())?
} else {
- route.handler.call(py, (), Some(&kwargs))?
+ route.handler.call(py, (request,), Some(&kwargs))?
};
convert_to_response(result, py)
} else {
diff --git a/src/lib.rs b/src/lib.rs
index 689a618..812a335 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -85,35 +85,35 @@ struct RequestContext {
/// HttpServer: A new server instance.
///
/// Example:
-/// ```python
-/// from oxapy import HttpServer, Router
+/// ```python
+/// from oxapy import HttpServer, Router
///
-/// # Create a server on localhost port 8000
-/// app = HttpServer(("127.0.0.1", 8000))
+/// # Create a server on localhost port 8000
+/// app = HttpServer(("127.0.0.1", 8000))
///
-/// # Create a router
-/// router = Router()
+/// # Create a router
+/// router = Router()
///
-/// # Define route handlers
-/// @router.get("/")
-/// def home(request):
-/// return "Hello, World!"
+/// # Define route handlers
+/// @router.get("/")
+/// def home(request):
+/// return "Hello, World!"
///
-/// @router.get("/users/{user_id}")
-/// def get_user(request, user_id: int):
-/// return {"user_id": user_id, "name": f"User {user_id}"}
+/// @router.get("/users/{user_id}")
+/// def get_user(request, user_id: int):
+/// return {"user_id": user_id, "name": f"User {user_id}"}
///
-/// @router.post("/api/data")
-/// def create_data(request):
-/// # Access JSON data from the request
-/// data = request.json()
-/// return {"status": "success", "received": data}
+/// @router.post("/api/data")
+/// def create_data(request):
+/// # Access JSON data from the request
+/// data = request.json()
+/// return {"status": "success", "received": data}
///
-/// # Attach the router to the server
-/// app.attach(router)
+/// # Attach the router to the server
+/// app.attach(router)
///
-/// # Run the server
-/// app.run()
+/// # Run the server
+/// app.run()
/// ```
#[derive(Clone)]
#[pyclass]
@@ -140,9 +140,9 @@ impl HttpServer {
/// HttpServer: A new server instance ready to be configured.
///
/// Example:
- /// ```python
- /// server = HttpServer(("127.0.0.1", 5555))
- /// ```
+ /// ```python
+ /// server = HttpServer(("127.0.0.1", 5555))
+ /// ```
#[new]
fn new(addr: (String, u16)) -> PyResult {
let (ip, port) = addr;
@@ -171,23 +171,23 @@ impl HttpServer {
/// None
///
/// Example:
- /// ```python
- /// class AppState:
- /// def __init__(self):
- /// self.counter = 0
- /// # You can store database connection pools here
- /// self.db_pool = create_database_pool()
- ///
- /// app = HttpServer(("127.0.0.1", 5555))
- /// app.app_data(AppState())
- ///
- /// # Example of a handler that increments the counter
- /// @router.get("/counter")
- /// def increment_counter(request):
- /// state = request.app_data
- /// state.counter += 1
- /// return {"count": state.counter}
- /// ```
+ /// ```python
+ /// class AppState:
+ /// def __init__(self):
+ /// self.counter = 0
+ /// # You can store database connection pools here
+ /// self.db_pool = create_database_pool()
+ ///
+ /// app = HttpServer(("127.0.0.1", 5555))
+ /// app.app_data(AppState())
+ ///
+ /// # Example of a handler that increments the counter
+ /// @router.get("/counter")
+ /// def increment_counter(request):
+ /// state = request.app_data
+ /// state.counter += 1
+ /// return {"count": state.counter}
+ /// ```
fn app_data(&mut self, app_data: Py) {
self.app_data = Some(Arc::new(app_data))
}
@@ -201,27 +201,27 @@ impl HttpServer {
/// None
///
/// Example:
- /// ```python
- /// router = Router()
- ///
- /// # Define a simple hello world handler
- /// @router.get("/")
- /// def hello(request):
- /// return "Hello, World!"
- ///
- /// # Handler with path parameters
- /// @router.get("/users/{user_id}")
- /// def get_user(request, user_id: int):
- /// return f"User ID: {user_id}"
- ///
- /// # Handler that returns JSON
- /// @router.get("/api/data")
- /// def get_data(request):
- /// return {"message": "Success", "data": [1, 2, 3]}
- ///
- /// # Attach the router to the server
- /// server.attach(router)
- /// ```
+ /// ```python
+ /// router = Router()
+ ///
+ /// # Define a simple hello world handler
+ /// @router.get("/")
+ /// def hello(request):
+ /// return "Hello, World!"
+ ///
+ /// # Handler with path parameters
+ /// @router.get("/users/{user_id}")
+ /// def get_user(request, user_id: int):
+ /// return f"User ID: {user_id}"
+ ///
+ /// # Handler that returns JSON
+ /// @router.get("/api/data")
+ /// def get_data(request):
+ /// return {"message": "Success", "data": [1, 2, 3]}
+ ///
+ /// # Attach the router to the server
+ /// server.attach(router)
+ /// ```
fn attach(&mut self, router: Router) {
self.routers.push(Arc::new(router));
}
@@ -237,9 +237,9 @@ impl HttpServer {
/// None
///
/// Example:
- /// ```python
- /// server.session_store(SessionStore())
- /// ```
+ /// ```python
+ /// server.session_store(SessionStore())
+ /// ```
fn session_store(&mut self, session_store: SessionStore) {
self.session_store = Some(Arc::new(session_store));
}
@@ -253,11 +253,11 @@ impl HttpServer {
/// None
///
/// Example:
- /// ```python
- /// from oxapy import templating
+ /// ```python
+ /// from oxapy import templating
///
- /// server.template(templating.Template())
- /// ```
+ /// server.template(templating.Template())
+ /// ```
fn template(&mut self, template: Template) {
self.template = Some(Arc::new(template))
}
@@ -271,11 +271,11 @@ impl HttpServer {
/// None
///
/// Example:
- /// ```python
- /// cors = Cors()
- /// cors.origins = ["https://example.com"]
- /// server.cors(cors)
- /// ```
+ /// ```python
+ /// cors = Cors()
+ /// cors.origins = ["https://example.com"]
+ /// server.cors(cors)
+ /// ```
fn cors(&mut self, cors: Cors) {
self.cors = Some(Arc::new(cors));
}
@@ -289,9 +289,9 @@ impl HttpServer {
/// None
///
/// Example:
- /// ```python
- /// server.max_connections(1000)
- /// ```
+ /// ```python
+ /// server.max_connections(1000)
+ /// ```
fn max_connections(&mut self, max_connections: usize) {
self.max_connections = Arc::new(Semaphore::new(max_connections));
}
@@ -308,9 +308,9 @@ impl HttpServer {
/// None
///
/// Example:
- /// ```python
- /// server.channel_capacity(200)
- /// ```
+ /// ```python
+ /// server.channel_capacity(200)
+ /// ```
fn channel_capacity(&mut self, channel_capacity: usize) {
self.channel_capacity = channel_capacity;
}
@@ -324,13 +324,13 @@ impl HttpServer {
/// None
///
/// Example:
- /// ```python
- /// @catcher(Status.NOT_FOUND)
- /// def not_found(request, response):
- /// return Response("Page Not Found ", content_type="text/html")
+ /// ```python
+ /// @catcher(Status.NOT_FOUND)
+ /// def not_found(request, response):
+ /// return Response("Page Not Found ", content_type="text/html")
///
- /// server.catchers([not_found])
- /// ```
+ /// server.catchers([not_found])
+ /// ```
fn catchers(&mut self, catchers: Vec>, py: Python<'_>) {
let mut map = HashMap::default();
@@ -353,15 +353,15 @@ impl HttpServer {
/// None
///
/// Example:
- /// ```python
- /// # Run with default number of workers
- /// server.run()
- ///
- /// # Or specify number of workers based on CPU count
- /// import multiprocessing
- /// workers = multiprocessing.cpu_count()
- /// server.run(workers)
- /// ```
+ /// ```python
+ /// # Run with default number of workers
+ /// server.run()
+ ///
+ /// # Or specify number of workers based on CPU count
+ /// import multiprocessing
+ /// workers = multiprocessing.cpu_count()
+ /// server.run(workers)
+ /// ```
#[pyo3(signature=(workers=None))]
fn run(&self, workers: Option, py: Python<'_>) -> PyResult<()> {
let mut runtime = tokio::runtime::Builder::new_multi_thread();
diff --git a/src/middleware.rs b/src/middleware.rs
index 6ebc0a5..1a1037b 100644
--- a/src/middleware.rs
+++ b/src/middleware.rs
@@ -1,6 +1,11 @@
use std::sync::Arc;
-use pyo3::{ffi::c_str, prelude::*, types::PyDict, Py, PyAny, PyResult, Python};
+use pyo3::{
+ ffi::c_str,
+ prelude::*,
+ types::{PyDict, PyTuple},
+ Py, PyAny, PyResult, Python,
+};
#[derive(Clone, Debug)]
pub struct Middleware {
@@ -24,14 +29,18 @@ impl MiddlewareChain {
Self { middlewares }
}
- pub fn execute<'py>(
+ pub fn execute<'py, A>(
&self,
py: Python<'py>,
route_handler: &Py,
+ args: A,
kwargs: Bound<'py, PyDict>,
- ) -> PyResult> {
+ ) -> PyResult>
+ where
+ A: IntoPyObject<'py, Target = PyTuple>,
+ {
let handler = self.build_middleware_chain(py, route_handler, 0)?;
- handler.call(py, (), Some(&kwargs))
+ handler.call(py, args, Some(&kwargs))
}
fn build_middleware_chain(
diff --git a/src/multipart.rs b/src/multipart.rs
index c1e8c27..28b0bec 100644
--- a/src/multipart.rs
+++ b/src/multipart.rs
@@ -18,20 +18,20 @@ use crate::IntoPyException;
/// File: A file object containing the uploaded data.
///
/// Example:
-/// ```python
-/// @router.post("/upload")
-/// def upload_handler(request):
-/// if request.files:
-/// image = request.files.get("profile_image")
-/// if image:
-/// # Access file properties
-/// filename = image.name
-/// content_type = image.content_type
-/// # Save the file
-/// image.save(f"uploads/{filename}")
-/// return {"status": "success", "filename": filename}
-/// return {"status": "error", "message": "No file uploaded"}
-/// ```
+/// ```python
+/// @router.post("/upload")
+/// def upload_handler(request):
+/// if request.files:
+/// image = request.files.get("profile_image")
+/// if image:
+/// # Access file properties
+/// filename = image.name
+/// content_type = image.content_type
+/// # Save the file
+/// image.save(f"uploads/{filename}")
+/// return {"status": "success", "filename": filename}
+/// return {"status": "error", "message": "No file uploaded"}
+/// ```
#[derive(Clone, Debug)]
#[pyclass]
pub struct File {
@@ -53,10 +53,10 @@ impl File {
/// bytes: The file content as a Python bytes object.
///
/// Example:
- /// ```python
- /// file_bytes = uploaded_file.content()
- /// file_size = len(file_bytes)
- /// ```
+ /// ```python
+ /// file_bytes = uploaded_file.content()
+ /// file_size = len(file_bytes)
+ /// ```
fn content<'py>(&'py self, py: Python<'py>) -> Bound<'py, PyBytes> {
let data = &self.data.to_vec()[..];
PyBytes::new(py, data)
@@ -74,12 +74,12 @@ impl File {
/// Exception: If the file cannot be written to disk.
///
/// Example:
- /// ```python
- /// # Save the uploaded file
- /// if "profile_image" in request.files:
- /// image = request.files["profile_image"]
- /// image.save(f"uploads/{image.name}")
- /// ```
+ /// ```python
+ /// # Save the uploaded file
+ /// if "profile_image" in request.files:
+ /// image = request.files["profile_image"]
+ /// image.save(f"uploads/{image.name}")
+ /// ```
fn save(&self, path: String) -> PyResult<()> {
std::fs::write(path, &self.data)?;
Ok(())
diff --git a/src/request.rs b/src/request.rs
index cda2609..b9d0272 100644
--- a/src/request.rs
+++ b/src/request.rs
@@ -33,15 +33,15 @@ use crate::{
/// Request: A new request object
///
/// Example:
-/// ```python
-/// # Request objects are typically created by the framework and
-/// # passed to your handler functions:
+/// ```python
+/// # Request objects are typically created by the framework and
+/// # passed to your handler functions:
///
-/// @router.get("/hello")
-/// def handler(request):
-/// user_agent = request.headers.get("user-agent")
-/// return f"Hello from {user_agent}"
-/// ```
+/// @router.get("/hello")
+/// def handler(request):
+/// user_agent = request.headers.get("user-agent")
+/// return f"Hello from {user_agent}"
+/// ```
#[derive(Clone, Debug, Default)]
#[pyclass]
pub struct Request {
@@ -106,13 +106,13 @@ impl Request {
/// Exception: If the body is not present or cannot be parsed as JSON
///
/// Example:
- /// ```python
- /// @router.post("/api/data")
- /// def handle_data(request):
- /// data = request.json()
- /// value = data["key"]
- /// return {"received": value}
- /// ```
+ /// ```python
+ /// @router.post("/api/data")
+ /// def handle_data(request):
+ /// data = request.json()
+ /// value = data["key"]
+ /// return {"received": value}
+ /// ```
pub fn json(&self) -> PyResult> {
let data = self
.body
@@ -130,13 +130,13 @@ impl Request {
/// any: The application data object, or None if no app_data was set
///
/// Example:
- /// ```python
- /// @router.get("/counter")
- /// def get_counter(request):
- /// app_state = request.app_data
- /// app_state.counter += 1
- /// return {"count": app_state.counter}
- /// ```
+ /// ```python
+ /// @router.get("/counter")
+ /// def get_counter(request):
+ /// app_state = request.app_data
+ /// app_state.counter += 1
+ /// return {"count": app_state.counter}
+ /// ```
#[getter]
fn app_data(&self, py: Python<'_>) -> Option> {
self.app_data.as_ref().map(|d| d.clone_ref(py))
@@ -154,15 +154,15 @@ impl Request {
/// Exception: If the URI cannot be parsed
///
/// Example:
- /// ```python
- /// # For a request to /api?name=John&age=30
- /// @router.get("/api")
- /// def api_handler(request):
- /// query = request.query()
- /// name = query.get("name")
- /// age = query.get("age")
- /// return {"name": name, "age": age}
- /// ```
+ /// ```python
+ /// # For a request to /api?name=John&age=30
+ /// @router.get("/api")
+ /// def api_handler(request):
+ /// query = request.query()
+ /// name = query.get("name")
+ /// age = query.get("age")
+ /// return {"name": name, "age": age}
+ /// ```
fn query(&self) -> PyResult>> {
let uri: Uri = self.uri.parse().into_py_exception()?;
if let Some(query_string) = uri.query() {
@@ -188,14 +188,14 @@ impl Request {
/// AttributeError: If session store is not configured on the server
///
/// Example:
- /// ```python
- /// @router.get("/login")
- /// def login(request):
- /// session = request.session()
- /// session["user_id"] = 123
- /// session["is_authenticated"] = True
- /// return "Logged in successfully"
- /// ```
+ /// ```python
+ /// @router.get("/login")
+ /// def login(request):
+ /// session = request.session()
+ /// session["user_id"] = 123
+ /// session["is_authenticated"] = True
+ /// return "Logged in successfully"
+ /// ```
pub fn session(&self) -> PyResult {
let message = "Session not available. Make sure you've configured SessionStore.";
let session = self
diff --git a/src/response.rs b/src/response.rs
index 255dbeb..6e5b500 100644
--- a/src/response.rs
+++ b/src/response.rs
@@ -22,16 +22,16 @@ use crate::{
/// Response: A new HTTP response.
///
/// Example:
-/// ```python
-/// # JSON response
-/// response = Response({"message": "Success"})
-///
-/// # Plain text response
-/// response = Response("Hello, World!", content_type="text/plain")
-///
-/// # HTML response with custom status
-/// response = Response("Not Found ", Status.NOT_FOUND, "text/html")
-/// ```
+/// ```python
+/// # JSON response
+/// response = Response({"message": "Success"})
+///
+/// # Plain text response
+/// response = Response("Hello, World!", content_type="text/plain")
+///
+/// # HTML response with custom status
+/// response = Response("Not Found ", Status.NOT_FOUND, "text/html")
+/// ```
#[derive(Clone)]
#[pyclass(subclass)]
pub struct Response {
@@ -55,16 +55,16 @@ impl Response {
/// Response: A new response object.
///
/// Example:
- /// ```python
- /// # Return JSON
- /// response = Response({"message": "Hello"})
- ///
- /// # Return plain text
- /// response = Response("Hello", content_type="text/plain")
- ///
- /// # Return error
- /// response = Response("Not authorized", status=Status.UNAUTHORIZED)
- /// ```
+ /// ```python
+ /// # Return JSON
+ /// response = Response({"message": "Hello"})
+ ///
+ /// # Return plain text
+ /// response = Response("Hello", content_type="text/plain")
+ ///
+ /// # Return error
+ /// response = Response("Not authorized", status=Status.UNAUTHORIZED)
+ /// ```
#[new]
#[pyo3(signature=(body, status = Status::OK , content_type="application/json"))]
pub fn new(
@@ -110,10 +110,10 @@ impl Response {
/// Response: The response instance (for method chaining).
///
/// Example:
- /// ```python
- /// response = Response("Hello")
- /// response.insert_header("Cache-Control", "no-cache")
- /// ```
+ /// ```python
+ /// response = Response("Hello")
+ /// response.insert_header("Cache-Control", "no-cache")
+ /// ```
pub fn insert_header(&mut self, key: &str, value: String) -> Self {
self.headers.insert(key.to_string(), value);
self.clone()
@@ -143,13 +143,13 @@ impl Response {
/// Redirect: A redirect response.
///
/// Example:
-/// ```python
-/// # Redirect to the home page
-/// return Redirect("/home")
-///
-/// # Redirect to an external site
-/// return Redirect("https://example.com")
-/// ```
+/// ```python
+/// # Redirect to the home page
+/// return Redirect("/home")
+///
+/// # Redirect to an external site
+/// return Redirect("https://example.com")
+/// ```
#[pyclass(subclass, extends=Response)]
pub struct Redirect;
@@ -164,13 +164,13 @@ impl Redirect {
/// Redirect: A redirect response with status 301 (Moved Permanently).
///
/// Example:
- /// ```python
- /// # Redirect user after form submission
- /// @router.post("/submit")
- /// def submit_form(request):
- /// # Process form...
- /// return Redirect("/thank-you")
- /// ```
+ /// ```python
+ /// # Redirect user after form submission
+ /// @router.post("/submit")
+ /// def submit_form(request):
+ /// # Process form...
+ /// return Redirect("/thank-you")
+ /// ```
#[new]
fn new(location: String) -> (Self, Response) {
(
diff --git a/src/routing.rs b/src/routing.rs
index 21d2f9d..b48e415 100644
--- a/src/routing.rs
+++ b/src/routing.rs
@@ -20,15 +20,15 @@ pub type MatchRoute<'l> = matchit::Match<'l, 'l, &'l Route>;
/// Route: A route object that can be registered with a router.
///
/// Example:
-/// ```python
-/// from oxapy import Route
+/// ```python
+/// from oxapy import Route
///
-/// def handler(request):
-/// return "Hello, World!"
+/// def handler(request):
+/// return "Hello, World!"
///
-/// route = Route("/hello", "GET")
-/// route = route(handler) # Attach the handler
-/// ```
+/// route = Route("/hello", "GET")
+/// route = route(handler) # Attach the handler
+/// ```
#[derive(Clone, Debug)]
#[pyclass]
pub struct Route {
@@ -72,8 +72,14 @@ impl Route {
}
macro_rules! method_decorator {
- ($($method:ident),*) => {
+ (
$(
+ $(#[$docs:meta])*
+ $method:ident;
+ )*
+ ) => {
+ $(
+ $(#[$docs])*
#[pyfunction]
#[pyo3(signature = (path, handler = None))]
pub fn $method(path: String, handler: Option>, py: Python<'_>) -> Route {
@@ -87,7 +93,112 @@ macro_rules! method_decorator {
};
}
-method_decorator!(get, post, put, patch, delete, head, options);
+method_decorator!(
+ /// Registers an HTTP GET route.
+ ///
+ /// Parameters:
+ /// path (str): The route path, which may include parameters (e.g. `/items/{id}`).
+ /// handler (callable | None): Optional Python function that handles the request.
+ ///
+ /// Returns:
+ /// Route: A GET Route instance.
+ ///
+ /// Example:
+ /// ```python
+ /// get("/hello/{name}", lambda req, name: f"Hello, {name}!")
+ /// ```
+ get;
+
+ /// Registers an HTTP POST route.
+ ///
+ /// Parameters:
+ /// path (str): The POST route path.
+ /// handler (callable | None): Optional Python function that handles the request.
+ ///
+ /// Returns:
+ /// Route: A POST Route instance.
+ ///
+ /// Example:
+ /// ```python
+ /// post("/users", lambda req: {"id": 1, "name": req.json()["name"]})
+ /// ```
+ post;
+
+ /// Registers an HTTP DELETE route.
+ ///
+ /// Parameters:
+ /// path (str): The DELETE route path.
+ /// handler (callable | None): Optional Python function that handles the request.
+ ///
+ /// Returns:
+ /// Route: A DELETE Route instance.
+ ///
+ /// Example:
+ /// ```python
+ /// delete("/items/{id}", lambda req, id: f"Deleted {id}")
+ /// ```
+ delete;
+
+ /// Registers an HTTP PATCH route.
+ ///
+ /// Parameters:
+ /// path (str): The PATCH route path.
+ /// handler (callable | None): Optional Python function for partial updates.
+ ///
+ /// Returns:
+ /// Route: A PATCH Route instance.
+ ///
+ /// Example:
+ /// ```python
+ /// patch("/users/{id}", lambda req, id: req.json())
+ /// ```
+ patch;
+
+ /// Registers an HTTP PUT route.
+ ///
+ /// Parameters:
+ /// path (str): The PUT route path.
+ /// handler (callable | None): Optional Python function for full replacement.
+ ///
+ /// Returns:
+ /// Route: A PUT Route instance.
+ ///
+ /// Example:
+ /// ```python
+ /// put("/users/{id}", lambda req, id: req.json())
+ /// ```
+ put;
+
+ /// Registers an HTTP HEAD route.
+ ///
+ /// Parameters:
+ /// path (str): The HEAD route path.
+ /// handler (callable | None): Optional function for returning headers only.
+ ///
+ /// Returns:
+ /// Route: A HEAD Route instance.
+ ///
+ /// Example:
+ /// ```python
+ /// head("/status", lambda req: None)
+ /// ```
+ head;
+
+ /// Registers an HTTP OPTIONS route.
+ ///
+ /// Parameters:
+ /// path (str): The OPTIONS route path.
+ /// handler (callable | None): Optional handler that returns allowed methods.
+ ///
+ /// Returns:
+ /// Route: An OPTIONS Route instance.
+ ///
+ /// Example:
+ /// ```python
+ /// options("/users", lambda req: {"Allow": "GET, POST"})
+ /// ```
+ options;
+);
#[derive(Clone)]
#[pyclass]
@@ -121,15 +232,15 @@ impl RouteBuilder {
/// Router: A new router instance.
///
/// Example:
-/// ```python
-/// from oxapy import Router, get
+/// ```python
+/// from oxapy import Router, get
///
-/// router = Router()
+/// router = Router()
///
-/// @router.get("/hello/{name}")
-/// def hello(request, name):
-/// return f"Hello, {name}!"
-/// ```
+/// @router.get("/hello/{name}")
+/// def hello(request, name):
+/// return f"Hello, {name}!"
+/// ```
#[derive(Default, Clone, Debug)]
#[pyclass]
pub struct Router {
@@ -138,7 +249,12 @@ pub struct Router {
}
macro_rules! impl_router {
- ($($method:ident),*) => {
+ (
+ $(
+ $(#[$docs:meta])*
+ $method:ident;
+ )*
+ ) => {
#[pymethods]
impl Router {
/// Create a new Router instance.
@@ -147,9 +263,9 @@ macro_rules! impl_router {
/// Router: A new router with no routes or middleware.
///
/// Example:
- /// ```python
- /// router = Router()
- /// ```
+ /// ```python
+ /// router = Router()
+ /// ```
#[new]
pub fn new() -> Self {
Router::default()
@@ -167,14 +283,14 @@ macro_rules! impl_router {
/// None
///
/// Example:
- /// ```python
- /// def auth_middleware(request, next, **kwargs):
- /// if "authorization" not in request.headers:
- /// return Status.UNAUTHORIZED
- /// return next(request, **kwargs)
+ /// ```python
+ /// def auth_middleware(request, next, **kwargs):
+ /// if "authorization" not in request.headers:
+ /// return Status.UNAUTHORIZED
+ /// return next(request, **kwargs)
///
- /// router.middleware(auth_middleware)
- /// ```
+ /// router.middleware(auth_middleware)
+ /// ```
fn middleware(&mut self, middleware: Py) {
let middleware = Middleware::new(middleware);
self.middlewares.push(middleware);
@@ -192,15 +308,15 @@ macro_rules! impl_router {
/// Exception: If the route cannot be added.
///
/// Example:
- /// ```python
- /// from oxapy import get
+ /// ```python
+ /// from oxapy import get
///
- /// def hello_handler(request):
- /// return "Hello World!"
+ /// def hello_handler(request):
+ /// return "Hello World!"
///
- /// route = get("/hello", hello_handler)
- /// router.route(route)
- /// ```
+ /// route = get("/hello", hello_handler)
+ /// router.route(route)
+ /// ```
fn route(&mut self, route: &Route) -> PyResult<()> {
let mut ptr_mr = self.routes.write().unwrap();
let method_router = ptr_mr.entry(route.method.clone()).or_default();
@@ -222,21 +338,21 @@ macro_rules! impl_router {
/// Exception: If any route cannot be added.
///
/// Example:
- /// ```python
- /// from oxapy import get, post
+ /// ```python
+ /// from oxapy import get, post
///
- /// def hello_handler(request):
- /// return "Hello World!"
+ /// def hello_handler(request):
+ /// return "Hello World!"
///
- /// def submit_handler(request):
- /// return "Form submitted!"
+ /// def submit_handler(request):
+ /// return "Form submitted!"
///
- /// routes = [
- /// get("/hello", hello_handler),
- /// post("/submit", submit_handler)
- /// ]
- /// router.routes(routes)
- /// ```
+ /// routes = [
+ /// get("/hello", hello_handler),
+ /// post("/submit", submit_handler)
+ /// ]
+ /// router.routes(routes)
+ /// ```
fn routes(&mut self, routes: Vec) -> PyResult<()> {
for ref route in routes {
self.route(route)?;
@@ -244,15 +360,16 @@ macro_rules! impl_router {
Ok(())
}
- $(
- fn $method(&self, path: String) -> PyResult {
- Ok(RouteBuilder {
- method: stringify!($method).to_string().to_uppercase(),
- router: self.clone(),
- path,
- })
- }
- )+
+ $(
+ $(#[$docs])*
+ fn $method(&self, path: String) -> PyResult {
+ Ok(RouteBuilder {
+ method: stringify!($method).to_string().to_uppercase(),
+ router: self.clone(),
+ path,
+ })
+ }
+ )+
fn __repr__(&self) -> String {
format!("{:#?}", self)
@@ -261,7 +378,77 @@ macro_rules! impl_router {
};
}
-impl_router!(get, post, put, patch, delete, head, options);
+impl_router!(
+ /// Register a GET route using the decorator `@router.get(path)`.
+ ///
+ /// Example:
+ /// ```python
+ /// @router.get("/hello")
+ /// def hello(request):
+ /// return "Hello, world!"
+ /// ```
+ get;
+
+ /// Register a POST route using the decorator `@router.post(path)`.
+ ///
+ /// Example:
+ /// ```python
+ /// @router.post("/submit")
+ /// def submit(request):
+ /// return "Submitted!"
+ /// ```
+ post;
+
+ /// Register a PUT route using the decorator `@router.put(path)`.
+ ///
+ /// Example:
+ /// ```python
+ /// @router.put("/items/{id}")
+ /// def update_item(request, id):
+ /// return f"Updated item {id}"
+ /// ```
+ put;
+
+ /// Register a PATCH route using the decorator `@router.patch(path)`.
+ ///
+ /// Example:
+ /// ```python
+ /// @router.patch("/items/{id}")
+ /// def patch_item(request, id):
+ /// return f"Patched item {id}"
+ /// ```
+ patch;
+
+ /// Register a DELETE route using the decorator `@router.delete(path)`.
+ ///
+ /// Example:
+ /// ```python
+ /// @router.delete("/items/{id}")
+ /// def delete_item(request, id):
+ /// return f"Deleted item {id}"
+ /// ```
+ delete;
+
+ /// Register a HEAD route using the decorator `@router.head(path)`.
+ ///
+ /// Example:
+ /// ```python
+ /// @router.head("/ping")
+ /// def head_ping(request):
+ /// return ""
+ /// ```
+ head;
+
+ /// Register an OPTIONS route using the decorator `@router.options(path)`.
+ ///
+ /// Example:
+ /// ```python
+ /// @router.options("/data")
+ /// def options_data(request):
+ /// return "OPTIONS OK"
+ /// ```
+ options;
+);
impl Router {
pub(crate) fn find<'l>(&'l self, method: &str, uri: &'l str) -> Option> {
@@ -284,13 +471,13 @@ impl Router {
/// Route: A route configured to serve static files.
///
/// Example:
-/// ```python
-/// from oxapy import Router, static_file
+/// ```python
+/// from oxapy import Router, static_file
///
-/// router = Router()
-/// router.route(static_file("./static", "static"))
-/// # This will serve files from ./static directory at /static URL path
-/// ```
+/// router = Router()
+/// router.route(static_file("./static", "static"))
+/// # This will serve files from ./static directory at /static URL path
+/// ```
#[pyfunction]
pub fn static_file(directory: String, path: String, py: Python<'_>) -> PyResult {
let pathlib = py.import("pathlib")?;
diff --git a/src/serializer/fields.rs b/src/serializer/fields.rs
index ecf9d3c..390fb63 100644
--- a/src/serializer/fields.rs
+++ b/src/serializer/fields.rs
@@ -79,19 +79,6 @@ impl Field {
}
}
-static TYPE_STR: &str = "type";
-static FORMAT_STR: &str = "format";
-static MIN_LEN_STR: &str = "minLength";
-static MAX_LEN_STR: &str = "maxLength";
-static MIN_STR: &str = "minimum";
-static MAX_STR: &str = "maximum";
-static PATTERN_STR: &str = "pattern";
-static ENUM_STR: &str = "enum";
-static TITLE_STR: &str = "title";
-static DESC_STR: &str = "description";
-static ARRAY_STR: &str = "array";
-static ITEMS_STR: &str = "items";
-
impl Field {
pub fn to_json_schema_value(&self) -> Value {
let capacity = 1
@@ -106,30 +93,30 @@ impl Field {
+ self.description.is_some() as usize;
let mut schema = serde_json::Map::with_capacity(capacity);
- schema.insert(TYPE_STR.to_string(), Value::String(self.ty.clone()));
+ schema.insert("type".to_string(), Value::String(self.ty.clone()));
if let Some(fmt) = &self.format {
- schema.insert(FORMAT_STR.to_string(), Value::String(fmt.clone()));
+ schema.insert("format".to_string(), Value::String(fmt.clone()));
}
if let Some(min_length) = self.min_length {
- schema.insert(MIN_LEN_STR.to_string(), Value::Number(min_length.into()));
+ schema.insert("minLength".to_string(), Value::Number(min_length.into()));
}
if let Some(max_length) = self.max_length {
- schema.insert(MAX_LEN_STR.to_string(), Value::Number(max_length.into()));
+ schema.insert("maxLength".to_string(), Value::Number(max_length.into()));
}
if let Some(minimum) = self.minimum {
- schema.insert(MIN_STR.to_string(), serde_json::json!(minimum));
+ schema.insert("minimum".to_string(), serde_json::json!(minimum));
}
if let Some(maximum) = self.maximum {
- schema.insert(MAX_STR.to_string(), serde_json::json!(maximum));
+ schema.insert("maximum".to_string(), serde_json::json!(maximum));
}
if let Some(pattern) = &self.pattern {
- schema.insert(PATTERN_STR.to_string(), Value::String(pattern.clone()));
+ schema.insert("pattern".to_string(), Value::String(pattern.clone()));
}
if let Some(enum_values) = &self.enum_values {
@@ -137,21 +124,24 @@ impl Field {
.iter()
.map(|v| Value::String(v.clone()))
.collect();
- schema.insert(ENUM_STR.to_string(), Value::Array(enum_array));
+ schema.insert("enum".to_string(), Value::Array(enum_array));
}
if let Some(title) = &self.title {
- schema.insert(TITLE_STR.to_string(), Value::String(title.clone()));
+ schema.insert("title".to_string(), Value::String(title.clone()));
}
if let Some(description) = &self.description {
- schema.insert(DESC_STR.to_string(), Value::String(description.clone()));
+ schema.insert(
+ "description".to_string(),
+ Value::String(description.clone()),
+ );
}
if self.many.unwrap_or(false) {
let mut array_schema = serde_json::Map::with_capacity(2);
- array_schema.insert(TYPE_STR.to_string(), Value::String(ARRAY_STR.to_string()));
- array_schema.insert(ITEMS_STR.to_string(), Value::Object(schema));
+ array_schema.insert("type".to_string(), Value::String("array".to_string()));
+ array_schema.insert("items".to_string(), Value::Object(schema));
return Value::Object(array_schema);
}
diff --git a/src/serializer/mod.rs b/src/serializer/mod.rs
index 862a558..9344e03 100644
--- a/src/serializer/mod.rs
+++ b/src/serializer/mod.rs
@@ -204,16 +204,6 @@ impl Serializer {
static CACHES_JSON_SCHEMA_VALUE: Lazy>> =
Lazy::new(|| Mutex::new(HashMap::new()));
-static TYPE_STR: &str = "type";
-static ARRAY_STR: &str = "array";
-static OBJECT_STR: &str = "object";
-static ITEMS_STR: &str = "items";
-static TITLE_STR: &str = "title";
-static DESC_STR: &str = "description";
-static PROPS_STR: &str = "properties";
-static ADD_PROPS_STR: &str = "additionalProperties";
-static REQUIRED_STR: &str = "required";
-
impl Serializer {
fn json_schema_value(cls: &Bound<'_, PyType>) -> PyResult {
let mut properties = serde_json::Map::with_capacity(16);
@@ -272,9 +262,8 @@ impl Serializer {
if is_field_many {
let mut array_schema = serde_json::Map::with_capacity(2);
- array_schema
- .insert(TYPE_STR.to_string(), Value::String(ARRAY_STR.to_string()));
- array_schema.insert(ITEMS_STR.to_string(), nested_schema);
+ array_schema.insert("type".to_string(), Value::String("array".to_string()));
+ array_schema.insert("items".to_string(), nested_schema);
properties.insert(attr_name, Value::Object(array_schema));
} else {
properties.insert(attr_name, nested_schema);
@@ -290,26 +279,26 @@ impl Serializer {
}
let mut schema = serde_json::Map::with_capacity(5);
- schema.insert(TYPE_STR.to_string(), Value::String(OBJECT_STR.to_string()));
- schema.insert(PROPS_STR.to_string(), Value::Object(properties));
- schema.insert(ADD_PROPS_STR.to_string(), Value::Bool(false));
+ schema.insert("type".to_string(), Value::String("object".to_string()));
+ schema.insert("properties".to_string(), Value::Object(properties));
+ schema.insert("additionalProperties".to_string(), Value::Bool(false));
if !required_fields.is_empty() {
let reqs: Vec = required_fields.into_iter().map(Value::String).collect();
- schema.insert(REQUIRED_STR.to_string(), Value::Array(reqs));
+ schema.insert("required".to_string(), Value::Array(reqs));
}
if let Some(t) = title {
- schema.insert(TITLE_STR.to_string(), Value::String(t));
+ schema.insert("title".to_string(), Value::String(t));
}
if let Some(d) = description {
- schema.insert(DESC_STR.to_string(), Value::String(d));
+ schema.insert("description".to_string(), Value::String(d));
}
let final_schema = if is_many {
let mut array_schema = serde_json::Map::with_capacity(2);
- array_schema.insert(TYPE_STR.to_string(), Value::String(ARRAY_STR.to_string()));
- array_schema.insert(ITEMS_STR.to_string(), Value::Object(schema));
+ array_schema.insert("type".to_string(), Value::String("array".to_string()));
+ array_schema.insert("items".to_string(), Value::Object(schema));
Value::Object(array_schema)
} else {
Value::Object(schema)
diff --git a/src/session.rs b/src/session.rs
index a55da0b..a1312e5 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -31,14 +31,14 @@ pub fn generate_session_id() -> String {
/// Session: A new session instance.
///
/// Example:
-/// ```python
-/// # Sessions are typically accessed from the request object:
-/// @router.get("/profile")
-/// def profile(request):
-/// session = request.session()
-/// session["last_visit"] = "today"
-/// return {"user_id": session.get("user_id")}
-/// ```
+/// ```python
+/// # Sessions are typically accessed from the request object:
+/// @router.get("/profile")
+/// def profile(request):
+/// session = request.session()
+/// session["last_visit"] = "today"
+/// return {"user_id": session.get("user_id")}
+/// ```
#[derive(Clone, Debug)]
#[pyclass]
pub struct Session {
@@ -62,10 +62,10 @@ impl Session {
/// Session: A new session instance.
///
/// Example:
- /// ```python
- /// # Manual session creation (normally handled by the framework)
- /// session = Session()
- /// ```
+ /// ```python
+ /// # Manual session creation (normally handled by the framework)
+ /// session = Session()
+ /// ```
#[new]
fn new(id: Option) -> PyResult {
let now = SystemTime::now()
@@ -91,11 +91,11 @@ impl Session {
/// any: The value associated with the key, or None if the key doesn't exist.
///
/// Example:
- /// ```python
- /// user_id = session.get("user_id")
- /// if user_id is not None:
- /// # User is logged in
- /// ```
+ /// ```python
+ /// user_id = session.get("user_id")
+ /// if user_id is not None:
+ /// # User is logged in
+ /// ```
fn get(&self, key: &str, py: Python<'_>) -> PyResult {
*self.last_accessed.lock().into_py_exception()? = SystemTime::now()
.duration_since(UNIX_EPOCH)
@@ -122,11 +122,11 @@ impl Session {
/// None
///
/// Example:
- /// ```python
- /// # Store user information in the session
- /// session.set("user_id", 123)
- /// session.set("is_admin", False)
- /// ```
+ /// ```python
+ /// # Store user information in the session
+ /// session.set("user_id", 123)
+ /// session.set("is_admin", False)
+ /// ```
fn set(&self, key: &str, value: PyObject) -> PyResult<()> {
let mut data = self.data.write().into_py_exception()?;
data.insert(key.to_string(), value);
@@ -143,11 +143,11 @@ impl Session {
/// None
///
/// Example:
- /// ```python
- /// # Log user out by removing their session data
- /// session.remove("user_id")
- /// session.remove("is_admin")
- /// ```
+ /// ```python
+ /// # Log user out by removing their session data
+ /// session.remove("user_id")
+ /// session.remove("is_admin")
+ /// ```
fn remove(&self, key: &str) -> PyResult<()> {
let mut data = self.data.write().into_py_exception()?;
if data.remove(key).is_some() {
@@ -165,10 +165,10 @@ impl Session {
/// None
///
/// Example:
- /// ```python
- /// # Clear all session data (e.g., during logout)
- /// session.clear()
- /// ```
+ /// ```python
+ /// # Clear all session data (e.g., during logout)
+ /// session.clear()
+ /// ```
fn clear(&self) -> PyResult<()> {
let mut data = self.data.write().into_py_exception()?;
if !data.is_empty() {
@@ -187,11 +187,11 @@ impl Session {
/// list: A list of all keys in the session.
///
/// Example:
- /// ```python
- /// # Check what data is stored in the session
- /// for key in session.keys():
- /// print(f"Session contains: {key}")
- /// ```
+ /// ```python
+ /// # Check what data is stored in the session
+ /// for key in session.keys():
+ /// print(f"Session contains: {key}")
+ /// ```
fn keys(&self, py: Python<'_>) -> PyResult {
let data = self.data.read().into_py_exception()?;
let keys: Vec = data.keys().cloned().collect();
@@ -283,19 +283,19 @@ impl Session {
/// SessionStore: A new session store instance.
///
/// Example:
-/// ```python
-/// from oxapy import HttpServer, SessionStore
-///
-/// app = HttpServer(("127.0.0.1", 8000))
-///
-/// # Configure sessions with custom settings
-/// store = SessionStore(
-/// cookie_name="my_app_session",
-/// cookie_secure=True,
-/// expiry_seconds=3600 # 1 hour
-/// )
-/// app.session_store(store)
-/// ```
+/// ```python
+/// from oxapy import HttpServer, SessionStore
+///
+/// app = HttpServer(("127.0.0.1", 8000))
+///
+/// # Configure sessions with custom settings
+/// store = SessionStore(
+/// cookie_name="my_app_session",
+/// cookie_secure=True,
+/// expiry_seconds=3600 # 1 hour
+/// )
+/// app.session_store(store)
+/// ```
#[derive(Clone, Debug)]
#[pyclass]
pub struct SessionStore {
@@ -333,17 +333,17 @@ impl SessionStore {
/// SessionStore: A new session store instance.
///
/// Example:
- /// ```python
- /// # Create a session store with default settings
- /// store = SessionStore()
- ///
- /// # Create a session store with custom settings
- /// secure_store = SessionStore(
- /// cookie_name="secure_session",
- /// cookie_secure=True,
- /// cookie_same_site="Strict"
- /// )
- /// ```
+ /// ```python
+ /// # Create a session store with default settings
+ /// store = SessionStore()
+ ///
+ /// # Create a session store with custom settings
+ /// secure_store = SessionStore(
+ /// cookie_name="secure_session",
+ /// cookie_secure=True,
+ /// cookie_same_site="Strict"
+ /// )
+ /// ```
#[new]
#[pyo3(signature = (
cookie_name = "session".to_string(),
@@ -415,10 +415,10 @@ impl SessionStore {
/// bool: True if the session was found and removed, False otherwise.
///
/// Example:
- /// ```python
- /// # Clear a specific session
- /// session_store.clear_session("abcd1234")
- /// ```
+ /// ```python
+ /// # Clear a specific session
+ /// session_store.clear_session("abcd1234")
+ /// ```
fn clear_session(&self, session_id: &str) -> PyResult {
let mut sessions = self.sessions.write().into_py_exception()?;
Ok(sessions.remove(session_id).is_some())
@@ -433,11 +433,11 @@ impl SessionStore {
/// int: The number of active sessions in the store.
///
/// Example:
- /// ```python
- /// # Check how many active sessions exist
- /// count = session_store.session_count()
- /// print(f"Active sessions: {count}")
- /// ```
+ /// ```python
+ /// # Check how many active sessions exist
+ /// count = session_store.session_count()
+ /// print(f"Active sessions: {count}")
+ /// ```
fn session_count(&self) -> PyResult {
let sessions = self.sessions.read().into_py_exception()?;
Ok(sessions.len())
diff --git a/src/status.rs b/src/status.rs
index 59e5aff..014d04c 100644
--- a/src/status.rs
+++ b/src/status.rs
@@ -16,20 +16,20 @@ use crate::response::Response;
/// - 5xx: Server error responses
///
/// Example:
-/// ```python
-/// from oxapy import Status, Response
-///
-/// # Create a not found response
-/// response = Response("Not found", status=Status.NOT_FOUND)
-///
-/// # Check status in a handler
-/// @router.get("/resource/{id}")
-/// def get_resource(request, id):
-/// resource = find_resource(id)
-/// if resource is None:
-/// return Status.NOT_FOUND
-/// return resource
-/// ```
+/// ```python
+/// from oxapy import Status, Response
+///
+/// # Create a not found response
+/// response = Response("Not found", status=Status.NOT_FOUND)
+///
+/// # Check status in a handler
+/// @router.get("/resource/{id}")
+/// def get_resource(request, id):
+/// resource = find_resource(id)
+/// if resource is None:
+/// return Status.NOT_FOUND
+/// return resource
+/// ```
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[pyclass]
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
@@ -180,11 +180,11 @@ impl Status {
/// bool: The result of the comparison.
///
/// Example:
- /// ```python
- /// # Check if a status code is a success code (2xx)
- /// if status >= Status.OK and status < Status.MULTIPLE_CHOICES:
- /// print("Success!")
- /// ```
+ /// ```python
+ /// # Check if a status code is a success code (2xx)
+ /// if status >= Status.OK and status < Status.MULTIPLE_CHOICES:
+ /// print("Success!")
+ /// ```
fn __richcmp__(
&self,
other: PyRef,
diff --git a/src/templating/mod.rs b/src/templating/mod.rs
index 370d371..395623b 100644
--- a/src/templating/mod.rs
+++ b/src/templating/mod.rs
@@ -26,18 +26,18 @@ mod tera;
/// PyException: If an invalid engine type is specified.
///
/// Example:
-/// ```python
-/// from oxapy import HttpServer
-/// from oxapy.templating import Template
+/// ```python
+/// from oxapy import HttpServer
+/// from oxapy.templating import Template
///
-/// app = HttpServer(("127.0.0.1", 8000))
+/// app = HttpServer(("127.0.0.1", 8000))
///
-/// # Configure templates with default settings (Jinja)
-/// app.template(Template())
+/// # Configure templates with default settings (Jinja)
+/// app.template(Template())
///
-/// # Or use Tera with custom template directory
-/// app.template(Template("./views/**/*.html", "tera"))
-/// ```
+/// # Or use Tera with custom template directory
+/// app.template(Template("./views/**/*.html", "tera"))
+/// ```
#[derive(Clone, Debug)]
#[pyclass]
pub enum Template {
@@ -60,13 +60,13 @@ impl Template {
/// PyException: If an invalid engine type is specified.
///
/// Example:
- /// ```python
- /// # Use Jinja with default template directory
- /// template = Template()
+ /// ```python
+ /// # Use Jinja with default template directory
+ /// template = Template()
///
- /// # Use Tera with custom template directory
- /// template = Template("./views/**/*.html", "tera")
- /// ```
+ /// # Use Tera with custom template directory
+ /// template = Template("./views/**/*.html", "tera")
+ /// ```
#[new]
#[pyo3(signature=(dir="./templates/**/*.html", engine="jinja"))]
fn new(dir: &str, engine: &str) -> PyResult {
@@ -98,16 +98,16 @@ impl Template {
/// PyValueError: If no template engine is configured for the request.
///
/// Example:
-/// ```python
-/// from oxapy import Router
-/// from oxapy import templating
+/// ```python
+/// from oxapy import Router
+/// from oxapy import templating
///
-/// router = Router()
+/// router = Router()
///
-/// @router.get("/")
-/// def index(request):
-/// return templating.render(request, "index.html", {"title": "Home Page"})
-/// ```
+/// @router.get("/")
+/// def index(request):
+/// return templating.render(request, "index.html", {"title": "Home Page"})
+/// ```
#[pyfunction]
#[pyo3(signature=(request, name, context=None))]
fn render(