Skip to content

Commit

Permalink
๐Ÿ“ Add docs recommending Union over Optional and migrate source exโ€ฆ
Browse files Browse the repository at this point in the history
โ€ฆamples (#4908)

* ๐Ÿ“ Add docs recommending Union over Optional

* ๐Ÿ“ Update docs recommending Union over Optional

* ๐Ÿ“ Update source examples for docs, recommend Union over Optional

* ๐Ÿ“ Update highlighted lines with updated source examples

* ๐Ÿ“ Update highlighted lines in Markdown with recent code changes

* ๐Ÿ“ Update docs, use Union instead of Optional

* โ™ป๏ธ Update source examples to recommend Union over Optional

* ๐ŸŽจ Update highlighted code in Markdown after moving from Optional to Union
  • Loading branch information
tiangolo committed May 14, 2022
1 parent c5be1b0 commit ca437cd
Show file tree
Hide file tree
Showing 131 changed files with 489 additions and 426 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ $ pip install "uvicorn[standard]"
* Create a file `main.py` with:

```Python
from typing import Optional
from typing import Union

from fastapi import FastAPI

Expand All @@ -164,7 +164,7 @@ def read_root():


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```

Expand All @@ -174,7 +174,7 @@ def read_item(item_id: int, q: Optional[str] = None):
If your code uses `async` / `await`, use `async def`:

```Python hl_lines="9 14"
from typing import Optional
from typing import Union

from fastapi import FastAPI

Expand All @@ -187,7 +187,7 @@ async def read_root():


@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```

Expand Down Expand Up @@ -266,7 +266,7 @@ Now modify the file `main.py` to receive a body from a `PUT` request.
Declare the body using standard Python types, thanks to Pydantic.

```Python hl_lines="4 9-12 25-27"
from typing import Optional
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel
Expand All @@ -277,7 +277,7 @@ app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: Optional[bool] = None
is_offer: Union[bool, None] = None


@app.get("/")
Expand All @@ -286,7 +286,7 @@ def read_root():


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}


Expand Down
14 changes: 7 additions & 7 deletions docs/de/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ $ pip install uvicorn[standard]
* Create a file `main.py` with:

```Python
from typing import Optional
from typing import Union

from fastapi import FastAPI

Expand All @@ -162,7 +162,7 @@ def read_root():


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```

Expand All @@ -172,7 +172,7 @@ def read_item(item_id: int, q: Optional[str] = None):
If your code uses `async` / `await`, use `async def`:

```Python hl_lines="9 14"
from typing import Optional
from typing import Union

from fastapi import FastAPI

Expand All @@ -185,7 +185,7 @@ async def read_root():


@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```

Expand Down Expand Up @@ -264,7 +264,7 @@ Now modify the file `main.py` to receive a body from a `PUT` request.
Declare the body using standard Python types, thanks to Pydantic.

```Python hl_lines="4 9-12 25-27"
from typing import Optional
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel
Expand All @@ -275,7 +275,7 @@ app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: Optional[bool] = None
is_offer: Union[bool, None] = None


@app.get("/")
Expand All @@ -284,7 +284,7 @@ def read_root():


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}


Expand Down
2 changes: 1 addition & 1 deletion docs/en/docs/advanced/additional-status-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ But you also want it to accept new items. And when the items didn't exist before

To achieve that, import `JSONResponse`, and return your content there directly, setting the `status_code` that you want:

