Get the request your handler is answering.

Use ResourceContext[Request] to read headers, cookies, raw body bytes, and request-local state.

A request is one message from a client to your backend. Its URL tells you which operation to run. Headers carry additional information, such as the format the client accepts. Cookies are named values a browser stores and sends back on later requests.

You already use typed parameters for URL values. Keep using those for inputs such as user_id: int. Add a context parameter when you need the rest of the request.

ResourceContext[Request] is a typed wrapper. The brackets name the value you want Karak to supply; .value accesses that value. The wrapper is a Python object, not data sent by the client. The same convention supplies shared application services with a longer lifetime.

Run a complete example

After the quickstart, replace main.py with:

from karak import Karak
from karak import Request
from karak import ResourceContext
from karak import Response
from karak import Router

router = Router()


@router.get("/users/{user_id}")
async def user(user_id: int, context: ResourceContext[Request]):
    request = context.value
    theme = request.cookies.get("theme", "light")
    client = request.headers.get("user-agent", "unknown")
    response = Response(content=f"User {user_id}; theme={theme}; client={client}")
    response.headers["content-type"] = "text/plain; charset=utf-8"
    return response


@router.post("/preferences")
async def preferences(theme: str):
    if theme not in ("light", "dark"):
        return Response(status_code=422, content="Choose light or dark")
    response = Response(content=f"Saved theme={theme}")
    response.set_cookie("theme", theme, max_age=604800, httponly=True)
    return response


@router.post("/preferences/reset")
async def reset_preferences():
    response = Response(content="Removed theme preference")
    response.delete_cookie("theme")
    return response


app = Karak(router=router)

Run uv run uvicorn main:app --reload. In another terminal:

curl -i -H 'User-Agent: learning-karak' http://127.0.0.1:8000/users/42

The body is User 42; theme=light; client=learning-karak. context.value is the current Request. Karak supplies it automatically; you do not register a factory for it or send a context query parameter. The name context is your choice; the annotation selects the value.

Use curl's cookie jar, a local file that acts like a browser's cookie storage:

curl -i -c cookies.txt -X POST 'http://127.0.0.1:8000/preferences?theme=dark'
curl -i -b cookies.txt -H 'User-Agent: learning-karak' http://127.0.0.1:8000/users/42

The first response includes Set-Cookie. -c saves it to cookies.txt. The second command uses -b to send that cookie; the response now says theme=dark. POST inputs still come from the query string in this example. Karak does not automatically bind a JSON request body.

Remove the preference and update the cookie jar:

curl -i -b cookies.txt -c cookies.txt -X POST http://127.0.0.1:8000/preferences/reset
curl -i -b cookies.txt http://127.0.0.1:8000/users/42

The theme is back to light. Deletion sends an expired cookie to the client; it does not modify the cookies on the request already being processed.

Cookies are client-controlled strings. This example stores a display preference, not an authenticated identity. Signing cookies and managing login sessions are not built into Karak.

Read headers and repeated values

request.headers.get("accept", "*/*")
request.headers.getlist("x-tag")

Header names are case-insensitive: User-Agent and user-agent find the same field. .get() returns the first value or your default. .getlist() returns all values in order, or an empty list. request.headers["missing"] raises KeyError. The request header collection is read-only.

Repeated headers are preserved instead of combined automatically. Repeated incoming Cookie fields are joined with ; before parsing. Cookie names are case-sensitive; duplicate names use the last parsed value. Cookie parsing uses Python's SimpleCookie; malformed input may be ignored or produce an empty cookie dictionary. Do not use a preference cookie as an authorization check.

Know which data you are reading

API Example Meaning
request.method "GET" HTTP operation
request.path "/users/42" URL path without query string
request.path_params {"user_id": "42"} Raw matched strings; the handler receives converted user_id=42
request.params {"tag": ["python", "http"]} Query values, always stored as lists
request.headers .get("user-agent") Read-only header lookup
request.cookies .get("theme", "light") Parsed cookie strings
request.state {"request_id": "..."} Your mutable data for this request
await request.body() b"hello" Complete body bytes

Read a body explicitly

Add this endpoint before constructing app:

@router.post("/echo")
async def echo(context: ResourceContext[Request]):
    body = await context.value.body()
    return Response(content=body, headers={"content-type": "application/octet-stream"})
curl --data-binary 'hello' http://127.0.0.1:8000/echo

The response is hello. await lets other work proceed while bytes arrive. The body is cached: reading it again returns the same bytes, including b"" for an empty body. Concurrent reads on the same request share that cache. A disconnect before the body completes raises RuntimeError.

The body is buffered in memory and currently has no built-in size limit. Streaming, form parsing, and automatic JSON body validation are not implemented.

Keep state inside one request

Use request.state to pass data between helpers handling the same request:

async def describe_request(request: Request) -> str:
    return request.state["operation"]


@router.get("/operation")
async def operation(context: ResourceContext[Request]):
    request = context.value
    request.state["operation"] = "show-operation"
    return await describe_request(request)

Each request starts with its own empty dictionary. Two simultaneous callers cannot overwrite each other's state through this dictionary. Multiple ResourceContext[Request] parameters in a handler refer to the same request. This state is separate from application resources and ASGI lifespan state. It is not persistent storage; do not retain the request in a shared service.

Use the same convention for shared services

service: ResourceContext[ProductService] supplies a registered application service; context: ResourceContext[Request] supplies the current request. Both expose .value. See the complete service example.

Karak checks context annotations and provider registration when constructing the application. Context parameters cannot have defaults, be positional-only, or use the name of a path placeholder. Query parameters cannot override them. A missing registered provider prevents startup. An application resource cannot depend on Request, because it lives longer than one request.

Validate values when you use them

Set-Cookie asks the client to store a preference; a later Cookie request header sends it back. Clients can also send values directly, such as curl -b 'theme=blue', even though the preference endpoint only sets light or dark. Validate cookie values before using them to control application behavior.

To share business logic between handlers, continue with application resources. To organize the handlers themselves, use routers and modules.

Type to find a page.