Parameters#
Path Parameters#
Path parameters are parameters declared as part of the path
component of
the URL. They are declared using a simple syntax {param_name:param_type}
:
from pydantic import BaseModel
from litestar import Litestar, get
USER_DB = {1: {"id": 1, "name": "John Doe"}}
class User(BaseModel):
id: int
name: str
@get("/user/{user_id:int}", sync_to_thread=False)
def get_user(user_id: int) -> User:
return User.model_validate(USER_DB[user_id])
app = Litestar(route_handlers=[get_user])
In the above there are two components:
The path parameter is defined in the
@get()
decorator, which declares both the parameter’s name (user_id
) and type (int
).The decorated function
get_user
defines a parameter with the same name as the parameter defined in thepath
kwarg.
The correlation of parameter name ensures that the value of the path parameter will be injected into the function when it is called.
Supported Path Parameter Types#
Currently, the following types are supported:
date
: Accepts date strings and time stamps.datetime
: Accepts date-time strings and time stamps.decimal
: Accepts decimal values and floats.float
: Accepts ints and floats.int
: Accepts ints and floats.path
: Accepts valid POSIX paths.str
: Accepts all string values.time
: Accepts time strings with optional timezone compatible with standard (Pydantic/Msgspec) datetime formats.timedelta
: Accepts duration strings compatible with the standard (Pydantic/Msgspec) timedelta formats.uuid
: Accepts all uuid values.
The types declared in the path parameter and the function do not need to match 1:1 - as long as parameter inside the function declaration is typed with a “higher” type to which the lower type can be coerced, this is fine. For example, consider this:
from datetime import datetime, timezone
from typing import List
from pydantic import BaseModel
from litestar import Litestar, get
class Order(BaseModel):
id: int
customer_id: int
ORDERS_BY_DATETIME = {
datetime.fromtimestamp(1667924386, tz=timezone.utc): [
Order(id=1, customer_id=2),
Order(id=2, customer_id=2),
]
}
@get(path="/orders/{from_date:int}", sync_to_thread=False)
def get_orders(from_date: datetime) -> List[Order]:
return ORDERS_BY_DATETIME[from_date]
app = Litestar(route_handlers=[get_orders])
from datetime import datetime, timezone
from pydantic import BaseModel
from litestar import Litestar, get
class Order(BaseModel):
id: int
customer_id: int
ORDERS_BY_DATETIME = {
datetime.fromtimestamp(1667924386, tz=timezone.utc): [
Order(id=1, customer_id=2),
Order(id=2, customer_id=2),
]
}
@get(path="/orders/{from_date:int}", sync_to_thread=False)
def get_orders(from_date: datetime) -> list[Order]:
return ORDERS_BY_DATETIME[from_date]
app = Litestar(route_handlers=[get_orders])
from datetime import datetime, UTC
from pydantic import BaseModel
from litestar import Litestar, get
class Order(BaseModel):
id: int
customer_id: int
ORDERS_BY_DATETIME = {
datetime.fromtimestamp(1667924386, tz=UTC): [
Order(id=1, customer_id=2),
Order(id=2, customer_id=2),
]
}
@get(path="/orders/{from_date:int}", sync_to_thread=False)
def get_orders(from_date: datetime) -> list[Order]:
return ORDERS_BY_DATETIME[from_date]
app = Litestar(route_handlers=[get_orders])
The parameter defined inside the path
kwarg is typed as int
, because the value
passed as part of the request will be a timestamp in milliseconds without any decimals. The parameter in
the function declaration though is typed as datetime.datetime
.
This works because the int value will be automatically coerced from an int
into a datetime
.
Thus, when the function is called it will be called with a datetime
-typed parameter.
Note
You only need to define the parameter in the function declaration if it is actually used inside the function. If the path parameter is part of the path, but the function does not use it, it is fine to omit it. It will still be validated and added to the OpenAPI schema correctly.
The Parameter function#
Parameter()
is a helper function wrapping a parameter with extra information to be
added to the OpenAPI schema.
Extra validation and documentation for path params#
If you want to add validation or enhance the OpenAPI documentation generated for a given path parameter, you can do so using the the parameter function:
from pydantic import BaseModel, Json, conint
from typing_extensions import Annotated
from litestar import Litestar, get
from litestar.openapi.spec.example import Example
from litestar.openapi.spec.external_documentation import ExternalDocumentation
from litestar.params import Parameter
class Version(BaseModel):
id: conint(ge=1, le=10) # type: ignore[valid-type]
specs: Json
VERSIONS = {1: Version(id=1, specs='{"some": "value"}')}
@get(path="/versions/{version:int}", sync_to_thread=False)
def get_product_version(
version: Annotated[
int,
Parameter(
ge=1,
le=10,
title="Available Product Versions",
description="Get a specific version spec from the available specs",
examples=[Example(value=1)],
external_docs=ExternalDocumentation(
url="https://mywebsite.com/documentation/product#versions", # type: ignore[arg-type]
),
),
],
) -> Version:
return VERSIONS[version]
app = Litestar(route_handlers=[get_product_version])
from pydantic import BaseModel, Json, conint
from typing import Annotated
from litestar import Litestar, get
from litestar.openapi.spec.example import Example
from litestar.openapi.spec.external_documentation import ExternalDocumentation
from litestar.params import Parameter
class Version(BaseModel):
id: conint(ge=1, le=10) # type: ignore[valid-type]
specs: Json
VERSIONS = {1: Version(id=1, specs='{"some": "value"}')}
@get(path="/versions/{version:int}", sync_to_thread=False)
def get_product_version(
version: Annotated[
int,
Parameter(
ge=1,
le=10,
title="Available Product Versions",
description="Get a specific version spec from the available specs",
examples=[Example(value=1)],
external_docs=ExternalDocumentation(
url="https://mywebsite.com/documentation/product#versions", # type: ignore[arg-type]
),
),
],
) -> Version:
return VERSIONS[version]
app = Litestar(route_handlers=[get_product_version])
In the above example, Parameter()
is used to restrict the value of version
to a range between 1 and 10, and then set the title
,
description
, examples
, and
externalDocs
sections of the OpenAPI schema.
Query Parameters#
Query parameters are defined as keyword arguments to handler functions. Every keyword argument that is not otherwise specified (for example as a path parameter) will be interpreted as a query parameter.
from typing import Dict
from litestar import Litestar, get
@get("/", sync_to_thread=False)
def index(param: str) -> Dict[str, str]:
return {"param": param}
app = Litestar(route_handlers=[index])
from litestar import Litestar, get
@get("/", sync_to_thread=False)
def index(param: str) -> dict[str, str]:
return {"param": param}
app = Litestar(route_handlers=[index])
Run it
> curl http://127.0.0.1:8000/?param=foo
{"param":"foo"}
> curl http://127.0.0.1:8000/?param=bar
{"param":"bar"}
Technical details
These parameters will be parsed from the function signature and used to generate an internal data model. This model in turn will be used to validate the parameters and generate the OpenAPI schema.
This ability allows you to use any number of schema/modelling libraries, including Pydantic, Msgspec, Attrs, and Dataclasses, and it will follow the same kind of validation and parsing as you would get from these libraries.
Query parameters come in three basic types:
Required
Required with a default value
Optional with a default value
Query parameters are required by default. If one such a parameter has no value,
a ValidationException
will be raised.
Default values#
In this example, param
will have the value "hello"
if it is not specified in the request.
If it is passed as a query parameter however, it will be overwritten:
from typing import Dict
from litestar import Litestar, get
@get("/", sync_to_thread=False)
def index(param: str = "hello") -> Dict[str, str]:
return {"param": param}
app = Litestar(route_handlers=[index])
from litestar import Litestar, get
@get("/", sync_to_thread=False)
def index(param: str = "hello") -> dict[str, str]:
return {"param": param}
app = Litestar(route_handlers=[index])
Run it
> curl http://127.0.0.1:8000/
{"param":"hello"}
> curl http://127.0.0.1:8000/?param=john
{"param":"john"}
Optional parameters#
Instead of only setting a default value, it is also possible to make a query parameter entirely optional.
Here, we give a default value of None
, but still declare the type of the query parameter
to be a string
. This means that this parameter is not required.
If it is given, it has to be a string
.
If it is not given, it will have a default value of None
from typing import Dict, Optional
from litestar import Litestar, get
@get("/", sync_to_thread=False)
def index(param: Optional[str] = None) -> Dict[str, Optional[str]]:
return {"param": param}
app = Litestar(route_handlers=[index])
from typing import Optional
from litestar import Litestar, get
@get("/", sync_to_thread=False)
def index(param: Optional[str] = None) -> dict[str, Optional[str]]:
return {"param": param}
app = Litestar(route_handlers=[index])
from litestar import Litestar, get
@get("/", sync_to_thread=False)
def index(param: str | None = None) -> dict[str, str | None]:
return {"param": param}
app = Litestar(route_handlers=[index])
Run it
> curl http://127.0.0.1:8000/
{"param":null}
> curl http://127.0.0.1:8000/?param=goodbye
{"param":"goodbye"}
Type coercion#
It is possible to coerce query parameters into different types.
A query starts out as a string
, but its values can be parsed into all kinds of types.
from datetime import datetime, timedelta
from typing import Any, Dict, List
from litestar import Litestar, get
@get("/", sync_to_thread=False)
def index(date: datetime, number: int, floating_number: float, strings: List[str]) -> Dict[str, Any]:
return {
"datetime": date + timedelta(days=1),
"int": number,
"float": floating_number,
"list": strings,
}
app = Litestar(route_handlers=[index])
from datetime import datetime, timedelta
from typing import Any
from litestar import Litestar, get
@get("/", sync_to_thread=False)
def index(date: datetime, number: int, floating_number: float, strings: list[str]) -> dict[str, Any]:
return {
"datetime": date + timedelta(days=1),
"int": number,
"float": floating_number,
"list": strings,
}
app = Litestar(route_handlers=[index])
Run it
> curl http://127.0.0.1:8000/?date=2022-11-28T13:22:06.916540&floating_number=0.1&number=42&strings=1&strings=2
{"datetime":"2022-11-29T13:22:06.916540","int":42,"float":0.1,"list":["1","2"]}
Alternative names and constraints#
Sometimes you might want to “remap” query parameters to allow a different name in the URL
than what is being used in the handler function. This can be done by making use of Parameter()
.
from typing import Dict
from typing_extensions import Annotated
from litestar import Litestar, get
from litestar.params import Parameter
@get("/", sync_to_thread=False)
def index(snake_case: Annotated[str, Parameter(query="camelCase")]) -> Dict[str, str]:
return {"param": snake_case}
app = Litestar(route_handlers=[index])
from typing import Annotated
from litestar import Litestar, get
from litestar.params import Parameter
@get("/", sync_to_thread=False)
def index(snake_case: Annotated[str, Parameter(query="camelCase")]) -> dict[str, str]:
return {"param": snake_case}
app = Litestar(route_handlers=[index])
Run it
> curl http://127.0.0.1:8000/?camelCase=foo
{"param":"foo"}
Here, we remap from snake_case
in the handler function to camelCase
in the URL.
This means that for the URL http://127.0.0.1:8000?camelCase=foo
, the value of camelCase
will be used for the value of the snake_case
parameter.
Parameter()
also allows us to define additional constraints:
from typing import Dict
from typing_extensions import Annotated
from litestar import Litestar, get
from litestar.params import Parameter
@get("/", sync_to_thread=False)
def index(param: Annotated[int, Parameter(gt=5)]) -> Dict[str, int]:
return {"param": param}
app = Litestar(route_handlers=[index])
from typing import Annotated
from litestar import Litestar, get
from litestar.params import Parameter
@get("/", sync_to_thread=False)
def index(param: Annotated[int, Parameter(gt=5)]) -> dict[str, int]:
return {"param": param}
app = Litestar(route_handlers=[index])
In this case, param
is validated to be an integer larger than 5.
Layered Parameters#
As part of Litestar’s layered architecture, you can declare parameters not only as part of individual route handler functions, but also on other layers of the application:
from typing import Dict, Union
from typing_extensions import Annotated
from litestar import Controller, Litestar, Router, get
from litestar.params import Parameter
class MyController(Controller):
path = "/controller"
parameters = {
"controller_param": Parameter(int, lt=100),
}
@get("/{path_param:int}", sync_to_thread=False)
def my_handler(
self,
path_param: int,
local_param: str,
router_param: str,
controller_param: Annotated[int, Parameter(int, lt=50)],
) -> Dict[str, Union[str, int]]:
return {
"path_param": path_param,
"local_param": local_param,
"router_param": router_param,
"controller_param": controller_param,
}
router = Router(
path="/router",
route_handlers=[MyController],
parameters={
"router_param": Parameter(str, pattern="^[a-zA-Z]$", header="MyHeader", required=False),
},
)
app = Litestar(
route_handlers=[router],
parameters={
"app_param": Parameter(str, cookie="special-cookie"),
},
)
from typing import Union
from typing import Annotated
from litestar import Controller, Litestar, Router, get
from litestar.params import Parameter
class MyController(Controller):
path = "/controller"
parameters = {
"controller_param": Parameter(int, lt=100),
}
@get("/{path_param:int}", sync_to_thread=False)
def my_handler(
self,
path_param: int,
local_param: str,
router_param: str,
controller_param: Annotated[int, Parameter(int, lt=50)],
) -> dict[str, Union[str, int]]:
return {
"path_param": path_param,
"local_param": local_param,
"router_param": router_param,
"controller_param": controller_param,
}
router = Router(
path="/router",
route_handlers=[MyController],
parameters={
"router_param": Parameter(str, pattern="^[a-zA-Z]$", header="MyHeader", required=False),
},
)
app = Litestar(
route_handlers=[router],
parameters={
"app_param": Parameter(str, cookie="special-cookie"),
},
)
from typing import Annotated
from litestar import Controller, Litestar, Router, get
from litestar.params import Parameter
class MyController(Controller):
path = "/controller"
parameters = {
"controller_param": Parameter(int, lt=100),
}
@get("/{path_param:int}", sync_to_thread=False)
def my_handler(
self,
path_param: int,
local_param: str,
router_param: str,
controller_param: Annotated[int, Parameter(int, lt=50)],
) -> dict[str, str | int]:
return {
"path_param": path_param,
"local_param": local_param,
"router_param": router_param,
"controller_param": controller_param,
}
router = Router(
path="/router",
route_handlers=[MyController],
parameters={
"router_param": Parameter(str, pattern="^[a-zA-Z]$", header="MyHeader", required=False),
},
)
app = Litestar(
route_handlers=[router],
parameters={
"app_param": Parameter(str, cookie="special-cookie"),
},
)
In the above we declare parameters on the Litestar app
,
router
, and controller
layers in addition to those
declared in the route handler. Now, examine these more closely.
app_param
is a cookie parameter with the keyspecial-cookie
. We type it asstr
by passing this as an arg to theParameter()
function. This is required for us to get typing in the OpenAPI doc. Additionally, this parameter is assumed to be required because it is not explicitly set asFalse
onrequired
.This is important because the route handler function does not declare a parameter called
app_param
at all, but it will still require this param to be sent as part of the request of validation will fail.router_param
is a header parameter with the keyMyHeader
. Because it is set asFalse
onrequired
, it will not fail validation if not present unless explicitly declared by a route handler - and in this case it is.Thus, it is actually required for the router handler function that declares it as an
str
and not anstr | None
. If astring
value is provided, it will be tested against the provided regex.controller_param
is a query param with the keycontroller_param
. It has anlt
set to100
defined on the controller, which means the provided value must be less than 100.Yet the route handler redeclares it with an
lt
set to50
, which means for the route handler this value must be less than 50.local_param
is a route handler local query parameter, andpath_param
is a path parameter.
Note
You cannot declare path parameters in different application layers. The reason for this is to ensure simplicity - otherwise parameter resolution becomes very difficult to do correctly.