```Python hl_lines="4 23"
```Python hl_lines="4 25"
{!../../../docs_src/additional_status_codes/tutorial001.py!}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/en/docs/advanced/testing-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ To override a dependency for testing, you put as a key the original dependency (

And then **FastAPI** will call that override instead of the original dependency.

```Python hl_lines="26-27 30"
```Python hl_lines="28-29 32"
{!../../../docs_src/dependency_testing/tutorial001.py!}
```

Expand Down
4 changes: 2 additions & 2 deletions docs/en/docs/deployment/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Successfully installed fastapi pydantic uvicorn
* Create a `main.py` file with:

```Python
from typing import Optional
from typing import Union

from fastapi import FastAPI

Expand All @@ -155,7 +155,7 @@ def read_root():


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```

Expand Down
14 changes: 7 additions & 7 deletions docs/en/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ $ pip install "uvicorn[standard]"
* Create a file `main.py` with:

```Python
from typing import Optional
from typing import Union

from fastapi import FastAPI

Expand All @@ -161,7 +161,7 @@ def read_root():


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```

Expand All @@ -171,7 +171,7 @@ def read_item(item_id: int, q: Optional[str] = None):
If your code uses `async` / `await`, use `async def`:

```Python hl_lines="9 14"
from typing import Optional
from typing import Union

from fastapi import FastAPI

Expand All @@ -184,7 +184,7 @@ async def read_root():


@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```

Expand Down Expand Up @@ -263,7 +263,7 @@ Now modify the file `main.py` to receive a body from a `PUT` request.
Declare the body using standard Python types, thanks to Pydantic.

```Python hl_lines="4 9-12 25-27"
from typing import Optional
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel
Expand All @@ -274,7 +274,7 @@ app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: Optional[bool] = None
is_offer: Union[bool, None] = None


@app.get("/")
Expand All @@ -283,7 +283,7 @@ def read_root():


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}


Expand Down
42 changes: 42 additions & 0 deletions docs/en/docs/python-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,45 @@ This also means that in Python 3.10, you can use `Something | None`:
{!> ../../../docs_src/python_types/tutorial009_py310.py!}
```

#### Using `Union` or `Optional`

If you are using a Python version below 3.10, here's a tip from my very **subjective** point of view:

* ๐Ÿšจ Avoid using `Optional[SomeType]`
* Instead โœจ **use `Union[SomeType, None]`** โœจ.

Both are equivalent and underneath they are the same, but I would recommend `Union` instead of `Optional` because the word "**optional**" would seem to imply that the value is optional, and it actually means "it can be `None`", even if it's not optional and is still required.

I think `Union[str, SomeType]` is more explicit about what it means.

It's just about the words and names. But those words can affect how you and your teammates think about the code.

As an example, let's take this function:

```Python hl_lines="1 4"
{!../../../docs_src/python_types/tutorial009c.py!}
```

The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter:

```Python
say_hi() # Oh, no, this throws an error! ๐Ÿ˜ฑ
```

The `name` parameter is **still required** (not *optional*) because it doesn't have a default value. Still, `name` accepts `None` as the value:

```Python
say_hi(name=None) # This works, None is valid ๐ŸŽ‰
```

The good news is, once you are on Python 3.10 you won't have to worry about that, as you will be able to simply use `|` to define unions of types:

```Python hl_lines="1 4"
{!../../../docs_src/python_types/tutorial009c_py310.py!}
```

And then you won't have to worry about names like `Optional` and `Union`. ๐Ÿ˜Ž

#### Generic types

These types that take type parameters in square brackets are called **Generic types** or **Generics**, for example:
Expand Down Expand Up @@ -422,6 +461,9 @@ An example from the official Pydantic docs:

You will see a lot more of all this in practice in the [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.

!!! tip
Pydantic has a special behavior when you use `Optional` or `Union[Something, None]` without a default value, you can read more about it in the Pydantic docs about <a href="https://pydantic-docs.helpmanual.io/usage/models/#required-optional-fields" class="external-link" target="_blank">Required Optional fields</a>.

## Type hints in **FastAPI**

**FastAPI** takes advantage of these type hints to do several things.
Expand Down
11 changes: 5 additions & 6 deletions docs/en/docs/tutorial/body-multiple-params.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,13 @@ But you can instruct **FastAPI** to treat it as another body key using `Body`:

=== "Python 3.6 and above"

```Python hl_lines="23"
```Python hl_lines="22"
{!> ../../../docs_src/body_multiple_params/tutorial003.py!}
```

