diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
index eba5fc57e..ccf964486 100644
--- a/.github/workflows/build-docs.yml
+++ b/.github/workflows/build-docs.yml
@@ -20,7 +20,7 @@ jobs:
id: cache
with:
path: ${{ env.pythonLocation }}
- key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-docs
+ key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-docs-v2
- name: Install Flit
if: steps.cache.outputs.cache-hit != 'true'
run: python3.7 -m pip install flit
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7ab0f1230..21ea7c1a8 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: [3.6, 3.7, 3.8, 3.9]
+ python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
fail-fast: false
steps:
diff --git a/README.md b/README.md
index 53de38bd9..c9c69d3e8 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,7 @@ The key features are:
+
diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml
index 72ca92a96..66220f63e 100644
--- a/docs/az/mkdocs.yml
+++ b/docs/az/mkdocs.yml
@@ -29,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -70,8 +67,12 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format ''
-- pymdownx.tabbed
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
diff --git a/docs/de/mkdocs.yml b/docs/de/mkdocs.yml
index 621305d20..360fa8c4a 100644
--- a/docs/de/mkdocs.yml
+++ b/docs/de/mkdocs.yml
@@ -29,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -71,8 +68,12 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format ''
-- pymdownx.tabbed
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml
index 8f1710d33..ebbe446ee 100644
--- a/docs/en/data/people.yml
+++ b/docs/en/data/people.yml
@@ -1,20 +1,20 @@
maintainers:
- login: tiangolo
- answers: 1230
- prs: 260
+ answers: 1237
+ prs: 280
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=5cad72c846b7aba2e960546af490edc7375dafc4&v=4
url: https://github.com/tiangolo
experts:
- login: Kludex
- count: 299
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
+ count: 319
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
url: https://github.com/Kludex
- login: dmontagu
count: 262
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
url: https://github.com/dmontagu
- login: ycd
- count: 219
+ count: 221
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
url: https://github.com/ycd
- login: Mause
@@ -30,29 +30,37 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4
url: https://github.com/phy25
- login: ArcLightSlavik
- count: 68
+ count: 71
avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4
url: https://github.com/ArcLightSlavik
+- login: raphaelauv
+ count: 68
+ avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
+ url: https://github.com/raphaelauv
- login: falkben
- count: 57
+ count: 58
avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4
url: https://github.com/falkben
- login: sm-Fifteen
count: 48
avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4
url: https://github.com/sm-Fifteen
-- login: raphaelauv
- count: 48
- avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
- url: https://github.com/raphaelauv
+- login: insomnes
+ count: 46
+ avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
+ url: https://github.com/insomnes
+- login: Dustyposa
+ count: 42
+ avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
+ url: https://github.com/Dustyposa
- login: includeamin
count: 38
avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4
url: https://github.com/includeamin
-- login: Dustyposa
- count: 38
- avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
- url: https://github.com/Dustyposa
+- login: STeveShary
+ count: 37
+ avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
+ url: https://github.com/STeveShary
- login: prostomarkeloff
count: 33
avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4
@@ -61,10 +69,10 @@ experts:
count: 31
avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4
url: https://github.com/krishnardt
-- login: insomnes
+- login: adriangb
count: 30
- avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
- url: https://github.com/insomnes
+ avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4
+ url: https://github.com/adriangb
- login: wshayes
count: 29
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
@@ -73,25 +81,29 @@ experts:
count: 29
avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4
url: https://github.com/frankie567
+- login: chbndrhnns
+ count: 25
+ avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
+ url: https://github.com/chbndrhnns
+- login: ghandic
+ count: 25
+ avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
+ url: https://github.com/ghandic
- login: dbanty
count: 25
avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4
url: https://github.com/dbanty
-- login: chbndrhnns
- count: 24
- avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
- url: https://github.com/chbndrhnns
+- login: panla
+ count: 25
+ avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
+ url: https://github.com/panla
- login: SirTelemak
count: 24
avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4
url: https://github.com/SirTelemak
-- login: STeveShary
- count: 24
- avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
- url: https://github.com/STeveShary
- login: acnebs
count: 22
- avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=bfd127b3e6200f4d00afd714f0fc95c2512df19b&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=c27e50269f1ef8ea950cc6f0268c8ec5cebbe9c9&v=4
url: https://github.com/acnebs
- login: nsidnev
count: 22
@@ -105,18 +117,10 @@ experts:
count: 19
avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4
url: https://github.com/retnikt
-- login: ghandic
- count: 18
- avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
- url: https://github.com/ghandic
- login: Hultner
count: 18
avatarUrl: https://avatars.githubusercontent.com/u/2669034?u=115e53df959309898ad8dc9443fbb35fee71df07&v=4
url: https://github.com/Hultner
-- login: panla
- count: 18
- avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
- url: https://github.com/panla
- login: jorgerpo
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/12537771?u=7444d20019198e34911082780cc7ad73f2b97cb3&v=4
@@ -125,26 +129,42 @@ experts:
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4
url: https://github.com/nkhitrov
-- login: adriangb
- count: 17
- avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4
- url: https://github.com/adriangb
+- login: acidjunk
+ count: 16
+ avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
+ url: https://github.com/acidjunk
- login: waynerv
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
url: https://github.com/waynerv
+- login: dstlny
+ count: 16
+ avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4
+ url: https://github.com/dstlny
+- login: jgould22
+ count: 14
+ avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
+ url: https://github.com/jgould22
+- login: harunyasar
+ count: 14
+ avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4
+ url: https://github.com/harunyasar
- login: haizaar
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/58201?u=4f1f9843d69433ca0d380d95146cfe119e5fdac4&v=4
url: https://github.com/haizaar
-- login: acidjunk
- count: 13
- avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
- url: https://github.com/acidjunk
+- login: hellocoldworld
+ count: 12
+ avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4
+ url: https://github.com/hellocoldworld
- login: David-Lor
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4
url: https://github.com/David-Lor
+- login: lowercase00
+ count: 11
+ avatarUrl: https://avatars.githubusercontent.com/u/21188280?v=4
+ url: https://github.com/lowercase00
- login: zamiramir
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/40475662?u=e58ef61034e8d0d6a312cc956fb09b9c3332b449&v=4
@@ -153,10 +173,6 @@ experts:
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4
url: https://github.com/juntatalor
-- login: dstlny
- count: 11
- avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4
- url: https://github.com/dstlny
- login: valentin994
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/42819267?u=fdeeaa9242a59b243f8603496b00994f6951d5a2&v=4
@@ -169,47 +185,47 @@ experts:
count: 10
avatarUrl: https://avatars.githubusercontent.com/u/20441825?u=ee1e59446b98f8ec2363caeda4c17164d0d9cc7d&v=4
url: https://github.com/stefanondisponibile
-- login: hellocoldworld
+- login: oligond
count: 10
- avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4
- url: https://github.com/hellocoldworld
+ avatarUrl: https://avatars.githubusercontent.com/u/2858306?u=1bb1182a5944e93624b7fb26585f22c8f7a9d76e&v=4
+ url: https://github.com/oligond
last_month_active:
-- login: Kludex
- count: 13
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
- url: https://github.com/Kludex
-- login: ghandic
- count: 12
- avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
- url: https://github.com/ghandic
+- login: harunyasar
+ count: 10
+ avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4
+ url: https://github.com/harunyasar
+- login: jgould22
+ count: 10
+ avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
+ url: https://github.com/jgould22
+- login: rafsaf
+ count: 9
+ avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=be9f06b8ced2d2b677297decc781fa8ce4f7ddbd&v=4
+ url: https://github.com/rafsaf
- login: STeveShary
- count: 8
+ count: 5
avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
url: https://github.com/STeveShary
-- login: Honda-a
+- login: ahnaf-zamil
count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/22759187?u=f45bd5fb17b4dca331529b8e9e5eab6122b84b8b&v=4
- url: https://github.com/Honda-a
-- login: frankie567
+ avatarUrl: https://avatars.githubusercontent.com/u/57180217?u=849128b146771ace47beca5b5ff68eb82905dd6d&v=4
+ url: https://github.com/ahnaf-zamil
+- login: lucastosetto
count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4
- url: https://github.com/frankie567
-- login: panla
+ avatarUrl: https://avatars.githubusercontent.com/u/89307132?u=56326696423df7126c9e7c702ee58f294db69a2a&v=4
+ url: https://github.com/lucastosetto
+- login: blokje
count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
- url: https://github.com/panla
-- login: dstlny
+ avatarUrl: https://avatars.githubusercontent.com/u/851418?v=4
+ url: https://github.com/blokje
+- login: MatthijsKok
count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4
- url: https://github.com/dstlny
-- login: trevorwang
+ avatarUrl: https://avatars.githubusercontent.com/u/7658129?u=1243e32d57e13abc45e3f5235ed5b9197e0d2b41&v=4
+ url: https://github.com/MatthijsKok
+- login: Kludex
count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/121966?v=4
- url: https://github.com/trevorwang
-- login: klaa97
- count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/39653693?v=4
- url: https://github.com/klaa97
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
+ url: https://github.com/Kludex
top_contributors:
- login: waynerv
count: 25
@@ -223,6 +239,10 @@ top_contributors:
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
url: https://github.com/dmontagu
+- login: jaystone776
+ count: 15
+ avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
+ url: https://github.com/jaystone776
- login: euri10
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4
@@ -231,10 +251,10 @@ top_contributors:
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
url: https://github.com/mariacamilagl
-- login: jaystone776
- count: 11
- avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
- url: https://github.com/jaystone776
+- login: Smlep
+ count: 9
+ avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4
+ url: https://github.com/Smlep
- login: Serrones
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4
@@ -245,11 +265,11 @@ top_contributors:
url: https://github.com/RunningIkkyu
- login: Kludex
count: 7
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
url: https://github.com/Kludex
- login: hard-coders
- count: 6
- avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=f2d3d2038c55d86d7f9348f4e6c5e30191e4ee8b&v=4
+ count: 7
+ avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
url: https://github.com/hard-coders
- login: wshayes
count: 5
@@ -263,10 +283,6 @@ top_contributors:
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4
url: https://github.com/Attsun1031
-- login: Smlep
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4
- url: https://github.com/Smlep
- login: jekirl
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/2546697?u=a027452387d85bd4a14834e19d716c99255fb3b7&v=4
@@ -275,14 +291,22 @@ top_contributors:
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/805749?v=4
url: https://github.com/jfunez
+- login: ycd
+ count: 4
+ avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
+ url: https://github.com/ycd
- login: komtaki
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4
url: https://github.com/komtaki
+- login: NinaHwang
+ count: 4
+ avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4
+ url: https://github.com/NinaHwang
top_reviewers:
- login: Kludex
- count: 90
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
+ count: 93
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
url: https://github.com/Kludex
- login: waynerv
count: 47
@@ -297,41 +321,61 @@ top_reviewers:
avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
url: https://github.com/tokusumi
- login: ycd
- count: 43
+ count: 45
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
url: https://github.com/ycd
- login: AdrianDeAnda
- count: 32
+ count: 33
avatarUrl: https://avatars.githubusercontent.com/u/1024932?u=bb7f8a0d6c9de4e9d0320a9f271210206e202250&v=4
url: https://github.com/AdrianDeAnda
- login: ArcLightSlavik
- count: 27
+ count: 31
avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4
url: https://github.com/ArcLightSlavik
+- login: cikay
+ count: 24
+ avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4
+ url: https://github.com/cikay
- login: dmontagu
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
url: https://github.com/dmontagu
+- login: cassiobotaro
+ count: 23
+ avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4
+ url: https://github.com/cassiobotaro
- login: komtaki
count: 21
avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4
url: https://github.com/komtaki
-- login: cassiobotaro
+- login: hard-coders
count: 19
- avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4
- url: https://github.com/cassiobotaro
+ avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
+ url: https://github.com/hard-coders
+- login: 0417taehyun
+ count: 19
+ avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4
+ url: https://github.com/0417taehyun
- login: yanever
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/21978760?v=4
url: https://github.com/yanever
+- login: lsglucas
+ count: 16
+ avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
+ url: https://github.com/lsglucas
- login: SwftAlpc
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4
url: https://github.com/SwftAlpc
-- login: hard-coders
+- login: Smlep
count: 16
- avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=f2d3d2038c55d86d7f9348f4e6c5e30191e4ee8b&v=4
- url: https://github.com/hard-coders
+ avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4
+ url: https://github.com/Smlep
+- login: DevDae
+ count: 16
+ avatarUrl: https://avatars.githubusercontent.com/u/87962045?u=08e10fa516e844934f4b3fc7c38b33c61697e4a1&v=4
+ url: https://github.com/DevDae
- login: pedabraham
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/16860088?u=abf922a7b920bf8fdb7867d8b43e091f1e796178&v=4
@@ -340,22 +384,18 @@ top_reviewers:
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4
url: https://github.com/delhi09
-- login: Smlep
- count: 15
- avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4
- url: https://github.com/Smlep
-- login: lsglucas
- count: 14
- avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
- url: https://github.com/lsglucas
- login: rjNemo
- count: 13
+ count: 14
avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4
url: https://github.com/rjNemo
- login: RunningIkkyu
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=706e1ee3f248245f2d68b976d149d06fd5a2010d&v=4
url: https://github.com/RunningIkkyu
+- login: yezz123
+ count: 12
+ avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4
+ url: https://github.com/yezz123
- login: sh0nk
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4
@@ -372,18 +412,38 @@ top_reviewers:
count: 10
avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4
url: https://github.com/maoyibo
+- login: graingert
+ count: 9
+ avatarUrl: https://avatars.githubusercontent.com/u/413772?v=4
+ url: https://github.com/graingert
- login: PandaHun
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/13096845?u=646eba44db720e37d0dbe8e98e77ab534ea78a20&v=4
url: https://github.com/PandaHun
+- login: kty4119
+ count: 9
+ avatarUrl: https://avatars.githubusercontent.com/u/49435654?v=4
+ url: https://github.com/kty4119
+- login: zy7y
+ count: 9
+ avatarUrl: https://avatars.githubusercontent.com/u/67154681?u=5d634834cc514028ea3f9115f7030b99a1f4d5a4&v=4
+ url: https://github.com/zy7y
- login: bezaca
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4
url: https://github.com/bezaca
+- login: solomein-sv
+ count: 9
+ avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=46acfb4aeefb1d7b9fdc5a8cbd9eb8744683c47a&v=4
+ url: https://github.com/solomein-sv
- login: blt232018
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/43393471?u=172b0e0391db1aa6c1706498d6dfcb003c8a4857&v=4
url: https://github.com/blt232018
+- login: ComicShrimp
+ count: 8
+ avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=b3e4d9a14d9a65d429ce62c566aef73178b7111d&v=4
+ url: https://github.com/ComicShrimp
- login: Serrones
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4
@@ -396,10 +456,10 @@ top_reviewers:
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
url: https://github.com/raphaelauv
-- login: graingert
+- login: BilalAlpaslan
count: 7
- avatarUrl: https://avatars.githubusercontent.com/u/413772?v=4
- url: https://github.com/graingert
+ avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4
+ url: https://github.com/BilalAlpaslan
- login: NastasiaSaby
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/8245071?u=b3afd005f9e4bf080c219ef61a592b3a8004b764&v=4
@@ -408,71 +468,35 @@ top_reviewers:
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4
url: https://github.com/Mause
-- login: solomein-sv
+- login: krocdort
count: 7
- avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=46acfb4aeefb1d7b9fdc5a8cbd9eb8744683c47a&v=4
- url: https://github.com/solomein-sv
-- login: ComicShrimp
+ avatarUrl: https://avatars.githubusercontent.com/u/34248814?v=4
+ url: https://github.com/krocdort
+- login: dimaqq
count: 7
- avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=b3e4d9a14d9a65d429ce62c566aef73178b7111d&v=4
- url: https://github.com/ComicShrimp
+ avatarUrl: https://avatars.githubusercontent.com/u/662249?v=4
+ url: https://github.com/dimaqq
- login: jovicon
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/21287303?u=b049eac3e51a4c0473c2efe66b4d28a7d8f2b572&v=4
url: https://github.com/jovicon
-- login: BilalAlpaslan
+- login: NinaHwang
count: 6
- avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4
- url: https://github.com/BilalAlpaslan
-- login: nimctl
+ avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4
+ url: https://github.com/NinaHwang
+- login: diogoduartec
count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=e39b11d47188744ee07b2a1c7ce1a1bdf3c80760&v=4
- url: https://github.com/nimctl
-- login: juntatalor
+ avatarUrl: https://avatars.githubusercontent.com/u/31852339?u=b50fc11c531e9b77922e19edfc9e7233d4d7b92e&v=4
+ url: https://github.com/diogoduartec
+- login: n25a
count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4
- url: https://github.com/juntatalor
-- login: SnkSynthesis
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/63564282?u=0078826509dbecb2fdb543f4e881c9cd06157893&v=4
- url: https://github.com/SnkSynthesis
-- login: anthonycepeda
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=892f700c79f9732211bd5221bf16eec32356a732&v=4
- url: https://github.com/anthonycepeda
-- login: oandersonmagalhaes
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4
- url: https://github.com/oandersonmagalhaes
-- login: euri10
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4
- url: https://github.com/euri10
-- login: rkbeatss
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/23391143?u=56ab6bff50be950fa8cae5cf736f2ae66e319ff3&v=4
- url: https://github.com/rkbeatss
+ avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=eb3c95338741c78fff7d9d5d7ace9617e53eee4a&v=4
+ url: https://github.com/n25a
- login: izaguerreiro
- count: 4
+ count: 5
avatarUrl: https://avatars.githubusercontent.com/u/2241504?v=4
url: https://github.com/izaguerreiro
-- login: aviramha
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/41201924?u=6883cc4fc13a7b2e60d4deddd4be06f9c5287880&v=4
- url: https://github.com/aviramha
-- login: Cajuteq
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/26676532?u=8ee0422981810e51480855de1c0d67b6b79cd3f2&v=4
- url: https://github.com/Cajuteq
-- login: Zxilly
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/31370133?v=4
- url: https://github.com/Zxilly
-- login: dukkee
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/36825394?u=ccfd86e6a4f2d093dad6f7544cc875af67fa2df8&v=4
- url: https://github.com/dukkee
-- login: Bluenix2
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/38372706?u=c9d28aff15958d6ebf1971148bfb3154ff943c4f&v=4
- url: https://github.com/Bluenix2
+- login: israteneda
+ count: 5
+ avatarUrl: https://avatars.githubusercontent.com/u/20668624?u=d7b2961d330aca65fbce5bdb26a0800a3d23ed2d&v=4
+ url: https://github.com/israteneda
diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml
index baa2e440d..b98e68b65 100644
--- a/docs/en/data/sponsors.yml
+++ b/docs/en/data/sponsors.yml
@@ -5,6 +5,9 @@ gold:
- url: https://cryptapi.io/
title: "CryptAPI: Your easy to use, secure and privacy oriented payment gateway."
img: https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg
+ - url: https://www.dropbase.io/careers
+ title: Dropbase - seamlessly collect, clean, and centralize data.
+ img: https://fastapi.tiangolo.com/img/sponsors/dropbase.svg
silver:
- url: https://www.deta.sh/?ref=fastapi
title: The launchpad for all your (team's) ideas
diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml
index 759748728..0c4e716d7 100644
--- a/docs/en/data/sponsors_badge.yml
+++ b/docs/en/data/sponsors_badge.yml
@@ -7,3 +7,4 @@ logins:
- koaning
- deepset-ai
- cryptapi
+ - DropbaseHQ
diff --git a/docs/en/docs/advanced/extending-openapi.md b/docs/en/docs/advanced/extending-openapi.md
index d8d280ba6..d1b14bc00 100644
--- a/docs/en/docs/advanced/extending-openapi.md
+++ b/docs/en/docs/advanced/extending-openapi.md
@@ -233,3 +233,82 @@ Now, to be able to test that everything works, create a *path operation*:
Now, you should be able to disconnect your WiFi, go to your docs at http://127.0.0.1:8000/docs, and reload the page.
And even without Internet, you would be able to see the docs for your API and interact with it.
+
+## Configuring Swagger UI
+
+You can configure some extra Swagger UI parameters.
+
+To configure them, pass the `swagger_ui_parameters` argument when creating the `FastAPI()` app object or to the `get_swagger_ui_html()` function.
+
+`swagger_ui_parameters` receives a dictionary with the configurations passed to Swagger UI directly.
+
+FastAPI converts the configurations to **JSON** to make them compatible with JavaScript, as that's what Swagger UI needs.
+
+### Disable Syntax Highlighting
+
+For example, you could disable syntax highlighting in Swagger UI.
+
+Without changing the settings, syntax highlighting is enabled by default:
+
+
+
+But you can disable it by setting `syntaxHighlight` to `False`:
+
+```Python hl_lines="3"
+{!../../../docs_src/extending_openapi/tutorial003.py!}
+```
+
+...and then Swagger UI won't show the syntax highlighting anymore:
+
+
+
+### Change the Theme
+
+The same way you could set the syntax highlighting theme with the key `"syntaxHighlight.theme"` (notice that it has a dot in the middle):
+
+```Python hl_lines="3"
+{!../../../docs_src/extending_openapi/tutorial004.py!}
+```
+
+That configuration would change the syntax highlighting color theme:
+
+
+
+### Change Default Swagger UI Parameters
+
+FastAPI includes some default configuration parameters appropriate for most of the use cases.
+
+It includes these default configurations:
+
+```Python
+{!../../../fastapi/openapi/docs.py[ln:7-13]!}
+```
+
+You can override any of them by setting a different value in the argument `swagger_ui_parameters`.
+
+For example, to disable `deepLinking` you could pass these settings to `swagger_ui_parameters`:
+
+```Python hl_lines="3"
+{!../../../docs_src/extending_openapi/tutorial005.py!}
+```
+
+### Other Swagger UI Parameters
+
+To see all the other possible configurations you can use, read the official docs for Swagger UI parameters.
+
+### JavaScript-only settings
+
+Swagger UI also allows other configurations to be **JavaScript-only** objects (for example, JavaScript functions).
+
+FastAPI also includes these JavaScript-only `presets` settings:
+
+```JavaScript
+presets: [
+ SwaggerUIBundle.presets.apis,
+ SwaggerUIBundle.SwaggerUIStandalonePreset
+]
+```
+
+These are **JavaScript** objects, not strings, so you can't pass them from Python code directly.
+
+If you need to use JavaScript-only configurations like those, you can use one of the methods above. Override all the Swagger UI *path operation* and manually write any JavaScript you need.
diff --git a/docs/en/docs/img/sponsors/dropbase-banner.svg b/docs/en/docs/img/sponsors/dropbase-banner.svg
new file mode 100644
index 000000000..d65abf1d9
--- /dev/null
+++ b/docs/en/docs/img/sponsors/dropbase-banner.svg
@@ -0,0 +1,117 @@
+
+
diff --git a/docs/en/docs/img/sponsors/dropbase.svg b/docs/en/docs/img/sponsors/dropbase.svg
new file mode 100644
index 000000000..d0defb4df
--- /dev/null
+++ b/docs/en/docs/img/sponsors/dropbase.svg
@@ -0,0 +1,124 @@
+
+
diff --git a/docs/en/docs/img/tutorial/extending-openapi/image02.png b/docs/en/docs/img/tutorial/extending-openapi/image02.png
new file mode 100644
index 000000000..91453fb56
Binary files /dev/null and b/docs/en/docs/img/tutorial/extending-openapi/image02.png differ
diff --git a/docs/en/docs/img/tutorial/extending-openapi/image03.png b/docs/en/docs/img/tutorial/extending-openapi/image03.png
new file mode 100644
index 000000000..e45a77d51
Binary files /dev/null and b/docs/en/docs/img/tutorial/extending-openapi/image03.png differ
diff --git a/docs/en/docs/img/tutorial/extending-openapi/image04.png b/docs/en/docs/img/tutorial/extending-openapi/image04.png
new file mode 100644
index 000000000..394d2bb43
Binary files /dev/null and b/docs/en/docs/img/tutorial/extending-openapi/image04.png differ
diff --git a/docs/en/docs/python-types.md b/docs/en/docs/python-types.md
index 9bbf955b9..fe56dadec 100644
--- a/docs/en/docs/python-types.md
+++ b/docs/en/docs/python-types.md
@@ -148,37 +148,62 @@ You can use, for example:
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.
-To declare those types and the internal types, you can use the standard Python module `typing`.
+These types that have internal types are called "**generic**" types. And it's possible to declare them, even with their internal types.
-It exists specifically to support these type hints.
+To declare those types and the internal types, you can use the standard Python module `typing`. It exists specifically to support these type hints.
-#### `List`
+#### Newer versions of Python
+
+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.
+
+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.
+
+If you can chose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity. See some examples below.
+
+#### List
For example, let's define a variable to be a `list` of `str`.
-From `typing`, import `List` (with a capital `L`):
+=== "Python 3.6 and above"
-```Python hl_lines="1"
-{!../../../docs_src/python_types/tutorial006.py!}
-```
+ From `typing`, import `List` (with a capital `L`):
-Declare the variable, with the same colon (`:`) syntax.
+ ``` Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial006.py!}
+ ```
-As the type, put the `List`.
+ Declare the variable, with the same colon (`:`) syntax.
-As the list is a type that contains some internal types, you put them in square brackets:
+ As the type, put the `List` that you imported from `typing`.
-```Python hl_lines="4"
-{!../../../docs_src/python_types/tutorial006.py!}
-```
+ As the list is a type that contains some internal types, you put them in square brackets:
-!!! tip
+ ```Python hl_lines="4"
+ {!> ../../../docs_src/python_types/tutorial006.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ Declare the variable, with the same colon (`:`) syntax.
+
+ As the type, put `list`.
+
+ As the list is a type that contains some internal types, you put them in square brackets:
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial006_py39.py!}
+ ```
+
+!!! info
Those internal types in the square brackets are called "type parameters".
- In this case, `str` is the type parameter passed to `List`.
+ In this case, `str` is the type parameter passed to `List` (or `list` in Python 3.9 and above).
That means: "the variable `items` is a `list`, and each of the items in this list is a `str`".
+!!! tip
+ If you use Python 3.9 or above, you don't have to import `List` from `typing`, you can use the same regular `list` type instead.
+
By doing that, your editor can provide support even while processing items from the list:
@@ -189,20 +214,28 @@ Notice that the variable `item` is one of the elements in the list `items`.
And still, the editor knows it is a `str`, and provides support for that.
-#### `Tuple` and `Set`
+#### Tuple and Set
You would do the same to declare `tuple`s and `set`s:
-```Python hl_lines="1 4"
-{!../../../docs_src/python_types/tutorial007.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial007.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial007_py39.py!}
+ ```
This means:
* The variable `items_t` is a `tuple` with 3 items, an `int`, another `int`, and a `str`.
* The variable `items_s` is a `set`, and each of its items is of type `bytes`.
-#### `Dict`
+#### Dict
To define a `dict`, you pass 2 type parameters, separated by commas.
@@ -210,9 +243,17 @@ The first type parameter is for the keys of the `dict`.
The second type parameter is for the values of the `dict`:
-```Python hl_lines="1 4"
-{!../../../docs_src/python_types/tutorial008.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial008.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial008_py39.py!}
+ ```
This means:
@@ -220,9 +261,33 @@ This means:
* The keys of this `dict` are of type `str` (let's say, the name of each item).
* The values of this `dict` are of type `float` (let's say, the price of each item).
-#### `Optional`
+#### Union
-You can also use `Optional` to declare that a variable has a type, like `str`, but that it is "optional", which means that it could also be `None`:
+You can declare that a variable can be any of **several types**, for example, an `int` or a `str`.
+
+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.
+
+In Python 3.10 there's also an **alternative syntax** were you can put the possible types separated by a vertical bar (`|`).
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial008b.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial008b_py310.py!}
+ ```
+
+In both cases this means that `item` could be an `int` or a `str`.
+
+#### Possibly `None`
+
+You can declare that a value could have a type, like `str`, but that it could also be `None`.
+
+In Python 3.6 and above (including Python 3.10) you can declare it by importing and using `Optional` from the `typing` module.
```Python hl_lines="1 4"
{!../../../docs_src/python_types/tutorial009.py!}
@@ -230,18 +295,73 @@ You can also use `Optional` to declare that a variable has a type, like `str`, b
Using `Optional[str]` instead of just `str` will let the editor help you detecting errors where you could be assuming that a value is always a `str`, when it could actually be `None` too.
+`Optional[Something]` is actually a shortcut for `Union[Something, None]`, they are equivalent.
+
+This also means that in Python 3.10, you can use `Something | None`:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial009.py!}
+ ```
+
+=== "Python 3.6 and above - alternative"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial009b.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial009_py310.py!}
+ ```
+
#### Generic types
-These types that take type parameters in square brackets, like:
+These types that take type parameters in square brackets are called **Generic types** or **Generics**, for example:
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Optional`
-* ...and others.
+=== "Python 3.6 and above"
-are called **Generic types** or **Generics**.
+ * `List`
+ * `Tuple`
+ * `Set`
+ * `Dict`
+ * `Union`
+ * `Optional`
+ * ...and others.
+
+=== "Python 3.9 and above"
+
+ You can use the same builtin types as generics (with square brakets and types inside):
+
+ * `list`
+ * `tuple`
+ * `set`
+ * `dict`
+
+ And the same as with Python 3.6, from the `typing` module:
+
+ * `Union`
+ * `Optional`
+ * ...and others.
+
+=== "Python 3.10 and above"
+
+ You can use the same builtin types as generics (with square brakets and types inside):
+
+ * `list`
+ * `tuple`
+ * `set`
+ * `dict`
+
+ And the same as with Python 3.6, from the `typing` module:
+
+ * `Union`
+ * `Optional` (the same as with Python 3.6)
+ * ...and others.
+
+ In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the vertical bar (`|`) to declare unions of types.
### Classes as types
@@ -275,11 +395,25 @@ Then you create an instance of that class with some values and it will validate
And you get all the editor support with that resulting object.
-Taken from the official Pydantic docs:
+An example from the official Pydantic docs:
-```Python
-{!../../../docs_src/python_types/tutorial011.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python
+ {!> ../../../docs_src/python_types/tutorial011.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python
+ {!> ../../../docs_src/python_types/tutorial011_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python
+ {!> ../../../docs_src/python_types/tutorial011_py310.py!}
+ ```
!!! info
To learn more about Pydantic, check its docs.
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index ca7553855..6d5ee8ea1 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -2,6 +2,79 @@
## Latest Changes
+* 👥 Update FastAPI People. PR [#4502](https://github.com/tiangolo/fastapi/pull/4502) by [@github-actions[bot]](https://github.com/apps/github-actions).
+## 0.73.0
+
+### Features
+
+* ✨ Add support for declaring `UploadFile` parameters without explicit `File()`. PR [#4469](https://github.com/tiangolo/fastapi/pull/4469) by [@tiangolo](https://github.com/tiangolo). New docs: [Request Files - File Parameters with UploadFile](https://fastapi.tiangolo.com/tutorial/request-files/#file-parameters-with-uploadfile).
+* ✨ Add support for tags with Enums. PR [#4468](https://github.com/tiangolo/fastapi/pull/4468) by [@tiangolo](https://github.com/tiangolo). New docs: [Path Operation Configuration - Tags with Enums](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags-with-enums).
+* ✨ Allow hiding from OpenAPI (and Swagger UI) `Query`, `Cookie`, `Header`, and `Path` parameters. PR [#3144](https://github.com/tiangolo/fastapi/pull/3144) by [@astraldawn](https://github.com/astraldawn). New docs: [Query Parameters and String Validations - Exclude from OpenAPI](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi).
+
+### Docs
+
+* 📝 Tweak and improve docs for Request Files. PR [#4470](https://github.com/tiangolo/fastapi/pull/4470) by [@tiangolo](https://github.com/tiangolo).
+
+### Fixes
+
+* 🐛 Fix bug preventing to use OpenAPI when using tuples. PR [#3874](https://github.com/tiangolo/fastapi/pull/3874) by [@victorbenichoux](https://github.com/victorbenichoux).
+* 🐛 Prefer custom encoder over defaults if specified in `jsonable_encoder`. PR [#2061](https://github.com/tiangolo/fastapi/pull/2061) by [@viveksunder](https://github.com/viveksunder).
+ * 💚 Duplicate PR to trigger CI. PR [#4467](https://github.com/tiangolo/fastapi/pull/4467) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* 🐛 Fix docs dependencies cache, to get the latest Material for MkDocs. PR [#4466](https://github.com/tiangolo/fastapi/pull/4466) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add sponsor Dropbase. PR [#4465](https://github.com/tiangolo/fastapi/pull/4465) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.72.0
+
+### Features
+
+* ✨ Enable configuring Swagger UI parameters. Original PR [#2568](https://github.com/tiangolo/fastapi/pull/2568) by [@jmriebold](https://github.com/jmriebold). Here are the new docs: [Configuring Swagger UI](https://fastapi.tiangolo.com/advanced/extending-openapi/#configuring-swagger-ui).
+
+### Docs
+
+* 📝 Update Python Types docs, add missing 3.6 / 3.9 example. PR [#4434](https://github.com/tiangolo/fastapi/pull/4434) by [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* 🌐 Update Chinese translation for `docs/help-fastapi.md`. PR [#3847](https://github.com/tiangolo/fastapi/pull/3847) by [@jaystone776](https://github.com/jaystone776).
+* 🌐 Fix Korean translation for `docs/ko/docs/index.md`. PR [#4195](https://github.com/tiangolo/fastapi/pull/4195) by [@kty4119](https://github.com/kty4119).
+* 🌐 Add Polish translation for `docs/pl/docs/index.md`. PR [#4245](https://github.com/tiangolo/fastapi/pull/4245) by [@MicroPanda123](https://github.com/MicroPanda123).
+* 🌐 Add Chinese translation for `docs\tutorial\path-operation-configuration.md`. PR [#3312](https://github.com/tiangolo/fastapi/pull/3312) by [@jaystone776](https://github.com/jaystone776).
+
+### Internal
+
+* 🔧 Enable MkDocs Material Insiders' `content.tabs.link`. PR [#4399](https://github.com/tiangolo/fastapi/pull/4399) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.71.0
+
+### Features
+
+* ✨ Add docs and tests for Python 3.9 and Python 3.10. PR [#3712](https://github.com/tiangolo/fastapi/pull/3712) by [@tiangolo](https://github.com/tiangolo).
+ * You can start with [Python Types Intro](https://fastapi.tiangolo.com/python-types/), it explains what changes between different Python versions, in Python 3.9 and in Python 3.10.
+ * All the FastAPI docs are updated. Each code example in the docs that could use different syntax in Python 3.9 or Python 3.10 now has all the alternatives in tabs.
+* ⬆️ Upgrade Starlette to 0.17.1. PR [#4145](https://github.com/tiangolo/fastapi/pull/4145) by [@simondale00](https://github.com/simondale00).
+
+### Internal
+
+* 👥 Update FastAPI People. PR [#4354](https://github.com/tiangolo/fastapi/pull/4354) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* 🔧 Add FastAPI Trove Classifier for PyPI as now there's one 🤷😁. PR [#4386](https://github.com/tiangolo/fastapi/pull/4386) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Upgrade MkDocs Material and configs. PR [#4385](https://github.com/tiangolo/fastapi/pull/4385) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.70.1
+
+There's nothing interesting in this particular FastAPI release. It is mainly to enable/unblock the release of the next version of Pydantic that comes packed with features and improvements. 🤩
+
+### Fixes
+
+* 🐛 Fix JSON Schema for dataclasses, supporting the fixes in Pydantic 1.9. PR [#4272](https://github.com/tiangolo/fastapi/pull/4272) by [@PrettyWood](https://github.com/PrettyWood).
+
+### Translations
+
+* 🌐 Add Korean translation for `docs/tutorial/request-forms-and-files.md`. PR [#3744](https://github.com/tiangolo/fastapi/pull/3744) by [@NinaHwang](https://github.com/NinaHwang).
+* 🌐 Add Korean translation for `docs/tutorial/request-files.md`. PR [#3743](https://github.com/tiangolo/fastapi/pull/3743) by [@NinaHwang](https://github.com/NinaHwang).
+* 🌐 Add portuguese translation for `docs/tutorial/query-params-str-validations.md`. PR [#3965](https://github.com/tiangolo/fastapi/pull/3965) by [@leandrodesouzadev](https://github.com/leandrodesouzadev).
* 🌐 Add Korean translation for `docs/tutorial/response-status-code.md`. PR [#3742](https://github.com/tiangolo/fastapi/pull/3742) by [@NinaHwang](https://github.com/NinaHwang).
* 🌐 Add Korean translation for Tutorial - JSON Compatible Encoder. PR [#3152](https://github.com/tiangolo/fastapi/pull/3152) by [@NEONKID](https://github.com/NEONKID).
* 🌐 Add Korean translation for Tutorial - Path Parameters and Numeric Validations. PR [#2432](https://github.com/tiangolo/fastapi/pull/2432) by [@hard-coders](https://github.com/hard-coders).
@@ -14,6 +87,10 @@
* 🌐 Add French translation for `docs/tutorial/query-params.md`. PR [#3556](https://github.com/tiangolo/fastapi/pull/3556) by [@Smlep](https://github.com/Smlep).
* 🌐 Add Turkish translation for `docs/python-types.md`. PR [#3926](https://github.com/tiangolo/fastapi/pull/3926) by [@BilalAlpaslan](https://github.com/BilalAlpaslan).
+### Internal
+
+* 👥 Update FastAPI People. PR [#4274](https://github.com/tiangolo/fastapi/pull/4274) by [@github-actions[bot]](https://github.com/apps/github-actions).
+
## 0.70.0
This release just upgrades Starlette to the latest version, `0.16.0`, which includes several bug fixes and some small breaking changes.
diff --git a/docs/en/docs/tutorial/background-tasks.md b/docs/en/docs/tutorial/background-tasks.md
index 6002ce075..69aeb6712 100644
--- a/docs/en/docs/tutorial/background-tasks.md
+++ b/docs/en/docs/tutorial/background-tasks.md
@@ -57,9 +57,17 @@ Using `BackgroundTasks` also works with the dependency injection system, you can
**FastAPI** knows what to do in each case and how to re-use the same object, so that all the background tasks are merged together and are run in the background afterwards:
-```Python hl_lines="13 15 22 25"
-{!../../../docs_src/background_tasks/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="13 15 22 25"
+ {!> ../../../docs_src/background_tasks/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="11 13 20 23"
+ {!> ../../../docs_src/background_tasks/tutorial002_py310.py!}
+ ```
In this example, the messages will be written to the `log.txt` file *after* the response is sent.
diff --git a/docs/en/docs/tutorial/body-fields.md b/docs/en/docs/tutorial/body-fields.md
index 6b3fd8fb5..1f38a0c5c 100644
--- a/docs/en/docs/tutorial/body-fields.md
+++ b/docs/en/docs/tutorial/body-fields.md
@@ -6,9 +6,17 @@ The same way you can declare additional validation and metadata in *path operati
First, you have to import it:
-```Python hl_lines="4"
-{!../../../docs_src/body_fields/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="4"
+ {!> ../../../docs_src/body_fields/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="2"
+ {!> ../../../docs_src/body_fields/tutorial001_py310.py!}
+ ```
!!! warning
Notice that `Field` is imported directly from `pydantic`, not from `fastapi` as are all the rest (`Query`, `Path`, `Body`, etc).
@@ -17,9 +25,17 @@ First, you have to import it:
You can then use `Field` with model attributes:
-```Python hl_lines="11-14"
-{!../../../docs_src/body_fields/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="11-14"
+ {!> ../../../docs_src/body_fields/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="9-12"
+ {!> ../../../docs_src/body_fields/tutorial001_py310.py!}
+ ```
`Field` works the same way as `Query`, `Path` and `Body`, it has all the same parameters, etc.
diff --git a/docs/en/docs/tutorial/body-multiple-params.md b/docs/en/docs/tutorial/body-multiple-params.md
index a4484147f..13de4c8ea 100644
--- a/docs/en/docs/tutorial/body-multiple-params.md
+++ b/docs/en/docs/tutorial/body-multiple-params.md
@@ -8,9 +8,17 @@ First, of course, you can mix `Path`, `Query` and request body parameter declara
And you can also declare body parameters as optional, by setting the default to `None`:
-```Python hl_lines="19-21"
-{!../../../docs_src/body_multiple_params/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="19-21"
+ {!> ../../../docs_src/body_multiple_params/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17-19"
+ {!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!}
+ ```
!!! note
Notice that, in this case, the `item` that would be taken from the body is optional. As it has a `None` default value.
@@ -30,9 +38,17 @@ In the previous example, the *path operations* would expect a JSON body with the
But you can also declare multiple body parameters, e.g. `item` and `user`:
-```Python hl_lines="22"
-{!../../../docs_src/body_multiple_params/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="22"
+ {!> ../../../docs_src/body_multiple_params/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!}
+ ```
In this case, **FastAPI** will notice that there are more than one body parameters in the function (two parameters that are Pydantic models).
@@ -71,14 +87,20 @@ If you declare it as is, because it is a singular value, **FastAPI** will assume
But you can instruct **FastAPI** to treat it as another body key using `Body`:
+=== "Python 3.6 and above"
-```Python hl_lines="23"
-{!../../../docs_src/body_multiple_params/tutorial003.py!}
-```
+ ```Python hl_lines="23"
+ {!> ../../../docs_src/body_multiple_params/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="21"
+ {!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!}
+ ```
In this case, **FastAPI** will expect a body like:
-
```JSON
{
"item": {
@@ -107,12 +129,26 @@ As, by default, singular values are interpreted as query parameters, you don't h
q: Optional[str] = None
```
-as in:
+Or in Python 3.10 and above:
-```Python hl_lines="28"
-{!../../../docs_src/body_multiple_params/tutorial004.py!}
+```Python
+q: str | None = None
```
+For example:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="28"
+ {!> ../../../docs_src/body_multiple_params/tutorial004.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="26"
+ {!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!}
+ ```
+
!!! info
`Body` also has all the same extra validation and metadata parameters as `Query`,`Path` and others you will see later.
@@ -131,9 +167,17 @@ item: Item = Body(..., embed=True)
as in:
-```Python hl_lines="17"
-{!../../../docs_src/body_multiple_params/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/body_multiple_params/tutorial005.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="15"
+ {!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!}
+ ```
In this case **FastAPI** will expect a body like:
diff --git a/docs/en/docs/tutorial/body-nested-models.md b/docs/en/docs/tutorial/body-nested-models.md
index 5bacf1666..fa38cfc48 100644
--- a/docs/en/docs/tutorial/body-nested-models.md
+++ b/docs/en/docs/tutorial/body-nested-models.md
@@ -6,9 +6,17 @@ With **FastAPI**, you can define, validate, document, and use arbitrarily deeply
You can define an attribute to be a subtype. For example, a Python `list`:
-```Python hl_lines="14"
-{!../../../docs_src/body_nested_models/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/body_nested_models/tutorial001_py310.py!}
+ ```
This will make `tags` be a list of items. Although it doesn't declare the type of each of the items.
@@ -18,19 +26,29 @@ But Python has a specific way to declare lists with internal types, or "type par
### Import typing's `List`
-First, import `List` from standard Python's `typing` module:
+In Python 3.9 and above you can use the standard `list` to declare these type annotations as we'll see below. 💡
+
+But in Python versions before 3.9 (3.6 and above), you first need to import `List` from standard Python's `typing` module:
```Python hl_lines="1"
-{!../../../docs_src/body_nested_models/tutorial002.py!}
+{!> ../../../docs_src/body_nested_models/tutorial002.py!}
```
-### Declare a `List` with a type parameter
+### Declare a `list` with a type parameter
To declare types that have type parameters (internal types), like `list`, `dict`, `tuple`:
-* Import them from the `typing` module
+* If you are in a Python version lower than 3.9, import their equivalent version from the `typing` module
* Pass the internal type(s) as "type parameters" using square brackets: `[` and `]`
+In Python 3.9 it would be:
+
+```Python
+my_list: list[str]
+```
+
+In versions of Python before 3.9, it would be:
+
```Python
from typing import List
@@ -43,9 +61,23 @@ Use that same standard syntax for model attributes with internal types.
So, in our example, we can make `tags` be specifically a "list of strings":
-```Python hl_lines="14"
-{!../../../docs_src/body_nested_models/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/body_nested_models/tutorial002_py310.py!}
+ ```
## Set types
@@ -53,11 +85,25 @@ But then we think about it, and realize that tags shouldn't repeat, they would p
And Python has a special data type for sets of unique items, the `set`.
-Then we can import `Set` and declare `tags` as a `set` of `str`:
+Then we can declare `tags` as a set of strings:
-```Python hl_lines="1 14"
-{!../../../docs_src/body_nested_models/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 14"
+ {!> ../../../docs_src/body_nested_models/tutorial003.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial003_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/body_nested_models/tutorial003_py310.py!}
+ ```
With this, even if you receive a request with duplicate data, it will be converted to a set of unique items.
@@ -79,17 +125,45 @@ All that, arbitrarily nested.
For example, we can define an `Image` model:
-```Python hl_lines="9-11"
-{!../../../docs_src/body_nested_models/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9-11"
+ {!> ../../../docs_src/body_nested_models/tutorial004.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="9-11"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7-9"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!}
+ ```
### Use the submodel as a type
And then we can use it as the type of an attribute:
-```Python hl_lines="20"
-{!../../../docs_src/body_nested_models/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial004.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!}
+ ```
This would mean that **FastAPI** would expect a body similar to:
@@ -122,9 +196,23 @@ To see all the options you have, checkout the docs for ../../../docs_src/body_nested_models/tutorial005.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="4 10"
+ {!> ../../../docs_src/body_nested_models/tutorial005_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="2 8"
+ {!> ../../../docs_src/body_nested_models/tutorial005_py310.py!}
+ ```
The string will be checked to be a valid URL, and documented in JSON Schema / OpenAPI as such.
@@ -132,9 +220,23 @@ The string will be checked to be a valid URL, and documented in JSON Schema / Op
You can also use Pydantic models as subtypes of `list`, `set`, etc:
-```Python hl_lines="20"
-{!../../../docs_src/body_nested_models/tutorial006.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial006.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial006_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/body_nested_models/tutorial006_py310.py!}
+ ```
This will expect (convert, validate, document, etc) a JSON body like:
@@ -169,9 +271,23 @@ This will expect (convert, validate, document, etc) a JSON body like:
You can define arbitrarily deeply nested models:
-```Python hl_lines="9 14 20 23 27"
-{!../../../docs_src/body_nested_models/tutorial007.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9 14 20 23 27"
+ {!> ../../../docs_src/body_nested_models/tutorial007.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="9 14 20 23 27"
+ {!> ../../../docs_src/body_nested_models/tutorial007_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7 12 18 21 25"
+ {!> ../../../docs_src/body_nested_models/tutorial007_py310.py!}
+ ```
!!! info
Notice how `Offer` has a list of `Item`s, which in turn have an optional list of `Image`s
@@ -184,11 +300,25 @@ If the top level value of the JSON body you expect is a JSON `array` (a Python `
images: List[Image]
```
+or in Python 3.9 and above:
+
+```Python
+images: list[Image]
+```
+
as in:
-```Python hl_lines="15"
-{!../../../docs_src/body_nested_models/tutorial008.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="15"
+ {!> ../../../docs_src/body_nested_models/tutorial008.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="13"
+ {!> ../../../docs_src/body_nested_models/tutorial008_py39.py!}
+ ```
## Editor support everywhere
@@ -218,9 +348,17 @@ That's what we are going to see here.
In this case, you would accept any `dict` as long as it has `int` keys with `float` values:
-```Python hl_lines="9"
-{!../../../docs_src/body_nested_models/tutorial009.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/body_nested_models/tutorial009.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/body_nested_models/tutorial009_py39.py!}
+ ```
!!! tip
Have in mind that JSON only supports `str` as keys.
diff --git a/docs/en/docs/tutorial/body-updates.md b/docs/en/docs/tutorial/body-updates.md
index 757d7bdbc..7d8675060 100644
--- a/docs/en/docs/tutorial/body-updates.md
+++ b/docs/en/docs/tutorial/body-updates.md
@@ -6,9 +6,23 @@ To update an item you can use the ../../../docs_src/body_updates/tutorial001.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="30-35"
+ {!> ../../../docs_src/body_updates/tutorial001_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="28-33"
+ {!> ../../../docs_src/body_updates/tutorial001_py310.py!}
+ ```
`PUT` is used to receive data that should replace the existing data.
@@ -53,9 +67,23 @@ That would generate a `dict` with only the data that was set when creating the `
Then you can use this to generate a `dict` with only the data that was set (sent in the request), omitting default values:
-```Python hl_lines="34"
-{!../../../docs_src/body_updates/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="34"
+ {!> ../../../docs_src/body_updates/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="34"
+ {!> ../../../docs_src/body_updates/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="32"
+ {!> ../../../docs_src/body_updates/tutorial002_py310.py!}
+ ```
### Using Pydantic's `update` parameter
@@ -63,9 +91,23 @@ Now, you can create a copy of the existing model using `.copy()`, and pass the `
Like `stored_item_model.copy(update=update_data)`:
-```Python hl_lines="35"
-{!../../../docs_src/body_updates/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="35"
+ {!> ../../../docs_src/body_updates/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="35"
+ {!> ../../../docs_src/body_updates/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="33"
+ {!> ../../../docs_src/body_updates/tutorial002_py310.py!}
+ ```
### Partial updates recap
@@ -82,9 +124,23 @@ In summary, to apply partial updates you would:
* Save the data to your DB.
* Return the updated model.
-```Python hl_lines="30-37"
-{!../../../docs_src/body_updates/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="30-37"
+ {!> ../../../docs_src/body_updates/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="30-37"
+ {!> ../../../docs_src/body_updates/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="28-35"
+ {!> ../../../docs_src/body_updates/tutorial002_py310.py!}
+ ```
!!! tip
You can actually use this same technique with an HTTP `PUT` operation.
diff --git a/docs/en/docs/tutorial/body.md b/docs/en/docs/tutorial/body.md
index f18afaea6..81441b41e 100644
--- a/docs/en/docs/tutorial/body.md
+++ b/docs/en/docs/tutorial/body.md
@@ -19,9 +19,17 @@ To declare a **request** body, you use ../../../docs_src/body/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="2"
+ {!> ../../../docs_src/body/tutorial001_py310.py!}
+ ```
## Create your data model
@@ -29,9 +37,17 @@ Then you declare your data model as a class that inherits from `BaseModel`.
Use standard Python types for all the attributes:
-```Python hl_lines="7-11"
-{!../../../docs_src/body/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="7-11"
+ {!> ../../../docs_src/body/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="5-9"
+ {!> ../../../docs_src/body/tutorial001_py310.py!}
+ ```
The same as when declaring query parameters, when a model attribute has a default value, it is not required. Otherwise, it is required. Use `None` to make it just optional.
@@ -59,9 +75,17 @@ For example, this model above declares a JSON "`object`" (or Python `dict`) like
To add it to your *path operation*, declare it the same way you declared path and query parameters:
-```Python hl_lines="18"
-{!../../../docs_src/body/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/body/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="16"
+ {!> ../../../docs_src/body/tutorial001_py310.py!}
+ ```
...and declare its type as the model you created, `Item`.
@@ -125,9 +149,17 @@ But you would get the same editor support with ../../../docs_src/body/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/body/tutorial002_py310.py!}
+ ```
## Request body + path parameters
@@ -135,9 +167,17 @@ You can declare path parameters and request body at the same time.
**FastAPI** will recognize that the function parameters that match path parameters should be **taken from the path**, and that function parameters that are declared to be Pydantic models should be **taken from the request body**.
-```Python hl_lines="17-18"
-{!../../../docs_src/body/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="17-18"
+ {!> ../../../docs_src/body/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="15-16"
+ {!> ../../../docs_src/body/tutorial003_py310.py!}
+ ```
## Request body + path + query parameters
@@ -145,9 +185,17 @@ You can also declare **body**, **path** and **query** parameters, all at the sam
**FastAPI** will recognize each of them and take the data from the correct place.
-```Python hl_lines="18"
-{!../../../docs_src/body/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/body/tutorial004.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="16"
+ {!> ../../../docs_src/body/tutorial004_py310.py!}
+ ```
The function parameters will be recognized as follows:
diff --git a/docs/en/docs/tutorial/cookie-params.md b/docs/en/docs/tutorial/cookie-params.md
index 9aa2300c4..221cdfffb 100644
--- a/docs/en/docs/tutorial/cookie-params.md
+++ b/docs/en/docs/tutorial/cookie-params.md
@@ -6,9 +6,17 @@ You can define Cookie parameters the same way you define `Query` and `Path` para
First import `Cookie`:
-```Python hl_lines="3"
-{!../../../docs_src/cookie_params/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/cookie_params/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/cookie_params/tutorial001_py310.py!}
+ ```
## Declare `Cookie` parameters
@@ -16,9 +24,17 @@ Then declare the cookie parameters using the same structure as with `Path` and `
The first value is the default value, you can pass all the extra validation or annotation parameters:
-```Python hl_lines="9"
-{!../../../docs_src/cookie_params/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/cookie_params/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/cookie_params/tutorial001_py310.py!}
+ ```
!!! note "Technical Details"
`Cookie` is a "sister" class of `Path` and `Query`. It also inherits from the same common `Param` class.
diff --git a/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md
index 7747e3e1b..663fff15b 100644
--- a/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md
+++ b/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md
@@ -6,9 +6,17 @@ Before diving deeper into the **Dependency Injection** system, let's upgrade the
In the previous example, we were returning a `dict` from our dependency ("dependable"):
-```Python hl_lines="9"
-{!../../../docs_src/dependencies/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
+ ```
But then we get a `dict` in the parameter `commons` of the *path operation function*.
@@ -71,21 +79,45 @@ That also applies to callables with no parameters at all. The same as it would b
Then, we can change the dependency "dependable" `common_parameters` from above to the class `CommonQueryParams`:
-```Python hl_lines="11-15"
-{!../../../docs_src/dependencies/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="11-15"
+ {!> ../../../docs_src/dependencies/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="9-13"
+ {!> ../../../docs_src/dependencies/tutorial002_py310.py!}
+ ```
Pay attention to the `__init__` method used to create the instance of the class:
-```Python hl_lines="12"
-{!../../../docs_src/dependencies/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/dependencies/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/dependencies/tutorial002_py310.py!}
+ ```
...it has the same parameters as our previous `common_parameters`:
-```Python hl_lines="8"
-{!../../../docs_src/dependencies/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="6"
+ {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
+ ```
Those parameters are what **FastAPI** will use to "solve" the dependency.
@@ -101,9 +133,17 @@ In both cases the data will be converted, validated, documented on the OpenAPI s
Now you can declare your dependency using this class.
-```Python hl_lines="19"
-{!../../../docs_src/dependencies/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/dependencies/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/dependencies/tutorial002_py310.py!}
+ ```
**FastAPI** calls the `CommonQueryParams` class. This creates an "instance" of that class and the instance will be passed as the parameter `commons` to your function.
@@ -143,9 +183,17 @@ commons = Depends(CommonQueryParams)
..as in:
-```Python hl_lines="19"
-{!../../../docs_src/dependencies/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/dependencies/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/dependencies/tutorial003_py310.py!}
+ ```
But declaring the type is encouraged as that way your editor will know what will be passed as the parameter `commons`, and then it can help you with code completion, type checks, etc:
@@ -179,9 +227,17 @@ You declare the dependency as the type of the parameter, and you use `Depends()`
The same example would then look like:
-```Python hl_lines="19"
-{!../../../docs_src/dependencies/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/dependencies/tutorial004.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/dependencies/tutorial004_py310.py!}
+ ```
...and **FastAPI** will know what to do.
diff --git a/docs/en/docs/tutorial/dependencies/index.md b/docs/en/docs/tutorial/dependencies/index.md
index cf0b5a92f..fe10facfb 100644
--- a/docs/en/docs/tutorial/dependencies/index.md
+++ b/docs/en/docs/tutorial/dependencies/index.md
@@ -31,9 +31,17 @@ Let's first focus on the dependency.
It is just a function that can take all the same parameters that a *path operation function* can take:
-```Python hl_lines="8-9"
-{!../../../docs_src/dependencies/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="8-9"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="6-7"
+ {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
+ ```
That's it.
@@ -55,17 +63,33 @@ And then it just returns a `dict` containing those values.
### Import `Depends`
-```Python hl_lines="3"
-{!../../../docs_src/dependencies/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
+ ```
### Declare the dependency, in the "dependant"
The same way you use `Body`, `Query`, etc. with your *path operation function* parameters, use `Depends` with a new parameter:
-```Python hl_lines="13 18"
-{!../../../docs_src/dependencies/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="13 18"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="11 16"
+ {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
+ ```
Although you use `Depends` in the parameters of your function the same way you use `Body`, `Query`, etc, `Depends` works a bit differently.
diff --git a/docs/en/docs/tutorial/dependencies/sub-dependencies.md b/docs/en/docs/tutorial/dependencies/sub-dependencies.md
index 097edb68b..51531228d 100644
--- a/docs/en/docs/tutorial/dependencies/sub-dependencies.md
+++ b/docs/en/docs/tutorial/dependencies/sub-dependencies.md
@@ -6,25 +6,41 @@ They can be as **deep** as you need them to be.
**FastAPI** will take care of solving them.
-### First dependency "dependable"
+## First dependency "dependable"
You could create a first dependency ("dependable") like:
-```Python hl_lines="8-9"
-{!../../../docs_src/dependencies/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="8-9"
+ {!> ../../../docs_src/dependencies/tutorial005.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="6-7"
+ {!> ../../../docs_src/dependencies/tutorial005_py310.py!}
+ ```
It declares an optional query parameter `q` as a `str`, and then it just returns it.
This is quite simple (not very useful), but will help us focus on how the sub-dependencies work.
-### Second dependency, "dependable" and "dependant"
+## Second dependency, "dependable" and "dependant"
Then you can create another dependency function (a "dependable") that at the same time declares a dependency of its own (so it is a "dependant" too):
-```Python hl_lines="13"
-{!../../../docs_src/dependencies/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="13"
+ {!> ../../../docs_src/dependencies/tutorial005.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="11"
+ {!> ../../../docs_src/dependencies/tutorial005_py310.py!}
+ ```
Let's focus on the parameters declared:
@@ -33,13 +49,21 @@ Let's focus on the parameters declared:
* It also declares an optional `last_query` cookie, as a `str`.
* If the user didn't provide any query `q`, we use the last query used, which we saved to a cookie before.
-### Use the dependency
+## Use the dependency
Then we can use the dependency with:
-```Python hl_lines="21"
-{!../../../docs_src/dependencies/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="21"
+ {!> ../../../docs_src/dependencies/tutorial005.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/dependencies/tutorial005_py310.py!}
+ ```
!!! info
Notice that we are only declaring one dependency in the *path operation function*, the `query_or_cookie_extractor`.
diff --git a/docs/en/docs/tutorial/encoder.md b/docs/en/docs/tutorial/encoder.md
index a2cbe45d5..7a69c5474 100644
--- a/docs/en/docs/tutorial/encoder.md
+++ b/docs/en/docs/tutorial/encoder.md
@@ -20,9 +20,17 @@ You can use `jsonable_encoder` for that.
It receives an object, like a Pydantic model, and returns a JSON compatible version:
-```Python hl_lines="5 22"
-{!../../../docs_src/encoder/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="5 22"
+ {!> ../../../docs_src/encoder/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="4 21"
+ {!> ../../../docs_src/encoder/tutorial001_py310.py!}
+ ```
In this example, it would convert the Pydantic model to a `dict`, and the `datetime` to a `str`.
diff --git a/docs/en/docs/tutorial/extra-data-types.md b/docs/en/docs/tutorial/extra-data-types.md
index 995f0b972..a00bd3212 100644
--- a/docs/en/docs/tutorial/extra-data-types.md
+++ b/docs/en/docs/tutorial/extra-data-types.md
@@ -55,12 +55,28 @@ Here are some of the additional data types you can use:
Here's an example *path operation* with parameters using some of the above types.
-```Python hl_lines="1 3 12-16"
-{!../../../docs_src/extra_data_types/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 3 12-16"
+ {!> ../../../docs_src/extra_data_types/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1 2 11-15"
+ {!> ../../../docs_src/extra_data_types/tutorial001_py310.py!}
+ ```
Note that the parameters inside the function have their natural data type, and you can, for example, perform normal date manipulations, like:
-```Python hl_lines="18-19"
-{!../../../docs_src/extra_data_types/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="18-19"
+ {!> ../../../docs_src/extra_data_types/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17-18"
+ {!> ../../../docs_src/extra_data_types/tutorial001_py310.py!}
+ ```
diff --git a/docs/en/docs/tutorial/extra-models.md b/docs/en/docs/tutorial/extra-models.md
index 4eced04ca..72fc74894 100644
--- a/docs/en/docs/tutorial/extra-models.md
+++ b/docs/en/docs/tutorial/extra-models.md
@@ -17,9 +17,17 @@ This is especially the case for user models, because:
Here's a general idea of how the models could look like with their password fields and the places where they are used:
-```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41"
-{!../../../docs_src/extra_models/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41"
+ {!> ../../../docs_src/extra_models/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39"
+ {!> ../../../docs_src/extra_models/tutorial001_py310.py!}
+ ```
### About `**user_in.dict()`
@@ -150,9 +158,17 @@ All the data conversion, validation, documentation, etc. will still work as norm
That way, we can declare just the differences between the models (with plaintext `password`, with `hashed_password` and without password):
-```Python hl_lines="9 15-16 19-20 23-24"
-{!../../../docs_src/extra_models/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9 15-16 19-20 23-24"
+ {!> ../../../docs_src/extra_models/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7 13-14 17-18 21-22"
+ {!> ../../../docs_src/extra_models/tutorial002_py310.py!}
+ ```
## `Union` or `anyOf`
@@ -165,19 +181,49 @@ To do that, use the standard Python type hint `Union`, include the most specific type first, followed by the less specific type. In the example below, the more specific `PlaneItem` comes before `CarItem` in `Union[PlaneItem, CarItem]`.
-```Python hl_lines="1 14-15 18-20 33"
-{!../../../docs_src/extra_models/tutorial003.py!}
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 14-15 18-20 33"
+ {!> ../../../docs_src/extra_models/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1 14-15 18-20 33"
+ {!> ../../../docs_src/extra_models/tutorial003_py310.py!}
+ ```
+
+### `Union` in Python 3.10
+
+In this example we pass `Union[PlaneItem, CarItem]` as the value of the argument `response_model`.
+
+Because we are passing it as a **value to an argument** instead of putting it in a **type annotation**, we have to use `Union` even in Python 3.10.
+
+If it was in a type annotation we could have used the vertical bar, as:
+
+```Python
+some_variable: PlaneItem | CarItem
```
+But if we put that in `response_model=PlaneItem | CarItem` we would get an error, because Python would try to perform an **invalid operation** between `PlaneItem` and `CarItem` instead of interpreting that as a type annotation.
+
## List of models
The same way, you can declare responses of lists of objects.
-For that, use the standard Python `typing.List`:
+For that, use the standard Python `typing.List` (or just `list` in Python 3.9 and above):
-```Python hl_lines="1 20"
-{!../../../docs_src/extra_models/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 20"
+ {!> ../../../docs_src/extra_models/tutorial004.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/extra_models/tutorial004_py39.py!}
+ ```
## Response with arbitrary `dict`
@@ -185,11 +231,19 @@ You can also declare a response using a plain arbitrary `dict`, declaring just t
This is useful if you don't know the valid field/attribute names (that would be needed for a Pydantic model) beforehand.
-In this case, you can use `typing.Dict`:
+In this case, you can use `typing.Dict` (or just `dict` in Python 3.9 and above):
-```Python hl_lines="1 8"
-{!../../../docs_src/extra_models/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 8"
+ {!> ../../../docs_src/extra_models/tutorial005.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="6"
+ {!> ../../../docs_src/extra_models/tutorial005_py39.py!}
+ ```
## Recap
diff --git a/docs/en/docs/tutorial/header-params.md b/docs/en/docs/tutorial/header-params.md
index 57570153f..8e294416c 100644
--- a/docs/en/docs/tutorial/header-params.md
+++ b/docs/en/docs/tutorial/header-params.md
@@ -6,9 +6,17 @@ You can define Header parameters the same way you define `Query`, `Path` and `Co
First import `Header`:
-```Python hl_lines="3"
-{!../../../docs_src/header_params/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/header_params/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/header_params/tutorial001_py310.py!}
+ ```
## Declare `Header` parameters
@@ -16,9 +24,17 @@ Then declare the header parameters using the same structure as with `Path`, `Que
The first value is the default value, you can pass all the extra validation or annotation parameters:
-```Python hl_lines="9"
-{!../../../docs_src/header_params/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/header_params/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/header_params/tutorial001_py310.py!}
+ ```
!!! note "Technical Details"
`Header` is a "sister" class of `Path`, `Query` and `Cookie`. It also inherits from the same common `Param` class.
@@ -44,14 +60,21 @@ So, you can use `user_agent` as you normally would in Python code, instead of ne
If for some reason you need to disable automatic conversion of underscores to hyphens, set the parameter `convert_underscores` of `Header` to `False`:
-```Python hl_lines="10"
-{!../../../docs_src/header_params/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/header_params/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/header_params/tutorial002_py310.py!}
+ ```
!!! warning
Before setting `convert_underscores` to `False`, bear in mind that some HTTP proxies and servers disallow the usage of headers with underscores.
-
## Duplicate headers
It is possible to receive duplicate headers. That means, the same header with multiple values.
@@ -62,9 +85,23 @@ You will receive all the values from the duplicate header as a Python `list`.
For example, to declare a header of `X-Token` that can appear more than once, you can write:
-```Python hl_lines="9"
-{!../../../docs_src/header_params/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/header_params/tutorial003.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/header_params/tutorial003_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/header_params/tutorial003_py310.py!}
+ ```
If you communicate with that *path operation* sending two HTTP headers like:
diff --git a/docs/en/docs/tutorial/path-operation-configuration.md b/docs/en/docs/tutorial/path-operation-configuration.md
index 0d606331d..884a762e2 100644
--- a/docs/en/docs/tutorial/path-operation-configuration.md
+++ b/docs/en/docs/tutorial/path-operation-configuration.md
@@ -13,9 +13,23 @@ You can pass directly the `int` code, like `404`.
But if you don't remember what each number code is for, you can use the shortcut constants in `status`:
-```Python hl_lines="3 17"
-{!../../../docs_src/path_operation_configuration/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3 17"
+ {!> ../../../docs_src/path_operation_configuration/tutorial001.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="3 17"
+ {!> ../../../docs_src/path_operation_configuration/tutorial001_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1 15"
+ {!> ../../../docs_src/path_operation_configuration/tutorial001_py310.py!}
+ ```
That status code will be used in the response and will be added to the OpenAPI schema.
@@ -28,21 +42,61 @@ That status code will be used in the response and will be added to the OpenAPI s
You can add tags to your *path operation*, pass the parameter `tags` with a `list` of `str` (commonly just one `str`):
-```Python hl_lines="17 22 27"
-{!../../../docs_src/path_operation_configuration/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="17 22 27"
+ {!> ../../../docs_src/path_operation_configuration/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="17 22 27"
+ {!> ../../../docs_src/path_operation_configuration/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="15 20 25"
+ {!> ../../../docs_src/path_operation_configuration/tutorial002_py310.py!}
+ ```
They will be added to the OpenAPI schema and used by the automatic documentation interfaces:
+### Tags with Enums
+
+If you have a big application, you might end up accumulating **several tags**, and you would want to make sure you always use the **same tag** for related *path operations*.
+
+In these cases, it could make sense to store the tags in an `Enum`.
+
+**FastAPI** supports that the same way as with plain strings:
+
+```Python hl_lines="1 8-10 13 18"
+{!../../../docs_src/path_operation_configuration/tutorial002b.py!}
+```
+
## Summary and description
You can add a `summary` and `description`:
-```Python hl_lines="20-21"
-{!../../../docs_src/path_operation_configuration/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="20-21"
+ {!> ../../../docs_src/path_operation_configuration/tutorial003.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="20-21"
+ {!> ../../../docs_src/path_operation_configuration/tutorial003_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="18-19"
+ {!> ../../../docs_src/path_operation_configuration/tutorial003_py310.py!}
+ ```
## Description from docstring
@@ -50,9 +104,23 @@ As descriptions tend to be long and cover multiple lines, you can declare the *p
You can write Markdown in the docstring, it will be interpreted and displayed correctly (taking into account docstring indentation).
-```Python hl_lines="19-27"
-{!../../../docs_src/path_operation_configuration/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="19-27"
+ {!> ../../../docs_src/path_operation_configuration/tutorial004.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="19-27"
+ {!> ../../../docs_src/path_operation_configuration/tutorial004_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17-25"
+ {!> ../../../docs_src/path_operation_configuration/tutorial004_py310.py!}
+ ```
It will be used in the interactive docs:
@@ -62,9 +130,23 @@ It will be used in the interactive docs:
You can specify the response description with the parameter `response_description`:
-```Python hl_lines="21"
-{!../../../docs_src/path_operation_configuration/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="21"
+ {!> ../../../docs_src/path_operation_configuration/tutorial005.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="21"
+ {!> ../../../docs_src/path_operation_configuration/tutorial005_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/path_operation_configuration/tutorial005_py310.py!}
+ ```
!!! info
Notice that `response_description` refers specifically to the response, the `description` refers to the *path operation* in general.
diff --git a/docs/en/docs/tutorial/path-params-numeric-validations.md b/docs/en/docs/tutorial/path-params-numeric-validations.md
index 5da69a21b..31bf91a0e 100644
--- a/docs/en/docs/tutorial/path-params-numeric-validations.md
+++ b/docs/en/docs/tutorial/path-params-numeric-validations.md
@@ -6,9 +6,17 @@ The same way you can declare more validations and metadata for query parameters
First, import `Path` from `fastapi`:
-```Python hl_lines="3"
-{!../../../docs_src/path_params_numeric_validations/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!}
+ ```
## Declare metadata
@@ -16,13 +24,21 @@ You can declare all the same parameters as for `Query`.
For example, to declare a `title` metadata value for the path parameter `item_id` you can type:
-```Python hl_lines="10"
-{!../../../docs_src/path_params_numeric_validations/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!}
+ ```
!!! note
A path parameter is always required as it has to be part of the path.
-
+
So, you should declare it with `...` to mark it as required.
Nevertheless, even if you declared it with `None` or set a default value, it would not affect anything, it would still be always required.
diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md
index 8deccda0b..ee62b9718 100644
--- a/docs/en/docs/tutorial/query-params-str-validations.md
+++ b/docs/en/docs/tutorial/query-params-str-validations.md
@@ -4,11 +4,19 @@
Let's take this application as example:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial001.py!}
-```
+=== "Python 3.6 and above"
-The query parameter `q` is of type `Optional[str]`, that means that it's of type `str` but could also be `None`, and indeed, the default value is `None`, so FastAPI will know it's not required.
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial001_py310.py!}
+ ```
+
+The query parameter `q` is of type `Optional[str]` (or `str | None` in Python 3.10), that means that it's of type `str` but could also be `None`, and indeed, the default value is `None`, so FastAPI will know it's not required.
!!! note
FastAPI will know that the value of `q` is not required because of the default value `= None`.
@@ -23,17 +31,33 @@ We are going to enforce that even though `q` is optional, whenever it is provide
To achieve that, first import `Query` from `fastapi`:
-```Python hl_lines="3"
-{!../../../docs_src/query_params_str_validations/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!}
+ ```
## Use `Query` as the default value
And now use it as the default value of your parameter, setting the parameter `max_length` to 50:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002_py310.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.
@@ -49,10 +73,22 @@ q: Optional[str] = Query(None)
q: Optional[str] = None
```
+And in Python 3.10 and above:
+
+```Python
+q: str | None = Query(None)
+```
+
+...makes the parameter optional, the same as:
+
+```Python
+q: str | None = None
+```
+
But it declares it explicitly as being a query parameter.
!!! info
- Have in mind that FastAPI cares about the part:
+ Have in mind that the most important part to make a parameter optional is the part:
```Python
= None
@@ -64,9 +100,9 @@ But it declares it explicitly as being a query parameter.
= Query(None)
```
- and will use that `None` to detect that the query parameter is not required.
+ as it will use that `None` as the default value, and that way make the parameter **not required**.
- The `Optional` part is only to allow your editor to provide better support.
+ The `Optional` part allows your editor to provide better support, but it is not what tells FastAPI that this parameter is not required.
Then, we can pass more parameters to `Query`. In this case, the `max_length` parameter that applies to strings:
@@ -80,17 +116,33 @@ This will validate the data, show a clear error when the data is not valid, and
You can also add a parameter `min_length`:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial003_py310.py!}
+ ```
## Add regular expressions
You can define a regular expression that the parameter should match:
-```Python hl_lines="10"
-{!../../../docs_src/query_params_str_validations/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial004.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/query_params_str_validations/tutorial004_py310.py!}
+ ```
This specific regular expression checks that the received parameter value:
@@ -152,9 +204,23 @@ When you define a query parameter explicitly with `Query` you can also declare i
For example, to declare a query parameter `q` that can appear multiple times in the URL, you can write:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial011.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial011.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial011_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial011_py310.py!}
+ ```
Then, with a URL like:
@@ -186,9 +252,17 @@ The interactive API docs will update accordingly, to allow multiple values:
And you can also define a default `list` of values if none are provided:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial012.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial012.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial012_py39.py!}
+ ```
If you go to:
@@ -209,7 +283,7 @@ the default of `q` will be: `["foo", "bar"]` and your response will be:
#### Using `list`
-You can also use `list` directly instead of `List[str]`:
+You can also use `list` directly instead of `List[str]` (or `list[str]` in Python 3.9+):
```Python hl_lines="7"
{!../../../docs_src/query_params_str_validations/tutorial013.py!}
@@ -233,15 +307,31 @@ That information will be included in the generated OpenAPI and used by the docum
You can add a `title`:
-```Python hl_lines="10"
-{!../../../docs_src/query_params_str_validations/tutorial007.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial007.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial007_py310.py!}
+ ```
And a `description`:
-```Python hl_lines="13"
-{!../../../docs_src/query_params_str_validations/tutorial008.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="13"
+ {!> ../../../docs_src/query_params_str_validations/tutorial008.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/query_params_str_validations/tutorial008_py310.py!}
+ ```
## Alias parameters
@@ -261,9 +351,17 @@ But you still need it to be exactly `item-query`...
Then you can declare an `alias`, and that alias is what will be used to find the parameter value:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial009.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial009.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial009_py310.py!}
+ ```
## Deprecating parameters
@@ -273,14 +371,38 @@ You have to leave it there a while because there are clients using it, but you w
Then pass the parameter `deprecated=True` to `Query`:
-```Python hl_lines="18"
-{!../../../docs_src/query_params_str_validations/tutorial010.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/query_params_str_validations/tutorial010.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/query_params_str_validations/tutorial010_py310.py!}
+ ```
The docs will show it like this:
+## Exclude from OpenAPI
+
+To exclude a query parameter from the generated OpenAPI schema (and thus, from the automatic documentation systems), set the parameter `include_in_schema` of `Query` to `False`:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial014.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial014_py310.py!}
+ ```
+
## Recap
You can declare additional validations and metadata for your parameters.
diff --git a/docs/en/docs/tutorial/query-params.md b/docs/en/docs/tutorial/query-params.md
index 4401745ac..eec55502a 100644
--- a/docs/en/docs/tutorial/query-params.md
+++ b/docs/en/docs/tutorial/query-params.md
@@ -63,27 +63,38 @@ The parameter values in your function will be:
The same way, you can declare optional query parameters, by setting their default to `None`:
-```Python hl_lines="9"
-{!../../../docs_src/query_params/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params/tutorial002_py310.py!}
+ ```
In this case, the function parameter `q` will be optional, and will be `None` by default.
!!! check
Also notice that **FastAPI** is smart enough to notice that the path parameter `item_id` is a path parameter and `q` is not, so, it's a query parameter.
-!!! note
- FastAPI will know that `q` is optional because of the `= None`.
-
- The `Optional` in `Optional[str]` is not used by FastAPI (FastAPI will only use the `str` part), but the `Optional[str]` will let your editor help you finding errors in your code.
-
## Query parameter type conversion
You can also declare `bool` types, and they will be converted:
-```Python hl_lines="9"
-{!../../../docs_src/query_params/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params/tutorial003_py310.py!}
+ ```
In this case, if you go to:
@@ -126,9 +137,17 @@ And you don't have to declare them in any specific order.
They will be detected by name:
-```Python hl_lines="8 10"
-{!../../../docs_src/query_params/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="8 10"
+ {!> ../../../docs_src/query_params/tutorial004.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="6 8"
+ {!> ../../../docs_src/query_params/tutorial004_py310.py!}
+ ```
## Required query parameters
@@ -184,9 +203,17 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
And of course, you can define some parameters as required, some as having a default value, and some entirely optional:
-```Python hl_lines="10"
-{!../../../docs_src/query_params/tutorial006.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params/tutorial006.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/query_params/tutorial006_py310.py!}
+ ```
In this case, there are 3 query parameters:
diff --git a/docs/en/docs/tutorial/request-files.md b/docs/en/docs/tutorial/request-files.md
index 68ea654c2..3ca471a91 100644
--- a/docs/en/docs/tutorial/request-files.md
+++ b/docs/en/docs/tutorial/request-files.md
@@ -17,7 +17,7 @@ Import `File` and `UploadFile` from `fastapi`:
{!../../../docs_src/request_files/tutorial001.py!}
```
-## Define `File` parameters
+## Define `File` Parameters
Create file parameters the same way you would for `Body` or `Form`:
@@ -41,9 +41,9 @@ Have in mind that this means that the whole contents will be stored in memory. T
But there are several cases in which you might benefit from using `UploadFile`.
-## `File` parameters with `UploadFile`
+## File Parameters with `UploadFile`
-Define a `File` parameter with a type of `UploadFile`:
+Define a file parameter with a type of `UploadFile`:
```Python hl_lines="12"
{!../../../docs_src/request_files/tutorial001.py!}
@@ -51,6 +51,7 @@ Define a `File` parameter with a type of `UploadFile`:
Using `UploadFile` has several advantages over `bytes`:
+* You don't have to use `File()` in the default value of the parameter.
* It uses a "spooled" file:
* A file stored in memory up to a maximum size limit, and after passing this limit it will be stored in disk.
* This means that it will work well for large files like images, videos, large binaries, etc. without consuming all the memory.
@@ -113,17 +114,49 @@ The way HTML forms (`
jinja2 - 기본 템플릿 설정을 사용하려면 필요.
* python-multipart - `request.form()`과 함께 "parsing"의 지원을 원하면 필요.
* itsdangerous - `SessionMiddleware` 지원을 위해 필요.
-* pyyaml - Starlette의 `SchemaGenerator` 지원을 위해 필요 (FastAPI와 쓸때는 필요가 없을 겁니다).
+* pyyaml - Starlette의 `SchemaGenerator` 지원을 위해 필요 (FastAPI와 쓸때는 필요 없을 것입니다).
* graphene - `GraphQLApp` 지원을 위해 필요.
* ujson - `UJSONResponse`를 사용하려면 필요.
diff --git a/docs/ko/docs/tutorial/request-forms-and-files.md b/docs/ko/docs/tutorial/request-forms-and-files.md
new file mode 100644
index 000000000..6750c7b23
--- /dev/null
+++ b/docs/ko/docs/tutorial/request-forms-and-files.md
@@ -0,0 +1,35 @@
+# 폼 및 파일 요청
+
+`File` 과 `Form` 을 사용하여 파일과 폼을 함께 정의할 수 있습니다.
+
+!!! info "정보"
+ 파일과 폼 데이터를 함께, 또는 각각 업로드하기 위해 먼저 `python-multipart`를 설치해야합니다.
+
+ 예 ) `pip install python-multipart`.
+
+## `File` 및 `Form` 업로드
+
+```Python hl_lines="1"
+{!../../../docs_src/request_forms_and_files/tutorial001.py!}
+```
+
+## `File` 및 `Form` 매개변수 정의
+
+`Body` 및 `Query`와 동일한 방식으로 파일과 폼의 매개변수를 생성합니다:
+
+```Python hl_lines="8"
+{!../../../docs_src/request_forms_and_files/tutorial001.py!}
+```
+
+파일과 폼 필드는 폼 데이터 형식으로 업로드되어 파일과 폼 필드로 전달됩니다.
+
+어떤 파일들은 `bytes`로, 또 어떤 파일들은 `UploadFile`로 선언할 수 있습니다.
+
+!!! warning "주의"
+ 다수의 `File`과 `Form` 매개변수를 한 *경로 작동*에 선언하는 것이 가능하지만, 요청의 본문이 `application/json`가 아닌 `multipart/form-data`로 인코딩 되기 때문에 JSON으로 받아야하는 `Body` 필드를 함께 선언할 수는 없습니다.
+
+ 이는 **FastAPI**의 한계가 아니라, HTTP 프로토콜에 의한 것입니다.
+
+## 요약
+
+하나의 요청으로 데이터와 파일들을 받아야 할 경우 `File`과 `Form`을 함께 사용하기 바랍니다.
diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml
index 52db5acb8..09cb70487 100644
--- a/docs/ko/mkdocs.yml
+++ b/docs/ko/mkdocs.yml
@@ -29,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -64,6 +61,7 @@ nav:
- tutorial/path-params-numeric-validations.md
- tutorial/response-status-code.md
- tutorial/request-files.md
+ - tutorial/request-forms-and-files.md
- 심화된 사용자 안내서:
- advanced/sub-applications.md
markdown_extensions:
@@ -81,8 +79,12 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format ''
-- pymdownx.tabbed
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
diff --git a/docs/pl/docs/index.md b/docs/pl/docs/index.md
index 95fb7ae21..4a300ae63 100644
--- a/docs/pl/docs/index.md
+++ b/docs/pl/docs/index.md
@@ -1,12 +1,8 @@
-
-{!../../../docs/missing-translation.md!}
-
-
- FastAPI framework, high performance, easy to learn, fast to code, ready for production + FastAPI to szybki, prosty w nauce i gotowy do użycia w produkcji framework
@@ -22,29 +18,28 @@
---
-**Documentation**: https://fastapi.tiangolo.com
+**Dokumentacja**: https://fastapi.tiangolo.com
-**Source Code**: https://github.com/tiangolo/fastapi
+**Kod żródłowy**: https://github.com/tiangolo/fastapi
---
-FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
+FastAPI to nowoczesny, wydajny framework webowy do budowania API z użyciem Pythona 3.6+ bazujący na standardowym typowaniu Pythona.
-The key features are:
+Kluczowe cechy:
-* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
+* **Wydajność**: FastAPI jest bardzo wydajny, na równi z **NodeJS** oraz **Go** (dzięki Starlette i Pydantic). [Jeden z najszybszych dostępnych frameworków Pythonowych](#wydajnosc).
+* **Szybkość kodowania**: Przyśpiesza szybkość pisania nowych funkcjonalności o około 200% do 300%. *
+* **Mniejsza ilość błędów**: Zmniejsza ilość ludzkich (dewelopera) błędy o około 40%. *
+* **Intuicyjność**: Wspaniałe wsparcie dla edytorów kodu. Dostępne wszędzie automatyczne uzupełnianie kodu. Krótszy czas debugowania.
+* **Łatwość**: Zaprojektowany by być prosty i łatwy do nauczenia. Mniej czasu spędzonego na czytanie dokumentacji.
+* **Kompaktowość**: Minimalizacja powtarzającego się kodu. Wiele funkcjonalności dla każdej deklaracji parametru. Mniej błędów.
+* **Solidność**: Kod gotowy dla środowiska produkcyjnego. Wraz z automatyczną interaktywną dokumentacją.
+* **Bazujący na standardach**: Oparty na (i w pełni kompatybilny z) otwartych standardach API: OpenAPI (wcześniej znane jako Swagger) oraz JSON Schema.
-* **Fast to code**: Increase the speed to develop features by about 200% to 300%. *
-* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
-* **Intuitive**: Great editor support. Completion everywhere. Less time debugging.
-* **Easy**: Designed to be easy to use and learn. Less time reading docs.
-* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
-* **Robust**: Get production-ready code. With automatic interactive documentation.
-* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema.
+* oszacowania bazowane na testach wykonanych przez wewnętrzny zespół deweloperów, budujących aplikacie używane na środowisku produkcyjnym.
-* estimation based on tests on an internal development team, building production applications.
-
-## Sponsors
+## Sponsorzy
@@ -59,9 +54,9 @@ The key features are:
-Other sponsors
+Inni sponsorzy
-## Opinions
+## Opinie
"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._"
@@ -101,24 +96,24 @@ The key features are:
---
-## **Typer**, the FastAPI of CLIs
+## **Typer**, FastAPI aplikacji konsolowych
-If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**.
+Jeżeli tworzysz aplikacje CLI, która ma być używana w terminalu zamiast API, sprawdź **Typer**.
-**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀
+**Typer** to młodsze rodzeństwo FastAPI. Jego celem jest pozostanie **FastAPI aplikacji konsolowych** . ⌨️ 🚀
-## Requirements
+## Wymagania
Python 3.6+
-FastAPI stands on the shoulders of giants:
+FastAPI oparty jest na:
-* Starlette for the web parts.
-* Pydantic for the data parts.
+* Starlette dla części webowej.
+* Pydantic dla części obsługujących dane.
-## Installation
+## Instalacja
async def...async def...uvicorn main:app --reload...uvicorn main:app --reload...ujson - for faster JSON "parsing".
-* email_validator - for email validation.
+* ujson - dla szybszego "parsowania" danych JSON.
+* email_validator - dla walidacji adresów email.
-Used by Starlette:
+Używane przez Starlette:
-* requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
-* jinja2 - Required if you want to use the default template configuration.
-* python-multipart - Required if you want to support form "parsing", with `request.form()`.
-* itsdangerous - Required for `SessionMiddleware` support.
-* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
-* graphene - Required for `GraphQLApp` support.
-* ujson - Required if you want to use `UJSONResponse`.
+* requests - Wymagane jeżeli chcesz korzystać z `TestClient`.
+* aiofiles - Wymagane jeżeli chcesz korzystać z `FileResponse` albo `StaticFiles`.
+* jinja2 - Wymagane jeżeli chcesz używać domyślnej konfiguracji szablonów.
+* python-multipart - Wymagane jeżelich chcesz wsparcie "parsowania" formularzy, używając `request.form()`.
+* itsdangerous - Wymagany dla wsparcia `SessionMiddleware`.
+* pyyaml - Wymagane dla wsparcia `SchemaGenerator` z Starlette (z FastAPI prawdopodobnie tego nie potrzebujesz).
+* graphene - Wymagane dla wsparcia `GraphQLApp`.
+* ujson - Wymagane jeżeli chcesz korzystać z `UJSONResponse`.
-Used by FastAPI / Starlette:
+Używane przez FastAPI / Starlette:
-* uvicorn - for the server that loads and serves your application.
-* orjson - Required if you want to use `ORJSONResponse`.
+* uvicorn - jako serwer, który ładuje i obsługuje Twoją aplikację.
+* orjson - Wymagane jeżeli chcesz używać `ORJSONResponse`.
-You can install all of these with `pip install fastapi[all]`.
+Możesz zainstalować wszystkie te aplikacje przy pomocy `pip install fastapi[all]`.
-## License
+## Licencja
-This project is licensed under the terms of the MIT license.
+Ten projekt jest na licencji MIT.
diff --git a/docs/pl/mkdocs.yml b/docs/pl/mkdocs.yml
index 220008607..3c1351a12 100644
--- a/docs/pl/mkdocs.yml
+++ b/docs/pl/mkdocs.yml
@@ -29,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -70,8 +67,12 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format ''
-- pymdownx.tabbed
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
diff --git a/docs/pt/mkdocs.yml b/docs/pt/mkdocs.yml
index ea4af852e..f202f306d 100644
--- a/docs/pt/mkdocs.yml
+++ b/docs/pt/mkdocs.yml
@@ -29,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -90,8 +87,12 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format ''
-- pymdownx.tabbed
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml
index e15a36bf2..6e17c287e 100644
--- a/docs/ru/mkdocs.yml
+++ b/docs/ru/mkdocs.yml
@@ -29,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -70,8 +67,12 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format ''
-- pymdownx.tabbed
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
diff --git a/docs/sq/mkdocs.yml b/docs/sq/mkdocs.yml
index a4879521e..d9c3dad4c 100644
--- a/docs/sq/mkdocs.yml
+++ b/docs/sq/mkdocs.yml
@@ -29,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -70,8 +67,12 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format ''
-- pymdownx.tabbed
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml
index 5ef8fd6a7..f6ed7f5b9 100644
--- a/docs/tr/mkdocs.yml
+++ b/docs/tr/mkdocs.yml
@@ -29,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -73,8 +70,12 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format ''
-- pymdownx.tabbed
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
diff --git a/docs/uk/mkdocs.yml b/docs/uk/mkdocs.yml
index 7f5785e3e..d0de8cc0e 100644
--- a/docs/uk/mkdocs.yml
+++ b/docs/uk/mkdocs.yml
@@ -29,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -70,8 +67,12 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format ''
-- pymdownx.tabbed
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
diff --git a/docs/zh/docs/help-fastapi.md b/docs/zh/docs/help-fastapi.md
index 99e37b7c1..6f3f5159b 100644
--- a/docs/zh/docs/help-fastapi.md
+++ b/docs/zh/docs/help-fastapi.md
@@ -1,107 +1,149 @@
-# 帮助 FastAPI - 获取帮助
+# 帮助 FastAPI 与求助
-你喜欢 **FastAPI** 吗?
+您喜欢 **FastAPI** 吗?
-您愿意去帮助 FastAPI,帮助其他用户以及作者吗?
+想帮助 FastAPI?其它用户?还有项目作者?
-或者你想要获得有关 **FastAPI** 的帮助?
+或要求助怎么使用 **FastAPI**?
-下面是一些非常简单的方式去提供帮助(有些只需单击一两次链接)。
+以下几种帮助的方式都非常简单(有些只需要点击一两下鼠标)。
-以及几种获取帮助的途径。
+求助的渠道也很多。
-## 在 GitHub 上 Star **FastAPI**
+## 订阅新闻邮件
-你可以在 GitHub 上 "star" FastAPI(点击右上角的 star 按钮):https://github.com/tiangolo/fastapi。
+您可以订阅 [**FastAPI 和它的小伙伴** 新闻邮件](/newsletter/){.internal-link target=_blank}(不会经常收到)
-通过添加 star,其他用户将会更容易发现 FastAPI,并了解已经有许多人认为它有用。
+* FastAPI 及其小伙伴的新闻 🚀
+* 指南 📝
+* 功能 ✨
+* 破坏性更改 🚨
+* 开发技巧 ✅
-## Watch GitHub 仓库的版本发布
+## 在推特上关注 FastAPI
-你可以在 GitHub 上 "watch" FastAPI(点击右上角的 watch 按钮):https://github.com/tiangolo/fastapi。
+在 **Twitter** 上关注 @fastapi 获取 **FastAPI** 的最新消息。🐦
-这时你可以选择 "Releases only" 选项。
+## 在 GitHub 上为 **FastAPI** 加星
-之后,只要有 **FastAPI** 的新版本(包含缺陷修复和新功能)发布,你都会(通过电子邮件)收到通知。
+您可以在 GitHub 上 **Star** FastAPI(只要点击右上角的星星就可以了): https://github.com/tiangolo/fastapi。⭐️
-## 加入聊天室
+**Star** 以后,其它用户就能更容易找到 FastAPI,并了解到已经有其他用户在使用它了。
-加入 Gitter 上的聊天室:https://gitter.im/tiangolo/fastapi。
+## 关注 GitHub 资源库的版本发布
-在这里你可以快速提问、帮助他人、分享想法等。
+您还可以在 GitHub 上 **Watch** FastAPI,(点击右上角的 **Watch** 按钮)https://github.com/tiangolo/fastapi。👀
-## 与作者联系
+您可以选择只关注发布(**Releases only**)。
-你可以联系 我 (Sebastián Ramírez / `tiangolo`) - FastAPI 的作者。
+这样,您就可以(在电子邮件里)接收到 **FastAPI** 新版发布的通知,及时了解 bug 修复与新功能。
-你可以:
+## 联系作者
-* 在 **GitHub** 上关注我。
- * 查看我创建的其他的可能对你有帮助的开源项目。
- * 关注我以了解我创建的新开源项目。
-* 在 **Twitter** 上关注我。
- * 告诉我你是如何使用 FastAPI 的(我很乐意听到)。
- * 提出问题。
-* 在 **Linkedin** 上联系我。
- * 与我交流。
- * 认可我的技能或推荐我 :)
-* 在 **Medium** 上阅读我写的文章(或关注我)。
- * 阅读我创建的其他想法,文章和工具。
- * 关注我以了解我发布的新内容。
+您可以联系项目作者,就是我(Sebastián Ramírez / `tiangolo`)。
-## 发布和 **FastAPI** 有关的推特
+您可以:
- 发布和 **FastAPI** 有关的推特 让我和其他人知道你为什么喜欢它。
+* 在 **GitHub** 上关注我
+ * 了解其它我创建的开源项目,或许对您会有帮助
+ * 关注我什么时候创建新的开源项目
+* 在 **Twitter** 上关注我
+ * 告诉我您使用 FastAPI(我非常乐意听到这种消息)
+ * 接收我发布公告或新工具的消息
+ * 您还可以关注@fastapi on Twitter,这是个独立的账号
+* 在**领英**上联系我
+ * 接收我发布公告或新工具的消息(虽然我用 Twitter 比较多)
+* 阅读我在 **Dev.to** 或 **Medium** 上的文章,或关注我
+ * 阅读我的其它想法、文章,了解我创建的工具
+ * 关注我,这样就可以随时看到我发布的新文章
-## 告诉我你正在如何使用 **FastAPI**
+## Tweet about **FastAPI**
-我很乐意听到有关 **FastAPI** 被如何使用、你喜欢它的哪一点、被投入使用的项目/公司等等信息。
+Tweet about **FastAPI** 让我和大家知道您为什么喜欢 FastAPI。🎉
-你可以通过以下平台让我知道:
-
-* **Twitter**。
-* **Linkedin**。
-* **Medium**。
+知道有人使用 **FastAPI**,我会很开心,我也想知道您为什么喜欢 FastAPI,以及您在什么项目/哪些公司使用 FastAPI,等等。
## 为 FastAPI 投票
-* 在 Slant 上为 **FastAPI** 投票。
+* 在 Slant 上为 **FastAPI** 投票
+* 在 AlternativeTo 上为 **FastAPI** 投票
-## 帮助他人解决 GitHub 的 issues
+## 在 GitHub 上帮助其他人解决问题
-你可以查看 已有的 issues 并尝试帮助其他人。
+您可以查看现有 issues,并尝试帮助其他人解决问题,说不定您能解决这些问题呢。🤓
-## Watch GitHub 仓库
+如果帮助很多人解决了问题,您就有可能成为 [FastAPI 的官方专家](fastapi-people.md#experts){.internal-link target=_blank}。🎉
-你可以在 GitHub 上 "watch" FastAPI(点击右上角的 "watch" 按钮):https://github.com/tiangolo/fastapi。
+## 监听 GitHub 资源库
-如果你选择的是 "Watching" 而不是 "Releases only" 选项,你会在其他人创建了新的 issue 时收到通知。
+您可以在 GitHub 上「监听」FastAPI(点击右上角的 "watch" 按钮): https://github.com/tiangolo/fastapi. 👀
-然后你可以尝试帮助他们解决这些 issue。
+如果您选择 "Watching" 而不是 "Releases only",有人创建新 Issue 时,您会接收到通知。
-## 创建 issue
+然后您就可以尝试并帮助他们解决问题。
-你可以在 GitHub 仓库中 创建一个新 issue 用来:
+## 创建 Issue
-* 报告 bug 或问题。
-* 提议新的特性。
-* 提问。
+您可以在 GitHub 资源库中创建 Issue,例如:
-## 创建 Pull Request
+* 提出**问题**或**意见**
+* 提出新**特性**建议
-你可以 创建一个 Pull Request 用来:
+**注意**:如果您创建 Issue,我会要求您也要帮助别的用户。😉
-* 纠正你在文档中发现的错别字。
-* 添加新的文档内容。
-* 修复已有的 bug 或问题。
-* 添加新的特性。
+## 创建 PR
+
+您可以创建 PR 为源代码做[贡献](contributing.md){.internal-link target=_blank},例如:
+
+* 修改文档错别字
+* 编辑这个文件,分享 FastAPI 的文章、视频、博客,不论是您自己的,还是您看到的都成
+ * 注意,添加的链接要放在对应区块的开头
+* [翻译文档](contributing.md#translations){.internal-link target=_blank}
+ * 审阅别人翻译的文档
+* 添加新的文档内容
+* 修复现有问题/Bug
+* 添加新功能
+
+## 加入聊天
+
+快加入 👥 Discord 聊天服务器 👥 和 FastAPI 社区里的小伙伴一起哈皮吧。
+
+!!! tip "提示"
+
+ 如有问题,请在 GitHub Issues 里提问,在这里更容易得到 [FastAPI 专家](fastapi-people.md#experts){.internal-link target=_blank}的帮助。
+
+ 聊天室仅供闲聊。
+
+我们之前还使用过 Gitter chat,但它不支持频道等高级功能,聊天也比较麻烦,所以现在推荐使用 Discord。
+
+### 别在聊天室里提问
+
+注意,聊天室更倾向于“闲聊”,经常有人会提出一些笼统得让人难以回答的问题,所以在这里提问一般没人回答。
+
+GitHub Issues 里提供了模板,指引您提出正确的问题,有利于获得优质的回答,甚至可能解决您还没有想到的问题。而且就算答疑解惑要耗费不少时间,我还是会尽量在 GitHub 里回答问题。但在聊天室里,我就没功夫这么做了。😅
+
+聊天室里的聊天内容也不如 GitHub 里好搜索,聊天里的问答很容易就找不到了。只有在 GitHub Issues 里的问答才能帮助您成为 [FastAPI 专家](fastapi-people.md#experts){.internal-link target=_blank},在 GitHub Issues 中为您带来更多关注。
+
+另一方面,聊天室里有成千上万的用户,在这里,您有很大可能遇到聊得来的人。😄
## 赞助作者
-你还可以通过 GitHub sponsors 在经济上支持作者(我)。
+您还可以通过 GitHub 赞助商资助本项目的作者(就是我)。
-这样你可以给我买杯咖啡☕️以示谢意😄。
+给我买杯咖啡 ☕️ 以示感谢 😄
+
+当然您也可以成为 FastAPI 的金牌或银牌赞助商。🏅🎉
+
+## 赞助 FastAPI 使用的工具
+
+如您在本文档中所见,FastAPI 站在巨人的肩膀上,它们分别是 Starlette 和 Pydantic。
+
+您还可以赞助:
+
+* Samuel Colvin (Pydantic)
+* Encode (Starlette, Uvicorn)
---
-感谢!
+谢谢!🚀
+
diff --git a/docs/zh/docs/tutorial/path-operation-configuration.md b/docs/zh/docs/tutorial/path-operation-configuration.md
new file mode 100644
index 000000000..ad0e08d30
--- /dev/null
+++ b/docs/zh/docs/tutorial/path-operation-configuration.md
@@ -0,0 +1,101 @@
+# 路径操作配置
+
+*路径操作装饰器*支持多种配置参数。
+
+!!! warning "警告"
+
+ 注意:以下参数应直接传递给**路径操作装饰器**,不能传递给*路径操作函数*。
+
+## `status_code` 状态码
+
+`status_code` 用于定义*路径操作*响应中的 HTTP 状态码。
+
+可以直接传递 `int` 代码, 比如 `404`。
+
+如果记不住数字码的涵义,也可以用 `status` 的快捷常量:
+
+```Python hl_lines="3 17"
+{!../../../docs_src/path_operation_configuration/tutorial001.py!}
+```
+
+状态码在响应中使用,并会被添加到 OpenAPI 概图。
+
+!!! note "技术细节"
+
+ 也可以使用 `from starlette import status` 导入状态码。
+
+ **FastAPI** 的`fastapi.status` 和 `starlette.status` 一样,只是快捷方式。实际上,`fastapi.status` 直接继承自 Starlette。
+
+## `tags` 参数
+
+`tags` 参数的值是由 `str` 组成的 `list` (一般只有一个 `str` ),`tags` 用于为*路径操作*添加标签:
+
+```Python hl_lines="17 22 27"
+{!../../../docs_src/path_operation_configuration/tutorial002.py!}
+```
+
+OpenAPI 概图会自动添加标签,供 API 文档接口使用:
+
+
+
+## `summary` 和 `description` 参数
+
+路径装饰器还支持 `summary` 和 `description` 这两个参数:
+
+```Python hl_lines="20-21"
+{!../../../docs_src/path_operation_configuration/tutorial003.py!}
+```
+
+## 文档字符串(`docstring`)
+
+描述内容比较长且占用多行时,可以在函数的 docstring 中声明*路径操作*的描述,**FastAPI** 支持从文档字符串中读取描述内容。
+
+文档字符串支持 Markdown,能正确解析和显示 Markdown 的内容,但要注意文档字符串的缩进。
+
+```Python hl_lines="19-27"
+{!../../../docs_src/path_operation_configuration/tutorial004.py!}
+```
+
+下图为 Markdown 文本在 API 文档中的显示效果:
+
+
+
+## 响应描述
+
+`response_description` 参数用于定义响应的描述说明:
+
+```Python hl_lines="21"
+{!../../../docs_src/path_operation_configuration/tutorial005.py!}
+```
+
+!!! info "说明"
+
+ 注意,`response_description` 只用于描述响应,`description` 一般则用于描述*路径操作*。
+
+!!! check "检查"
+
+ OpenAPI 规定每个*路径操作*都要有响应描述。
+
+ 如果没有定义响应描述,**FastAPI** 则自动生成内容为 "Successful response" 的响应描述。
+
+
+
+## 弃用*路径操作*
+
+`deprecated` 参数可以把*路径操作*标记为弃用,无需直接删除:
+
+```Python hl_lines="16"
+{!../../../docs_src/path_operation_configuration/tutorial006.py!}
+```
+
+API 文档会把该路径操作标记为弃用:
+
+
+
+下图显示了正常*路径操作*与弃用*路径操作* 的区别:
+
+
+
+## 小结
+
+通过传递参数给*路径操作装饰器* ,即可轻松地配置*路径操作*、添加元数据。
diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml
index 07ffa367f..1d050fddd 100644
--- a/docs/zh/mkdocs.yml
+++ b/docs/zh/mkdocs.yml
@@ -29,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -81,6 +78,7 @@ nav:
- tutorial/request-files.md
- tutorial/request-forms-and-files.md
- tutorial/handling-errors.md
+ - tutorial/path-operation-configuration.md
- tutorial/body-updates.md
- 依赖项:
- tutorial/dependencies/index.md
@@ -120,8 +118,12 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format ''
-- pymdownx.tabbed
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
diff --git a/docs_src/app_testing/app_b/__init__.py b/docs_src/app_testing/app_b/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/app_testing/main_b.py b/docs_src/app_testing/app_b/main.py
similarity index 100%
rename from docs_src/app_testing/main_b.py
rename to docs_src/app_testing/app_b/main.py
diff --git a/docs_src/app_testing/test_main_b.py b/docs_src/app_testing/app_b/test_main.py
similarity index 98%
rename from docs_src/app_testing/test_main_b.py
rename to docs_src/app_testing/app_b/test_main.py
index 83cc7d255..d186b8ecb 100644
--- a/docs_src/app_testing/test_main_b.py
+++ b/docs_src/app_testing/app_b/test_main.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from .main_b import app
+from .main import app
client = TestClient(app)
diff --git a/docs_src/app_testing/app_b_py310/__init__.py b/docs_src/app_testing/app_b_py310/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/app_testing/app_b_py310/main.py b/docs_src/app_testing/app_b_py310/main.py
new file mode 100644
index 000000000..d44ab9e7c
--- /dev/null
+++ b/docs_src/app_testing/app_b_py310/main.py
@@ -0,0 +1,36 @@
+from fastapi import FastAPI, Header, HTTPException
+from pydantic import BaseModel
+
+fake_secret_token = "coneofsilence"
+
+fake_db = {
+ "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
+ "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
+}
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ id: str
+ title: str
+ description: str | None = None
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_main(item_id: str, x_token: str = Header(...)):
+ if x_token != fake_secret_token:
+ raise HTTPException(status_code=400, detail="Invalid X-Token header")
+ if item_id not in fake_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+ return fake_db[item_id]
+
+
+@app.post("/items/", response_model=Item)
+async def create_item(item: Item, x_token: str = Header(...)):
+ if x_token != fake_secret_token:
+ raise HTTPException(status_code=400, detail="Invalid X-Token header")
+ if item.id in fake_db:
+ raise HTTPException(status_code=400, detail="Item already exists")
+ fake_db[item.id] = item
+ return item
diff --git a/docs_src/app_testing/app_b_py310/test_main.py b/docs_src/app_testing/app_b_py310/test_main.py
new file mode 100644
index 000000000..d186b8ecb
--- /dev/null
+++ b/docs_src/app_testing/app_b_py310/test_main.py
@@ -0,0 +1,65 @@
+from fastapi.testclient import TestClient
+
+from .main import app
+
+client = TestClient(app)
+
+
+def test_read_item():
+ response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "id": "foo",
+ "title": "Foo",
+ "description": "There goes my hero",
+ }
+
+
+def test_read_item_bad_token():
+ response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Invalid X-Token header"}
+
+
+def test_read_inexistent_item():
+ response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
+ assert response.status_code == 404
+ assert response.json() == {"detail": "Item not found"}
+
+
+def test_create_item():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "coneofsilence"},
+ json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "id": "foobar",
+ "title": "Foo Bar",
+ "description": "The Foo Barters",
+ }
+
+
+def test_create_item_bad_token():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "hailhydra"},
+ json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
+ )
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Invalid X-Token header"}
+
+
+def test_create_existing_item():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "coneofsilence"},
+ json={
+ "id": "foo",
+ "title": "The Foo ID Stealers",
+ "description": "There goes my stealer",
+ },
+ )
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Item already exists"}
diff --git a/docs_src/background_tasks/tutorial002_py310.py b/docs_src/background_tasks/tutorial002_py310.py
new file mode 100644
index 000000000..626af1358
--- /dev/null
+++ b/docs_src/background_tasks/tutorial002_py310.py
@@ -0,0 +1,24 @@
+from fastapi import BackgroundTasks, Depends, FastAPI
+
+app = FastAPI()
+
+
+def write_log(message: str):
+ with open("log.txt", mode="a") as log:
+ log.write(message)
+
+
+def get_query(background_tasks: BackgroundTasks, q: str | None = None):
+ if q:
+ message = f"found query: {q}\n"
+ background_tasks.add_task(write_log, message)
+ return q
+
+
+@app.post("/send-notification/{email}")
+async def send_notification(
+ email: str, background_tasks: BackgroundTasks, q: str = Depends(get_query)
+):
+ message = f"message to {email}\n"
+ background_tasks.add_task(write_log, message)
+ return {"message": "Message sent"}
diff --git a/docs_src/body/tutorial001_py310.py b/docs_src/body/tutorial001_py310.py
new file mode 100644
index 000000000..b71a52b62
--- /dev/null
+++ b/docs_src/body/tutorial001_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/body/tutorial002_py310.py b/docs_src/body/tutorial002_py310.py
new file mode 100644
index 000000000..8928b72b8
--- /dev/null
+++ b/docs_src/body/tutorial002_py310.py
@@ -0,0 +1,21 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+async def create_item(item: Item):
+ item_dict = item.dict()
+ if item.tax:
+ price_with_tax = item.price + item.tax
+ item_dict.update({"price_with_tax": price_with_tax})
+ return item_dict
diff --git a/docs_src/body/tutorial003_py310.py b/docs_src/body/tutorial003_py310.py
new file mode 100644
index 000000000..a936f28fd
--- /dev/null
+++ b/docs_src/body/tutorial003_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+app = FastAPI()
+
+
+@app.put("/items/{item_id}")
+async def create_item(item_id: int, item: Item):
+ return {"item_id": item_id, **item.dict()}
diff --git a/docs_src/body/tutorial004_py310.py b/docs_src/body/tutorial004_py310.py
new file mode 100644
index 000000000..60cfd9610
--- /dev/null
+++ b/docs_src/body/tutorial004_py310.py
@@ -0,0 +1,20 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+app = FastAPI()
+
+
+@app.put("/items/{item_id}")
+async def create_item(item_id: int, item: Item, q: str | None = None):
+ result = {"item_id": item_id, **item.dict()}
+ if q:
+ result.update({"q": q})
+ return result
diff --git a/docs_src/body_fields/tutorial001_py310.py b/docs_src/body_fields/tutorial001_py310.py
new file mode 100644
index 000000000..01e02a050
--- /dev/null
+++ b/docs_src/body_fields/tutorial001_py310.py
@@ -0,0 +1,19 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = Field(
+ None, title="The description of the item", max_length=300
+ )
+ price: float = Field(..., gt=0, description="The price must be greater than zero")
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item = Body(..., embed=True)):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial001_py310.py b/docs_src/body_multiple_params/tutorial001_py310.py
new file mode 100644
index 000000000..b08d397b3
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial001_py310.py
@@ -0,0 +1,26 @@
+from fastapi import FastAPI, Path
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000),
+ q: str | None = None,
+ item: Item | None = None,
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ if item:
+ results.update({"item": item})
+ return results
diff --git a/docs_src/body_multiple_params/tutorial002_py310.py b/docs_src/body_multiple_params/tutorial002_py310.py
new file mode 100644
index 000000000..2c4eb824c
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial002_py310.py
@@ -0,0 +1,22 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: str | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item, user: User):
+ results = {"item_id": item_id, "item": item, "user": user}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial003_py310.py b/docs_src/body_multiple_params/tutorial003_py310.py
new file mode 100644
index 000000000..9ddbda3f7
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial003_py310.py
@@ -0,0 +1,24 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: str | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: int, item: Item, user: User, importance: int = Body(...)
+):
+ results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial004_py310.py b/docs_src/body_multiple_params/tutorial004_py310.py
new file mode 100644
index 000000000..77321300e
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial004_py310.py
@@ -0,0 +1,31 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: str | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Item,
+ user: User,
+ importance: int = Body(..., gt=0),
+ q: str | None = None
+):
+ results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/body_multiple_params/tutorial005_py310.py b/docs_src/body_multiple_params/tutorial005_py310.py
new file mode 100644
index 000000000..97b213b16
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial005_py310.py
@@ -0,0 +1,17 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item = Body(..., embed=True)):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial001_py310.py b/docs_src/body_nested_models/tutorial001_py310.py
new file mode 100644
index 000000000..d89be35d4
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial001_py310.py
@@ -0,0 +1,18 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: list = []
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial002_py310.py b/docs_src/body_nested_models/tutorial002_py310.py
new file mode 100644
index 000000000..71340e9fd
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial002_py310.py
@@ -0,0 +1,18 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: list[str] = []
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial002_py39.py b/docs_src/body_nested_models/tutorial002_py39.py
new file mode 100644
index 000000000..af523a74e
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial002_py39.py
@@ -0,0 +1,20 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: list[str] = []
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial003_py310.py b/docs_src/body_nested_models/tutorial003_py310.py
new file mode 100644
index 000000000..194f2dc23
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial003_py310.py
@@ -0,0 +1,18 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial003_py39.py b/docs_src/body_nested_models/tutorial003_py39.py
new file mode 100644
index 000000000..931d92f88
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial003_py39.py
@@ -0,0 +1,20 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial004_py310.py b/docs_src/body_nested_models/tutorial004_py310.py
new file mode 100644
index 000000000..ab0b63db1
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial004_py310.py
@@ -0,0 +1,24 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: str
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = []
+ image: Image | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial004_py39.py b/docs_src/body_nested_models/tutorial004_py39.py
new file mode 100644
index 000000000..19985ec7a
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial004_py39.py
@@ -0,0 +1,26 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: str
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = []
+ image: Optional[Image] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial005_py310.py b/docs_src/body_nested_models/tutorial005_py310.py
new file mode 100644
index 000000000..009628453
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial005_py310.py
@@ -0,0 +1,24 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+ image: Image | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial005_py39.py b/docs_src/body_nested_models/tutorial005_py39.py
new file mode 100644
index 000000000..504551883
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial005_py39.py
@@ -0,0 +1,26 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+ image: Optional[Image] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial006_py310.py b/docs_src/body_nested_models/tutorial006_py310.py
new file mode 100644
index 000000000..c87cda3c8
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial006_py310.py
@@ -0,0 +1,24 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+ images: list[Image] | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial006_py39.py b/docs_src/body_nested_models/tutorial006_py39.py
new file mode 100644
index 000000000..61898178e
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial006_py39.py
@@ -0,0 +1,26 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+ images: Optional[list[Image]] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial007_py310.py b/docs_src/body_nested_models/tutorial007_py310.py
new file mode 100644
index 000000000..665ac56e5
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial007_py310.py
@@ -0,0 +1,30 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+ images: list[Image] | None = None
+
+
+class Offer(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ items: list[Item]
+
+
+@app.post("/offers/")
+async def create_offer(offer: Offer):
+ return offer
diff --git a/docs_src/body_nested_models/tutorial007_py39.py b/docs_src/body_nested_models/tutorial007_py39.py
new file mode 100644
index 000000000..0c7d32fbb
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial007_py39.py
@@ -0,0 +1,32 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+ images: Optional[list[Image]] = None
+
+
+class Offer(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ items: list[Item]
+
+
+@app.post("/offers/")
+async def create_offer(offer: Offer):
+ return offer
diff --git a/docs_src/body_nested_models/tutorial008_py39.py b/docs_src/body_nested_models/tutorial008_py39.py
new file mode 100644
index 000000000..854a7a5a4
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial008_py39.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+@app.post("/images/multiple/")
+async def create_multiple_images(images: list[Image]):
+ return images
diff --git a/docs_src/body_nested_models/tutorial009_py39.py b/docs_src/body_nested_models/tutorial009_py39.py
new file mode 100644
index 000000000..59c1e5082
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial009_py39.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.post("/index-weights/")
+async def create_index_weights(weights: dict[int, float]):
+ return weights
diff --git a/docs_src/body_updates/tutorial001_py310.py b/docs_src/body_updates/tutorial001_py310.py
new file mode 100644
index 000000000..d275d7f84
--- /dev/null
+++ b/docs_src/body_updates/tutorial001_py310.py
@@ -0,0 +1,32 @@
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str | None = None
+ description: str | None = None
+ price: float | None = None
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_item(item_id: str):
+ return items[item_id]
+
+
+@app.put("/items/{item_id}", response_model=Item)
+async def update_item(item_id: str, item: Item):
+ update_item_encoded = jsonable_encoder(item)
+ items[item_id] = update_item_encoded
+ return update_item_encoded
diff --git a/docs_src/body_updates/tutorial001_py39.py b/docs_src/body_updates/tutorial001_py39.py
new file mode 100644
index 000000000..5d5388b56
--- /dev/null
+++ b/docs_src/body_updates/tutorial001_py39.py
@@ -0,0 +1,34 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: Optional[str] = None
+ description: Optional[str] = None
+ price: Optional[float] = None
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_item(item_id: str):
+ return items[item_id]
+
+
+@app.put("/items/{item_id}", response_model=Item)
+async def update_item(item_id: str, item: Item):
+ update_item_encoded = jsonable_encoder(item)
+ items[item_id] = update_item_encoded
+ return update_item_encoded
diff --git a/docs_src/body_updates/tutorial002_py310.py b/docs_src/body_updates/tutorial002_py310.py
new file mode 100644
index 000000000..349841496
--- /dev/null
+++ b/docs_src/body_updates/tutorial002_py310.py
@@ -0,0 +1,35 @@
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str | None = None
+ description: str | None = None
+ price: float | None = None
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_item(item_id: str):
+ return items[item_id]
+
+
+@app.patch("/items/{item_id}", response_model=Item)
+async def update_item(item_id: str, item: Item):
+ stored_item_data = items[item_id]
+ stored_item_model = Item(**stored_item_data)
+ update_data = item.dict(exclude_unset=True)
+ updated_item = stored_item_model.copy(update=update_data)
+ items[item_id] = jsonable_encoder(updated_item)
+ return updated_item
diff --git a/docs_src/body_updates/tutorial002_py39.py b/docs_src/body_updates/tutorial002_py39.py
new file mode 100644
index 000000000..ab85bd5ae
--- /dev/null
+++ b/docs_src/body_updates/tutorial002_py39.py
@@ -0,0 +1,37 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: Optional[str] = None
+ description: Optional[str] = None
+ price: Optional[float] = None
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_item(item_id: str):
+ return items[item_id]
+
+
+@app.patch("/items/{item_id}", response_model=Item)
+async def update_item(item_id: str, item: Item):
+ stored_item_data = items[item_id]
+ stored_item_model = Item(**stored_item_data)
+ update_data = item.dict(exclude_unset=True)
+ updated_item = stored_item_model.copy(update=update_data)
+ items[item_id] = jsonable_encoder(updated_item)
+ return updated_item
diff --git a/docs_src/cookie_params/tutorial001_py310.py b/docs_src/cookie_params/tutorial001_py310.py
new file mode 100644
index 000000000..d0b004631
--- /dev/null
+++ b/docs_src/cookie_params/tutorial001_py310.py
@@ -0,0 +1,8 @@
+from fastapi import Cookie, FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(ads_id: str | None = Cookie(None)):
+ return {"ads_id": ads_id}
diff --git a/docs_src/dependencies/tutorial001_py310.py b/docs_src/dependencies/tutorial001_py310.py
new file mode 100644
index 000000000..662af9b1b
--- /dev/null
+++ b/docs_src/dependencies/tutorial001_py310.py
@@ -0,0 +1,17 @@
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+@app.get("/items/")
+async def read_items(commons: dict = Depends(common_parameters)):
+ return commons
+
+
+@app.get("/users/")
+async def read_users(commons: dict = Depends(common_parameters)):
+ return commons
diff --git a/docs_src/dependencies/tutorial002_py310.py b/docs_src/dependencies/tutorial002_py310.py
new file mode 100644
index 000000000..23c048ab9
--- /dev/null
+++ b/docs_src/dependencies/tutorial002_py310.py
@@ -0,0 +1,23 @@
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial003_py310.py b/docs_src/dependencies/tutorial003_py310.py
new file mode 100644
index 000000000..10d3771c1
--- /dev/null
+++ b/docs_src/dependencies/tutorial003_py310.py
@@ -0,0 +1,23 @@
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons=Depends(CommonQueryParams)):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial004_py310.py b/docs_src/dependencies/tutorial004_py310.py
new file mode 100644
index 000000000..5245bb0f6
--- /dev/null
+++ b/docs_src/dependencies/tutorial004_py310.py
@@ -0,0 +1,23 @@
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: CommonQueryParams = Depends()):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial005_py310.py b/docs_src/dependencies/tutorial005_py310.py
new file mode 100644
index 000000000..5e1d7e0ef
--- /dev/null
+++ b/docs_src/dependencies/tutorial005_py310.py
@@ -0,0 +1,20 @@
+from fastapi import Cookie, Depends, FastAPI
+
+app = FastAPI()
+
+
+def query_extractor(q: str | None = None):
+ return q
+
+
+def query_or_cookie_extractor(
+ q: str = Depends(query_extractor), last_query: str | None = Cookie(None)
+):
+ if not q:
+ return last_query
+ return q
+
+
+@app.get("/items/")
+async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
+ return {"q_or_cookie": query_or_default}
diff --git a/docs_src/encoder/tutorial001_py310.py b/docs_src/encoder/tutorial001_py310.py
new file mode 100644
index 000000000..5baf13628
--- /dev/null
+++ b/docs_src/encoder/tutorial001_py310.py
@@ -0,0 +1,22 @@
+from datetime import datetime
+
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+fake_db = {}
+
+
+class Item(BaseModel):
+ title: str
+ timestamp: datetime
+ description: str | None = None
+
+
+app = FastAPI()
+
+
+@app.put("/items/{id}")
+def update_item(id: str, item: Item):
+ json_compatible_item_data = jsonable_encoder(item)
+ fake_db[id] = json_compatible_item_data
diff --git a/docs_src/extending_openapi/tutorial003.py b/docs_src/extending_openapi/tutorial003.py
new file mode 100644
index 000000000..6c24ce758
--- /dev/null
+++ b/docs_src/extending_openapi/tutorial003.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI(swagger_ui_parameters={"syntaxHighlight": False})
+
+
+@app.get("/users/{username}")
+async def read_user(username: str):
+ return {"message": f"Hello {username}"}
diff --git a/docs_src/extending_openapi/tutorial004.py b/docs_src/extending_openapi/tutorial004.py
new file mode 100644
index 000000000..cc569ce45
--- /dev/null
+++ b/docs_src/extending_openapi/tutorial004.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI(swagger_ui_parameters={"syntaxHighlight.theme": "obsidian"})
+
+
+@app.get("/users/{username}")
+async def read_user(username: str):
+ return {"message": f"Hello {username}"}
diff --git a/docs_src/extending_openapi/tutorial005.py b/docs_src/extending_openapi/tutorial005.py
new file mode 100644
index 000000000..b4449f5c6
--- /dev/null
+++ b/docs_src/extending_openapi/tutorial005.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI(swagger_ui_parameters={"deepLinking": False})
+
+
+@app.get("/users/{username}")
+async def read_user(username: str):
+ return {"message": f"Hello {username}"}
diff --git a/docs_src/extra_data_types/tutorial001_py310.py b/docs_src/extra_data_types/tutorial001_py310.py
new file mode 100644
index 000000000..4a33481b7
--- /dev/null
+++ b/docs_src/extra_data_types/tutorial001_py310.py
@@ -0,0 +1,27 @@
+from datetime import datetime, time, timedelta
+from uuid import UUID
+
+from fastapi import Body, FastAPI
+
+app = FastAPI()
+
+
+@app.put("/items/{item_id}")
+async def read_items(
+ item_id: UUID,
+ start_datetime: datetime | None = Body(None),
+ end_datetime: datetime | None = Body(None),
+ repeat_at: time | None = Body(None),
+ process_after: timedelta | None = Body(None),
+):
+ start_process = start_datetime + process_after
+ duration = end_datetime - start_process
+ return {
+ "item_id": item_id,
+ "start_datetime": start_datetime,
+ "end_datetime": end_datetime,
+ "repeat_at": repeat_at,
+ "process_after": process_after,
+ "start_process": start_process,
+ "duration": duration,
+ }
diff --git a/docs_src/extra_models/tutorial001_py310.py b/docs_src/extra_models/tutorial001_py310.py
new file mode 100644
index 000000000..669386ae6
--- /dev/null
+++ b/docs_src/extra_models/tutorial001_py310.py
@@ -0,0 +1,41 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class UserIn(BaseModel):
+ username: str
+ password: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+class UserOut(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+class UserInDB(BaseModel):
+ username: str
+ hashed_password: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+def fake_password_hasher(raw_password: str):
+ return "supersecret" + raw_password
+
+
+def fake_save_user(user_in: UserIn):
+ hashed_password = fake_password_hasher(user_in.password)
+ user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
+ print("User saved! ..not really")
+ return user_in_db
+
+
+@app.post("/user/", response_model=UserOut)
+async def create_user(user_in: UserIn):
+ user_saved = fake_save_user(user_in)
+ return user_saved
diff --git a/docs_src/extra_models/tutorial002_py310.py b/docs_src/extra_models/tutorial002_py310.py
new file mode 100644
index 000000000..5b8ed7de3
--- /dev/null
+++ b/docs_src/extra_models/tutorial002_py310.py
@@ -0,0 +1,39 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class UserBase(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+class UserIn(UserBase):
+ password: str
+
+
+class UserOut(UserBase):
+ pass
+
+
+class UserInDB(UserBase):
+ hashed_password: str
+
+
+def fake_password_hasher(raw_password: str):
+ return "supersecret" + raw_password
+
+
+def fake_save_user(user_in: UserIn):
+ hashed_password = fake_password_hasher(user_in.password)
+ user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
+ print("User saved! ..not really")
+ return user_in_db
+
+
+@app.post("/user/", response_model=UserOut)
+async def create_user(user_in: UserIn):
+ user_saved = fake_save_user(user_in)
+ return user_saved
diff --git a/docs_src/extra_models/tutorial003_py310.py b/docs_src/extra_models/tutorial003_py310.py
new file mode 100644
index 000000000..065439acc
--- /dev/null
+++ b/docs_src/extra_models/tutorial003_py310.py
@@ -0,0 +1,35 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class BaseItem(BaseModel):
+ description: str
+ type: str
+
+
+class CarItem(BaseItem):
+ type = "car"
+
+
+class PlaneItem(BaseItem):
+ type = "plane"
+ size: int
+
+
+items = {
+ "item1": {"description": "All my friends drive a low rider", "type": "car"},
+ "item2": {
+ "description": "Music is my aeroplane, it's my aeroplane",
+ "type": "plane",
+ "size": 5,
+ },
+}
+
+
+@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem])
+async def read_item(item_id: str):
+ return items[item_id]
diff --git a/docs_src/extra_models/tutorial004_py39.py b/docs_src/extra_models/tutorial004_py39.py
new file mode 100644
index 000000000..28cacde4d
--- /dev/null
+++ b/docs_src/extra_models/tutorial004_py39.py
@@ -0,0 +1,20 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str
+
+
+items = [
+ {"name": "Foo", "description": "There comes my hero"},
+ {"name": "Red", "description": "It's my aeroplane"},
+]
+
+
+@app.get("/items/", response_model=list[Item])
+async def read_items():
+ return items
diff --git a/docs_src/extra_models/tutorial005_py39.py b/docs_src/extra_models/tutorial005_py39.py
new file mode 100644
index 000000000..9da2a0a0f
--- /dev/null
+++ b/docs_src/extra_models/tutorial005_py39.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/keyword-weights/", response_model=dict[str, float])
+async def read_keyword_weights():
+ return {"foo": 2.3, "bar": 3.4}
diff --git a/docs_src/header_params/tutorial001_py310.py b/docs_src/header_params/tutorial001_py310.py
new file mode 100644
index 000000000..b28463346
--- /dev/null
+++ b/docs_src/header_params/tutorial001_py310.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(user_agent: str | None = Header(None)):
+ return {"User-Agent": user_agent}
diff --git a/docs_src/header_params/tutorial002_py310.py b/docs_src/header_params/tutorial002_py310.py
new file mode 100644
index 000000000..98ab5a807
--- /dev/null
+++ b/docs_src/header_params/tutorial002_py310.py
@@ -0,0 +1,10 @@
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ strange_header: str | None = Header(None, convert_underscores=False)
+):
+ return {"strange_header": strange_header}
diff --git a/docs_src/header_params/tutorial003_py310.py b/docs_src/header_params/tutorial003_py310.py
new file mode 100644
index 000000000..2dac2c13c
--- /dev/null
+++ b/docs_src/header_params/tutorial003_py310.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(x_token: list[str] | None = Header(None)):
+ return {"X-Token values": x_token}
diff --git a/docs_src/header_params/tutorial003_py39.py b/docs_src/header_params/tutorial003_py39.py
new file mode 100644
index 000000000..359766527
--- /dev/null
+++ b/docs_src/header_params/tutorial003_py39.py
@@ -0,0 +1,10 @@
+from typing import Optional
+
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(x_token: Optional[list[str]] = Header(None)):
+ return {"X-Token values": x_token}
diff --git a/docs_src/path_operation_configuration/tutorial001.py b/docs_src/path_operation_configuration/tutorial001.py
index 66ea442ea..1316d9237 100644
--- a/docs_src/path_operation_configuration/tutorial001.py
+++ b/docs_src/path_operation_configuration/tutorial001.py
@@ -11,7 +11,7 @@ class Item(BaseModel):
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED)
diff --git a/docs_src/path_operation_configuration/tutorial001_py310.py b/docs_src/path_operation_configuration/tutorial001_py310.py
new file mode 100644
index 000000000..da078fdf5
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial001_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI, status
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial001_py39.py b/docs_src/path_operation_configuration/tutorial001_py39.py
new file mode 100644
index 000000000..5c04d8bac
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial001_py39.py
@@ -0,0 +1,19 @@
+from typing import Optional
+
+from fastapi import FastAPI, status
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial002.py b/docs_src/path_operation_configuration/tutorial002.py
index 01df041b8..2df537d86 100644
--- a/docs_src/path_operation_configuration/tutorial002.py
+++ b/docs_src/path_operation_configuration/tutorial002.py
@@ -11,7 +11,7 @@ class Item(BaseModel):
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post("/items/", response_model=Item, tags=["items"])
diff --git a/docs_src/path_operation_configuration/tutorial002_py310.py b/docs_src/path_operation_configuration/tutorial002_py310.py
new file mode 100644
index 000000000..9a8af5432
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial002_py310.py
@@ -0,0 +1,27 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, tags=["items"])
+async def create_item(item: Item):
+ return item
+
+
+@app.get("/items/", tags=["items"])
+async def read_items():
+ return [{"name": "Foo", "price": 42}]
+
+
+@app.get("/users/", tags=["users"])
+async def read_users():
+ return [{"username": "johndoe"}]
diff --git a/docs_src/path_operation_configuration/tutorial002_py39.py b/docs_src/path_operation_configuration/tutorial002_py39.py
new file mode 100644
index 000000000..766d9fb0b
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial002_py39.py
@@ -0,0 +1,29 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, tags=["items"])
+async def create_item(item: Item):
+ return item
+
+
+@app.get("/items/", tags=["items"])
+async def read_items():
+ return [{"name": "Foo", "price": 42}]
+
+
+@app.get("/users/", tags=["users"])
+async def read_users():
+ return [{"username": "johndoe"}]
diff --git a/docs_src/path_operation_configuration/tutorial002b.py b/docs_src/path_operation_configuration/tutorial002b.py
new file mode 100644
index 000000000..d53b4d817
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial002b.py
@@ -0,0 +1,20 @@
+from enum import Enum
+
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+class Tags(Enum):
+ items = "items"
+ users = "users"
+
+
+@app.get("/items/", tags=[Tags.items])
+async def get_items():
+ return ["Portal gun", "Plumbus"]
+
+
+@app.get("/users/", tags=[Tags.users])
+async def read_users():
+ return ["Rick", "Morty"]
diff --git a/docs_src/path_operation_configuration/tutorial003.py b/docs_src/path_operation_configuration/tutorial003.py
index 29bcb4a22..269a1a253 100644
--- a/docs_src/path_operation_configuration/tutorial003.py
+++ b/docs_src/path_operation_configuration/tutorial003.py
@@ -11,7 +11,7 @@ class Item(BaseModel):
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post(
diff --git a/docs_src/path_operation_configuration/tutorial003_py310.py b/docs_src/path_operation_configuration/tutorial003_py310.py
new file mode 100644
index 000000000..3d94afe2c
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial003_py310.py
@@ -0,0 +1,22 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.post(
+ "/items/",
+ response_model=Item,
+ summary="Create an item",
+ description="Create an item with all the information, name, description, price, tax and a set of unique tags",
+)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial003_py39.py b/docs_src/path_operation_configuration/tutorial003_py39.py
new file mode 100644
index 000000000..446198b5c
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial003_py39.py
@@ -0,0 +1,24 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.post(
+ "/items/",
+ response_model=Item,
+ summary="Create an item",
+ description="Create an item with all the information, name, description, price, tax and a set of unique tags",
+)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial004.py b/docs_src/path_operation_configuration/tutorial004.py
index ac95cc4bb..de83be836 100644
--- a/docs_src/path_operation_configuration/tutorial004.py
+++ b/docs_src/path_operation_configuration/tutorial004.py
@@ -11,7 +11,7 @@ class Item(BaseModel):
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post("/items/", response_model=Item, summary="Create an item")
diff --git a/docs_src/path_operation_configuration/tutorial004_py310.py b/docs_src/path_operation_configuration/tutorial004_py310.py
new file mode 100644
index 000000000..4cb8bdd43
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial004_py310.py
@@ -0,0 +1,26 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, summary="Create an item")
+async def create_item(item: Item):
+ """
+ Create an item with all the information:
+
+ - **name**: each item must have a name
+ - **description**: a long description
+ - **price**: required
+ - **tax**: if the item doesn't have tax, you can omit this
+ - **tags**: a set of unique tag strings for this item
+ """
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial004_py39.py b/docs_src/path_operation_configuration/tutorial004_py39.py
new file mode 100644
index 000000000..bf6005b95
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial004_py39.py
@@ -0,0 +1,28 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, summary="Create an item")
+async def create_item(item: Item):
+ """
+ Create an item with all the information:
+
+ - **name**: each item must have a name
+ - **description**: a long description
+ - **price**: required
+ - **tax**: if the item doesn't have tax, you can omit this
+ - **tags**: a set of unique tag strings for this item
+ """
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial005.py b/docs_src/path_operation_configuration/tutorial005.py
index c1b809887..0f62c3814 100644
--- a/docs_src/path_operation_configuration/tutorial005.py
+++ b/docs_src/path_operation_configuration/tutorial005.py
@@ -11,7 +11,7 @@ class Item(BaseModel):
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post(
diff --git a/docs_src/path_operation_configuration/tutorial005_py310.py b/docs_src/path_operation_configuration/tutorial005_py310.py
new file mode 100644
index 000000000..b176631d8
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial005_py310.py
@@ -0,0 +1,31 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.post(
+ "/items/",
+ response_model=Item,
+ summary="Create an item",
+ response_description="The created item",
+)
+async def create_item(item: Item):
+ """
+ Create an item with all the information:
+
+ - **name**: each item must have a name
+ - **description**: a long description
+ - **price**: required
+ - **tax**: if the item doesn't have tax, you can omit this
+ - **tags**: a set of unique tag strings for this item
+ """
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial005_py39.py b/docs_src/path_operation_configuration/tutorial005_py39.py
new file mode 100644
index 000000000..5ef320405
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial005_py39.py
@@ -0,0 +1,33 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.post(
+ "/items/",
+ response_model=Item,
+ summary="Create an item",
+ response_description="The created item",
+)
+async def create_item(item: Item):
+ """
+ Create an item with all the information:
+
+ - **name**: each item must have a name
+ - **description**: a long description
+ - **price**: required
+ - **tax**: if the item doesn't have tax, you can omit this
+ - **tags**: a set of unique tag strings for this item
+ """
+ return item
diff --git a/docs_src/path_params_numeric_validations/tutorial001_py310.py b/docs_src/path_params_numeric_validations/tutorial001_py310.py
new file mode 100644
index 000000000..b940a0949
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial001_py310.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI, Path, Query
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ item_id: int = Path(..., title="The ID of the item to get"),
+ q: str | None = Query(None, alias="item-query"),
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/python_types/tutorial006_py39.py b/docs_src/python_types/tutorial006_py39.py
new file mode 100644
index 000000000..486b67caf
--- /dev/null
+++ b/docs_src/python_types/tutorial006_py39.py
@@ -0,0 +1,3 @@
+def process_items(items: list[str]):
+ for item in items:
+ print(item)
diff --git a/docs_src/python_types/tutorial007_py39.py b/docs_src/python_types/tutorial007_py39.py
new file mode 100644
index 000000000..ea96c7964
--- /dev/null
+++ b/docs_src/python_types/tutorial007_py39.py
@@ -0,0 +1,2 @@
+def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
+ return items_t, items_s
diff --git a/docs_src/python_types/tutorial008_py39.py b/docs_src/python_types/tutorial008_py39.py
new file mode 100644
index 000000000..a393385b0
--- /dev/null
+++ b/docs_src/python_types/tutorial008_py39.py
@@ -0,0 +1,4 @@
+def process_items(prices: dict[str, float]):
+ for item_name, item_price in prices.items():
+ print(item_name)
+ print(item_price)
diff --git a/docs_src/python_types/tutorial008b.py b/docs_src/python_types/tutorial008b.py
new file mode 100644
index 000000000..e52539ead
--- /dev/null
+++ b/docs_src/python_types/tutorial008b.py
@@ -0,0 +1,5 @@
+from typing import Union
+
+
+def process_item(item: Union[int, str]):
+ print(item)
diff --git a/docs_src/python_types/tutorial008b_py310.py b/docs_src/python_types/tutorial008b_py310.py
new file mode 100644
index 000000000..6bb437841
--- /dev/null
+++ b/docs_src/python_types/tutorial008b_py310.py
@@ -0,0 +1,2 @@
+def process_item(item: int | str):
+ print(item)
diff --git a/docs_src/python_types/tutorial009_py310.py b/docs_src/python_types/tutorial009_py310.py
new file mode 100644
index 000000000..8464c6ff0
--- /dev/null
+++ b/docs_src/python_types/tutorial009_py310.py
@@ -0,0 +1,5 @@
+def say_hi(name: str | None = None):
+ if name is not None:
+ print(f"Hey {name}!")
+ else:
+ print("Hello World")
diff --git a/docs_src/python_types/tutorial009b.py b/docs_src/python_types/tutorial009b.py
new file mode 100644
index 000000000..9f1a05bc0
--- /dev/null
+++ b/docs_src/python_types/tutorial009b.py
@@ -0,0 +1,8 @@
+from typing import Union
+
+
+def say_hi(name: Union[str, None] = None):
+ if name is not None:
+ print(f"Hey {name}!")
+ else:
+ print("Hello World")
diff --git a/docs_src/python_types/tutorial011_py310.py b/docs_src/python_types/tutorial011_py310.py
new file mode 100644
index 000000000..7f173880f
--- /dev/null
+++ b/docs_src/python_types/tutorial011_py310.py
@@ -0,0 +1,22 @@
+from datetime import datetime
+
+from pydantic import BaseModel
+
+
+class User(BaseModel):
+ id: int
+ name = "John Doe"
+ signup_ts: datetime | None = None
+ friends: list[int] = []
+
+
+external_data = {
+ "id": "123",
+ "signup_ts": "2017-06-01 12:22",
+ "friends": [1, "2", b"3"],
+}
+user = User(**external_data)
+print(user)
+# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
+print(user.id)
+# > 123
diff --git a/docs_src/python_types/tutorial011_py39.py b/docs_src/python_types/tutorial011_py39.py
new file mode 100644
index 000000000..af79e2df0
--- /dev/null
+++ b/docs_src/python_types/tutorial011_py39.py
@@ -0,0 +1,23 @@
+from datetime import datetime
+from typing import Optional
+
+from pydantic import BaseModel
+
+
+class User(BaseModel):
+ id: int
+ name = "John Doe"
+ signup_ts: Optional[datetime] = None
+ friends: list[int] = []
+
+
+external_data = {
+ "id": "123",
+ "signup_ts": "2017-06-01 12:22",
+ "friends": [1, "2", b"3"],
+}
+user = User(**external_data)
+print(user)
+# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
+print(user.id)
+# > 123
diff --git a/docs_src/query_params/tutorial002_py310.py b/docs_src/query_params/tutorial002_py310.py
new file mode 100644
index 000000000..bedb58ce8
--- /dev/null
+++ b/docs_src/query_params/tutorial002_py310.py
@@ -0,0 +1,10 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_item(item_id: str, q: str | None = None):
+ if q:
+ return {"item_id": item_id, "q": q}
+ return {"item_id": item_id}
diff --git a/docs_src/query_params/tutorial003_py310.py b/docs_src/query_params/tutorial003_py310.py
new file mode 100644
index 000000000..1eec66d90
--- /dev/null
+++ b/docs_src/query_params/tutorial003_py310.py
@@ -0,0 +1,15 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_item(item_id: str, q: str | None = None, short: bool = False):
+ item = {"item_id": item_id}
+ if q:
+ item.update({"q": q})
+ if not short:
+ item.update(
+ {"description": "This is an amazing item that has a long description"}
+ )
+ return item
diff --git a/docs_src/query_params/tutorial004_py310.py b/docs_src/query_params/tutorial004_py310.py
new file mode 100644
index 000000000..8ec4338df
--- /dev/null
+++ b/docs_src/query_params/tutorial004_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/users/{user_id}/items/{item_id}")
+async def read_user_item(
+ user_id: int, item_id: str, q: str | None = None, short: bool = False
+):
+ item = {"item_id": item_id, "owner_id": user_id}
+ if q:
+ item.update({"q": q})
+ if not short:
+ item.update(
+ {"description": "This is an amazing item that has a long description"}
+ )
+ return item
diff --git a/docs_src/query_params/tutorial006_py310.py b/docs_src/query_params/tutorial006_py310.py
new file mode 100644
index 000000000..be2a44c31
--- /dev/null
+++ b/docs_src/query_params/tutorial006_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_user_item(
+ item_id: str, needy: str, skip: int = 0, limit: int | None = None
+):
+ item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
+ return item
diff --git a/docs_src/query_params/tutorial006b.py b/docs_src/query_params/tutorial006b.py
new file mode 100644
index 000000000..f0dbfe08f
--- /dev/null
+++ b/docs_src/query_params/tutorial006b.py
@@ -0,0 +1,13 @@
+from typing import Union
+
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_user_item(
+ item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None
+):
+ item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
+ return item
diff --git a/docs_src/query_params_str_validations/tutorial001_py310.py b/docs_src/query_params_str_validations/tutorial001_py310.py
new file mode 100644
index 000000000..eeefddfa7
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial001_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: str | None = None):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial002_py310.py b/docs_src/query_params_str_validations/tutorial002_py310.py
new file mode 100644
index 000000000..fa3139d5a
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial002_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: str | None = Query(None, max_length=50)):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial003_py310.py b/docs_src/query_params_str_validations/tutorial003_py310.py
new file mode 100644
index 000000000..335858a40
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial003_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: str | None = Query(None, min_length=3, max_length=50)):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial004_py310.py b/docs_src/query_params_str_validations/tutorial004_py310.py
new file mode 100644
index 000000000..518b779f7
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial004_py310.py
@@ -0,0 +1,13 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: str | None = Query(None, min_length=3, max_length=50, regex="^fixedquery$")
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial007_py310.py b/docs_src/query_params_str_validations/tutorial007_py310.py
new file mode 100644
index 000000000..14ef4cb69
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial007_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: str | None = Query(None, title="Query string", min_length=3)):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial008_py310.py b/docs_src/query_params_str_validations/tutorial008_py310.py
new file mode 100644
index 000000000..06bb02442
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial008_py310.py
@@ -0,0 +1,19 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: str
+ | None = Query(
+ None,
+ title="Query string",
+ description="Query string for the items to search in the database that have a good match",
+ min_length=3,
+ )
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial009_py310.py b/docs_src/query_params_str_validations/tutorial009_py310.py
new file mode 100644
index 000000000..e84c116f1
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial009_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: str | None = Query(None, alias="item-query")):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial010_py310.py b/docs_src/query_params_str_validations/tutorial010_py310.py
new file mode 100644
index 000000000..c35800858
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial010_py310.py
@@ -0,0 +1,23 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: str
+ | None = Query(
+ None,
+ alias="item-query",
+ title="Query string",
+ description="Query string for the items to search in the database that have a good match",
+ min_length=3,
+ max_length=50,
+ regex="^fixedquery$",
+ deprecated=True,
+ )
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial011_py310.py b/docs_src/query_params_str_validations/tutorial011_py310.py
new file mode 100644
index 000000000..c3d992e62
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial011_py310.py
@@ -0,0 +1,9 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: list[str] | None = Query(None)):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial011_py39.py b/docs_src/query_params_str_validations/tutorial011_py39.py
new file mode 100644
index 000000000..38ba764d6
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial011_py39.py
@@ -0,0 +1,11 @@
+from typing import Optional
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Optional[list[str]] = Query(None)):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial012_py39.py b/docs_src/query_params_str_validations/tutorial012_py39.py
new file mode 100644
index 000000000..1900133d9
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial012_py39.py
@@ -0,0 +1,9 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: list[str] = Query(["foo", "bar"])):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial014.py b/docs_src/query_params_str_validations/tutorial014.py
new file mode 100644
index 000000000..fb50bc27b
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial014.py
@@ -0,0 +1,15 @@
+from typing import Optional
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ hidden_query: Optional[str] = Query(None, include_in_schema=False)
+):
+ if hidden_query:
+ return {"hidden_query": hidden_query}
+ else:
+ return {"hidden_query": "Not found"}
diff --git a/docs_src/query_params_str_validations/tutorial014_py310.py b/docs_src/query_params_str_validations/tutorial014_py310.py
new file mode 100644
index 000000000..7ae39c7f9
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial014_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(hidden_query: str | None = Query(None, include_in_schema=False)):
+ if hidden_query:
+ return {"hidden_query": hidden_query}
+ else:
+ return {"hidden_query": "Not found"}
diff --git a/docs_src/request_files/tutorial001.py b/docs_src/request_files/tutorial001.py
index fffb56af8..0fb1dd571 100644
--- a/docs_src/request_files/tutorial001.py
+++ b/docs_src/request_files/tutorial001.py
@@ -9,5 +9,5 @@ async def create_file(file: bytes = File(...)):
@app.post("/uploadfile/")
-async def create_upload_file(file: UploadFile = File(...)):
+async def create_upload_file(file: UploadFile):
return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_02.py b/docs_src/request_files/tutorial001_02.py
new file mode 100644
index 000000000..26a4c9cbf
--- /dev/null
+++ b/docs_src/request_files/tutorial001_02.py
@@ -0,0 +1,21 @@
+from typing import Optional
+
+from fastapi import FastAPI, File, UploadFile
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: Optional[bytes] = File(None)):
+ if not file:
+ return {"message": "No file sent"}
+ else:
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(file: Optional[UploadFile] = None):
+ if not file:
+ return {"message": "No upload file sent"}
+ else:
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_02_py310.py b/docs_src/request_files/tutorial001_02_py310.py
new file mode 100644
index 000000000..0e576251b
--- /dev/null
+++ b/docs_src/request_files/tutorial001_02_py310.py
@@ -0,0 +1,19 @@
+from fastapi import FastAPI, File, UploadFile
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: bytes | None = File(None)):
+ if not file:
+ return {"message": "No file sent"}
+ else:
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(file: UploadFile | None = None):
+ if not file:
+ return {"message": "No upload file sent"}
+ else:
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_03.py b/docs_src/request_files/tutorial001_03.py
new file mode 100644
index 000000000..abcac9e4c
--- /dev/null
+++ b/docs_src/request_files/tutorial001_03.py
@@ -0,0 +1,15 @@
+from fastapi import FastAPI, File, UploadFile
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: bytes = File(..., description="A file read as bytes")):
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(
+ file: UploadFile = File(..., description="A file read as UploadFile")
+):
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial002.py b/docs_src/request_files/tutorial002.py
index 6fdf16a75..94abb7c6c 100644
--- a/docs_src/request_files/tutorial002.py
+++ b/docs_src/request_files/tutorial002.py
@@ -12,7 +12,7 @@ async def create_files(files: List[bytes] = File(...)):
@app.post("/uploadfiles/")
-async def create_upload_files(files: List[UploadFile] = File(...)):
+async def create_upload_files(files: List[UploadFile]):
return {"filenames": [file.filename for file in files]}
diff --git a/docs_src/request_files/tutorial002_py39.py b/docs_src/request_files/tutorial002_py39.py
new file mode 100644
index 000000000..2779618bd
--- /dev/null
+++ b/docs_src/request_files/tutorial002_py39.py
@@ -0,0 +1,31 @@
+from fastapi import FastAPI, File, UploadFile
+from fastapi.responses import HTMLResponse
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_files(files: list[bytes] = File(...)):
+ return {"file_sizes": [len(file) for file in files]}
+
+
+@app.post("/uploadfiles/")
+async def create_upload_files(files: list[UploadFile]):
+ return {"filenames": [file.filename for file in files]}
+
+
+@app.get("/")
+async def main():
+ content = """
+
+
+
+
+ """
+ return HTMLResponse(content=content)
diff --git a/docs_src/request_files/tutorial003.py b/docs_src/request_files/tutorial003.py
new file mode 100644
index 000000000..4a91b7a8b
--- /dev/null
+++ b/docs_src/request_files/tutorial003.py
@@ -0,0 +1,37 @@
+from typing import List
+
+from fastapi import FastAPI, File, UploadFile
+from fastapi.responses import HTMLResponse
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_files(
+ files: List[bytes] = File(..., description="Multiple files as bytes")
+):
+ return {"file_sizes": [len(file) for file in files]}
+
+
+@app.post("/uploadfiles/")
+async def create_upload_files(
+ files: List[UploadFile] = File(..., description="Multiple files as UploadFile")
+):
+ return {"filenames": [file.filename for file in files]}
+
+
+@app.get("/")
+async def main():
+ content = """
+
+
+
+
+ """
+ return HTMLResponse(content=content)
diff --git a/docs_src/request_files/tutorial003_py39.py b/docs_src/request_files/tutorial003_py39.py
new file mode 100644
index 000000000..d853f48d1
--- /dev/null
+++ b/docs_src/request_files/tutorial003_py39.py
@@ -0,0 +1,35 @@
+from fastapi import FastAPI, File, UploadFile
+from fastapi.responses import HTMLResponse
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_files(
+ files: list[bytes] = File(..., description="Multiple files as bytes")
+):
+ return {"file_sizes": [len(file) for file in files]}
+
+
+@app.post("/uploadfiles/")
+async def create_upload_files(
+ files: list[UploadFile] = File(..., description="Multiple files as UploadFile")
+):
+ return {"filenames": [file.filename for file in files]}
+
+
+@app.get("/")
+async def main():
+ content = """
+
+
+
+
+ """
+ return HTMLResponse(content=content)
diff --git a/docs_src/response_model/tutorial001_py310.py b/docs_src/response_model/tutorial001_py310.py
new file mode 100644
index 000000000..59efecde4
--- /dev/null
+++ b/docs_src/response_model/tutorial001_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: list[str] = []
+
+
+@app.post("/items/", response_model=Item)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/response_model/tutorial001_py39.py b/docs_src/response_model/tutorial001_py39.py
new file mode 100644
index 000000000..37b866864
--- /dev/null
+++ b/docs_src/response_model/tutorial001_py39.py
@@ -0,0 +1,19 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: list[str] = []
+
+
+@app.post("/items/", response_model=Item)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/response_model/tutorial002_py310.py b/docs_src/response_model/tutorial002_py310.py
new file mode 100644
index 000000000..29ab9c9d2
--- /dev/null
+++ b/docs_src/response_model/tutorial002_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class UserIn(BaseModel):
+ username: str
+ password: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+# Don't do this in production!
+@app.post("/user/", response_model=UserIn)
+async def create_user(user: UserIn):
+ return user
diff --git a/docs_src/response_model/tutorial003_py310.py b/docs_src/response_model/tutorial003_py310.py
new file mode 100644
index 000000000..fc9693e3c
--- /dev/null
+++ b/docs_src/response_model/tutorial003_py310.py
@@ -0,0 +1,22 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class UserIn(BaseModel):
+ username: str
+ password: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+class UserOut(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+@app.post("/user/", response_model=UserOut)
+async def create_user(user: UserIn):
+ return user
diff --git a/docs_src/response_model/tutorial004_py310.py b/docs_src/response_model/tutorial004_py310.py
new file mode 100644
index 000000000..a3e21b434
--- /dev/null
+++ b/docs_src/response_model/tutorial004_py310.py
@@ -0,0 +1,24 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
+async def read_item(item_id: str):
+ return items[item_id]
diff --git a/docs_src/response_model/tutorial004_py39.py b/docs_src/response_model/tutorial004_py39.py
new file mode 100644
index 000000000..07ccbbf41
--- /dev/null
+++ b/docs_src/response_model/tutorial004_py39.py
@@ -0,0 +1,26 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
+async def read_item(item_id: str):
+ return items[item_id]
diff --git a/docs_src/response_model/tutorial005_py310.py b/docs_src/response_model/tutorial005_py310.py
new file mode 100644
index 000000000..acb3035b9
--- /dev/null
+++ b/docs_src/response_model/tutorial005_py310.py
@@ -0,0 +1,37 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float = 10.5
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
+ "baz": {
+ "name": "Baz",
+ "description": "There goes my baz",
+ "price": 50.2,
+ "tax": 10.5,
+ },
+}
+
+
+@app.get(
+ "/items/{item_id}/name",
+ response_model=Item,
+ response_model_include={"name", "description"},
+)
+async def read_item_name(item_id: str):
+ return items[item_id]
+
+
+@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
+async def read_item_public_data(item_id: str):
+ return items[item_id]
diff --git a/docs_src/response_model/tutorial006_py310.py b/docs_src/response_model/tutorial006_py310.py
new file mode 100644
index 000000000..9ccec324e
--- /dev/null
+++ b/docs_src/response_model/tutorial006_py310.py
@@ -0,0 +1,37 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float = 10.5
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
+ "baz": {
+ "name": "Baz",
+ "description": "There goes my baz",
+ "price": 50.2,
+ "tax": 10.5,
+ },
+}
+
+
+@app.get(
+ "/items/{item_id}/name",
+ response_model=Item,
+ response_model_include=["name", "description"],
+)
+async def read_item_name(item_id: str):
+ return items[item_id]
+
+
+@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude=["tax"])
+async def read_item_public_data(item_id: str):
+ return items[item_id]
diff --git a/docs_src/schema_extra_example/tutorial001_py310.py b/docs_src/schema_extra_example/tutorial001_py310.py
new file mode 100644
index 000000000..77ceedd60
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial001_py310.py
@@ -0,0 +1,27 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+ class Config:
+ schema_extra = {
+ "example": {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ }
+ }
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial002_py310.py b/docs_src/schema_extra_example/tutorial002_py310.py
new file mode 100644
index 000000000..4f8f8304e
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial002_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str = Field(..., example="Foo")
+ description: str | None = Field(None, example="A very nice Item")
+ price: float = Field(..., example=35.4)
+ tax: float | None = Field(None, example=3.2)
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial003_py310.py b/docs_src/schema_extra_example/tutorial003_py310.py
new file mode 100644
index 000000000..cf4c99dc0
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial003_py310.py
@@ -0,0 +1,28 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: int,
+ item: Item = Body(
+ ...,
+ example={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ ),
+):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial004_py310.py b/docs_src/schema_extra_example/tutorial004_py310.py
new file mode 100644
index 000000000..6f29c1a5c
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial004_py310.py
@@ -0,0 +1,50 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Item = Body(
+ ...,
+ examples={
+ "normal": {
+ "summary": "A normal example",
+ "description": "A **normal** item works correctly.",
+ "value": {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ },
+ "converted": {
+ "summary": "An example with converted data",
+ "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
+ "value": {
+ "name": "Bar",
+ "price": "35.4",
+ },
+ },
+ "invalid": {
+ "summary": "Invalid data is rejected with an error",
+ "value": {
+ "name": "Baz",
+ "price": "thirty five point four",
+ },
+ },
+ },
+ ),
+):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/security/tutorial002_py310.py b/docs_src/security/tutorial002_py310.py
new file mode 100644
index 000000000..3c2018f54
--- /dev/null
+++ b/docs_src/security/tutorial002_py310.py
@@ -0,0 +1,30 @@
+from fastapi import Depends, FastAPI
+from fastapi.security import OAuth2PasswordBearer
+from pydantic import BaseModel
+
+app = FastAPI()
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+def fake_decode_token(token):
+ return User(
+ username=token + "fakedecoded", email="john@example.com", full_name="John Doe"
+ )
+
+
+async def get_current_user(token: str = Depends(oauth2_scheme)):
+ user = fake_decode_token(token)
+ return user
+
+
+@app.get("/users/me")
+async def read_users_me(current_user: User = Depends(get_current_user)):
+ return current_user
diff --git a/docs_src/security/tutorial003_py310.py b/docs_src/security/tutorial003_py310.py
new file mode 100644
index 000000000..af935e997
--- /dev/null
+++ b/docs_src/security/tutorial003_py310.py
@@ -0,0 +1,88 @@
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from pydantic import BaseModel
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "fakehashedsecret",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Wonderson",
+ "email": "alice@example.com",
+ "hashed_password": "fakehashedsecret2",
+ "disabled": True,
+ },
+}
+
+app = FastAPI()
+
+
+def fake_hash_password(password: str):
+ return "fakehashed" + password
+
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def fake_decode_token(token):
+ # This doesn't provide any security at all
+ # Check the next version
+ user = get_user(fake_users_db, token)
+ return user
+
+
+async def get_current_user(token: str = Depends(oauth2_scheme)):
+ user = fake_decode_token(token)
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Invalid authentication credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ return user
+
+
+async def get_current_active_user(current_user: User = Depends(get_current_user)):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token")
+async def login(form_data: OAuth2PasswordRequestForm = Depends()):
+ user_dict = fake_users_db.get(form_data.username)
+ if not user_dict:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ user = UserInDB(**user_dict)
+ hashed_password = fake_hash_password(form_data.password)
+ if not hashed_password == user.hashed_password:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+
+ return {"access_token": user.username, "token_type": "bearer"}
+
+
+@app.get("/users/me")
+async def read_users_me(current_user: User = Depends(get_current_active_user)):
+ return current_user
diff --git a/docs_src/security/tutorial004_py310.py b/docs_src/security/tutorial004_py310.py
new file mode 100644
index 000000000..797d56d04
--- /dev/null
+++ b/docs_src/security/tutorial004_py310.py
@@ -0,0 +1,137 @@
+from datetime import datetime, timedelta
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ }
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: str | None = None
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: timedelta | None = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(token: str = Depends(oauth2_scheme)):
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_data = TokenData(username=username)
+ except JWTError:
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ return user
+
+
+async def get_current_active_user(current_user: User = Depends(get_current_user)):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Incorrect username or password",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username}, expires_delta=access_token_expires
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(current_user: User = Depends(get_current_active_user)):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(current_user: User = Depends(get_current_active_user)):
+ return [{"item_id": "Foo", "owner": current_user.username}]
diff --git a/docs_src/security/tutorial005_py310.py b/docs_src/security/tutorial005_py310.py
new file mode 100644
index 000000000..c6a095d2c
--- /dev/null
+++ b/docs_src/security/tutorial005_py310.py
@@ -0,0 +1,172 @@
+from datetime import datetime, timedelta
+
+from fastapi import Depends, FastAPI, HTTPException, Security, status
+from fastapi.security import (
+ OAuth2PasswordBearer,
+ OAuth2PasswordRequestForm,
+ SecurityScopes,
+)
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel, ValidationError
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Chains",
+ "email": "alicechains@example.com",
+ "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
+ "disabled": True,
+ },
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: str | None = None
+ scopes: list[str] = []
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(
+ tokenUrl="token",
+ scopes={"me": "Read information about the current user.", "items": "Read items."},
+)
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: timedelta | None = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(
+ security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
+):
+ if security_scopes.scopes:
+ authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
+ else:
+ authenticate_value = f"Bearer"
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_scopes = payload.get("scopes", [])
+ token_data = TokenData(scopes=token_scopes, username=username)
+ except (JWTError, ValidationError):
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ for scope in security_scopes.scopes:
+ if scope not in token_data.scopes:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Not enough permissions",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ return user
+
+
+async def get_current_active_user(
+ current_user: User = Security(get_current_user, scopes=["me"])
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username, "scopes": form_data.scopes},
+ expires_delta=access_token_expires,
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(current_user: User = Depends(get_current_active_user)):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(
+ current_user: User = Security(get_current_active_user, scopes=["items"])
+):
+ return [{"item_id": "Foo", "owner": current_user.username}]
+
+
+@app.get("/status/")
+async def read_system_status(current_user: User = Depends(get_current_user)):
+ return {"status": "ok"}
diff --git a/docs_src/security/tutorial005_py39.py b/docs_src/security/tutorial005_py39.py
new file mode 100644
index 000000000..d45c08ce6
--- /dev/null
+++ b/docs_src/security/tutorial005_py39.py
@@ -0,0 +1,173 @@
+from datetime import datetime, timedelta
+from typing import Optional
+
+from fastapi import Depends, FastAPI, HTTPException, Security, status
+from fastapi.security import (
+ OAuth2PasswordBearer,
+ OAuth2PasswordRequestForm,
+ SecurityScopes,
+)
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel, ValidationError
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Chains",
+ "email": "alicechains@example.com",
+ "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
+ "disabled": True,
+ },
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: Optional[str] = None
+ scopes: list[str] = []
+
+
+class User(BaseModel):
+ username: str
+ email: Optional[str] = None
+ full_name: Optional[str] = None
+ disabled: Optional[bool] = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(
+ tokenUrl="token",
+ scopes={"me": "Read information about the current user.", "items": "Read items."},
+)
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(
+ security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
+):
+ if security_scopes.scopes:
+ authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
+ else:
+ authenticate_value = f"Bearer"
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_scopes = payload.get("scopes", [])
+ token_data = TokenData(scopes=token_scopes, username=username)
+ except (JWTError, ValidationError):
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ for scope in security_scopes.scopes:
+ if scope not in token_data.scopes:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Not enough permissions",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ return user
+
+
+async def get_current_active_user(
+ current_user: User = Security(get_current_user, scopes=["me"])
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username, "scopes": form_data.scopes},
+ expires_delta=access_token_expires,
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(current_user: User = Depends(get_current_active_user)):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(
+ current_user: User = Security(get_current_active_user, scopes=["items"])
+):
+ return [{"item_id": "Foo", "owner": current_user.username}]
+
+
+@app.get("/status/")
+async def read_system_status(current_user: User = Depends(get_current_user)):
+ return {"status": "ok"}
diff --git a/docs_src/sql_databases/sql_app_py310/__init__.py b/docs_src/sql_databases/sql_app_py310/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/sql_databases/sql_app_py310/alt_main.py b/docs_src/sql_databases/sql_app_py310/alt_main.py
new file mode 100644
index 000000000..5de88ec3a
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/alt_main.py
@@ -0,0 +1,60 @@
+from fastapi import Depends, FastAPI, HTTPException, Request, Response
+from sqlalchemy.orm import Session
+
+from . import crud, models, schemas
+from .database import SessionLocal, engine
+
+models.Base.metadata.create_all(bind=engine)
+
+app = FastAPI()
+
+
+@app.middleware("http")
+async def db_session_middleware(request: Request, call_next):
+ response = Response("Internal server error", status_code=500)
+ try:
+ request.state.db = SessionLocal()
+ response = await call_next(request)
+ finally:
+ request.state.db.close()
+ return response
+
+
+# Dependency
+def get_db(request: Request):
+ return request.state.db
+
+
+@app.post("/users/", response_model=schemas.User)
+def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
+ db_user = crud.get_user_by_email(db, email=user.email)
+ if db_user:
+ raise HTTPException(status_code=400, detail="Email already registered")
+ return crud.create_user(db=db, user=user)
+
+
+@app.get("/users/", response_model=list[schemas.User])
+def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ users = crud.get_users(db, skip=skip, limit=limit)
+ return users
+
+
+@app.get("/users/{user_id}", response_model=schemas.User)
+def read_user(user_id: int, db: Session = Depends(get_db)):
+ db_user = crud.get_user(db, user_id=user_id)
+ if db_user is None:
+ raise HTTPException(status_code=404, detail="User not found")
+ return db_user
+
+
+@app.post("/users/{user_id}/items/", response_model=schemas.Item)
+def create_item_for_user(
+ user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
+):
+ return crud.create_user_item(db=db, item=item, user_id=user_id)
+
+
+@app.get("/items/", response_model=list[schemas.Item])
+def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ items = crud.get_items(db, skip=skip, limit=limit)
+ return items
diff --git a/docs_src/sql_databases/sql_app_py310/crud.py b/docs_src/sql_databases/sql_app_py310/crud.py
new file mode 100644
index 000000000..679acdb5c
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/crud.py
@@ -0,0 +1,36 @@
+from sqlalchemy.orm import Session
+
+from . import models, schemas
+
+
+def get_user(db: Session, user_id: int):
+ return db.query(models.User).filter(models.User.id == user_id).first()
+
+
+def get_user_by_email(db: Session, email: str):
+ return db.query(models.User).filter(models.User.email == email).first()
+
+
+def get_users(db: Session, skip: int = 0, limit: int = 100):
+ return db.query(models.User).offset(skip).limit(limit).all()
+
+
+def create_user(db: Session, user: schemas.UserCreate):
+ fake_hashed_password = user.password + "notreallyhashed"
+ db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
+ db.add(db_user)
+ db.commit()
+ db.refresh(db_user)
+ return db_user
+
+
+def get_items(db: Session, skip: int = 0, limit: int = 100):
+ return db.query(models.Item).offset(skip).limit(limit).all()
+
+
+def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
+ db_item = models.Item(**item.dict(), owner_id=user_id)
+ db.add(db_item)
+ db.commit()
+ db.refresh(db_item)
+ return db_item
diff --git a/docs_src/sql_databases/sql_app_py310/database.py b/docs_src/sql_databases/sql_app_py310/database.py
new file mode 100644
index 000000000..45a8b9f69
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/database.py
@@ -0,0 +1,13 @@
+from sqlalchemy import create_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+
+SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
+# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
+
+engine = create_engine(
+ SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+)
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+Base = declarative_base()
diff --git a/docs_src/sql_databases/sql_app_py310/main.py b/docs_src/sql_databases/sql_app_py310/main.py
new file mode 100644
index 000000000..a9856d0b6
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/main.py
@@ -0,0 +1,53 @@
+from fastapi import Depends, FastAPI, HTTPException
+from sqlalchemy.orm import Session
+
+from . import crud, models, schemas
+from .database import SessionLocal, engine
+
+models.Base.metadata.create_all(bind=engine)
+
+app = FastAPI()
+
+
+# Dependency
+def get_db():
+ db = SessionLocal()
+ try:
+ yield db
+ finally:
+ db.close()
+
+
+@app.post("/users/", response_model=schemas.User)
+def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
+ db_user = crud.get_user_by_email(db, email=user.email)
+ if db_user:
+ raise HTTPException(status_code=400, detail="Email already registered")
+ return crud.create_user(db=db, user=user)
+
+
+@app.get("/users/", response_model=list[schemas.User])
+def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ users = crud.get_users(db, skip=skip, limit=limit)
+ return users
+
+
+@app.get("/users/{user_id}", response_model=schemas.User)
+def read_user(user_id: int, db: Session = Depends(get_db)):
+ db_user = crud.get_user(db, user_id=user_id)
+ if db_user is None:
+ raise HTTPException(status_code=404, detail="User not found")
+ return db_user
+
+
+@app.post("/users/{user_id}/items/", response_model=schemas.Item)
+def create_item_for_user(
+ user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
+):
+ return crud.create_user_item(db=db, item=item, user_id=user_id)
+
+
+@app.get("/items/", response_model=list[schemas.Item])
+def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ items = crud.get_items(db, skip=skip, limit=limit)
+ return items
diff --git a/docs_src/sql_databases/sql_app_py310/models.py b/docs_src/sql_databases/sql_app_py310/models.py
new file mode 100644
index 000000000..62d8ab4aa
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/models.py
@@ -0,0 +1,26 @@
+from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
+from sqlalchemy.orm import relationship
+
+from .database import Base
+
+
+class User(Base):
+ __tablename__ = "users"
+
+ id = Column(Integer, primary_key=True, index=True)
+ email = Column(String, unique=True, index=True)
+ hashed_password = Column(String)
+ is_active = Column(Boolean, default=True)
+
+ items = relationship("Item", back_populates="owner")
+
+
+class Item(Base):
+ __tablename__ = "items"
+
+ id = Column(Integer, primary_key=True, index=True)
+ title = Column(String, index=True)
+ description = Column(String, index=True)
+ owner_id = Column(Integer, ForeignKey("users.id"))
+
+ owner = relationship("User", back_populates="items")
diff --git a/docs_src/sql_databases/sql_app_py310/schemas.py b/docs_src/sql_databases/sql_app_py310/schemas.py
new file mode 100644
index 000000000..aea2e3f10
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/schemas.py
@@ -0,0 +1,35 @@
+from pydantic import BaseModel
+
+
+class ItemBase(BaseModel):
+ title: str
+ description: str | None = None
+
+
+class ItemCreate(ItemBase):
+ pass
+
+
+class Item(ItemBase):
+ id: int
+ owner_id: int
+
+ class Config:
+ orm_mode = True
+
+
+class UserBase(BaseModel):
+ email: str
+
+
+class UserCreate(UserBase):
+ password: str
+
+
+class User(UserBase):
+ id: int
+ is_active: bool
+ items: list[Item] = []
+
+ class Config:
+ orm_mode = True
diff --git a/docs_src/sql_databases/sql_app_py310/tests/__init__.py b/docs_src/sql_databases/sql_app_py310/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py b/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py
new file mode 100644
index 000000000..c60c3356f
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py
@@ -0,0 +1,47 @@
+from fastapi.testclient import TestClient
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+from ..database import Base
+from ..main import app, get_db
+
+SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
+
+engine = create_engine(
+ SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+)
+TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+
+Base.metadata.create_all(bind=engine)
+
+
+def override_get_db():
+ try:
+ db = TestingSessionLocal()
+ yield db
+ finally:
+ db.close()
+
+
+app.dependency_overrides[get_db] = override_get_db
+
+client = TestClient(app)
+
+
+def test_create_user():
+ response = client.post(
+ "/users/",
+ json={"email": "deadpool@example.com", "password": "chimichangas4life"},
+ )
+ assert response.status_code == 200, response.text
+ data = response.json()
+ assert data["email"] == "deadpool@example.com"
+ assert "id" in data
+ user_id = data["id"]
+
+ response = client.get(f"/users/{user_id}")
+ assert response.status_code == 200, response.text
+ data = response.json()
+ assert data["email"] == "deadpool@example.com"
+ assert data["id"] == user_id
diff --git a/docs_src/sql_databases/sql_app_py39/__init__.py b/docs_src/sql_databases/sql_app_py39/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/sql_databases/sql_app_py39/alt_main.py b/docs_src/sql_databases/sql_app_py39/alt_main.py
new file mode 100644
index 000000000..5de88ec3a
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/alt_main.py
@@ -0,0 +1,60 @@
+from fastapi import Depends, FastAPI, HTTPException, Request, Response
+from sqlalchemy.orm import Session
+
+from . import crud, models, schemas
+from .database import SessionLocal, engine
+
+models.Base.metadata.create_all(bind=engine)
+
+app = FastAPI()
+
+
+@app.middleware("http")
+async def db_session_middleware(request: Request, call_next):
+ response = Response("Internal server error", status_code=500)
+ try:
+ request.state.db = SessionLocal()
+ response = await call_next(request)
+ finally:
+ request.state.db.close()
+ return response
+
+
+# Dependency
+def get_db(request: Request):
+ return request.state.db
+
+
+@app.post("/users/", response_model=schemas.User)
+def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
+ db_user = crud.get_user_by_email(db, email=user.email)
+ if db_user:
+ raise HTTPException(status_code=400, detail="Email already registered")
+ return crud.create_user(db=db, user=user)
+
+
+@app.get("/users/", response_model=list[schemas.User])
+def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ users = crud.get_users(db, skip=skip, limit=limit)
+ return users
+
+
+@app.get("/users/{user_id}", response_model=schemas.User)
+def read_user(user_id: int, db: Session = Depends(get_db)):
+ db_user = crud.get_user(db, user_id=user_id)
+ if db_user is None:
+ raise HTTPException(status_code=404, detail="User not found")
+ return db_user
+
+
+@app.post("/users/{user_id}/items/", response_model=schemas.Item)
+def create_item_for_user(
+ user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
+):
+ return crud.create_user_item(db=db, item=item, user_id=user_id)
+
+
+@app.get("/items/", response_model=list[schemas.Item])
+def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ items = crud.get_items(db, skip=skip, limit=limit)
+ return items
diff --git a/docs_src/sql_databases/sql_app_py39/crud.py b/docs_src/sql_databases/sql_app_py39/crud.py
new file mode 100644
index 000000000..679acdb5c
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/crud.py
@@ -0,0 +1,36 @@
+from sqlalchemy.orm import Session
+
+from . import models, schemas
+
+
+def get_user(db: Session, user_id: int):
+ return db.query(models.User).filter(models.User.id == user_id).first()
+
+
+def get_user_by_email(db: Session, email: str):
+ return db.query(models.User).filter(models.User.email == email).first()
+
+
+def get_users(db: Session, skip: int = 0, limit: int = 100):
+ return db.query(models.User).offset(skip).limit(limit).all()
+
+
+def create_user(db: Session, user: schemas.UserCreate):
+ fake_hashed_password = user.password + "notreallyhashed"
+ db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
+ db.add(db_user)
+ db.commit()
+ db.refresh(db_user)
+ return db_user
+
+
+def get_items(db: Session, skip: int = 0, limit: int = 100):
+ return db.query(models.Item).offset(skip).limit(limit).all()
+
+
+def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
+ db_item = models.Item(**item.dict(), owner_id=user_id)
+ db.add(db_item)
+ db.commit()
+ db.refresh(db_item)
+ return db_item
diff --git a/docs_src/sql_databases/sql_app_py39/database.py b/docs_src/sql_databases/sql_app_py39/database.py
new file mode 100644
index 000000000..45a8b9f69
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/database.py
@@ -0,0 +1,13 @@
+from sqlalchemy import create_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+
+SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
+# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
+
+engine = create_engine(
+ SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+)
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+Base = declarative_base()
diff --git a/docs_src/sql_databases/sql_app_py39/main.py b/docs_src/sql_databases/sql_app_py39/main.py
new file mode 100644
index 000000000..a9856d0b6
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/main.py
@@ -0,0 +1,53 @@
+from fastapi import Depends, FastAPI, HTTPException
+from sqlalchemy.orm import Session
+
+from . import crud, models, schemas
+from .database import SessionLocal, engine
+
+models.Base.metadata.create_all(bind=engine)
+
+app = FastAPI()
+
+
+# Dependency
+def get_db():
+ db = SessionLocal()
+ try:
+ yield db
+ finally:
+ db.close()
+
+
+@app.post("/users/", response_model=schemas.User)
+def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
+ db_user = crud.get_user_by_email(db, email=user.email)
+ if db_user:
+ raise HTTPException(status_code=400, detail="Email already registered")
+ return crud.create_user(db=db, user=user)
+
+
+@app.get("/users/", response_model=list[schemas.User])
+def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ users = crud.get_users(db, skip=skip, limit=limit)
+ return users
+
+
+@app.get("/users/{user_id}", response_model=schemas.User)
+def read_user(user_id: int, db: Session = Depends(get_db)):
+ db_user = crud.get_user(db, user_id=user_id)
+ if db_user is None:
+ raise HTTPException(status_code=404, detail="User not found")
+ return db_user
+
+
+@app.post("/users/{user_id}/items/", response_model=schemas.Item)
+def create_item_for_user(
+ user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
+):
+ return crud.create_user_item(db=db, item=item, user_id=user_id)
+
+
+@app.get("/items/", response_model=list[schemas.Item])
+def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ items = crud.get_items(db, skip=skip, limit=limit)
+ return items
diff --git a/docs_src/sql_databases/sql_app_py39/models.py b/docs_src/sql_databases/sql_app_py39/models.py
new file mode 100644
index 000000000..62d8ab4aa
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/models.py
@@ -0,0 +1,26 @@
+from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
+from sqlalchemy.orm import relationship
+
+from .database import Base
+
+
+class User(Base):
+ __tablename__ = "users"
+
+ id = Column(Integer, primary_key=True, index=True)
+ email = Column(String, unique=True, index=True)
+ hashed_password = Column(String)
+ is_active = Column(Boolean, default=True)
+
+ items = relationship("Item", back_populates="owner")
+
+
+class Item(Base):
+ __tablename__ = "items"
+
+ id = Column(Integer, primary_key=True, index=True)
+ title = Column(String, index=True)
+ description = Column(String, index=True)
+ owner_id = Column(Integer, ForeignKey("users.id"))
+
+ owner = relationship("User", back_populates="items")
diff --git a/docs_src/sql_databases/sql_app_py39/schemas.py b/docs_src/sql_databases/sql_app_py39/schemas.py
new file mode 100644
index 000000000..a19f1cdfe
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/schemas.py
@@ -0,0 +1,37 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+
+class ItemBase(BaseModel):
+ title: str
+ description: Optional[str] = None
+
+
+class ItemCreate(ItemBase):
+ pass
+
+
+class Item(ItemBase):
+ id: int
+ owner_id: int
+
+ class Config:
+ orm_mode = True
+
+
+class UserBase(BaseModel):
+ email: str
+
+
+class UserCreate(UserBase):
+ password: str
+
+
+class User(UserBase):
+ id: int
+ is_active: bool
+ items: list[Item] = []
+
+ class Config:
+ orm_mode = True
diff --git a/docs_src/sql_databases/sql_app_py39/tests/__init__.py b/docs_src/sql_databases/sql_app_py39/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py b/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py
new file mode 100644
index 000000000..c60c3356f
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py
@@ -0,0 +1,47 @@
+from fastapi.testclient import TestClient
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+from ..database import Base
+from ..main import app, get_db
+
+SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
+
+engine = create_engine(
+ SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+)
+TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+
+Base.metadata.create_all(bind=engine)
+
+
+def override_get_db():
+ try:
+ db = TestingSessionLocal()
+ yield db
+ finally:
+ db.close()
+
+
+app.dependency_overrides[get_db] = override_get_db
+
+client = TestClient(app)
+
+
+def test_create_user():
+ response = client.post(
+ "/users/",
+ json={"email": "deadpool@example.com", "password": "chimichangas4life"},
+ )
+ assert response.status_code == 200, response.text
+ data = response.json()
+ assert data["email"] == "deadpool@example.com"
+ assert "id" in data
+ user_id = data["id"]
+
+ response = client.get(f"/users/{user_id}")
+ assert response.status_code == 200, response.text
+ data = response.json()
+ assert data["email"] == "deadpool@example.com"
+ assert data["id"] == user_id
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index 4d4d4333d..8718788fa 100644
--- a/fastapi/__init__.py
+++ b/fastapi/__init__.py
@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
-__version__ = "0.70.0"
+__version__ = "0.73.0"
from starlette import status as status
diff --git a/fastapi/applications.py b/fastapi/applications.py
index 0c25026e2..dbfd76fb9 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -1,3 +1,4 @@
+from enum import Enum
from typing import Any, Callable, Coroutine, Dict, List, Optional, Sequence, Type, Union
from fastapi import routing
@@ -65,6 +66,7 @@ class FastAPI(Starlette):
callbacks: Optional[List[BaseRoute]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
+ swagger_ui_parameters: Optional[Dict[str, Any]] = None,
**extra: Any,
) -> None:
self._debug: bool = debug
@@ -120,6 +122,7 @@ class FastAPI(Starlette):
self.redoc_url = redoc_url
self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
self.swagger_ui_init_oauth = swagger_ui_init_oauth
+ self.swagger_ui_parameters = swagger_ui_parameters
self.extra = extra
self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {}
@@ -174,6 +177,7 @@ class FastAPI(Starlette):
title=self.title + " - Swagger UI",
oauth2_redirect_url=oauth2_redirect_url,
init_oauth=self.swagger_ui_init_oauth,
+ swagger_ui_parameters=self.swagger_ui_parameters,
)
self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False)
@@ -216,7 +220,7 @@ class FastAPI(Starlette):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -270,7 +274,7 @@ class FastAPI(Starlette):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -339,7 +343,7 @@ class FastAPI(Starlette):
router: routing.APIRouter,
*,
prefix: str = "",
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
@@ -365,7 +369,7 @@ class FastAPI(Starlette):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -416,7 +420,7 @@ class FastAPI(Starlette):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -467,7 +471,7 @@ class FastAPI(Starlette):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -518,7 +522,7 @@ class FastAPI(Starlette):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -569,7 +573,7 @@ class FastAPI(Starlette):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -620,7 +624,7 @@ class FastAPI(Starlette):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -671,7 +675,7 @@ class FastAPI(Starlette):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -722,7 +726,7 @@ class FastAPI(Starlette):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py
index b13171287..b20a25ab6 100644
--- a/fastapi/datastructures.py
+++ b/fastapi/datastructures.py
@@ -1,4 +1,4 @@
-from typing import Any, Callable, Iterable, Type, TypeVar
+from typing import Any, Callable, Dict, Iterable, Type, TypeVar
from starlette.datastructures import URL as URL # noqa: F401
from starlette.datastructures import Address as Address # noqa: F401
@@ -20,6 +20,10 @@ class UploadFile(StarletteUploadFile):
raise ValueError(f"Expected UploadFile, received: {type(v)}")
return v
+ @classmethod
+ def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
+ field_schema.update({"type": "string", "format": "binary"})
+
class DefaultPlaceholder:
"""
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index 35ba44aab..d4028d067 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -390,6 +390,8 @@ def get_param_field(
field.required = required
if not had_schema and not is_scalar_field(field=field):
field.field_info = params.Body(field_info.default)
+ if not had_schema and lenient_issubclass(field.type_, UploadFile):
+ field.field_info = params.File(field_info.default)
return field
@@ -701,25 +703,6 @@ def get_missing_field_error(loc: Tuple[str, ...]) -> ErrorWrapper:
return missing_field_error
-def get_schema_compatible_field(*, field: ModelField) -> ModelField:
- out_field = field
- if lenient_issubclass(field.type_, UploadFile):
- use_type: type = bytes
- if field.shape in sequence_shapes:
- use_type = List[bytes]
- out_field = create_response_field(
- name=field.name,
- type_=use_type,
- class_validators=field.class_validators,
- model_config=field.model_config,
- default=field.default,
- required=field.required,
- alias=field.alias,
- field_info=field.field_info,
- )
- return out_field
-
-
def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
flat_dependant = get_flat_dependant(dependant)
if not flat_dependant.body_params:
@@ -729,9 +712,8 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
embed = getattr(field_info, "embed", None)
body_param_names_set = {param.name for param in flat_dependant.body_params}
if len(body_param_names_set) == 1 and not embed:
- final_field = get_schema_compatible_field(field=first_param)
- check_file_field(final_field)
- return final_field
+ check_file_field(first_param)
+ return first_param
# If one field requires to embed, all have to be embedded
# in case a sub-dependency is evaluated with a single unique body field
# That is combined (embedded) with other body fields
@@ -740,7 +722,7 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
model_name = "Body_" + name
BodyModel: Type[BaseModel] = create_model(model_name)
for f in flat_dependant.body_params:
- BodyModel.__fields__[f.name] = get_schema_compatible_field(field=f)
+ BodyModel.__fields__[f.name] = f
required = any(True for f in flat_dependant.body_params if f.required)
BodyFieldInfo_kwargs: Dict[str, Any] = dict(default=None)
diff --git a/fastapi/encoders.py b/fastapi/encoders.py
index 3f599c9fa..4b7ffe313 100644
--- a/fastapi/encoders.py
+++ b/fastapi/encoders.py
@@ -34,9 +34,17 @@ def jsonable_encoder(
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
- custom_encoder: Dict[Any, Callable[[Any], Any]] = {},
+ custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None,
sqlalchemy_safe: bool = True,
) -> Any:
+ custom_encoder = custom_encoder or {}
+ if custom_encoder:
+ if type(obj) in custom_encoder:
+ return custom_encoder[type(obj)](obj)
+ else:
+ for encoder_type, encoder_instance in custom_encoder.items():
+ if isinstance(obj, encoder_type):
+ return encoder_instance(obj)
if include is not None and not isinstance(include, (set, dict)):
include = set(include)
if exclude is not None and not isinstance(exclude, (set, dict)):
@@ -118,14 +126,6 @@ def jsonable_encoder(
)
return encoded_list
- if custom_encoder:
- if type(obj) in custom_encoder:
- return custom_encoder[type(obj)](obj)
- else:
- for encoder_type, encoder in custom_encoder.items():
- if isinstance(obj, encoder_type):
- return encoder(obj)
-
if type(obj) in ENCODERS_BY_TYPE:
return ENCODERS_BY_TYPE[type(obj)](obj)
for encoder, classes_tuple in encoders_by_class_tuples.items():
diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py
index fd22e4e8c..1be90d188 100644
--- a/fastapi/openapi/docs.py
+++ b/fastapi/openapi/docs.py
@@ -4,6 +4,14 @@ from typing import Any, Dict, Optional
from fastapi.encoders import jsonable_encoder
from starlette.responses import HTMLResponse
+swagger_ui_default_parameters = {
+ "dom_id": "#swagger-ui",
+ "layout": "BaseLayout",
+ "deepLinking": True,
+ "showExtensions": True,
+ "showCommonExtensions": True,
+}
+
def get_swagger_ui_html(
*,
@@ -14,7 +22,11 @@ def get_swagger_ui_html(
swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
oauth2_redirect_url: Optional[str] = None,
init_oauth: Optional[Dict[str, Any]] = None,
+ swagger_ui_parameters: Optional[Dict[str, Any]] = None,
) -> HTMLResponse:
+ current_swagger_ui_parameters = swagger_ui_default_parameters.copy()
+ if swagger_ui_parameters:
+ current_swagger_ui_parameters.update(swagger_ui_parameters)
html = f"""
@@ -34,19 +46,17 @@ def get_swagger_ui_html(
url: '{openapi_url}',
"""
+ for key, value in current_swagger_ui_parameters.items():
+ html += f"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n"
+
if oauth2_redirect_url:
html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
html += """
- dom_id: '#swagger-ui',
- presets: [
+ presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
- layout: "BaseLayout",
- deepLinking: true,
- showExtensions: true,
- showCommonExtensions: true
})"""
if init_oauth:
diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py
index 361c75005..9c6598d2d 100644
--- a/fastapi/openapi/models.py
+++ b/fastapi/openapi/models.py
@@ -123,7 +123,7 @@ class Schema(BaseModel):
oneOf: Optional[List["Schema"]] = None
anyOf: Optional[List["Schema"]] = None
not_: Optional["Schema"] = Field(None, alias="not")
- items: Optional["Schema"] = None
+ items: Optional[Union["Schema", List["Schema"]]] = None
properties: Optional[Dict[str, "Schema"]] = None
additionalProperties: Optional[Union["Schema", Reference, bool]] = None
description: Optional[str] = None
diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
index 0e73e21bf..aff76b15e 100644
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -92,6 +92,8 @@ def get_openapi_operation_parameters(
for param in all_route_params:
field_info = param.field_info
field_info = cast(Param, field_info)
+ if not field_info.include_in_schema:
+ continue
parameter = {
"name": param.alias,
"in": field_info.in_.value,
diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py
index ff65d7271..a553a1461 100644
--- a/fastapi/param_functions.py
+++ b/fastapi/param_functions.py
@@ -20,6 +20,7 @@ def Path( # noqa: N802
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
) -> Any:
return params.Path(
@@ -37,6 +38,7 @@ def Path( # noqa: N802
example=example,
examples=examples,
deprecated=deprecated,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -57,6 +59,7 @@ def Query( # noqa: N802
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
) -> Any:
return params.Query(
@@ -74,6 +77,7 @@ def Query( # noqa: N802
example=example,
examples=examples,
deprecated=deprecated,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -95,6 +99,7 @@ def Header( # noqa: N802
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
) -> Any:
return params.Header(
@@ -113,6 +118,7 @@ def Header( # noqa: N802
example=example,
examples=examples,
deprecated=deprecated,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -133,6 +139,7 @@ def Cookie( # noqa: N802
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
) -> Any:
return params.Cookie(
@@ -150,6 +157,7 @@ def Cookie( # noqa: N802
example=example,
examples=examples,
deprecated=deprecated,
+ include_in_schema=include_in_schema,
**extra,
)
diff --git a/fastapi/params.py b/fastapi/params.py
index 3cab98b78..042bbd42f 100644
--- a/fastapi/params.py
+++ b/fastapi/params.py
@@ -31,11 +31,13 @@ class Param(FieldInfo):
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
):
self.deprecated = deprecated
self.example = example
self.examples = examples
+ self.include_in_schema = include_in_schema
super().__init__(
default,
alias=alias,
@@ -75,6 +77,7 @@ class Path(Param):
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
):
self.in_ = self.in_
@@ -93,6 +96,7 @@ class Path(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -117,6 +121,7 @@ class Query(Param):
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
):
super().__init__(
@@ -134,6 +139,7 @@ class Query(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -159,6 +165,7 @@ class Header(Param):
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
):
self.convert_underscores = convert_underscores
@@ -177,6 +184,7 @@ class Header(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -201,6 +209,7 @@ class Cookie(Param):
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
):
super().__init__(
@@ -218,6 +227,7 @@ class Cookie(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ include_in_schema=include_in_schema,
**extra,
)
diff --git a/fastapi/routing.py b/fastapi/routing.py
index 63ad72964..f6d5370d6 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -1,9 +1,9 @@
import asyncio
import dataclasses
import email.message
-import enum
import inspect
import json
+from enum import Enum, IntEnum
from typing import (
Any,
Callable,
@@ -305,7 +305,7 @@ class APIRoute(routing.Route):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -330,7 +330,7 @@ class APIRoute(routing.Route):
openapi_extra: Optional[Dict[str, Any]] = None,
) -> None:
# normalise enums e.g. http.HTTPStatus
- if isinstance(status_code, enum.IntEnum):
+ if isinstance(status_code, IntEnum):
status_code = int(status_code)
self.path = path
self.endpoint = endpoint
@@ -438,7 +438,7 @@ class APIRouter(routing.Router):
self,
*,
prefix: str = "",
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
default_response_class: Type[Response] = Default(JSONResponse),
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
@@ -466,7 +466,7 @@ class APIRouter(routing.Router):
"/"
), "A path prefix must not end with '/', as the routes will start with '/'"
self.prefix = prefix
- self.tags: List[str] = tags or []
+ self.tags: List[Union[str, Enum]] = tags or []
self.dependencies = list(dependencies or []) or []
self.deprecated = deprecated
self.include_in_schema = include_in_schema
@@ -483,7 +483,7 @@ class APIRouter(routing.Router):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -557,7 +557,7 @@ class APIRouter(routing.Router):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -634,7 +634,7 @@ class APIRouter(routing.Router):
router: "APIRouter",
*,
prefix: str = "",
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
default_response_class: Type[Response] = Default(JSONResponse),
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
@@ -738,7 +738,7 @@ class APIRouter(routing.Router):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -790,7 +790,7 @@ class APIRouter(routing.Router):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -842,7 +842,7 @@ class APIRouter(routing.Router):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -894,7 +894,7 @@ class APIRouter(routing.Router):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -946,7 +946,7 @@ class APIRouter(routing.Router):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -998,7 +998,7 @@ class APIRouter(routing.Router):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -1050,7 +1050,7 @@ class APIRouter(routing.Router):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -1102,7 +1102,7 @@ class APIRouter(routing.Router):
*,
response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None,
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
diff --git a/pyproject.toml b/pyproject.toml
index 50e0afe87..77c01322f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,6 +22,7 @@ classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Framework :: AsyncIO",
+ "Framework :: FastAPI",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
@@ -29,11 +30,12 @@ classifiers = [
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
"Topic :: Internet :: WWW/HTTP",
]
requires = [
- "starlette ==0.16.0",
+ "starlette ==0.17.1",
"pydantic >=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0",
]
description-file = "README.md"
@@ -69,7 +71,7 @@ test = [
]
doc = [
"mkdocs >=1.1.2,<2.0.0",
- "mkdocs-material >=7.1.9,<8.0.0",
+ "mkdocs-material >=8.1.4,<9.0.0",
"mdx-include >=1.4.1,<2.0.0",
"mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0",
"typer-cli >=0.0.12,<0.0.13",
diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py
index e2aa8adf8..fa82b5ea8 100644
--- a/tests/test_jsonable_encoder.py
+++ b/tests/test_jsonable_encoder.py
@@ -161,6 +161,21 @@ def test_custom_encoders():
assert encoded_instance["dt_field"] == instance.dt_field.isoformat()
+def test_custom_enum_encoders():
+ def custom_enum_encoder(v: Enum):
+ return v.value.lower()
+
+ class MyEnum(Enum):
+ ENUM_VAL_1 = "ENUM_VAL_1"
+
+ instance = MyEnum.ENUM_VAL_1
+
+ encoded_instance = jsonable_encoder(
+ instance, custom_encoder={MyEnum: custom_enum_encoder}
+ )
+ assert encoded_instance == custom_enum_encoder(instance)
+
+
def test_encode_model_with_path(model_with_path):
if isinstance(model_with_path.path, PureWindowsPath):
expected = "\\foo\\bar"
diff --git a/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py
new file mode 100644
index 000000000..4eaac72d8
--- /dev/null
+++ b/tests/test_param_include_in_schema.py
@@ -0,0 +1,239 @@
+from typing import Optional
+
+import pytest
+from fastapi import Cookie, FastAPI, Header, Path, Query
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.get("/hidden_cookie")
+async def hidden_cookie(
+ hidden_cookie: Optional[str] = Cookie(None, include_in_schema=False)
+):
+ return {"hidden_cookie": hidden_cookie}
+
+
+@app.get("/hidden_header")
+async def hidden_header(
+ hidden_header: Optional[str] = Header(None, include_in_schema=False)
+):
+ return {"hidden_header": hidden_header}
+
+
+@app.get("/hidden_path/{hidden_path}")
+async def hidden_path(hidden_path: str = Path(..., include_in_schema=False)):
+ return {"hidden_path": hidden_path}
+
+
+@app.get("/hidden_query")
+async def hidden_query(
+ hidden_query: Optional[str] = Query(None, include_in_schema=False)
+):
+ return {"hidden_query": hidden_query}
+
+
+client = TestClient(app)
+
+openapi_shema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/hidden_cookie": {
+ "get": {
+ "summary": "Hidden Cookie",
+ "operationId": "hidden_cookie_hidden_cookie_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/hidden_header": {
+ "get": {
+ "summary": "Hidden Header",
+ "operationId": "hidden_header_hidden_header_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/hidden_path/{hidden_path}": {
+ "get": {
+ "summary": "Hidden Path",
+ "operationId": "hidden_path_hidden_path__hidden_path__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/hidden_query": {
+ "get": {
+ "summary": "Hidden Query",
+ "operationId": "hidden_query_hidden_query_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == openapi_shema
+
+
+@pytest.mark.parametrize(
+ "path,cookies,expected_status,expected_response",
+ [
+ (
+ "/hidden_cookie",
+ {},
+ 200,
+ {"hidden_cookie": None},
+ ),
+ (
+ "/hidden_cookie",
+ {"hidden_cookie": "somevalue"},
+ 200,
+ {"hidden_cookie": "somevalue"},
+ ),
+ ],
+)
+def test_hidden_cookie(path, cookies, expected_status, expected_response):
+ response = client.get(path, cookies=cookies)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
+
+
+@pytest.mark.parametrize(
+ "path,headers,expected_status,expected_response",
+ [
+ (
+ "/hidden_header",
+ {},
+ 200,
+ {"hidden_header": None},
+ ),
+ (
+ "/hidden_header",
+ {"Hidden-Header": "somevalue"},
+ 200,
+ {"hidden_header": "somevalue"},
+ ),
+ ],
+)
+def test_hidden_header(path, headers, expected_status, expected_response):
+ response = client.get(path, headers=headers)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
+
+
+def test_hidden_path():
+ response = client.get("/hidden_path/hidden_path")
+ assert response.status_code == 200
+ assert response.json() == {"hidden_path": "hidden_path"}
+
+
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ (
+ "/hidden_query",
+ 200,
+ {"hidden_query": None},
+ ),
+ (
+ "/hidden_query?hidden_query=somevalue",
+ 200,
+ {"hidden_query": "somevalue"},
+ ),
+ ],
+)
+def test_hidden_query(path, expected_status, expected_response):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tuples.py b/tests/test_tuples.py
new file mode 100644
index 000000000..4cd5ee3af
--- /dev/null
+++ b/tests/test_tuples.py
@@ -0,0 +1,267 @@
+from typing import List, Tuple
+
+from fastapi import FastAPI, Form
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class ItemGroup(BaseModel):
+ items: List[Tuple[str, str]]
+
+
+class Coordinate(BaseModel):
+ x: float
+ y: float
+
+
+@app.post("/model-with-tuple/")
+def post_model_with_tuple(item_group: ItemGroup):
+ return item_group
+
+
+@app.post("/tuple-of-models/")
+def post_tuple_of_models(square: Tuple[Coordinate, Coordinate]):
+ return square
+
+
+@app.post("/tuple-form/")
+def hello(values: Tuple[int, int] = Form(...)):
+ return values
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/model-with-tuple/": {
+ "post": {
+ "summary": "Post Model With Tuple",
+ "operationId": "post_model_with_tuple_model_with_tuple__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/ItemGroup"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/tuple-of-models/": {
+ "post": {
+ "summary": "Post Tuple Of Models",
+ "operationId": "post_tuple_of_models_tuple_of_models__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Square",
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "items": [
+ {"$ref": "#/components/schemas/Coordinate"},
+ {"$ref": "#/components/schemas/Coordinate"},
+ ],
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/tuple-form/": {
+ "post": {
+ "summary": "Hello",
+ "operationId": "hello_tuple_form__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_hello_tuple_form__post"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_hello_tuple_form__post": {
+ "title": "Body_hello_tuple_form__post",
+ "required": ["values"],
+ "type": "object",
+ "properties": {
+ "values": {
+ "title": "Values",
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "items": [{"type": "integer"}, {"type": "integer"}],
+ }
+ },
+ },
+ "Coordinate": {
+ "title": "Coordinate",
+ "required": ["x", "y"],
+ "type": "object",
+ "properties": {
+ "x": {"title": "X", "type": "number"},
+ "y": {"title": "Y", "type": "number"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ItemGroup": {
+ "title": "ItemGroup",
+ "required": ["items"],
+ "type": "object",
+ "properties": {
+ "items": {
+ "title": "Items",
+ "type": "array",
+ "items": {
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "items": [{"type": "string"}, {"type": "string"}],
+ },
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_model_with_tuple_valid():
+ data = {"items": [["foo", "bar"], ["baz", "whatelse"]]}
+ response = client.post("/model-with-tuple/", json=data)
+ assert response.status_code == 200, response.text
+ assert response.json() == data
+
+
+def test_model_with_tuple_invalid():
+ data = {"items": [["foo", "bar"], ["baz", "whatelse", "too", "much"]]}
+ response = client.post("/model-with-tuple/", json=data)
+ assert response.status_code == 422, response.text
+
+ data = {"items": [["foo", "bar"], ["baz"]]}
+ response = client.post("/model-with-tuple/", json=data)
+ assert response.status_code == 422, response.text
+
+
+def test_tuple_with_model_valid():
+ data = [{"x": 1, "y": 2}, {"x": 3, "y": 4}]
+ response = client.post("/tuple-of-models/", json=data)
+ assert response.status_code == 200, response.text
+ assert response.json() == data
+
+
+def test_tuple_with_model_invalid():
+ data = [{"x": 1, "y": 2}, {"x": 3, "y": 4}, {"x": 5, "y": 6}]
+ response = client.post("/tuple-of-models/", json=data)
+ assert response.status_code == 422, response.text
+
+ data = [{"x": 1, "y": 2}]
+ response = client.post("/tuple-of-models/", json=data)
+ assert response.status_code == 422, response.text
+
+
+def test_tuple_form_valid():
+ response = client.post("/tuple-form/", data=[("values", "1"), ("values", "2")])
+ assert response.status_code == 200, response.text
+ assert response.json() == [1, 2]
+
+
+def test_tuple_form_invalid():
+ response = client.post(
+ "/tuple-form/", data=[("values", "1"), ("values", "2"), ("values", "3")]
+ )
+ assert response.status_code == 422, response.text
+
+ response = client.post("/tuple-form/", data=[("values", "1")])
+ assert response.status_code == 422, response.text
diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002_py310.py b/tests/test_tutorial/test_background_tasks/test_tutorial002_py310.py
new file mode 100644
index 000000000..6937c8fab
--- /dev/null
+++ b/tests/test_tutorial/test_background_tasks/test_tutorial002_py310.py
@@ -0,0 +1,21 @@
+import os
+from pathlib import Path
+
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@needs_py310
+def test():
+ from docs_src.background_tasks.tutorial002_py310 import app
+
+ client = TestClient(app)
+ log = Path("log.txt")
+ if log.is_file():
+ os.remove(log) # pragma: no cover
+ response = client.post("/send-notification/foo@example.com?q=some-query")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Message sent"}
+ with open("./log.txt") as f:
+ assert "found query: some-query\nmessage to foo@example.com" in f.read()
diff --git a/tests/test_tutorial/test_body/test_tutorial001_py310.py b/tests/test_tutorial/test_body/test_tutorial001_py310.py
new file mode 100644
index 000000000..e292b5346
--- /dev/null
+++ b/tests/test_tutorial/test_body/test_tutorial001_py310.py
@@ -0,0 +1,292 @@
+from unittest.mock import patch
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number"},
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture
+def client():
+ from docs_src.body.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+price_missing = {
+ "detail": [
+ {
+ "loc": ["body", "price"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+}
+
+price_not_float = {
+ "detail": [
+ {
+ "loc": ["body", "price"],
+ "msg": "value is not a valid float",
+ "type": "type_error.float",
+ }
+ ]
+}
+
+name_price_missing = {
+ "detail": [
+ {
+ "loc": ["body", "name"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "price"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+}
+
+body_missing = {
+ "detail": [
+ {"loc": ["body"], "msg": "field required", "type": "value_error.missing"}
+ ]
+}
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,body,expected_status,expected_response",
+ [
+ (
+ "/items/",
+ {"name": "Foo", "price": 50.5},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": None, "tax": None},
+ ),
+ (
+ "/items/",
+ {"name": "Foo", "price": "50.5"},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": None, "tax": None},
+ ),
+ (
+ "/items/",
+ {"name": "Foo", "price": "50.5", "description": "Some Foo"},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": "Some Foo", "tax": None},
+ ),
+ (
+ "/items/",
+ {"name": "Foo", "price": "50.5", "description": "Some Foo", "tax": 0.3},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": "Some Foo", "tax": 0.3},
+ ),
+ ("/items/", {"name": "Foo"}, 422, price_missing),
+ ("/items/", {"name": "Foo", "price": "twenty"}, 422, price_not_float),
+ ("/items/", {}, 422, name_price_missing),
+ ("/items/", None, 422, body_missing),
+ ],
+)
+def test_post_body(path, body, expected_status, expected_response, client: TestClient):
+ response = client.post(path, json=body)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
+
+
+@needs_py310
+def test_post_broken_body(client: TestClient):
+ response = client.post(
+ "/items/",
+ headers={"content-type": "application/json"},
+ data="{some broken json}",
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", 1],
+ "msg": "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
+ "type": "value_error.jsondecode",
+ "ctx": {
+ "msg": "Expecting property name enclosed in double quotes",
+ "doc": "{some broken json}",
+ "pos": 1,
+ "lineno": 1,
+ "colno": 2,
+ },
+ }
+ ]
+ }
+
+
+@needs_py310
+def test_post_form_for_json(client: TestClient):
+ response = client.post("/items/", data={"name": "Foo", "price": 50.5})
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body"],
+ "msg": "value is not a valid dict",
+ "type": "type_error.dict",
+ }
+ ]
+ }
+
+
+@needs_py310
+def test_explicit_content_type(client: TestClient):
+ response = client.post(
+ "/items/",
+ data='{"name": "Foo", "price": 50.5}',
+ headers={"Content-Type": "application/json"},
+ )
+ assert response.status_code == 200, response.text
+
+
+@needs_py310
+def test_geo_json(client: TestClient):
+ response = client.post(
+ "/items/",
+ data='{"name": "Foo", "price": 50.5}',
+ headers={"Content-Type": "application/geo+json"},
+ )
+ assert response.status_code == 200, response.text
+
+
+@needs_py310
+def test_no_content_type_is_json(client: TestClient):
+ response = client.post(
+ "/items/",
+ data='{"name": "Foo", "price": 50.5}',
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Foo",
+ "description": None,
+ "price": 50.5,
+ "tax": None,
+ }
+
+
+@needs_py310
+def test_wrong_headers(client: TestClient):
+ data = '{"name": "Foo", "price": 50.5}'
+ invalid_dict = {
+ "detail": [
+ {
+ "loc": ["body"],
+ "msg": "value is not a valid dict",
+ "type": "type_error.dict",
+ }
+ ]
+ }
+
+ response = client.post("/items/", data=data, headers={"Content-Type": "text/plain"})
+ assert response.status_code == 422, response.text
+ assert response.json() == invalid_dict
+
+ response = client.post(
+ "/items/", data=data, headers={"Content-Type": "application/geo+json-seq"}
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == invalid_dict
+ response = client.post(
+ "/items/", data=data, headers={"Content-Type": "application/not-really-json"}
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == invalid_dict
+
+
+@needs_py310
+def test_other_exceptions(client: TestClient):
+ with patch("json.loads", side_effect=Exception):
+ response = client.post("/items/", json={"test": "test2"})
+ assert response.status_code == 400, response.text
diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py b/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py
new file mode 100644
index 000000000..d7a525ea7
--- /dev/null
+++ b/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py
@@ -0,0 +1,176 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "integer"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {
+ "title": "The description of the item",
+ "maxLength": 300,
+ "type": "string",
+ },
+ "price": {
+ "title": "Price",
+ "exclusiveMinimum": 0.0,
+ "type": "number",
+ "description": "The price must be greater than zero",
+ },
+ "tax": {"title": "Tax", "type": "number"},
+ },
+ },
+ "Body_update_item_items__item_id__put": {
+ "title": "Body_update_item_items__item_id__put",
+ "required": ["item"],
+ "type": "object",
+ "properties": {"item": {"$ref": "#/components/schemas/Item"}},
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_fields.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+price_not_greater = {
+ "detail": [
+ {
+ "ctx": {"limit_value": 0},
+ "loc": ["body", "item", "price"],
+ "msg": "ensure this value is greater than 0",
+ "type": "value_error.number.not_gt",
+ }
+ ]
+}
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,body,expected_status,expected_response",
+ [
+ (
+ "/items/5",
+ {"item": {"name": "Foo", "price": 3.0}},
+ 200,
+ {
+ "item_id": 5,
+ "item": {"name": "Foo", "price": 3.0, "description": None, "tax": None},
+ },
+ ),
+ (
+ "/items/6",
+ {
+ "item": {
+ "name": "Bar",
+ "price": 0.2,
+ "description": "Some bar",
+ "tax": "5.4",
+ }
+ },
+ 200,
+ {
+ "item_id": 6,
+ "item": {
+ "name": "Bar",
+ "price": 0.2,
+ "description": "Some bar",
+ "tax": 5.4,
+ },
+ },
+ ),
+ ("/items/5", {"item": {"name": "Foo", "price": -3.0}}, 422, price_not_greater),
+ ],
+)
+def test(path, body, expected_status, expected_response, client: TestClient):
+ response = client.put(path, json=body)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py
new file mode 100644
index 000000000..85ba41ce6
--- /dev/null
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py
@@ -0,0 +1,155 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "title": "The ID of the item to get",
+ "maximum": 1000.0,
+ "minimum": 0.0,
+ "type": "integer",
+ },
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Q", "type": "string"},
+ "name": "q",
+ "in": "query",
+ },
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number"},
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_multiple_params.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+item_id_not_int = {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer",
+ }
+ ]
+}
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,body,expected_status,expected_response",
+ [
+ (
+ "/items/5?q=bar",
+ {"name": "Foo", "price": 50.5},
+ 200,
+ {
+ "item_id": 5,
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": None,
+ "tax": None,
+ },
+ "q": "bar",
+ },
+ ),
+ ("/items/5?q=bar", None, 200, {"item_id": 5, "q": "bar"}),
+ ("/items/5", None, 200, {"item_id": 5}),
+ ("/items/foo", None, 422, item_id_not_int),
+ ],
+)
+def test_post_body(path, body, expected_status, expected_response, client: TestClient):
+ response = client.put(path, json=body)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py
new file mode 100644
index 000000000..f896f7bf5
--- /dev/null
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py
@@ -0,0 +1,206 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "integer"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number"},
+ },
+ },
+ "User": {
+ "title": "User",
+ "required": ["username"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "full_name": {"title": "Full Name", "type": "string"},
+ },
+ },
+ "Body_update_item_items__item_id__put": {
+ "title": "Body_update_item_items__item_id__put",
+ "required": ["item", "user", "importance"],
+ "type": "object",
+ "properties": {
+ "item": {"$ref": "#/components/schemas/Item"},
+ "user": {"$ref": "#/components/schemas/User"},
+ "importance": {"title": "Importance", "type": "integer"},
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_multiple_params.tutorial003_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+# Test required and embedded body parameters with no bodies sent
+@needs_py310
+@pytest.mark.parametrize(
+ "path,body,expected_status,expected_response",
+ [
+ (
+ "/items/5",
+ {
+ "importance": 2,
+ "item": {"name": "Foo", "price": 50.5},
+ "user": {"username": "Dave"},
+ },
+ 200,
+ {
+ "item_id": 5,
+ "importance": 2,
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": None,
+ "tax": None,
+ },
+ "user": {"username": "Dave", "full_name": None},
+ },
+ ),
+ (
+ "/items/5",
+ None,
+ 422,
+ {
+ "detail": [
+ {
+ "loc": ["body", "item"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "user"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "importance"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ },
+ ),
+ (
+ "/items/5",
+ [],
+ 422,
+ {
+ "detail": [
+ {
+ "loc": ["body", "item"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "user"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "importance"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ },
+ ),
+ ],
+)
+def test_post_body(path, body, expected_status, expected_response, client: TestClient):
+ response = client.put(path, json=body)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py
new file mode 100644
index 000000000..17ca29ce5
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py
@@ -0,0 +1,113 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/index-weights/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create Index Weights",
+ "operationId": "create_index_weights_index_weights__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Weights",
+ "type": "object",
+ "additionalProperties": {"type": "number"},
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_nested_models.tutorial009_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_post_body(client: TestClient):
+ data = {"2": 2.2, "3": 3.3}
+ response = client.post("/index-weights/", json=data)
+ assert response.status_code == 200, response.text
+ assert response.json() == data
+
+
+@needs_py39
+def test_post_invalid_body(client: TestClient):
+ data = {"foo": 2.2, "3": 3.3}
+ response = client.post("/index-weights/", json=data)
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "__key__"],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer",
+ }
+ ]
+ }
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py
new file mode 100644
index 000000000..ca1d8c585
--- /dev/null
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py
@@ -0,0 +1,172 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ },
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ },
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "tax": {"title": "Tax", "type": "number", "default": 10.5},
+ "tags": {
+ "title": "Tags",
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_updates.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_get(client: TestClient):
+ response = client.get("/items/baz")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Baz",
+ "description": None,
+ "price": 50.2,
+ "tax": 10.5,
+ "tags": [],
+ }
+
+
+@needs_py310
+def test_put(client: TestClient):
+ response = client.put(
+ "/items/bar", json={"name": "Barz", "price": 3, "description": None}
+ )
+ assert response.json() == {
+ "name": "Barz",
+ "description": None,
+ "price": 3,
+ "tax": 10.5,
+ "tags": [],
+ }
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py
new file mode 100644
index 000000000..f2b184c4f
--- /dev/null
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py
@@ -0,0 +1,172 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ },
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ },
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "tax": {"title": "Tax", "type": "number", "default": 10.5},
+ "tags": {
+ "title": "Tags",
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_updates.tutorial001_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_get(client: TestClient):
+ response = client.get("/items/baz")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Baz",
+ "description": None,
+ "price": 50.2,
+ "tax": 10.5,
+ "tags": [],
+ }
+
+
+@needs_py39
+def test_put(client: TestClient):
+ response = client.put(
+ "/items/bar", json={"name": "Barz", "price": 3, "description": None}
+ )
+ assert response.json() == {
+ "name": "Barz",
+ "description": None,
+ "price": 3,
+ "tax": 10.5,
+ "tags": [],
+ }
diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py
new file mode 100644
index 000000000..587a328da
--- /dev/null
+++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py
@@ -0,0 +1,100 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {"title": "Ads Id", "type": "string"},
+ "name": "ads_id",
+ "in": "cookie",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.cookie_params.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,cookies,expected_status,expected_response",
+ [
+ ("/openapi.json", None, 200, openapi_schema),
+ ("/items", None, 200, {"ads_id": None}),
+ ("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}),
+ (
+ "/items",
+ {"ads_id": "ads_track", "session": "cookiesession"},
+ 200,
+ {"ads_id": "ads_track"},
+ ),
+ ("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
+ ],
+)
+def test(path, cookies, expected_status, expected_response, client: TestClient):
+ response = client.get(path, cookies=cookies)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py
index 10d8d227d..34aeb0be5 100644
--- a/tests/test_tutorial/test_dataclasses/test_tutorial002.py
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py
@@ -1,3 +1,5 @@
+from copy import deepcopy
+
from fastapi.testclient import TestClient
from docs_src.dataclasses.tutorial002 import app
@@ -29,7 +31,7 @@ openapi_schema = {
"schemas": {
"Item": {
"title": "Item",
- "required": ["name", "price", "tags"],
+ "required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
@@ -51,7 +53,18 @@ openapi_schema = {
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
- assert response.json() == openapi_schema
+ # TODO: remove this once Pydantic 1.9 is released
+ # Ref: https://github.com/samuelcolvin/pydantic/pull/2557
+ data = response.json()
+ alternative_data1 = deepcopy(data)
+ alternative_data2 = deepcopy(data)
+ alternative_data1["components"]["schemas"]["Item"]["required"] = ["name", "price"]
+ alternative_data2["components"]["schemas"]["Item"]["required"] = [
+ "name",
+ "price",
+ "tags",
+ ]
+ assert alternative_data1 == openapi_schema or alternative_data2 == openapi_schema
def test_get_item():
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py
new file mode 100644
index 000000000..a7991170e
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py
@@ -0,0 +1,157 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {"title": "Q", "type": "string"},
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Skip", "type": "integer", "default": 0},
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Limit", "type": "integer", "default": 100},
+ "name": "limit",
+ "in": "query",
+ },
+ ],
+ }
+ },
+ "/users/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Users",
+ "operationId": "read_users_users__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {"title": "Q", "type": "string"},
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Skip", "type": "integer", "default": 0},
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Limit", "type": "integer", "default": 100},
+ "name": "limit",
+ "in": "query",
+ },
+ ],
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.dependencies.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ ("/items", 200, {"q": None, "skip": 0, "limit": 100}),
+ ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
+ ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
+ ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
+ ("/users", 200, {"q": None, "skip": 0, "limit": 100}),
+ ("/openapi.json", 200, openapi_schema),
+ ],
+)
+def test_get(path, expected_status, expected_response, client: TestClient):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py
new file mode 100644
index 000000000..f66a36a99
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py
@@ -0,0 +1,152 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {"title": "Q", "type": "string"},
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Skip", "type": "integer", "default": 0},
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Limit", "type": "integer", "default": 100},
+ "name": "limit",
+ "in": "query",
+ },
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.dependencies.tutorial004_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ (
+ "/items",
+ 200,
+ {
+ "items": [
+ {"item_name": "Foo"},
+ {"item_name": "Bar"},
+ {"item_name": "Baz"},
+ ]
+ },
+ ),
+ (
+ "/items?q=foo",
+ 200,
+ {
+ "items": [
+ {"item_name": "Foo"},
+ {"item_name": "Bar"},
+ {"item_name": "Baz"},
+ ],
+ "q": "foo",
+ },
+ ),
+ (
+ "/items?q=foo&skip=1",
+ 200,
+ {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
+ ),
+ (
+ "/items?q=bar&limit=2",
+ 200,
+ {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
+ ),
+ (
+ "/items?q=bar&skip=1&limit=1",
+ 200,
+ {"items": [{"item_name": "Bar"}], "q": "bar"},
+ ),
+ (
+ "/items?limit=1&q=bar&skip=1",
+ 200,
+ {"items": [{"item_name": "Bar"}], "q": "bar"},
+ ),
+ ],
+)
+def test_get(path, expected_status, expected_response, client: TestClient):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial003.py b/tests/test_tutorial/test_extending_openapi/test_tutorial003.py
new file mode 100644
index 000000000..0184dd9f8
--- /dev/null
+++ b/tests/test_tutorial/test_extending_openapi/test_tutorial003.py
@@ -0,0 +1,41 @@
+from fastapi.testclient import TestClient
+
+from docs_src.extending_openapi.tutorial003 import app
+
+client = TestClient(app)
+
+
+def test_swagger_ui():
+ response = client.get("/docs")
+ assert response.status_code == 200, response.text
+ assert (
+ '"syntaxHighlight": false' in response.text
+ ), "syntaxHighlight should be included and converted to JSON"
+ assert (
+ '"dom_id": "#swagger-ui"' in response.text
+ ), "default configs should be preserved"
+ assert "presets: [" in response.text, "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.presets.apis," in response.text
+ ), "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"layout": "BaseLayout",' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"deepLinking": true,' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showExtensions": true,' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showCommonExtensions": true,' in response.text
+ ), "default configs should be preserved"
+
+
+def test_get_users():
+ response = client.get("/users/foo")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Hello foo"}
diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial004.py b/tests/test_tutorial/test_extending_openapi/test_tutorial004.py
new file mode 100644
index 000000000..4f7615126
--- /dev/null
+++ b/tests/test_tutorial/test_extending_openapi/test_tutorial004.py
@@ -0,0 +1,44 @@
+from fastapi.testclient import TestClient
+
+from docs_src.extending_openapi.tutorial004 import app
+
+client = TestClient(app)
+
+
+def test_swagger_ui():
+ response = client.get("/docs")
+ assert response.status_code == 200, response.text
+ assert (
+ '"syntaxHighlight": false' not in response.text
+ ), "not used parameters should not be included"
+ assert (
+ '"syntaxHighlight.theme": "obsidian"' in response.text
+ ), "parameters with middle dots should be included in a JSON compatible way"
+ assert (
+ '"dom_id": "#swagger-ui"' in response.text
+ ), "default configs should be preserved"
+ assert "presets: [" in response.text, "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.presets.apis," in response.text
+ ), "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"layout": "BaseLayout",' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"deepLinking": true,' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showExtensions": true,' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showCommonExtensions": true,' in response.text
+ ), "default configs should be preserved"
+
+
+def test_get_users():
+ response = client.get("/users/foo")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Hello foo"}
diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial005.py b/tests/test_tutorial/test_extending_openapi/test_tutorial005.py
new file mode 100644
index 000000000..24aeb93db
--- /dev/null
+++ b/tests/test_tutorial/test_extending_openapi/test_tutorial005.py
@@ -0,0 +1,44 @@
+from fastapi.testclient import TestClient
+
+from docs_src.extending_openapi.tutorial005 import app
+
+client = TestClient(app)
+
+
+def test_swagger_ui():
+ response = client.get("/docs")
+ assert response.status_code == 200, response.text
+ assert (
+ '"deepLinking": false,' in response.text
+ ), "overridden configs should be preserved"
+ assert (
+ '"deepLinking": true' not in response.text
+ ), "overridden configs should not include the old value"
+ assert (
+ '"syntaxHighlight": false' not in response.text
+ ), "not used parameters should not be included"
+ assert (
+ '"dom_id": "#swagger-ui"' in response.text
+ ), "default configs should be preserved"
+ assert "presets: [" in response.text, "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.presets.apis," in response.text
+ ), "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"layout": "BaseLayout",' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showExtensions": true,' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showCommonExtensions": true,' in response.text
+ ), "default configs should be preserved"
+
+
+def test_get_users():
+ response = client.get("/users/foo")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Hello foo"}
diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py
new file mode 100644
index 000000000..3d4c1d07d
--- /dev/null
+++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py
@@ -0,0 +1,146 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "string",
+ "format": "uuid",
+ },
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_read_items_items__item_id__put"
+ }
+ }
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Body_read_items_items__item_id__put": {
+ "title": "Body_read_items_items__item_id__put",
+ "type": "object",
+ "properties": {
+ "start_datetime": {
+ "title": "Start Datetime",
+ "type": "string",
+ "format": "date-time",
+ },
+ "end_datetime": {
+ "title": "End Datetime",
+ "type": "string",
+ "format": "date-time",
+ },
+ "repeat_at": {
+ "title": "Repeat At",
+ "type": "string",
+ "format": "time",
+ },
+ "process_after": {
+ "title": "Process After",
+ "type": "number",
+ "format": "time-delta",
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.extra_data_types.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_extra_types(client: TestClient):
+ item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
+ data = {
+ "start_datetime": "2018-12-22T14:00:00+00:00",
+ "end_datetime": "2018-12-24T15:00:00+00:00",
+ "repeat_at": "15:30:00",
+ "process_after": 300,
+ }
+ expected_response = data.copy()
+ expected_response.update(
+ {
+ "start_process": "2018-12-22T14:05:00+00:00",
+ "duration": 176_100,
+ "item_id": item_id,
+ }
+ )
+ response = client.put(f"/items/{item_id}", json=data)
+ assert response.status_code == 200, response.text
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py
new file mode 100644
index 000000000..185bc3a37
--- /dev/null
+++ b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py
@@ -0,0 +1,135 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Read Item Items Item Id Get",
+ "anyOf": [
+ {"$ref": "#/components/schemas/PlaneItem"},
+ {"$ref": "#/components/schemas/CarItem"},
+ ],
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PlaneItem": {
+ "title": "PlaneItem",
+ "required": ["description", "size"],
+ "type": "object",
+ "properties": {
+ "description": {"title": "Description", "type": "string"},
+ "type": {"title": "Type", "type": "string", "default": "plane"},
+ "size": {"title": "Size", "type": "integer"},
+ },
+ },
+ "CarItem": {
+ "title": "CarItem",
+ "required": ["description"],
+ "type": "object",
+ "properties": {
+ "description": {"title": "Description", "type": "string"},
+ "type": {"title": "Type", "type": "string", "default": "car"},
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.extra_models.tutorial003_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_get_car(client: TestClient):
+ response = client.get("/items/item1")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "description": "All my friends drive a low rider",
+ "type": "car",
+ }
+
+
+@needs_py310
+def test_get_plane(client: TestClient):
+ response = client.get("/items/item2")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "description": "Music is my aeroplane, it's my aeroplane",
+ "type": "plane",
+ "size": 5,
+ }
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py b/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py
new file mode 100644
index 000000000..7f4f5b9be
--- /dev/null
+++ b/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py
@@ -0,0 +1,69 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Read Items Items Get",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ }
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "description"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ },
+ }
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.extra_models.tutorial004_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_get_items(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {"name": "Foo", "description": "There comes my hero"},
+ {"name": "Red", "description": "It's my aeroplane"},
+ ]
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py b/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py
new file mode 100644
index 000000000..3bb5a99f1
--- /dev/null
+++ b/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py
@@ -0,0 +1,53 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/keyword-weights/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Read Keyword Weights Keyword Weights Get",
+ "type": "object",
+ "additionalProperties": {"type": "number"},
+ }
+ }
+ },
+ }
+ },
+ "summary": "Read Keyword Weights",
+ "operationId": "read_keyword_weights_keyword_weights__get",
+ }
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.extra_models.tutorial005_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_get_items(client: TestClient):
+ response = client.get("/keyword-weights/")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"foo": 2.3, "bar": 3.4}
diff --git a/tests/test_tutorial/test_header_params/test_tutorial001_py310.py b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py
new file mode 100644
index 000000000..f5ee17428
--- /dev/null
+++ b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py
@@ -0,0 +1,94 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {"title": "User-Agent", "type": "string"},
+ "name": "user-agent",
+ "in": "header",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.header_params.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,headers,expected_status,expected_response",
+ [
+ ("/openapi.json", None, 200, openapi_schema),
+ ("/items", None, 200, {"User-Agent": "testclient"}),
+ ("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}),
+ ("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}),
+ ],
+)
+def test(path, headers, expected_status, expected_response, client: TestClient):
+ response = client.get(path, headers=headers)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py
new file mode 100644
index 000000000..be9f2afec
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py
@@ -0,0 +1,56 @@
+from fastapi.testclient import TestClient
+
+from docs_src.path_operation_configuration.tutorial002b import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "tags": ["items"],
+ "summary": "Get Items",
+ "operationId": "get_items_items__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ "/users/": {
+ "get": {
+ "tags": ["users"],
+ "summary": "Read Users",
+ "operationId": "read_users_users__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_get_items():
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == ["Portal gun", "Plumbus"]
+
+
+def test_get_users():
+ response = client.get("/users/")
+ assert response.status_code == 200, response.text
+ assert response.json() == ["Rick", "Morty"]
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py
new file mode 100644
index 000000000..1f617da70
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py
@@ -0,0 +1,121 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "The created item",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create an item",
+ "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number"},
+ "tags": {
+ "title": "Tags",
+ "uniqueItems": True,
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.path_operation_configuration.tutorial005_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_query_params_str_validations(client: TestClient):
+ response = client.post("/items/", json={"name": "Foo", "price": 42})
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Foo",
+ "price": 42,
+ "description": None,
+ "tax": None,
+ "tags": [],
+ }
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py
new file mode 100644
index 000000000..ffdf05081
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py
@@ -0,0 +1,121 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "The created item",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create an item",
+ "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number"},
+ "tags": {
+ "title": "Tags",
+ "uniqueItems": True,
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.path_operation_configuration.tutorial005_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_query_params_str_validations(client: TestClient):
+ response = client.post("/items/", json={"name": "Foo", "price": 42})
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Foo",
+ "price": 42,
+ "description": None,
+ "tax": None,
+ "tags": [],
+ }
diff --git a/tests/test_tutorial/test_query_params/test_tutorial006_py310.py b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py
new file mode 100644
index 000000000..1986d27d0
--- /dev/null
+++ b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py
@@ -0,0 +1,148 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read User Item",
+ "operationId": "read_user_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": True,
+ "schema": {"title": "Needy", "type": "string"},
+ "name": "needy",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Skip", "type": "integer", "default": 0},
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Limit", "type": "integer"},
+ "name": "limit",
+ "in": "query",
+ },
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+query_required = {
+ "detail": [
+ {
+ "loc": ["query", "needy"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params.tutorial006_py310 import app
+
+ c = TestClient(app)
+ return c
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ ("/openapi.json", 200, openapi_schema),
+ (
+ "/items/foo?needy=very",
+ 200,
+ {"item_id": "foo", "needy": "very", "skip": 0, "limit": None},
+ ),
+ (
+ "/items/foo?skip=a&limit=b",
+ 422,
+ {
+ "detail": [
+ {
+ "loc": ["query", "needy"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["query", "skip"],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer",
+ },
+ {
+ "loc": ["query", "limit"],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer",
+ },
+ ]
+ },
+ ),
+ ],
+)
+def test(path, expected_status, expected_response, client: TestClient):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py
new file mode 100644
index 000000000..66b24017e
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py
@@ -0,0 +1,132 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "description": "Query string for the items to search in the database that have a good match",
+ "required": False,
+ "deprecated": True,
+ "schema": {
+ "title": "Query string",
+ "maxLength": 50,
+ "minLength": 3,
+ "pattern": "^fixedquery$",
+ "type": "string",
+ "description": "Query string for the items to search in the database that have a good match",
+ },
+ "name": "item-query",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params_str_validations.tutorial010_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+regex_error = {
+ "detail": [
+ {
+ "ctx": {"pattern": "^fixedquery$"},
+ "loc": ["query", "item-query"],
+ "msg": 'string does not match regex "^fixedquery$"',
+ "type": "value_error.str.regex",
+ }
+ ]
+}
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "q_name,q,expected_status,expected_response",
+ [
+ (None, None, 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}),
+ (
+ "item-query",
+ "fixedquery",
+ 200,
+ {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}], "q": "fixedquery"},
+ ),
+ ("q", "fixedquery", 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}),
+ ("item-query", "nonregexquery", 422, regex_error),
+ ],
+)
+def test_query_params_str_validations(
+ q_name, q, expected_status, expected_response, client: TestClient
+):
+ url = "/items/"
+ if q_name and q:
+ url = f"{url}?{q_name}={q}"
+ response = client.get(url)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py
new file mode 100644
index 000000000..8894ee1b5
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py
@@ -0,0 +1,105 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Q",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params_str_validations.tutorial011_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_multi_query_values(client: TestClient):
+ url = "/items/?q=foo&q=bar"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": ["foo", "bar"]}
+
+
+@needs_py310
+def test_query_no_values(client: TestClient):
+ url = "/items/"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": None}
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py
new file mode 100644
index 000000000..b10e70af7
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py
@@ -0,0 +1,105 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Q",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params_str_validations.tutorial011_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_multi_query_values(client: TestClient):
+ url = "/items/?q=foo&q=bar"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": ["foo", "bar"]}
+
+
+@needs_py39
+def test_query_no_values(client: TestClient):
+ url = "/items/"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": None}
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py
new file mode 100644
index 000000000..a9cbce02a
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py
@@ -0,0 +1,106 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Q",
+ "type": "array",
+ "items": {"type": "string"},
+ "default": ["foo", "bar"],
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params_str_validations.tutorial012_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_default_query_values(client: TestClient):
+ url = "/items/"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": ["foo", "bar"]}
+
+
+@needs_py39
+def test_multi_query_values(client: TestClient):
+ url = "/items/?q=baz&q=foobar"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": ["baz", "foobar"]}
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
new file mode 100644
index 000000000..98ae5a684
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
@@ -0,0 +1,82 @@
+from fastapi.testclient import TestClient
+
+from docs_src.query_params_str_validations.tutorial014 import app
+
+client = TestClient(app)
+
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_hidden_query():
+ response = client.get("/items?hidden_query=somevalue")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"hidden_query": "somevalue"}
+
+
+def test_no_hidden_query():
+ response = client.get("/items")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"hidden_query": "Not found"}
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py
new file mode 100644
index 000000000..33f3d5f77
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py
@@ -0,0 +1,91 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params_str_validations.tutorial014_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_hidden_query(client: TestClient):
+ response = client.get("/items?hidden_query=somevalue")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"hidden_query": "somevalue"}
+
+
+@needs_py310
+def test_no_hidden_query(client: TestClient):
+ response = client.get("/items")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"hidden_query": "Not found"}
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py
new file mode 100644
index 000000000..e852a1b31
--- /dev/null
+++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py
@@ -0,0 +1,157 @@
+from fastapi.testclient import TestClient
+
+from docs_src.request_files.tutorial001_02 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/files/": {
+ "post": {
+ "summary": "Create File",
+ "operationId": "create_file_files__post",
+ "requestBody": {
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_create_file_files__post"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/uploadfile/": {
+ "post": {
+ "summary": "Create Upload File",
+ "operationId": "create_upload_file_uploadfile__post",
+ "requestBody": {
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_create_file_files__post": {
+ "title": "Body_create_file_files__post",
+ "type": "object",
+ "properties": {
+ "file": {"title": "File", "type": "string", "format": "binary"}
+ },
+ },
+ "Body_create_upload_file_uploadfile__post": {
+ "title": "Body_create_upload_file_uploadfile__post",
+ "type": "object",
+ "properties": {
+ "file": {"title": "File", "type": "string", "format": "binary"}
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_post_form_no_body():
+ response = client.post("/files/")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "No file sent"}
+
+
+def test_post_uploadfile_no_body():
+ response = client.post("/uploadfile/")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "No upload file sent"}
+
+
+def test_post_file(tmp_path):
+ path = tmp_path / "test.txt"
+ path.write_bytes(b"