Query Parameters¶
It is very common to simply want to declare some query parameters in your controllers. Sometimes those are also used as filters for a given search or simply extra parameters in general.
What are query parameters in Ravyn?¶
Query parameters are those parameters that are not part of the path parameters and therefore those are automatically
injected as query parameters for you.
from ravyn import Ravyn, Gateway, JSONResponse, get
fake_users = [{"last_name": "Doe", "email": "john.doe@example.com"}]
@get("/users")
async def read_user(skip: int = 1, limit: int = 5) -> JSONResponse:
return fake_users[skip : skip + limit]
app = Ravyn(
routes=[
Gateway(read_user),
]
)
As you can see, the query is a key-pair value that goes after the ? in the URL and seperated by a &.
Applying the previous example, it would look like this:
http://127.0.0.1/users?skip=1&limit=5
The previous url will be translated as the following query_params:
skip: with a value of 1.limit: with a value of 5.
Since they are an integral part of the URL, it will automatically populate the parameters of the function that corresponds each value.
Tip
We are will be using direct examples but Ravyn supports the Annotated declaration as well.
Declaring defaults¶
Query parameters are not by design, part of a fixed URL path and that also means they can assume the following:
- They can have defaults, like
skip=1andlimit=5. - They can be
optional.
In the previous example, the URL had already defaults for skip and limit and the corresponding typing as per requirement
of Ravyn but what if we want to make them optional?
There are different ways of achieving that, using the Optional or Union.
Tip
from Python 3.10+ the Union can be replaced with | syntax.
from typing import Optional
from ravyn import Ravyn, Gateway, JSONResponse, get
@get("/users/{id}")
async def read_user(id: int, q: Optional[int] = None) -> JSONResponse: ...
app = Ravyn(
routes=[
Gateway(read_user),
]
)
from typing import Union
from ravyn import Ravyn, Gateway, JSONResponse, get
@get("/users/{id}")
async def read_user(id: int, q: Union[int, None] = None) -> JSONResponse: ...
app = Ravyn(
routes=[
Gateway(read_user),
]
)
Check
Ravyn is intelligent enough to understand what is a query param and what is a path param.
Now we can call the URL and ignore the q or call it when needed, like this:
Without query params
http://127.0.0.1/users/1
With query params
http://127.0.0.1/users/1?q=searchValue
Query and Path parameters¶
Since Ravyn is intelligent enough to distinguish path parameters and query parameters automatically, that also means you can have multiple of both combined.
Warning
You can't have a query and path parameters with the same name as in the end, it is still Python parameters being declared in a function.
from ravyn import Ravyn, Gateway, JSONResponse, get
@get("/users/{id}/{last_name}")
async def read_user(id: int, last_name: str, skip: int = 1, limit: int = 5) -> JSONResponse: ...
app = Ravyn(
routes=[
Gateway(read_user),
]
)
Query parameters as list or dict¶
In Ravyn you can also have query parameters passed as a list or as a dictionary.
This can be particularly useful when you are building complex filters, for example.
As a list¶
Let us see how it would look like if we were building a list of query params.
from ravyn import Ravyn, Gateway, JSONResponse, get
@get("/users/{id}")
async def read_user(id: int, role: list[int] | None = None) -> JSONResponse: ...
app = Ravyn(
routes=[
Gateway(read_user),
]
)
As you can see, we made the role optional. This will allow you to do something like this:
http://127.0.0.1/users/1?role=admin&role=user&role=other
The role value will automatically populated similar to this:
[
"admin",
"user",
"other"
]
Using Annotated¶
As mentioned before, we can use the Annotated syntax as well. This is how it would look like:
from typing import Annotated
from ravyn import Ravyn, Gateway, JSONResponse, Query, get
@get("/users/{id}")
async def read_user(
id: int,
role: Annotated[list[int] | None, Query()] = None,
) -> JSONResponse: ...
app = Ravyn(
routes=[
Gateway(read_user),
]
)
Same principle as before but now using the Annotated syntax approach. Its up to you to decide the best.
As a dict¶
There is also the option to pass query parameters as a dictionary. The uses cases are many but in the end, it will always be up tp you to decide.
Let us see how it would look like.
from typing import Any
from ravyn import Ravyn, Gateway, JSONResponse, get
@get("/users/{id}")
async def read_user(id: int, roles: dict[str, Any] | None = None) -> JSONResponse: ...
app = Ravyn(
routes=[
Gateway(read_user),
]
)
Here we call it roles and the reason for that is to make it more readable for us, also, we made it optional.
Now you can do something like this:
http://127.0.0.1/users/1?name=Ravyn&version=2&position=awesome
The roles will be automatically populated with:
{
"name": "Ravyn",
"version": 2,
"position": "awesome"
}
Pretty cool if you want to send completely different set of query parameters.
Using the Annotated¶
Same as for the list, we can also use the Annotated.
from typing import Any, Annotated
from ravyn import Ravyn, Gateway, JSONResponse, Query, get
from ravyn.params import Query
@get("/users/{id}")
async def read_user(
id: int, roles: Annotated[dict[str, Any] | None, Query()] = None
) -> JSONResponse: ...
app = Ravyn(
routes=[
Gateway(read_user),
]
)
Mixing parameters¶
What if we want to mix a lot of parameters in one go? It could happen quite often.
There are differences when mixing lists and dicts. if there is no mix between dict and list in the same signature, then list and dict operate as described above but when both come into the mix, then dict when called in the API is declared differently.
Let us see an example:
from ravyn import Ravyn, Gateway, JSONResponse, get
@get("/users/{id}")
async def read_user(
id: int,
roles: list[str] | None = None,
positions: list[str] | None = None,
indexes: list[int] | None = None,
others: dict[str, str] | None = None,
q: str | None = None,
) -> JSONResponse:
"""
A lot of query parameters passed here
"""
return JSONResponse(
{
"id": id,
"roles": roles,
"positions": positions,
"indexes": indexes,
"others": others,
"q": q,
}
)
app = Ravyn(
routes=[
Gateway(read_user),
]
)
Ok, let us now call the API with all of this in mind.
http://127.0.0.1/users/1?roles=admin&roles=user&positions=senior&positions=junior&indexes=1&indexes=2&others[internal]=hr&others[company]=ACME&q=search
This is quite the querystring isn't it? Well, its a normal one to populate lists* and dicts**.
Did you notice the syntax for the dict others? Its not the same as the example and the reason for that its
because we are mixing different datastructures in one go.
When this happens you must explicitly pass the dict[key]=value which here translates to others[internal]=hr and others[company]=ACME.
Note
This only happens when list and dict datastructures are mixed in the query parameters. The rest remains as is.
Required parameters¶
When you declare a query parameter without a default and without being optional when you call the URL
it will raise an error of missing value for the corresponding.
from ravyn import Ravyn, Gateway, JSONResponse, get
@get("/users")
async def read_user(limit: int) -> JSONResponse: ...
app = Ravyn(
routes=[
Gateway(read_user),
]
)
If you call the URL like this:
http://127.0.0.1/users
It will raise an error of missing value, something like this:
{
"detail": "Validation failed for <URL> with method GET.",
"errors": [
{
"type": "int_type",
"loc": [
"limit"
],
"msg": "Input should be a valid integer",
"input": null,
"url": "https://errors.pydantic.dev/2.8/v/int_type"
}
]
}
Which means, you need to call with the declared parameter, like this, for example:
http://127.0.0.1/users?limit=10
Extra with Ravyn params¶
Because everything in Ravyn just works you can also add restrictions and limits to your query parameters. For example.
you might want to add a limit into the size of a string q when searching not to exceed an X value.
from ravyn import Ravyn, Gateway, JSONResponse, Query, get
@get("/users")
async def read_user(q: str = Query(max_length=10)) -> JSONResponse: ...
app = Ravyn(
routes=[
Gateway(read_user),
]
)
This basically tells that a q query parameters must not exceed the length of 10 of an exception will be raised.