Skip to content

Requests

Understanding request handling in Wiverno.

Overview

The Request class provides access to all incoming HTTP request data including headers, query parameters, POST data, cookies, and more.

Request Object

Every view function receives a Request object:

from wiverno.core.requests import Request

def my_view(request: Request) -> tuple[str, str]:
    """View function with request parameter."""
    # Access request data here
    return "Response"

Request Attributes

HTTP Method

def handle_request(request):
    """Check the HTTP method."""
    method = request.method  # "GET", "POST", "PUT", etc.

    if request.method == "GET":
        return "GET request"
    elif request.method == "POST":
        return 201, "POST request"

Path Information

def show_path(request):
    """Display request path."""
    path = request.path  # e.g., "/users/123"
    return f"<p>Path: {path}</p>"

Path Parameters

Extract parameters from dynamic URL segments:

@app.get("/users/{id:int}")
def get_user(request):
    """Get user by ID from path parameter."""
    user_id = request.path_params["id"]  # Already converted to int
    return f"<h1>User ID: {user_id}</h1>"

@app.get("/posts/{slug}/comments/{comment_id:int}")
def get_comment(request):
    """Get comment with multiple path parameters."""
    slug = request.path_params["slug"]           # str
    comment_id = request.path_params["comment_id"]  # int
    return f"<p>Post: {slug}, Comment: {comment_id}</p>"

@app.get("/files/{filepath:path}")
def serve_file(request):
    """Path parameter can contain slashes."""
    filepath = request.path_params["filepath"]  # e.g., "docs/guide/intro.md"
    return f"<p>File: {filepath}</p>"

Supported types:

  • {name} or {name:str} - String (default)
  • {name:int} - Integer (automatically converted)
  • {name:float} - Float (automatically converted)
  • {name:path} - Path (can contain slashes)

Query Parameters

Parse query strings from GET requests using QueryDict:

def search(request):
    """Handle search with query parameters."""
    # URL: /search?q=python&page=2&limit=10

    query = request.query_params.get("q", "")         # "python"
    page = request.query_params.get("page", "1")      # "2"
    limit = request.query_params.get("limit", "10")   # "10"

    return f"<p>Search: {query}, Page: {page}</p>"

QueryDict supports both single and multiple values:

def search_with_tags(request):
    """Handle multiple query parameter values."""
    # URL: /search?q=python&tag=web&tag=framework&tag=wsgi

    # Get single value (first occurrence)
    query = request.query_params.get("q", "")  # "python"
    query = request.query_params["q"]          # Same, but raises KeyError if missing

    # Get all values for a parameter
    tags = request.query_params.getlist("tag")  # ["web", "framework", "wsgi"]

    # Get with default if not present
    categories = request.query_params.getlist("category", ["general"])  # ["general"]

    return f"<p>Query: {query}, Tags: {', '.join(tags)}</p>"

POST Data

Access form data and JSON from POST requests:

