Requests

You can easily interact with incoming requests – as well as associated input, files and cookies – by leveraging intuitive methods, helpers and the powerful dependency injection system at the heart of Expanse.

Interacting with the request

Accessing the request

By default, the route functions or controller methods do not give access to the incoming request, however you easily inject it by adding a type-hint for the expanse.http.request.Request class. It will be automatically be resolved via the service container:

# app/http/controller/user_controller.py

from expanse.http.request import Request


class UserController:

    def list(request: Request) -> Response:
        active = request.query("active")

        # Retrieve the users
        users = ...

        ...

Request data

URL

The url property of the request object provides access to a URL object that allows you to easily retrieve information about the current request URL, like the full URL, its path or query parameters.

from expanse.http.request import Request

# Original URL: http://127.0.0.1:8001/users?active=true
request: Request
request.url.full  # http://127.0.0.1:8001/users
request.url.hostname  # 127.0.0.1
request.url.port  # 8001
request.url.path  # /users
request.url.query  # active=true

Method

You can retrieve the HTTP method of the request by using the method property of the request object.

from expanse.http.request import Request

request: Request
request.method  # GET

Note that the method is always in uppercase.

Headers

You can retrieve the headers of the request by using the headers property of the request object. The returned objects behaves like a case-insensitive dictionary and you can access the headers by their name.

from expanse.http.request import Request

request: Request
assert request.headers["Content-Type"] == request.headers["content-type"]

Form data

Expanse provides a few ways to retrieve the request form data depending on the use cases.

Raw form data via dependency injection

The easiest way to have access to the raw form data is by type-hinting your route with expanse.http.form.Form and retrieve the value through the fields attribute.

from expanse.common.http.form import Form


def create_user(form: Form) -> Response:
    name = form.fields["username"].value

    ...

Note that every piece of data in this case will be a string and the validation of the data will need to be done manually. However, you can leverage the built-in validation mechanism instead.

Validated form data via dependency injection

Most of the time, you will want to validate the form data.

For that purpose, you can specify a validation model that will be used to validate the form data. You can technically put your validation models anywhere but the recommended location for them are app/http/request/models/forms/.

# app/http/request/models/forms/create_user_form.py

from pydantic import BaseModel


class CreateUserFormModel(BaseModel):
    username: str
    age: int | None = None

Now that you have a validation model you can use it to validate the form data:

from expanse.common.http.form import Form

from app.http.request.models.forms.create_user_form import CreateUserFormModel


def create_user(form: Form[CreateUserFormModel]) -> Response:
    if form.is_submitted() and form.is_valid():
        name = form.data.username
        age = form.data.age

    ...

data is only available if there were no validation errors so, before accessing it, be sure to check if the data is valid with the is_valid() method.

JSON data

Expanse provides a few ways to retrieve the request form data depending on the use cases.

Raw JSON data using the request

The easiest way to have access to the raw form data is retrieving the request in your route handler and access its form attribute.

from expanse.http.request import Request


def create_user(request: Request) -> Response:
    raw_data = request.form

    ...

Note that in this case it will be exactly what was sent to your application and any validation of the data will need to be done manually. However, you can leverage the built-in validation mechanism instead.

Validated JSON data via dependency injection

Most of the time, you will want to validate the JSON data.

For that purpose, you can specify a validation model that will be used to validate the JSON data. You can technically put your validation models anywhere but the recommended location for them are app/http/request/models/json/.

# app/http/request/models/json/article.py

from pydantic import BaseModel


class ArticleData(BaseModel):
    title: str
    content: str
    draft: bool = True

Now that you have a validation model you can use it to validate the JSON data:

from expanse.common.http.json import JSON

from app.http.request.models.json.article import ArticleData


def create_article(data: JSON[ArticleData]) -> Response:
    # data is a validated instance of ArticleData
    ...

If the data sent is invalid, Expanse will automatically handle the validation error and return a 422 Unprocessable Entity HTTP error with a detailed JSON representation of the error.

For instance, with the model defined above and the following request:

POST https://example.com/articles HTTP/1.1
Content-Type: application/json

{"content": "Content"}

Expanse would automatically send the following response:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "code": "validation_error",
  "detail": [
    {
      "loc": ["title"],
      "message": "Field required",
      "type": "missing",
    }
  ]
}

Query parameters

Expanse provides a few ways to retrieve the query parameters depending on the use cases.

Raw query parameters using the request

The easiest way to have access to the raw query parameters is by retrieving the request in your route handler and access its query_params attribute.

from expanse.http.request import Request


def create_user(request: Request):
    params = request.query_params

Request.params is a dictionary-like object that allows you to access the query parameters by their name.

Validated query parameters via dependency injection

Most of the time, you will want to validate the query parameters.

For that purpose, you can specify a validation model that will be used to validate the query parameters. You can technically put your validation models anywhere but the recommended location for them are app/http/request/models/queries/.

# app/http/request/models/queries/list_users.py

from pydantic import BaseModel


class ListUsersParameters(BaseModel):
    active: bool | None = None

Now that you have a validation model you can use it to validate the query parameters:

from expanse.common.http.query import Query

from app.http.request.models.queries.list_users import ListUsersParameters


def create_article(q: Query[ListUsersParameters]) -> Response:
    assert isinstance(q, ListUsersParameters)

    if q.active:
        # Retrieve active users
        ...
    else:
        # Retrieve all users
        ...

If the data sent is invalid, Expanse will automatically handle the validation error and return a 422 Unprocessable Entity HTTP error with a detailed JSON representation of the error.