Skip to content

Request

Lilya brings the Request class object. This object is a nice interface between the incoming request and the ASGI scope.

This means, you don't need to access directly the scope and extract all the information required for a request type of object.

from lilya.requests import Request

The Request class

A Request instance receives a scope, a receive and a send parameter.

from lilya.enums import ScopeType
from lilya.requests import Request
from lilya.responses import PlainText
from lilya.types import Receive, Scope, Send


async def app(scope: Scope, receive: Receive, send: Send):
    assert scope["type"] == ScopeType.HTTP

    request = Request(scope=scope, receive=receive, send=send)
    data = f"{request.method} {request.url.path}"

    response = PlainText(content=data)
    await response(scope, receive, send)

The requests, as mentioned before, present an interface to the scope, which means if you use requests['app'] or requests['headers'] or requests['path'] it will retrieve the same information as it was retrieving from the scope.

Note

If there is not a need to access the request body, you can instantiate a request without providing a receive argument.

Example

from lilya.requests import Request

request = Request(scope)

Attributes

There are many available attributes that you can access within the request.

Method

The request method that is used to access.

from lilya.requests import Request

request = Request(scope)

request.method

URL

from lilya.requests import Request

request = Request(scope)

request.url

This property exposes all the components that can be parsed out of the URL.

Example

from lilya.requests import Request

request = Request(scope)

request.url.port
request.url.path
request.url.scheme
request.url.netloc
request.url.query

Lilya uses the multidict for its headers and adds some extra flavours on the top of it.

from lilya.requests import Request

request = Request(scope)

request.headers['content-type']

Query Params

Lilya uses the multidict for its query parameters and adds some extra flavours on the top of it.

from lilya.requests import Request

request = Request(scope)

request.query_params['search']

Path Params

Extracted directly from the scope as a dictionary like python object

from lilya.requests import Request

request = Request(scope)

request.path_params['username']

Client Address

The client's remote address is exposed as a dataclass request.client.

from lilya.requests import Request

request = Request(scope)

request.client.host
request.client.port

Cookies

Extracted directly from the headers and parsed as a dictionary like python object.

from lilya.requests import Request

request = Request(scope)

request.cookies.get('a-cookie')

Body

Now here it is different. To extract and use the body, a receive must be passed into the request instance and it can be extracted in different ways.

As bytes
from lilya.requests import Request

request = Request(scope, receive)

await request.body()
As JSON
from lilya.requests import Request

request = Request(scope, receive)

await request.json()
As text
from lilya.requests import Request

request = Request(scope, receive)

await request.text()
As form data or multipart form
from lilya.requests import Request

request = Request(scope, receive)

async with request.form() as form:
    ...
As data
from lilya.requests import Request

request = Request(scope, receive)

await request.data()
As a stream
from lilya.enums import ScopeType
from lilya.requests import Request
from lilya.responses import PlainText
from lilya.types import Receive, Scope, Send


async def app(scope: Scope, receive: Receive, send: Send):
    assert scope["type"] == ScopeType.HTTP

    request = Request(scope=scope, receive=receive, send=send)
    data = b""

    async for chunk in request.stream():
        data += chunk

    response = PlainText(content=data)
    await response(scope, receive, send)

When employing .stream(), byte chunks are furnished without the necessity of storing the entire body in memory. Subsequent calls to .body(), .form(), or .json() will result in an error.

In specific situations, such as long-polling or streaming responses, it becomes crucial to determine whether the client has disconnected.

This status can be ascertained using the following: disconnected = await request.is_disconnected().

Request files

Typically, files are transmitted as multipart form data (multipart/form-data).

from lilya.requests import Request

request = Request(scope, receive)

request.form(max_files=1000, max_fields=1000)

You have the flexibility to set the maximum number of fields or files using the max_files and max_fields parameters:

async with request.form(max_files=1000, max_fields=1000):
    ...

Warning

These limitations serve security purposes. Allowing an unlimited number of fields or files could pose a risk of a denial-of-service attack, consuming excessive CPU and memory resources by parsing numerous empty fields.

When invoking async with request.form() as form, you obtain a lilya.datastructures.FormData, which is an immutable multidict containing both file uploads and text input.

File upload items are represented as instances of lilya.datastructures.DataUpload.

DataUpload

DataUpload has the following attributes:

  • filename: A str with the original file name that was uploaded or None if its not available (e.g. profile.png).
  • file: A SpooledTemporaryFile (a file-like object). This is the actual Python file that you can pass directly to other functions or libraries that expect a "file-like" object.
  • headers: A Header object. Often this will only be the Content-Type header, but if additional headers were included in the multipart field they will be included here. Note that these headers have no relationship with the headers in Request.headers.
  • size: An int with uploaded file's size in bytes. This value is calculated from request's contents, making it better choice to find uploaded file's size than Content-Length header. None if not set.

The DataUpload class provides several asynchronous methods that invoke the corresponding file operations using the internal SpooledTemporaryFile:

  • async write(data): Writes the specified data (in bytes) to the file.
  • async read(size): Reads the specified number of bytes (as an integer) from the file.
  • async seek(offset): Positions the file cursor at the byte offset specified (as an integer). For example, using await profile.seek(0) would move the cursor to the beginning of the file.
  • async close(): Closes the file.

Since all these methods are asynchronous, the await keyword is necessary when invoking them.

Example

async with request.form() as form:
    filename = form["upload_file"].filename
    contents = await form["upload_file"].read()

Application

The Lilya application.

from lilya.requests import Request

request = Request(scope)

request.app

State

If you wish to include supplementary information with the request, you can achieve this by using the request.state.

from lilya.requests import Request

request = Request(scope)

request.state.admin = "example@lilya.dev"