def handle_form(request):
    """Handle form submission."""
    if request.method == "POST":
        # Access POST data
        username = request.data.get("username", "")
        email = request.data.get("email", "")
        return f"User: {username}, Email: {email}"
    """

JSON Data

Handle JSON POST requests:

def api_create(request):
    """Handle JSON API request."""
    if request.method == "POST":
        # POST data is automatically parsed from JSON
        # Content-Type: application/json
        data = request.data

        name = data.get("name")
        age = data.get("age")

        return 201, f'{{"name": "{name}", "age": {age}}}'

Headers

Access HTTP headers:

def show_headers(request):
    """Display request headers."""
    headers = request.headers

    user_agent = headers.get("User-Agent", "Unknown")
    content_type = headers.get("Content-Type", "")
    accept = headers.get("Accept", "")

    return f"User-Agent: {user_agent}"

# Check for specific headers
def check_auth(request):
    """Check authorization header."""
    auth = request.headers.get("Authorization", "")
    if not auth:
        return 401, "Missing auth"
    return "Authorized"

Cookies

Access request cookies:

def show_cookies(request):
    """Display cookies."""
    cookies = request.cookies

    session_id = cookies.get("session_id", "")
    user_pref = cookies.get("preference", "default")

    return f"Session: {session_id}"

Raw WSGI Environ

Access the raw WSGI environment:

def debug_info(request):
    """Show raw WSGI environ."""
    environ = request.environ

    # Access any WSGI variable
    server_name = environ.get("SERVER_NAME")
    server_port = environ.get("SERVER_PORT")
    scheme = environ.get("wsgi.url_scheme")

    return f"Server: {server_name}:{server_port}"

Request Body

Reading Raw Body

Access raw request body:

def handle_raw_body(request):
    """Read raw request body."""
    # Get content length
    content_length = int(request.environ.get("CONTENT_LENGTH", 0))

    if content_length > 0:
        # Read raw body
        wsgi_input = request.environ.get("wsgi.input")
        raw_body = wsgi_input.read(content_length)

        # Process raw bytes
        # raw_body is bytes

        return "Body processed"

    return 400, "No body"

Multipart Form Data

Handle file uploads:

def upload_file(request):
    """Handle file upload."""
    if request.method == "POST":
        # Multipart form data is parsed automatically
        # Content-Type: multipart/form-data

        file_content = request.data.get("file")
        filename = request.data.get("filename", "upload.txt")

        # Save file or process content
        # file_content is the file data

        return f"Uploaded: {filename}"

    return """
        <form method="post" enctype="multipart/form-data">
            <input type="file" name="file">
            <button type="submit">Upload</button>
        </form>
    """

Content Types

Wiverno automatically handles different content types:

application/x-www-form-urlencoded

Standard HTML form submission:

def handle_form(request):
    """Handle URL-encoded form."""
    # Content-Type: application/x-www-form-urlencoded
    username = request.data.get("username")
    password = request.data.get("password")
    return "Form received"

application/json

JSON API requests:

def api_endpoint(request):
    """Handle JSON request."""
    # Content-Type: application/json
    data = request.data
    # data is already a dict
    return "JSON received"

multipart/form-data

File uploads and mixed data:

def upload(request):
    """Handle multipart form."""
    # Content-Type: multipart/form-data; boundary=...
    file_data = request.data.get("file")
    description = request.data.get("description")
    return "Upload received"

Request Parsing Utilities

Wiverno provides utility classes for parsing:

QueryDict

The QueryDict class handles query parameters with support for multiple values:

from wiverno.core.requests import QueryDict

# Parse query string
query_dict = QueryDict("name=John&age=30&tag=python&tag=web")

# Get single values
name = query_dict.get("name")  # "John"
age = query_dict["age"]        # "30"

# Get multiple values
tags = query_dict.getlist("tag")  # ["python", "web"]

# Iteration (returns first value for each key)
for key in query_dict:
    print(f"{key}: {query_dict[key]}")

ParseBody

from wiverno.core.requests import ParseBody

# Parse POST body
raw_data = b'{"name": "John"}'
params = ParseBody.get_request_params(environ, raw_data)

HeaderParser

from wiverno.core.requests import HeaderParser

# Parse headers
headers = HeaderParser.get_headers(environ)

Common Patterns

Check Request Method

def resource(request):
    """Handle different HTTP methods."""
    if request.method == "GET":
        return "Get resource"
    elif request.method == "POST":
        return 201, "Created resource"
    elif request.method == "PUT":
        return "Updated resource"
    elif request.method == "DELETE":
        return 204, ""
    else:
        return 405, ""

Validate Input

def create_user(request):
    """Create user with validation."""
    if request.method != "POST":
        return 405, ""

    # Get data
    username = request.data.get("username", "").strip()
    email = request.data.get("email", "").strip()

    # Validate
    if not username:
        return 400, "Username required"
    if not email:
        return 400, "Email required"
    if "@" not in email:
        return 400, "Invalid email"

    # Process
    return 201, f"User {username} created"

Parse Pagination

def list_items(request):
    """List items with pagination."""
    # Get page and limit from query
    page = int(request.query_params.get("page", "1"))
    limit = int(request.query_params.get("limit", "10"))

    # Validate
    if page < 1:
        page = 1
    if limit < 1 or limit > 100:
        limit = 10

    # Calculate offset
    offset = (page - 1) * limit

    return f"Page {page}, Limit {limit}, Offset {offset}"

Handle API Authentication

def protected_endpoint(request):
    """Require API key."""
    api_key = request.headers.get("X-API-Key", "")

    if not api_key:
        return 401, '{"error": "API key required"}'

    if api_key != "secret-key":
        return 403, '{"error": "Invalid API key"}'

    # Authorized
    return '{"data": "protected data"}'

Best Practices

1. Always Validate Input

def safe_handler(request):
    """Validate all input."""
    value = request.query_params.get("value", "")

    # Validate
    if not value:
        return 400, "Value required"

    # Sanitize
    value = value.strip()[:100]  # Limit length

    return f"Value: {value}"

2. Use Type Conversion Safely

def get_page(request):
    """Safe type conversion."""
    try:
        page = int(request.query_params.get("page", "1"))
    except ValueError:
        page = 1

    # Ensure valid range
    page = max(1, min(page, 1000))

    return f"Page {page}"

3. Handle Missing Data

def safe_access(request):
    """Use .get() with defaults."""
    # Good
    name = request.query_params.get("name", "Guest")

    # Bad - may raise KeyError
    # name = request.query_params["name"]

Next Steps