Refactor param extraction using Pydantic Field (#278)

*  Refactor parameter dependency using Pydantic Field

* ⬆️ Upgrade required Pydantic version with latest Shape values

*  Add tutorials and code for using Enum and Optional

*  Add tests for tutorials with new types and extra cases

* ♻️ Format, clean, and add annotations to dependencies.utils

* 📝 Update tutorial for query parameters with list defaults

*  Add tests for query param with list default
This commit is contained in:
Sebastián Ramírez
2019-05-30 17:40:43 +04:00
committed by GitHub
parent 83b1a117cc
commit bd407cc4ed
14 changed files with 623 additions and 126 deletions

View File

@@ -35,7 +35,7 @@ If you run this example and open your browser at <a href="http://127.0.0.1:8000/
!!! check
Notice that the value your function received (and returned) is `3`, as a Python `int`, not a string `"3"`.
So, with that type declaration, **FastAPI** gives you automatic request <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>.
## Data validation
@@ -61,12 +61,11 @@ because the path parameter `item_id` had a value of `"foo"`, which is not an `in
The same error would appear if you provided a `float` instead of an int, as in: <a href="http://127.0.0.1:8000/items/4.2" target="_blank">http://127.0.0.1:8000/items/4.2</a>
!!! check
So, with the same Python type declaration, **FastAPI** gives you data validation.
Notice that the error also clearly states exactly the point where the validation didn't pass.
Notice that the error also clearly states exactly the point where the validation didn't pass.
This is incredibly helpful while developing and debugging code that interacts with your API.
## Documentation
@@ -96,8 +95,7 @@ All the data validation is performed under the hood by <a href="https://pydantic
You can use the same type declarations with `str`, `float`, `bool` and many other complex data types.
These are explored in the next chapters of the tutorial.
Several of these are explored in the next chapters of the tutorial.
## Order matters
@@ -115,6 +113,73 @@ Because path operations are evaluated in order, you need to make sure that the p
Otherwise, the path for `/users/{user_id}` would match also for `/users/me`, "thinking" that it's receiving a parameter `user_id` with a value of `"me"`.
## Predefined values
If you have a *path operation* that receives a *path parameter*, but you want the possible valid *path parameter* values to be predefined, you can use a standard Python <abbr title="Enumeration">`Enum`</abbr>.
### Create an `Enum` class
Import `Enum` and create a sub-class that inherits from it.
And create class attributes with fixed values, those fixed values will be the available valid values:
```Python hl_lines="1 6 7 8 9"
{!./src/path_params/tutorial005.py!}
```
!!! info
<a href="https://docs.python.org/3/library/enum.html" target="_blank">Enumerations (or enums) are available in Python</a> since version 3.4.
!!! tip
If you are wondering, "AlexNet", "ResNet", and "LeNet" are just names of Machine Learning <abbr title="Technically, Deep Learning model architectures">models</abbr>.
### Declare a *path parameter*
Then create a *path parameter* with a type annotation using the enum class you created (`ModelName`):
```Python hl_lines="16"
{!./src/path_params/tutorial005.py!}
```
### Check the docs
Because the available values for the *path parameter* are specified, the interactive docs can show them nicely:
<img src="/img/tutorial/path-params/image03.png">
### Working with Python *enumerations*
The value of the *path parameter* will be an *enumeration member*.
#### Compare *enumeration members*
You can compare it with the *enumeration member* in your created enum `ModelName`:
```Python hl_lines="17"
{!./src/path_params/tutorial005.py!}
```
#### Get the *enumeration value*
You can get the actual value (a `str` in this case) using `model_name.value`, or in general, `your_enum_member.value`:
```Python hl_lines="19"
{!./src/path_params/tutorial005.py!}
```
!!! tip
You could also access the value `"lenet"` with `ModelName.lenet.value`.
#### Return *enumeration members*
You can return *enum members* from your *path operation*, even nested in a JSON body (e.g. a `dict`).
They will be converted to their corresponding values before returning them to the client:
```Python hl_lines="18 20 21"
{!./src/path_params/tutorial005.py!}
```
## Path parameters containing paths
Let's say you have a *path operation* with a path `/files/{file_path}`.

View File

@@ -12,7 +12,6 @@ The query parameter `q` is of type `str`, and by default is `None`, so it is opt
We are going to enforce that even though `q` is optional, whenever it is provided, it **doesn't exceed a length of 50 characters**.
### Import `Query`
To achieve that, first import `Query` from `fastapi`:
@@ -29,7 +28,7 @@ And now use it as the default value of your parameter, setting the parameter `ma
{!./src/query_params_str_validations/tutorial002.py!}
```
As we have to replace the default value `None` with `Query(None)`, the first parameter to `Query` serves the same purpose of defining that default value.
As we have to replace the default value `None` with `Query(None)`, the first parameter to `Query` serves the same purpose of defining that default value.
So:
@@ -41,7 +40,7 @@ q: str = Query(None)
```Python
q: str = None
```
```
But it declares it explicitly as being a query parameter.
@@ -53,7 +52,6 @@ q: str = Query(None, max_length=50)
This will validate the data, show a clear error when the data is not valid, and document the parameter in the OpenAPI schema path operation.
## Add more validations
You can also add a parameter `min_length`:
@@ -119,7 +117,7 @@ So, when you need to declare a value as required while using `Query`, you can us
{!./src/query_params_str_validations/tutorial006.py!}
```
!!! info
!!! info
If you hadn't seen that `...` before: it is a a special single value, it is <a href="https://docs.python.org/3/library/constants.html#Ellipsis" target="_blank">part of Python and is called "Ellipsis"</a>.
This will let **FastAPI** know that this parameter is required.
@@ -156,11 +154,35 @@ So, the response to that URL would be:
!!! tip
To declare a query parameter with a type of `list`, like in the example above, you need to explicitly use `Query`, otherwise it would be interpreted as a request body.
The interactive API docs will update accordingly, to allow multiple values:
<img src="/img/tutorial/query-params-str-validations/image02.png">
### Query parameter list / multiple values with defaults
And you can also define a default `list` of values if none are provided:
```Python hl_lines="9"
{!./src/query_params_str_validations/tutorial012.py!}
```
If you go to:
```
http://localhost:8000/items/
```
the default of `q` will be: `["foo", "bar"]` and your response will be:
```JSON
{
"q": [
"foo",
"bar"
]
}
```
## Declare more metadata
You can add more information about the parameter.

View File

@@ -186,3 +186,39 @@ In this case, there are 3 query parameters:
* `needy`, a required `str`.
* `skip`, an `int` with a default value of `0`.
* `limit`, an optional `int`.
!!! tip
You could also use `Enum`s <a href="https://fastapi.tiangolo.com/tutorial/path-params/#predefined-values" target="_blank">the same way as with *path parameters*</a>.
## Optional type declarations
!!! warning
This might be an advanced use case.
You might want to skip it.
If you are using `mypy` it could complain with type declarations like:
```Python
limit: int = None
```
With an error like:
```
Incompatible types in assignment (expression has type "None", variable has type "int")
```
In those cases you can use `Optional` to tell `mypy` that the value could be `None`, like:
```Python
from typing import Optional
limit: Optional[int] = None
```
In a *path operation* that could look like:
```Python hl_lines="9"
{!./src/query_params/tutorial007.py!}
```