Skip to content

Commit ad4e8e0

Browse files
➖ Drop support for Python 3.9 (fastapi#14897)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent bdd2005 commit ad4e8e0

16 files changed

Lines changed: 486 additions & 2231 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,10 @@ jobs:
5656
- starlette-pypi
5757
- starlette-git
5858
include:
59-
- os: ubuntu-latest
60-
python-version: "3.9"
61-
coverage: coverage
62-
uv-resolution: lowest-direct
6359
- os: macos-latest
6460
python-version: "3.10"
6561
coverage: coverage
66-
uv-resolution: highest
62+
uv-resolution: lowest-direct
6763
- os: windows-latest
6864
python-version: "3.12"
6965
coverage: coverage
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Advanced Python Types { #advanced-python-types }
2+
3+
Here are some additional ideas that might be useful when working with Python types.
4+
5+
## Using `Union` or `Optional` { #using-union-or-optional }
6+
7+
If your code for some reason can't use `|`, for example if it's not in a type annotation but in something like `response_model=`, instead of using the vertical bar (`|`) you can use `Union` from `typing`.
8+
9+
For example, you could declare that something could be a `str` or `None`:
10+
11+
```python
12+
from typing import Union
13+
14+
15+
def say_hi(name: Union[str, None]):
16+
print(f"Hi {name}!")
17+
```
18+
19+
`typing` also has a shortcut to declare that something could be `None`, with `Optional`.
20+
21+
Here's a tip from my very **subjective** point of view:
22+
23+
* 🚨 Avoid using `Optional[SomeType]`
24+
* Instead ✨ **use `Union[SomeType, None]`** ✨.
25+
26+
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.
27+
28+
I think `Union[SomeType, None]` is more explicit about what it means.
29+
30+
It's just about the words and names. But those words can affect how you and your teammates think about the code.
31+
32+
As an example, let's take this function:
33+
34+
```python
35+
from typing import Optional
36+
37+
38+
def say_hi(name: Optional[str]):
39+
print(f"Hey {name}!")
40+
```
41+
42+
The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter:
43+
44+
```Python
45+
say_hi() # Oh, no, this throws an error! 😱
46+
```
47+
48+
The `name` parameter is **still required** (not *optional*) because it doesn't have a default value. Still, `name` accepts `None` as the value:
49+
50+
```Python
51+
say_hi(name=None) # This works, None is valid 🎉
52+
```
53+
54+
The good news is, in most cases, you will be able to simply use `|` to define unions of types:
55+
56+
```python
57+
def say_hi(name: str | None):
58+
print(f"Hey {name}!")
59+
```
60+
61+
So, normally you don't have to worry about names like `Optional` and `Union`. 😎

docs/en/docs/deployment/docker.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ In a hurry and already know this stuff? Jump to the [`Dockerfile` below 👇](#b
1414
<summary>Dockerfile Preview 👀</summary>
1515

1616
```Dockerfile
17-
FROM python:3.9
17+
FROM python:3.14
1818

1919
WORKDIR /code
2020

@@ -166,7 +166,7 @@ Now in the same project directory create a file `Dockerfile` with:
166166

167167
```{ .dockerfile .annotate }
168168
# (1)!
169-
FROM python:3.9
169+
FROM python:3.14
170170

171171
# (2)!
172172
WORKDIR /code
@@ -390,7 +390,7 @@ If your FastAPI is a single file, for example, `main.py` without an `./app` dire
390390
Then you would just have to change the corresponding paths to copy the file inside the `Dockerfile`:
391391

392392
```{ .dockerfile .annotate hl_lines="10 13" }
393-
FROM python:3.9
393+
FROM python:3.14
394394

395395
WORKDIR /code
396396

@@ -499,7 +499,7 @@ Of course, there are **special cases** where you could want to have **a containe
499499
In those cases, you can use the `--workers` command line option to set the number of workers that you want to run:
500500

501501
```{ .dockerfile .annotate }
502-
FROM python:3.9
502+
FROM python:3.14
503503

504504
WORKDIR /code
505505

docs/en/docs/python-types.md

Lines changed: 20 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -135,27 +135,30 @@ You can use, for example:
135135

136136
{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
137137

138-
### Generic types with type parameters { #generic-types-with-type-parameters }
138+
### `typing` module { #typing-module }
139139

140-
There are some data structures that can contain other values, like `dict`, `list`, `set` and `tuple`. And the internal values can have their own type too.
140+
For some additional use cases, you might need to import some things from the standard library `typing` module, for example when you want to declare that something has "any type", you can use `Any` from `typing`:
141141

142-
These types that have internal types are called "**generic**" types. And it's possible to declare them, even with their internal types.
142+
```python
143+
from typing import Any
143144

144-
To declare those types and the internal types, you can use the standard Python module `typing`. It exists specifically to support these type hints.
145145

146-
#### Newer versions of Python { #newer-versions-of-python }
147-
148-
The syntax using `typing` is **compatible** with all versions, from Python 3.6 to the latest ones, including Python 3.9, Python 3.10, etc.
146+
def some_function(data: Any):
147+
print(data)
148+
```
149149

150-
As Python advances, **newer versions** come with improved support for these type annotations and in many cases you won't even need to import and use the `typing` module to declare the type annotations.
150+
### Generic types { #generic-types }
151151

152-
If you can choose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity.
152+
Some types can take "type parameters" in square brackets, to define their internal types, for example a "list of strings" would be declared `list[str]`.
153153

154-
In all the docs there are examples compatible with each version of Python (when there's a difference).
154+
These types that can take type parameters are called **Generic types** or **Generics**.
155155

156-
For example "**Python 3.6+**" means it's compatible with Python 3.6 or above (including 3.7, 3.8, 3.9, 3.10, etc). And "**Python 3.9+**" means it's compatible with Python 3.9 or above (including 3.10, etc).
156+
You can use the same builtin types as generics (with square brackets and types inside):
157157

158-
If you can use the **latest versions of Python**, use the examples for the latest version, those will have the **best and simplest syntax**, for example, "**Python 3.10+**".
158+
* `list`
159+
* `tuple`
160+
* `set`
161+
* `dict`
159162

160163
#### List { #list }
161164

@@ -220,44 +223,20 @@ This means:
220223

221224
You can declare that a variable can be any of **several types**, for example, an `int` or a `str`.
222225

223-
In Python 3.6 and above (including Python 3.10) you can use the `Union` type from `typing` and put inside the square brackets the possible types to accept.
224-
225-
In Python 3.10 there's also a **new syntax** where you can put the possible types separated by a <dfn title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</dfn>.
226+
To define it you use the <dfn title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</dfn> to separate both types.
226227

227-
//// tab | Python 3.10+
228+
This is called a "union", because the variable can be anything in the union of those two sets of types.
228229

229230
```Python hl_lines="1"
230231
{!> ../../docs_src/python_types/tutorial008b_py310.py!}
231232
```
232233

233-
////
234-
235-
//// tab | Python 3.9+
236-
237-
```Python hl_lines="1 4"
238-
{!> ../../docs_src/python_types/tutorial008b_py39.py!}
239-
```
240-
241-
////
242-
243-
In both cases this means that `item` could be an `int` or a `str`.
234+
This means that `item` could be an `int` or a `str`.
244235

245236
#### Possibly `None` { #possibly-none }
246237

247238
You can declare that a value could have a type, like `str`, but that it could also be `None`.
248239

249-
In Python 3.6 and above (including Python 3.10) you can declare it by importing and using `Optional` from the `typing` module.
250-
251-
```Python hl_lines="1 4"
252-
{!../../docs_src/python_types/tutorial009_py39.py!}
253-
```
254-
255-
Using `Optional[str]` instead of just `str` will let the editor help you detect errors where you could be assuming that a value is always a `str`, when it could actually be `None` too.
256-
257-
`Optional[Something]` is actually a shortcut for `Union[Something, None]`, they are equivalent.
258-
259-
This also means that in Python 3.10, you can use `Something | None`:
260-
261240
//// tab | Python 3.10+
262241

263242
```Python hl_lines="1"
@@ -266,96 +245,7 @@ This also means that in Python 3.10, you can use `Something | None`:
266245

267246
////
268247

269-
//// tab | Python 3.9+
270-
271-
```Python hl_lines="1 4"
272-
{!> ../../docs_src/python_types/tutorial009_py39.py!}
273-
```
274-
275-
////
276-
277-
//// tab | Python 3.9+ alternative
278-
279-
```Python hl_lines="1 4"
280-
{!> ../../docs_src/python_types/tutorial009b_py39.py!}
281-
```
282-
283-
////
284-
285-
#### Using `Union` or `Optional` { #using-union-or-optional }
286-
287-
If you are using a Python version below 3.10, here's a tip from my very **subjective** point of view:
288-
289-
* 🚨 Avoid using `Optional[SomeType]`
290-
* Instead ✨ **use `Union[SomeType, None]`** ✨.
291-
292-
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.
293-
294-
I think `Union[SomeType, None]` is more explicit about what it means.
295-
296-
It's just about the words and names. But those words can affect how you and your teammates think about the code.
297-
298-
As an example, let's take this function:
299-
300-
{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
301-
302-
The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter:
303-
304-
```Python
305-
say_hi() # Oh, no, this throws an error! 😱
306-
```
307-
308-
The `name` parameter is **still required** (not *optional*) because it doesn't have a default value. Still, `name` accepts `None` as the value:
309-
310-
```Python
311-
say_hi(name=None) # This works, None is valid 🎉
312-
```
313-
314-
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:
315-
316-
{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
317-
318-
And then you won't have to worry about names like `Optional` and `Union`. 😎
319-
320-
#### Generic types { #generic-types }
321-
322-
These types that take type parameters in square brackets are called **Generic types** or **Generics**, for example:
323-
324-
//// tab | Python 3.10+
325-
326-
You can use the same builtin types as generics (with square brackets and types inside):
327-
328-
* `list`
329-
* `tuple`
330-
* `set`
331-
* `dict`
332-
333-
And the same as with previous Python versions, from the `typing` module:
334-
335-
* `Union`
336-
* `Optional`
337-
* ...and others.
338-
339-
In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the <dfn title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</dfn> to declare unions of types, that's a lot better and simpler.
340-
341-
////
342-
343-
//// tab | Python 3.9+
344-
345-
You can use the same builtin types as generics (with square brackets and types inside):
346-
347-
* `list`
348-
* `tuple`
349-
* `set`
350-
* `dict`
351-
352-
And generics from the `typing` module:
353-
354-
* `Union`
355-
* `Optional`
356-
* ...and others.
357-
358-
////
248+
Using `str | None` instead of just `str` will let the editor help you detect errors where you could be assuming that a value is always a `str`, when it could actually be `None` too.
359249

360250
### Classes as types { #classes-as-types }
361251

@@ -403,17 +293,11 @@ To learn more about <a href="https://docs.pydantic.dev/" class="external-link" t
403293

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

406-
/// tip
407-
408-
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://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">Required Optional fields</a>.
409-
410-
///
411-
412296
## Type Hints with Metadata Annotations { #type-hints-with-metadata-annotations }
413297

414298
Python also has a feature that allows putting **additional <dfn title="Data about the data, in this case, information about the type, e.g. a description.">metadata</dfn>** in these type hints using `Annotated`.
415299

416-
Since Python 3.9, `Annotated` is a part of the standard library, so you can import it from `typing`.
300+
You can import `Annotated` from `typing`.
417301

418302
{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
419303

docs/en/docs/tutorial/body-multiple-params.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,6 @@ As, by default, singular values are interpreted as query parameters, you don't h
106106
q: str | None = None
107107
```
108108

109-
Or in Python 3.9:
110-
111-
```Python
112-
q: Union[str, None] = None
113-
```
114-
115-
116109
For example:
117110

118111
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *}

docs/en/docs/tutorial/body.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ The function parameters will be recognized as follows:
155155

156156
FastAPI will know that the value of `q` is not required because of the default value `= None`.
157157

158-
The `str | None` (Python 3.10+) or `Union` in `Union[str, None]` (Python 3.9+) is not used by FastAPI to determine that the value is not required, it will know it's not required because it has a default value of `= None`.
158+
The `str | None` is not used by FastAPI to determine that the value is not required, it will know it's not required because it has a default value of `= None`.
159159

160160
But adding the type annotations will allow your editor to give you better support and detect errors.
161161

0 commit comments

Comments
 (0)