Skip to content

Commit

Permalink
Fix OpenAPI for query parameters (#371)
Browse files Browse the repository at this point in the history
* Clean unused query_param_names
* Fix regression for query params
* Add extra test for dependencies
* Add extra openapi test + is_union definition
* Add missing query param docs
* Add Path Parameters doc and extra tests
* Release notes for version 3.3.4
  • Loading branch information
tarsil authored Aug 1, 2024
1 parent 06da9f0 commit a15908d
Show file tree
Hide file tree
Showing 28 changed files with 1,005 additions and 61 deletions.
2 changes: 1 addition & 1 deletion docs/en/docs/extras/cookie-fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ from esmerald.datastructures import Cookie as ResponseCookie
### Cookie from params

The cookie used with the [example](#cookie-as-a-param) as param is not a datastructure but a `FieldInfo` so it cannot
be used to set and create a new `cookie` like the one from [response cookies](#cookie-from-response-cookies).
be used to set and create a new `cookie` like the one from [response cookies](#response-cookies).

To import it:

Expand Down
4 changes: 3 additions & 1 deletion docs/en/docs/extras/index.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# Extras
# Advanced & Useful

Here you will find some more details about how to use some specials from Esmerald within your handlers.
112 changes: 112 additions & 0 deletions docs/en/docs/extras/path-params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Path Parameters

Path parameters are those, as the name suggests, parameters that are part of a URL (path) definition.

You can declare the path parameters (you can think of them as variables) using the same python syntax for strings.

```python
{!> ../../../docs_src/extras/path/example.py !}
```

As you can see, the `user_id` declared in the path was also passed and provided in the function `user`. This is how
you must declare path parameters in Esmerald.

You can now access the URL:

```shell
http://127.0.0.1/users/1
```

## Declaration of the parameters

**Esmerald** being developed on top of [Lilya][lilya] also means that it allows **two** different syntaxes for the declaration
of the parameters.

=== "Default"

```python
{!> ../../../docs_src/extras/path/example.py !}
```

=== "Less than/Greater than"

```python
{!> ../../../docs_src/extras/path/example_gt.py !}
```

Esmerald allows the use of `{}` and `<>` syntaxes. Both work in an `equal` way and the reasoning for that is only to
allow the users to choose their own preference.

## Default parameters

When declaring a controller with path parameters, if no type is specified in the `string` declaration, Esmerald will assume
it will be of type `string`.

```python
{!> ../../../docs_src/extras/path/example.py !}
```

If you wish to specifiy exactly the type for the path parameter, you can do it by liteally specifiying the type of the
parameter.

```python
{!> ../../../docs_src/extras/path/example_type.py !}
```

This will make sure it will enforce the type `int` and throws an error if an invalid integer is provided.

When providing the path parameters with a proper typing, this will also make sure the [OpenAPI](../openapi.md) documentation
will have the right type for you to test.

## Custom typing and transformers

What if you need to create a custom typing that is not natively supported by Esmerald? Well, Esmerald has your back with
the [transformers](../routing/routes.md#custom-transformers).

This will make sure you have your own transformer for your own unique typing and Esmerald can understand it.

Since Esmerald is built on top of [Lilya][lilya], that means it also supports natively the same types.

You can [check here for more details](../routing/routes.md#path-parameters).

## Enums

Esmerald also supports `Enum` types as typing. Yes, that's right, natively Esmerald handles Enums for you. This will
trigger automatic validations of Esmerald in case a wrong value is provided.

```python
{!> ../../../docs_src/extras/path/enum.py !}
```

You can now call:

```shell
http://127.0.0.1/users/admin
```

If you, for example, provide something like this:

```shell
http://127.0.0.1/users/something
```

And since `something` is not declared in the Enum type, you will get an error similar to this:

```json
{
"detail": "Validation failed for http://127.0.0.1/users/something with method GET.",
"errors": [
{
"type": "enum",
"loc": ["item_type"],
"msg": "Input should be 'user' or 'admin'",
"input": "something",
"ctx": {"expected": "'user' or 'admin'"},
"url": "https://errors.pydantic.dev/2.8/v/enum",
}
],
}
```


[lilya]: https//lilya.dev
138 changes: 138 additions & 0 deletions docs/en/docs/extras/query-params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# 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 Esmerald?

Query parameters are those parameters that are not part of the path parameters and therefore those are automatically
injected as `query` parameters for you.

```python
{!> ../../../docs_src/extras/query/example1.py !}
```

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:

```shell
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.

## 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=1` and `limit=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 Esmerald 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.


=== "Using Optional"

```python
{!> ../../../docs_src/extras/query/example_optional.py !}
```

=== "Using Union"

```python
{!> ../../../docs_src/extras/query/example_union.py !}
```

!!! Check
Esmerald 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**

```shell
http://127.0.0.1/users/1
```

**With query params**

```shell
http://127.0.0.1/users/1?q=searchValue
```

## Query and Path parameters

Since Esmerald 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.

```python
{!> ../../../docs_src/extras/query/example_combined.py !}
```

## 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.

```python
{!> ../../../docs_src/extras/query/example_mandatory.py !}
```

If you call the URL like this:

```
http://127.0.0.1/users
```

It will raise an error of missing value, something like this:

```json
{
"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:

```shell
http://127.0.0.1/users?limit=10
```

## Extra with Esmerald params

Because everything in Esmerald 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.

```python
{!> ../../../docs_src/extras/query/example_query.py !}
```

This basically tells that a `q` query parameters must not exceed the length of `10` of an exception will be raised.
1 change: 0 additions & 1 deletion docs/en/docs/extras/useful-index.md

This file was deleted.

18 changes: 18 additions & 0 deletions docs/en/docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ hide:

# Release Notes

## 3.3.4

### Added

- Missing documentation for [Query Parameters](./extras/query-params.md) and [Path Parameters](./extras/path-params.md).

### Changed

- Documentation for `Extra, Advanced && Useful` is now renamed `Advanced & Useful` and its located in the `Features`
section.
- Removed unused internal functions for validations now used by Esmerald encoders.

### Fixed

- Regression caused by the introduction of the dynamic encoders when diplaying the query parameters in the OpenAPI
documentation.
- Annotation discovery for the Signature.

## 3.3.3

### Changed
Expand Down
19 changes: 10 additions & 9 deletions docs/en/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ nav:
- background-tasks.md
- lifespan-events.md
- protocols.md
- Advanced & Useful:
- extras/index.md
- extras/path-params.md
- extras/query-params.md
- extras/request-data.md
- extras/upload-files.md
- extras/forms.md
- extras/body-fields.md
- extras/header-fields.md
- extras/cookie-fields.md
- Scheduler:
- scheduler/index.md
- Asyncz:
Expand Down Expand Up @@ -159,15 +169,6 @@ nav:
- databases/mongoz/example.md
- openapi.md
- Extras:
- extras/index.md
- Extra, Advanced & Useful:
- extras/useful-index.md
- extras/request-data.md
- extras/upload-files.md
- extras/forms.md
- extras/body-fields.md
- extras/header-fields.md
- extras/cookie-fields.md
- wsgi.md
- testclient.md
- Deployment:
Expand Down
19 changes: 19 additions & 0 deletions docs_src/extras/path/enum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from enum import Enum

from esmerald import Esmerald, Gateway, JSONResponse, get


class UserEnum(Enum):
user = "user"
admin = "admin"


@get("/users/{user_type}")
async def read_user(user_type: UserEnum) -> JSONResponse: ...


app = Esmerald(
routes=[
Gateway(read_user),
]
)
12 changes: 12 additions & 0 deletions docs_src/extras/path/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from esmerald import Esmerald, Gateway, JSONResponse, get


@get("/users/{user_id}")
async def read_user(user_id: str) -> JSONResponse: ...


app = Esmerald(
routes=[
Gateway(read_user),
]
)
12 changes: 12 additions & 0 deletions docs_src/extras/path/example_gt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from esmerald import Esmerald, Gateway, JSONResponse, get


@get("/users/<user_id>")
async def read_user(user_id: str) -> JSONResponse: ...


app = Esmerald(
routes=[
Gateway(read_user),
]
)
12 changes: 12 additions & 0 deletions docs_src/extras/path/example_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from esmerald import Esmerald, Gateway, JSONResponse, get


@get("/users/{user_id:int}")
async def read_user(user_id: int) -> JSONResponse: ...


app = Esmerald(
routes=[
Gateway(read_user),
]
)
Loading

0 comments on commit a15908d

Please sign in to comment.