=== "Python 3.10 and above"

```Python hl_lines="21"
```Python hl_lines="20"
{!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!}
```

Expand Down Expand Up @@ -126,7 +126,7 @@ Of course, you can also declare additional query parameters whenever you need, a
As, by default, singular values are interpreted as query parameters, you don't have to explicitly add a `Query`, you can just do:

```Python
q: Optional[str] = None
q: Union[str, None] = None
```

Or in Python 3.10 and above:
Expand All @@ -139,7 +139,7 @@ For example:

=== "Python 3.6 and above"

```Python hl_lines="28"
```Python hl_lines="27"
{!> ../../../docs_src/body_multiple_params/tutorial004.py!}
```

Expand All @@ -152,7 +152,6 @@ For example:
!!! info
`Body` also has all the same extra validation and metadata parameters as `Query`,`Path` and others you will see later.


## Embed a single body parameter

Let's say you only have a single `item` body parameter from a Pydantic model `Item`.
Expand All @@ -162,7 +161,7 @@ By default, **FastAPI** will then expect its body directly.
But if you want it to expect a JSON with a key `item` and inside of it the model contents, as it does when you declare extra body parameters, you can use the special `Body` parameter `embed`:

```Python
item: Item = Body(..., embed=True)
item: Item = Body(embed=True)
```

as in:
Expand Down
2 changes: 1 addition & 1 deletion docs/en/docs/tutorial/body.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ The function parameters will be recognized as follows:
!!! note
FastAPI will know that the value of `q` is not required because of the default value `= None`.

The `Optional` in `Optional[str]` is not used by FastAPI, but will allow your editor to give you better support and detect errors.
The `Union` in `Union[str, None]` is not used by FastAPI, but will allow your editor to give you better support and detect errors.

## Without Pydantic

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Pay attention to the `__init__` method used to create the instance of the class:

=== "Python 3.6 and above"

```Python hl_lines="8"
```Python hl_lines="9"
{!> ../../../docs_src/dependencies/tutorial001.py!}
```

Expand Down
4 changes: 2 additions & 2 deletions docs/en/docs/tutorial/dependencies/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ It is just a function that can take all the same parameters that a *path operati

=== "Python 3.6 and above"

```Python hl_lines="8-9"
```Python hl_lines="8-11"
{!> ../../../docs_src/dependencies/tutorial001.py!}
```

Expand Down Expand Up @@ -81,7 +81,7 @@ The same way you use `Body`, `Query`, etc. with your *path operation function* p

=== "Python 3.6 and above"

```Python hl_lines="13 18"
```Python hl_lines="15 20"
{!> ../../../docs_src/dependencies/tutorial001.py!}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/en/docs/tutorial/dependencies/sub-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Then we can use the dependency with:

=== "Python 3.6 and above"

```Python hl_lines="21"
```Python hl_lines="22"
{!> ../../../docs_src/dependencies/tutorial005.py!}
```

Expand Down
4 changes: 2 additions & 2 deletions docs/en/docs/tutorial/path-params-numeric-validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ It doesn't matter for **FastAPI**. It will detect the parameters by their names,

So, you can declare your function as:

```Python hl_lines="8"
```Python hl_lines="7"
{!../../../docs_src/path_params_numeric_validations/tutorial002.py!}
```

Expand All @@ -71,7 +71,7 @@ Pass `*`, as the first parameter of the function.

Python won't do anything with that `*`, but it will know that all the following parameters should be called as keyword arguments (key-value pairs), also known as <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>. Even if they don't have a default value.

```Python hl_lines="8"
```Python hl_lines="7"
{!../../../docs_src/path_params_numeric_validations/tutorial003.py!}
```

Expand Down

0 comments on commit ca437cd

Please sign in to comment.