diff --git a/README.md b/README.md
index 266213426..73f70cd6a 100644
--- a/README.md
+++ b/README.md
@@ -50,12 +50,12 @@ The key features are:
+
-
diff --git a/docs/en/data/github_sponsors.yml b/docs/en/data/github_sponsors.yml
index 3a68ba62b..1ec71dd49 100644
--- a/docs/en/data/github_sponsors.yml
+++ b/docs/en/data/github_sponsors.yml
@@ -2,6 +2,9 @@ sponsors:
- - login: cryptapi
avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4
url: https://github.com/cryptapi
+ - login: porter-dev
+ avatarUrl: https://avatars.githubusercontent.com/u/62078005?v=4
+ url: https://github.com/porter-dev
- login: fern-api
avatarUrl: https://avatars.githubusercontent.com/u/102944815?v=4
url: https://github.com/fern-api
@@ -17,24 +20,21 @@ sponsors:
- - login: mikeckennedy
avatarUrl: https://avatars.githubusercontent.com/u/2035561?u=1bb18268bcd4d9249e1f783a063c27df9a84c05b&v=4
url: https://github.com/mikeckennedy
+ - login: ndimares
+ avatarUrl: https://avatars.githubusercontent.com/u/6267663?u=cfb27efde7a7212be8142abb6c058a1aeadb41b1&v=4
+ url: https://github.com/ndimares
- login: deta
avatarUrl: https://avatars.githubusercontent.com/u/47275976?v=4
url: https://github.com/deta
- login: deepset-ai
avatarUrl: https://avatars.githubusercontent.com/u/51827949?v=4
url: https://github.com/deepset-ai
- - login: svix
- avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4
- url: https://github.com/svix
- login: databento-bot
avatarUrl: https://avatars.githubusercontent.com/u/98378480?u=494f679996e39427f7ddb1a7de8441b7c96fb670&v=4
url: https://github.com/databento-bot
- login: VincentParedes
avatarUrl: https://avatars.githubusercontent.com/u/103889729?v=4
url: https://github.com/VincentParedes
-- - login: arcticfly
- avatarUrl: https://avatars.githubusercontent.com/u/41524992?u=03c88529a86cf51f7a380e890d84d84c71468848&v=4
- url: https://github.com/arcticfly
- - login: getsentry
avatarUrl: https://avatars.githubusercontent.com/u/1396951?v=4
url: https://github.com/getsentry
@@ -89,18 +89,18 @@ sponsors:
- - login: povilasb
avatarUrl: https://avatars.githubusercontent.com/u/1213442?u=b11f58ed6ceea6e8297c9b310030478ebdac894d&v=4
url: https://github.com/povilasb
+ - login: mukulmantosh
+ avatarUrl: https://avatars.githubusercontent.com/u/15572034?u=006f0a33c0e15bb2de57474a2cf7d2f8ee05f5a0&v=4
+ url: https://github.com/mukulmantosh
- login: primer-io
avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4
url: https://github.com/primer-io
- - login: indeedeng
avatarUrl: https://avatars.githubusercontent.com/u/2905043?v=4
url: https://github.com/indeedeng
- - login: iguit0
- avatarUrl: https://avatars.githubusercontent.com/u/12905770?u=63a1a96d1e6c27d85c4f946b84836599de047f65&v=4
- url: https://github.com/iguit0
- - login: JacobKochems
- avatarUrl: https://avatars.githubusercontent.com/u/41692189?u=a75f62ddc0d060ee6233a91e19c433d2687b8eb6&v=4
- url: https://github.com/JacobKochems
+ - login: NateXVI
+ avatarUrl: https://avatars.githubusercontent.com/u/48195620?u=4bc8751ae50cb087c40c1fe811764aa070b9eea6&v=4
+ url: https://github.com/NateXVI
- - login: Kludex
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
@@ -200,12 +200,12 @@ sponsors:
- login: hiancdtrsnm
avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4
url: https://github.com/hiancdtrsnm
- - login: Shackelford-Arden
- avatarUrl: https://avatars.githubusercontent.com/u/7362263?v=4
- url: https://github.com/Shackelford-Arden
- login: wdwinslow
avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4
url: https://github.com/wdwinslow
+ - login: jsoques
+ avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4
+ url: https://github.com/jsoques
- login: joeds13
avatarUrl: https://avatars.githubusercontent.com/u/13631604?u=628eb122e08bef43767b3738752b883e8e7f6259&v=4
url: https://github.com/joeds13
@@ -227,9 +227,6 @@ sponsors:
- login: Filimoa
avatarUrl: https://avatars.githubusercontent.com/u/21352040?u=0be845711495bbd7b756e13fcaeb8efc1ebd78ba&v=4
url: https://github.com/Filimoa
- - login: LarryGF
- avatarUrl: https://avatars.githubusercontent.com/u/26148349?u=431bb34d36d41c172466252242175281ae132152&v=4
- url: https://github.com/LarryGF
- login: BrettskiPy
avatarUrl: https://avatars.githubusercontent.com/u/30988215?u=d8a94a67e140d5ee5427724b292cc52d8827087a&v=4
url: https://github.com/BrettskiPy
@@ -239,6 +236,9 @@ sponsors:
- login: Leay15
avatarUrl: https://avatars.githubusercontent.com/u/32212558?u=c4aa9c1737e515959382a5515381757b1fd86c53&v=4
url: https://github.com/Leay15
+ - login: dvlpjrs
+ avatarUrl: https://avatars.githubusercontent.com/u/32254642?u=fbd6ad0324d4f1eb6231cf775be1c7bd4404e961&v=4
+ url: https://github.com/dvlpjrs
- login: ygorpontelo
avatarUrl: https://avatars.githubusercontent.com/u/32963605?u=35f7103f9c4c4c2589ae5737ee882e9375ef072e&v=4
url: https://github.com/ygorpontelo
@@ -284,15 +284,15 @@ sponsors:
- login: DelfinaCare
avatarUrl: https://avatars.githubusercontent.com/u/83734439?v=4
url: https://github.com/DelfinaCare
+ - login: hbakri
+ avatarUrl: https://avatars.githubusercontent.com/u/92298226?u=36eee6d75db1272264d3ee92f1871f70b8917bd8&v=4
+ url: https://github.com/hbakri
- login: osawa-koki
avatarUrl: https://avatars.githubusercontent.com/u/94336223?u=59c6fe6945bcbbaff87b2a794238671b060620d2&v=4
url: https://github.com/osawa-koki
- login: pyt3h
avatarUrl: https://avatars.githubusercontent.com/u/99658549?v=4
url: https://github.com/pyt3h
- - login: Dagmaara
- avatarUrl: https://avatars.githubusercontent.com/u/115501964?v=4
- url: https://github.com/Dagmaara
- - login: SebTota
avatarUrl: https://avatars.githubusercontent.com/u/25122511?v=4
url: https://github.com/SebTota
@@ -329,6 +329,9 @@ sponsors:
- login: janfilips
avatarUrl: https://avatars.githubusercontent.com/u/870699?u=80702ec63f14e675cd4cdcc6ce3821d2ed207fd7&v=4
url: https://github.com/janfilips
+ - login: dodo5522
+ avatarUrl: https://avatars.githubusercontent.com/u/1362607?u=9bf1e0e520cccc547c046610c468ce6115bbcf9f&v=4
+ url: https://github.com/dodo5522
- login: WillHogan
avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=7036c064cf29781470573865264ec8e60b6b809f&v=4
url: https://github.com/WillHogan
@@ -374,9 +377,6 @@ sponsors:
- login: katnoria
avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4
url: https://github.com/katnoria
- - login: mattwelke
- avatarUrl: https://avatars.githubusercontent.com/u/7719209?u=80f02a799323b1472b389b836d95957c93a6d856&v=4
- url: https://github.com/mattwelke
- login: harsh183
avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4
url: https://github.com/harsh183
@@ -422,6 +422,9 @@ sponsors:
- login: jangia
avatarUrl: https://avatars.githubusercontent.com/u/17927101?u=9261b9bb0c3e3bb1ecba43e8915dc58d8c9a077e&v=4
url: https://github.com/jangia
+ - login: timzaz
+ avatarUrl: https://avatars.githubusercontent.com/u/19709244?u=e6658f6b0b188294ce2db20dad94a678fcea718d&v=4
+ url: https://github.com/timzaz
- login: shuheng-liu
avatarUrl: https://avatars.githubusercontent.com/u/22414322?u=813c45f30786c6b511b21a661def025d8f7b609e&v=4
url: https://github.com/shuheng-liu
@@ -431,6 +434,9 @@ sponsors:
- login: kxzk
avatarUrl: https://avatars.githubusercontent.com/u/25046261?u=e185e58080090f9e678192cd214a14b14a2b232b&v=4
url: https://github.com/kxzk
+ - login: nisutec
+ avatarUrl: https://avatars.githubusercontent.com/u/25281462?u=e562484c451fdfc59053163f64405f8eb262b8b0&v=4
+ url: https://github.com/nisutec
- login: hoenie-ams
avatarUrl: https://avatars.githubusercontent.com/u/25708487?u=cda07434f0509ac728d9edf5e681117c0f6b818b&v=4
url: https://github.com/hoenie-ams
@@ -450,7 +456,7 @@ sponsors:
avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4
url: https://github.com/engineerjoe440
- login: bnkc
- avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=527044d90b5ebb7f8dad517db5da1f45253b774b&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=1a104991a2ea90bfe304bc0b9ef191c7e4891a0e&v=4
url: https://github.com/bnkc
- login: declon
avatarUrl: https://avatars.githubusercontent.com/u/36180226?v=4
@@ -482,6 +488,15 @@ sponsors:
- login: 0417taehyun
avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4
url: https://github.com/0417taehyun
+ - login: romabozhanovgithub
+ avatarUrl: https://avatars.githubusercontent.com/u/67696229?u=e4b921eef096415300425aca249348f8abb78ad7&v=4
+ url: https://github.com/romabozhanovgithub
+ - login: mnicolleUTC
+ avatarUrl: https://avatars.githubusercontent.com/u/68548924?u=1fdd7f436bb1f4857c3415e62aa8fbc055fc1a76&v=4
+ url: https://github.com/mnicolleUTC
+ - login: mbukeRepo
+ avatarUrl: https://avatars.githubusercontent.com/u/70356088?u=e125069c6ac5c49355e2b3ca2aa66a445fc4cccd&v=4
+ url: https://github.com/mbukeRepo
- - login: ssbarnea
avatarUrl: https://avatars.githubusercontent.com/u/102495?u=b4bf6818deefe59952ac22fec6ed8c76de1b8f7c&v=4
url: https://github.com/ssbarnea
@@ -494,21 +509,18 @@ sponsors:
- login: sadikkuzu
avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=d179c06bb9f65c4167fcab118526819f8e0dac17&v=4
url: https://github.com/sadikkuzu
- - login: ruizdiazever
- avatarUrl: https://avatars.githubusercontent.com/u/29817086?u=2df54af55663d246e3a4dc8273711c37f1adb117&v=4
- url: https://github.com/ruizdiazever
- login: samnimoh
avatarUrl: https://avatars.githubusercontent.com/u/33413170?u=147bc516be6cb647b28d7e3b3fea3a018a331145&v=4
url: https://github.com/samnimoh
- login: danburonline
avatarUrl: https://avatars.githubusercontent.com/u/34251194?u=2cad4388c1544e539ecb732d656e42fb07b4ff2d&v=4
url: https://github.com/danburonline
- - login: iharshgor
- avatarUrl: https://avatars.githubusercontent.com/u/35490011?u=2dea054476e752d9e92c9d71a9a7cc919b1c2f8e&v=4
- url: https://github.com/iharshgor
+ - login: koalawangyang
+ avatarUrl: https://avatars.githubusercontent.com/u/40114367?u=3bb94010f0473b8d4c2edea5a8c1cab61e6a1b22&v=4
+ url: https://github.com/koalawangyang
- login: rwxd
avatarUrl: https://avatars.githubusercontent.com/u/40308458?u=cd04a39e3655923be4f25c2ba8a5a07b3da3230a&v=4
url: https://github.com/rwxd
- - login: ThomasPalma1
- avatarUrl: https://avatars.githubusercontent.com/u/66331874?u=5763f7402d784ba189b60d704ff5849b4d0a63fb&v=4
- url: https://github.com/ThomasPalma1
+ - login: lodine-software
+ avatarUrl: https://avatars.githubusercontent.com/u/133536046?v=4
+ url: https://github.com/lodine-software
diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml
index 89d189564..8ebd2f84c 100644
--- a/docs/en/data/people.yml
+++ b/docs/en/data/people.yml
@@ -1,16 +1,16 @@
maintainers:
- login: tiangolo
- answers: 1849
- prs: 466
+ answers: 1866
+ prs: 486
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=740f11212a731f56798f558ceddb0bd07642afa7&v=4
url: https://github.com/tiangolo
experts:
- login: Kludex
- count: 463
+ count: 480
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: dmontagu
- count: 239
+ count: 240
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4
url: https://github.com/dmontagu
- login: Mause
@@ -26,7 +26,7 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4
url: https://github.com/JarroVGIT
- login: jgould22
- count: 157
+ count: 164
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
url: https://github.com/jgould22
- login: euri10
@@ -38,7 +38,7 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4
url: https://github.com/phy25
- login: iudeen
- count: 121
+ count: 122
avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
url: https://github.com/iudeen
- login: raphaelauv
@@ -61,34 +61,34 @@ experts:
count: 49
avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4
url: https://github.com/sm-Fifteen
-- login: insomnes
- count: 45
- avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
- url: https://github.com/insomnes
- login: yinziyan1206
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4
url: https://github.com/yinziyan1206
-- login: acidjunk
+- login: insomnes
count: 45
- avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
- url: https://github.com/acidjunk
+ avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
+ url: https://github.com/insomnes
- login: Dustyposa
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
url: https://github.com/Dustyposa
+- login: acidjunk
+ count: 45
+ avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
+ url: https://github.com/acidjunk
- login: adriangb
count: 44
avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4
url: https://github.com/adriangb
-- login: frankie567
- count: 43
- avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4
- url: https://github.com/frankie567
- login: odiseo0
count: 43
avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=241a71f6b7068738b81af3e57f45ffd723538401&v=4
url: https://github.com/odiseo0
+- login: frankie567
+ count: 43
+ avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=c159fe047727aedecbbeeaa96a1b03ceb9d39add&v=4
+ url: https://github.com/frankie567
- login: includeamin
count: 40
avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4
@@ -97,14 +97,14 @@ experts:
count: 37
avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
url: https://github.com/STeveShary
+- login: chbndrhnns
+ count: 36
+ avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
+ url: https://github.com/chbndrhnns
- login: krishnardt
count: 35
avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4
url: https://github.com/krishnardt
-- login: chbndrhnns
- count: 35
- avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
- url: https://github.com/chbndrhnns
- login: panla
count: 32
avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
@@ -117,26 +117,30 @@ experts:
count: 26
avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4
url: https://github.com/dbanty
+- login: n8sty
+ count: 25
+ avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4
+ url: https://github.com/n8sty
- login: wshayes
count: 25
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
url: https://github.com/wshayes
-- login: acnebs
- count: 23
- avatarUrl: https://avatars.githubusercontent.com/u/9054108?v=4
- url: https://github.com/acnebs
- login: SirTelemak
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4
url: https://github.com/SirTelemak
+- login: acnebs
+ count: 23
+ avatarUrl: https://avatars.githubusercontent.com/u/9054108?v=4
+ url: https://github.com/acnebs
- login: rafsaf
count: 21
- avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=f8f0d6d6e90fac39fa786228158ba7f013c74271&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=5fe59a56e1f2f9ccd8005d71752a8276f133ae1a&v=4
url: https://github.com/rafsaf
-- login: n8sty
+- login: JavierSanchezCastro
count: 20
- avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4
- url: https://github.com/n8sty
+ avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
+ url: https://github.com/JavierSanchezCastro
- login: nsidnev
count: 20
avatarUrl: https://avatars.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4
@@ -173,55 +177,51 @@ experts:
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4
url: https://github.com/jonatasoli
+- login: ebottos94
+ count: 16
+ avatarUrl: https://avatars.githubusercontent.com/u/100039558?u=e2c672da5a7977fd24d87ce6ab35f8bf5b1ed9fa&v=4
+ url: https://github.com/ebottos94
- login: dstlny
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4
url: https://github.com/dstlny
-- login: abhint
+- login: chrisK824
count: 15
- avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4
- url: https://github.com/abhint
+ avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4
+ url: https://github.com/chrisK824
- login: nymous
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/4216559?u=360a36fb602cded27273cbfc0afc296eece90662&v=4
url: https://github.com/nymous
-- login: ghost
+- login: abhint
count: 15
- avatarUrl: https://avatars.githubusercontent.com/u/10137?u=b1951d34a583cf12ec0d3b0781ba19be97726318&v=4
- url: https://github.com/ghost
-- login: simondale00
- count: 15
- avatarUrl: https://avatars.githubusercontent.com/u/33907262?v=4
- url: https://github.com/simondale00
-- login: jorgerpo
- count: 15
- avatarUrl: https://avatars.githubusercontent.com/u/12537771?u=7444d20019198e34911082780cc7ad73f2b97cb3&v=4
- url: https://github.com/jorgerpo
+ avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4
+ url: https://github.com/abhint
last_month_active:
+- login: JavierSanchezCastro
+ count: 12
+ avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
+ url: https://github.com/JavierSanchezCastro
- login: Kludex
- count: 24
+ count: 10
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: jgould22
- count: 17
+ count: 8
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
url: https://github.com/jgould22
-- login: arjwilliams
- count: 8
- avatarUrl: https://avatars.githubusercontent.com/u/22227620?v=4
- url: https://github.com/arjwilliams
-- login: Ahmed-Abdou14
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/104530599?u=05365b155a1ff911532e8be316acfad2e0736f98&v=4
- url: https://github.com/Ahmed-Abdou14
-- login: iudeen
+- login: romabozhanovgithub
+ count: 5
+ avatarUrl: https://avatars.githubusercontent.com/u/67696229?u=e4b921eef096415300425aca249348f8abb78ad7&v=4
+ url: https://github.com/romabozhanovgithub
+- login: n8sty
+ count: 5
+ avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4
+ url: https://github.com/n8sty
+- login: chrisK824
count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
- url: https://github.com/iudeen
-- login: mikeedjones
- count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/4087139?u=cc4a242896ac2fcf88a53acfaf190d0fe0a1f0c9&v=4
- url: https://github.com/mikeedjones
+ avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4
+ url: https://github.com/chrisK824
top_contributors:
- login: waynerv
count: 25
@@ -235,22 +235,22 @@ top_contributors:
count: 21
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
+- login: dmontagu
+ count: 17
+ avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4
+ url: https://github.com/dmontagu
- login: jaystone776
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
url: https://github.com/jaystone776
-- login: dmontagu
- count: 16
- avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4
- url: https://github.com/dmontagu
+- login: Xewus
+ count: 14
+ avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4
+ url: https://github.com/Xewus
- login: euri10
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4
url: https://github.com/euri10
-- login: Xewus
- count: 13
- avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4
- url: https://github.com/Xewus
- login: mariacamilagl
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
@@ -360,6 +360,10 @@ top_reviewers:
count: 78
avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=d7062cbc6eb7671d5dc9cc0e32a24ae335e0f225&v=4
url: https://github.com/yezz123
+- login: iudeen
+ count: 53
+ avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
+ url: https://github.com/iudeen
- login: tokusumi
count: 51
avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
@@ -372,10 +376,6 @@ top_reviewers:
count: 47
avatarUrl: https://avatars.githubusercontent.com/u/59285379?v=4
url: https://github.com/Laineyzhang55
-- login: iudeen
- count: 46
- avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
- url: https://github.com/iudeen
- login: ycd
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=bba5af018423a2858d49309bed2a899bb5c34ac5&v=4
@@ -385,7 +385,7 @@ top_reviewers:
avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4
url: https://github.com/cikay
- login: Xewus
- count: 38
+ count: 39
avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4
url: https://github.com/Xewus
- login: JarroVGIT
@@ -482,7 +482,7 @@ top_reviewers:
url: https://github.com/sh0nk
- login: peidrao
count: 13
- avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=5b94b548ef0002ef3219d7c07ac0fac17c6201a2&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=a66902b40c13647d0ed0e573d598128240a4dd04&v=4
url: https://github.com/peidrao
- login: r0b2g1t
count: 13
diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml
index 0d9597f07..e700a5770 100644
--- a/docs/en/data/sponsors.yml
+++ b/docs/en/data/sponsors.yml
@@ -11,6 +11,9 @@ gold:
- url: https://www.porter.run
title: Deploy FastAPI on AWS with a few clicks
img: https://fastapi.tiangolo.com/img/sponsors/porter.png
+ - url: https://bump.sh/fastapi?utm_source=fastapi&utm_medium=referral&utm_campaign=sponsor
+ title: Automate FastAPI documentation generation with Bump.sh
+ img: https://fastapi.tiangolo.com/img/sponsors/bump-sh.png
silver:
- url: https://www.deta.sh/?ref=fastapi
title: The launchpad for all your (team's) ideas
@@ -27,9 +30,6 @@ silver:
- url: https://careers.powens.com/
title: Powens is hiring!
img: https://fastapi.tiangolo.com/img/sponsors/powens.png
- - url: https://www.svix.com/
- title: Svix - Webhooks as a service
- img: https://fastapi.tiangolo.com/img/sponsors/svix.svg
- url: https://databento.com/
title: Pay as you go for market data
img: https://fastapi.tiangolo.com/img/sponsors/databento.svg
diff --git a/docs/en/docs/css/custom.css b/docs/en/docs/css/custom.css
index 066b51725..187040792 100644
--- a/docs/en/docs/css/custom.css
+++ b/docs/en/docs/css/custom.css
@@ -144,3 +144,39 @@ code {
margin-top: 2em;
margin-bottom: 2em;
}
+
+/* Screenshots */
+/*
+Simulate a browser window frame.
+Inspired by Termynal's CSS tricks with modifications
+*/
+
+.screenshot {
+ display: block;
+ background-color: #d3e0de;
+ border-radius: 4px;
+ padding: 45px 5px 5px;
+ position: relative;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.screenshot img {
+ display: block;
+ border-radius: 2px;
+}
+
+.screenshot:before {
+ content: '';
+ position: absolute;
+ top: 15px;
+ left: 15px;
+ display: inline-block;
+ width: 15px;
+ height: 15px;
+ border-radius: 50%;
+ /* A little hack to display the window buttons in one pseudo element. */
+ background: #d9515d;
+ -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
+ box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
+}
diff --git a/docs/en/docs/deployment/server-workers.md b/docs/en/docs/deployment/server-workers.md
index 4ccd9d9f6..2df9f3d43 100644
--- a/docs/en/docs/deployment/server-workers.md
+++ b/docs/en/docs/deployment/server-workers.md
@@ -90,7 +90,9 @@ Let's see what each of those options mean:
```
* So, the colon in `main:app` would be equivalent to the Python `import` part in `from main import app`.
+
* `--workers`: The number of worker processes to use, each will run a Uvicorn worker, in this case, 4 workers.
+
* `--worker-class`: The Gunicorn-compatible worker class to use in the worker processes.
* Here we pass the class that Gunicorn can import and use with:
diff --git a/docs/en/docs/how-to/separate-openapi-schemas.md b/docs/en/docs/how-to/separate-openapi-schemas.md
new file mode 100644
index 000000000..d38be3c59
--- /dev/null
+++ b/docs/en/docs/how-to/separate-openapi-schemas.md
@@ -0,0 +1,231 @@
+# Separate OpenAPI Schemas for Input and Output or Not
+
+When using **Pydantic v2**, the generated OpenAPI is a bit more exact and **correct** than before. 😎
+
+In fact, in some cases, it will even have **two JSON Schemas** in OpenAPI for the same Pydantic model, for input and output, depending on if they have **default values**.
+
+Let's see how that works and how to change it if you need to do that.
+
+## Pydantic Models for Input and Output
+
+Let's say you have a Pydantic model with default values, like this one:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py[ln:1-7]!}
+
+ # Code below omitted 👇
+ ```
+
+ 👀 Full file preview
+
+ ```Python
+ {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py!}
+ ```
+
+ 👀 Full file preview
+
+ ```Python
+ {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py!}
+ ```
+
+ 👀 Full file preview
+
+ ```Python
+ {!> ../../../docs_src/separate_openapi_schemas/tutorial001.py!}
+ ```
+
+ 👀 Full file preview
+
+ ```Python
+ {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py!}
+ ```
+
+ 👀 Full file preview
+
+ ```Python
+ {!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py!}
+ ```
+
+ 👀 Full file preview
+
+ ```Python
+ {!> ../../../docs_src/separate_openapi_schemas/tutorial001.py!}
+ ```
+
+
+
+
+
+
+
@@ -210,20 +285,8 @@ OpenAPI also added `example` and `examples` fields to other parts of the specifi
* `File()`
* `Form()`
-### OpenAPI's `examples` field
-
-The shape of this field `examples` from OpenAPI is a `dict` with **multiple examples**, each with extra information that will be added to **OpenAPI** too.
-
-The keys of the `dict` identify each example, and each value is another `dict`.
-
-Each specific example `dict` in the `examples` can contain:
-
-* `summary`: Short description for the example.
-* `description`: A long description that can contain Markdown text.
-* `value`: This is the actual example shown, e.g. a `dict`.
-* `externalValue`: alternative to `value`, a URL pointing to the example. Although this might not be supported by as many tools as `value`.
-
-This applies to those other parts of the OpenAPI specification apart from JSON Schema.
+!!! info
+ This old OpenAPI-specific `examples` parameter is now `openapi_examples` since FastAPI `0.103.0`.
### JSON Schema's `examples` field
@@ -250,6 +313,12 @@ In versions of FastAPI before 0.99.0 (0.99.0 and above use the newer OpenAPI 3.1
But now that FastAPI 0.99.0 and above uses OpenAPI 3.1.0, that uses JSON Schema 2020-12, and Swagger UI 5.0.0 and above, everything is more consistent and the examples are included in JSON Schema.
+### Swagger UI and OpenAPI-specific `examples`
+
+Now, as Swagger UI didn't support multiple JSON Schema examples (as of 2023-08-26), users didn't have a way to show multiple examples in the docs.
+
+To solve that, FastAPI `0.103.0` **added support** for declaring the same old **OpenAPI-specific** `examples` field with the new parameter `openapi_examples`. 🤓
+
### Summary
I used to say I didn't like history that much... and look at me now giving "tech history" lessons. 😅
diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml
index f75b84ff5..ba1ac7924 100644
--- a/docs/en/mkdocs.yml
+++ b/docs/en/mkdocs.yml
@@ -72,6 +72,7 @@ nav:
- uk: /uk/
- ur: /ur/
- vi: /vi/
+ - yo: /yo/
- zh: /zh/
- features.md
- fastapi-people.md
@@ -176,6 +177,7 @@ nav:
- how-to/custom-request-and-route.md
- how-to/conditional-openapi.md
- how-to/extending-openapi.md
+ - how-to/separate-openapi-schemas.md
- how-to/custom-docs-ui-assets.md
- how-to/configure-swagger-ui.md
- project-generation.md
@@ -260,6 +262,8 @@ extra:
name: ur
- link: /vi/
name: vi - Tiếng Việt
+ - link: /yo/
+ name: yo - Yorùbá
- link: /zh/
name: zh - 汉语
extra_css:
diff --git a/docs/en/overrides/main.html b/docs/en/overrides/main.html
index 983d7eb3e..d867d2ee5 100644
--- a/docs/en/overrides/main.html
+++ b/docs/en/overrides/main.html
@@ -46,6 +46,12 @@
+
+
+
+
+### Додайте типи
+
+Давайте змінимо один рядок з попередньої версії.
+
+Ми змінимо саме цей фрагмент, параметри функції, з:
+
+```Python
+ first_name, last_name
+```
+
+на:
+
+```Python
+ first_name: str, last_name: str
+```
+
+Ось і все.
+
+Це "type hints":
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial002.py!}
+```
+
+Це не те саме, що оголошення значень за замовчуванням, як це було б з:
+
+```Python
+ first_name="john", last_name="doe"
+```
+
+Це зовсім інше.
+
+Ми використовуємо двокрапку (`:`), не дорівнює (`=`).
+
+І додавання анотації типу зазвичай не змінює того, що сталось би без них.
+
+Але тепер, уявіть що ви посеред процесу створення функції, але з анотаціями типів.
+
+В цей же момент, ви спробуєте викликати автозаповнення з допомогою `Ctrl+Space` і побачите:
+
+
+
+Разом з цим, ви можете прокручувати, переглядати опції, допоки ви не знайдете одну, що звучить схоже:
+
+
+
+## Більше мотивації
+
+Перевірте цю функцію, вона вже має анотацію типу:
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial003.py!}
+```
+
+Оскільки редактор знає типи змінних, ви не тільки отримаєте автозаповнення, ви також отримаєте перевірку помилок:
+
+
+
+Тепер ви знаєте, щоб виправити це, вам потрібно перетворити `age` у строку з допомогою `str(age)`:
+
+```Python hl_lines="2"
+{!../../../docs_src/python_types/tutorial004.py!}
+```
+
+## Оголошення типів
+
+Щойно ви побачили основне місце для оголошення анотацій типу. Як параметри функції.
+
+Це також основне місце, де ви б їх використовували у **FastAPI**.
+
+### Прості типи
+
+Ви можете оголошувати усі стандартні типи у Python, не тільки `str`.
+
+Ви можете використовувати, наприклад:
+
+* `int`
+* `float`
+* `bool`
+* `bytes`
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial005.py!}
+```
+
+### Generic-типи з параметрами типів
+
+Існують деякі структури даних, які можуть містити інші значення, наприклад `dict`, `list`, `set` та `tuple`. І внутрішні значення також можуть мати свій тип.
+
+Ці типи, які мають внутрішні типи, називаються "**generic**" типами. І оголосити їх можна навіть із внутрішніми типами.
+
+Щоб оголосити ці типи та внутрішні типи, ви можете використовувати стандартний модуль Python `typing`. Він існує спеціально для підтримки анотацій типів.
+
+#### Новіші версії Python
+
+Синтаксис із використанням `typing` **сумісний** з усіма версіями, від Python 3.6 до останніх, включаючи Python 3.9, Python 3.10 тощо.
+
+У міру розвитку Python **новіші версії** мають покращену підтримку анотацій типів і в багатьох випадках вам навіть не потрібно буде імпортувати та використовувати модуль `typing` для оголошення анотацій типу.
+
+Якщо ви можете вибрати новішу версію Python для свого проекту, ви зможете скористатися цією додатковою простотою. Дивіться кілька прикладів нижче.
+
+#### List (список)
+
+Наприклад, давайте визначимо змінну, яка буде `list` із `str`.
+
+=== "Python 3.6 і вище"
+
+ З модуля `typing`, імпортуємо `List` (з великої літери `L`):
+
+ ``` Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial006.py!}
+ ```
+
+ Оголосимо змінну з тим самим синтаксисом двокрапки (`:`).
+
+ Як тип вкажемо `List`, який ви імпортували з `typing`.
+
+ Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки:
+
+ ```Python hl_lines="4"
+ {!> ../../../docs_src/python_types/tutorial006.py!}
+ ```
+
+=== "Python 3.9 і вище"
+
+ Оголосимо змінну з тим самим синтаксисом двокрапки (`:`).
+
+ Як тип вкажемо `list`.
+
+ Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки:
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial006_py39.py!}
+ ```
+
+!!! info
+ Ці внутрішні типи в квадратних дужках називаються "параметрами типу".
+
+ У цьому випадку, `str` це параметр типу переданий у `List` (або `list` у Python 3.9 і вище).
+
+Це означає: "змінна `items` це `list`, і кожен з елементів у цьому списку - `str`".
+
+!!! tip
+ Якщо ви використовуєте Python 3.9 і вище, вам не потрібно імпортувати `List` з `typing`, ви можете використовувати натомість тип `list`.
+
+Зробивши це, ваш редактор може надати підтримку навіть під час обробки елементів зі списку:
+
+
+
+Без типів цього майже неможливо досягти.
+
+Зверніть увагу, що змінна `item` є одним із елементів у списку `items`.
+
+І все ж редактор знає, що це `str`, і надає підтримку для цього.
+
+#### Tuple and Set (кортеж та набір)
+
+Ви повинні зробити те ж саме, щоб оголосити `tuple` і `set`:
+
+=== "Python 3.6 і вище"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial007.py!}
+ ```
+
+=== "Python 3.9 і вище"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial007_py39.py!}
+ ```
+
+Це означає:
+
+* Змінна `items_t` це `tuple` з 3 елементами, `int`, ще `int`, та `str`.
+* Змінна `items_s` це `set`, і кожен його елемент типу `bytes`.
+
+#### Dict (словник)
+
+Щоб оголосити `dict`, вам потрібно передати 2 параметри типу, розділені комами.
+
+Перший параметр типу для ключа у `dict`.
+
+Другий параметр типу для значення у `dict`:
+
+=== "Python 3.6 і вище"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial008.py!}
+ ```
+
+=== "Python 3.9 і вище"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial008_py39.py!}
+ ```
+
+Це означає:
+
+* Змінна `prices` це `dict`:
+ * Ключі цього `dict` типу `str` (наприклад, назва кожного елементу).
+ * Значення цього `dict` типу `float` (наприклад, ціна кожного елементу).
+
+#### Union (об'єднання)
+
+Ви можете оголосити, що змінна може бути будь-яким із **кількох типів**, наприклад, `int` або `str`.
+
+У Python 3.6 і вище (включаючи Python 3.10) ви можете використовувати тип `Union` з `typing` і вставляти в квадратні дужки можливі типи, які можна прийняти.
+
+У Python 3.10 також є **альтернативний синтаксис**, у якому ви можете розділити можливі типи за допомогою вертикальної смуги (`|`).
+
+=== "Python 3.6 і вище"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial008b.py!}
+ ```
+
+=== "Python 3.10 і вище"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial008b_py310.py!}
+ ```
+
+В обох випадках це означає, що `item` може бути `int` або `str`.
+
+#### Possibly `None` (Optional)
+
+Ви можете оголосити, що значення може мати тип, наприклад `str`, але також може бути `None`.
+
+У Python 3.6 і вище (включаючи Python 3.10) ви можете оголосити його, імпортувавши та використовуючи `Optional` з модуля `typing`.
+
+```Python hl_lines="1 4"
+{!../../../docs_src/python_types/tutorial009.py!}
+```
+
+Використання `Optional[str]` замість просто `str` дозволить редактору допомогти вам виявити помилки, коли ви могли б вважати, що значенням завжди є `str`, хоча насправді воно також може бути `None`.
+
+`Optional[Something]` насправді є скороченням для `Union[Something, None]`, вони еквівалентні.
+
+Це також означає, що в Python 3.10 ви можете використовувати `Something | None`:
+
+=== "Python 3.6 і вище"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial009.py!}
+ ```
+
+=== "Python 3.6 і вище - альтернатива"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial009b.py!}
+ ```
+
+=== "Python 3.10 і вище"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial009_py310.py!}
+ ```
+
+#### Generic типи
+
+Ці типи, які приймають параметри типу у квадратних дужках, називаються **Generic types** or **Generics**, наприклад:
+
+=== "Python 3.6 і вище"
+
+ * `List`
+ * `Tuple`
+ * `Set`
+ * `Dict`
+ * `Union`
+ * `Optional`
+ * ...та інші.
+
+=== "Python 3.9 і вище"
+
+ Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині):
+
+ * `list`
+ * `tuple`
+ * `set`
+ * `dict`
+
+ І те саме, що й у Python 3.6, із модуля `typing`:
+
+ * `Union`
+ * `Optional`
+ * ...та інші.
+
+=== "Python 3.10 і вище"
+
+ Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині):
+
+ * `list`
+ * `tuple`
+ * `set`
+ * `dict`
+
+ І те саме, що й у Python 3.6, із модуля `typing`:
+
+ * `Union`
+ * `Optional` (так само як у Python 3.6)
+ * ...та інші.
+
+ У Python 3.10, як альтернатива використанню `Union` та `Optional`, ви можете використовувати вертикальну смугу (`|`) щоб оголосити об'єднання типів.
+
+### Класи як типи
+
+Ви також можете оголосити клас як тип змінної.
+
+Скажімо, у вас є клас `Person` з імʼям:
+
+```Python hl_lines="1-3"
+{!../../../docs_src/python_types/tutorial010.py!}
+```
+
+Потім ви можете оголосити змінну типу `Person`:
+
+```Python hl_lines="6"
+{!../../../docs_src/python_types/tutorial010.py!}
+```
+
+І знову ж таки, ви отримуєте всю підтримку редактора:
+
+
+
+## Pydantic моделі
+
+Pydantic це бібліотека Python для валідації даних.
+
+Ви оголошуєте «форму» даних як класи з атрибутами.
+
+І кожен атрибут має тип.
+
+Потім ви створюєте екземпляр цього класу з деякими значеннями, і він перевірить ці значення, перетворить їх у відповідний тип (якщо є потреба) і надасть вам об’єкт з усіма даними.
+
+І ви отримуєте всю підтримку редактора з цим отриманим об’єктом.
+
+Приклад з документації Pydantic:
+
+=== "Python 3.6 і вище"
+
+ ```Python
+ {!> ../../../docs_src/python_types/tutorial011.py!}
+ ```
+
+=== "Python 3.9 і вище"
+
+ ```Python
+ {!> ../../../docs_src/python_types/tutorial011_py39.py!}
+ ```
+
+=== "Python 3.10 і вище"
+
+ ```Python
+ {!> ../../../docs_src/python_types/tutorial011_py310.py!}
+ ```
+
+!!! info
+ Щоб дізнатись більше про Pydantic, перегляньте його документацію.
+
+**FastAPI** повністю базується на Pydantic.
+
+Ви побачите набагато більше цього всього на практиці в [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
+
+## Анотації типів у **FastAPI**
+
+**FastAPI** використовує ці підказки для виконання кількох речей.
+
+З **FastAPI** ви оголошуєте параметри з підказками типу, і отримуєте:
+
+* **Підтримку редактора**.
+* **Перевірку типів**.
+
+...і **FastAPI** використовує ті самі оголошення для:
+
+* **Визначення вимог**: з параметрів шляху запиту, параметрів запиту, заголовків, тіл, залежностей тощо.
+* **Перетворення даних**: із запиту в необхідний тип.
+* **Перевірка даних**: що надходять від кожного запиту:
+ * Генерування **автоматичних помилок**, що повертаються клієнту, коли дані недійсні.
+* **Документування** API за допомогою OpenAPI:
+ * який потім використовується для автоматичної інтерактивної документації користувальницьких інтерфейсів.
+
+Все це може здатися абстрактним. Не хвилюйтеся. Ви побачите все це в дії в [Туторіал - Посібник користувача](tutorial/index.md){.internal-link target=_blank}.
+
+Важливо те, що за допомогою стандартних типів Python в одному місці (замість того, щоб додавати більше класів, декораторів тощо), **FastAPI** зробить багато роботи за вас.
+
+!!! info
+ Якщо ви вже пройшли весь навчальний посібник і повернулися, щоб дізнатися більше про типи, ось хороший ресурс "шпаргалка" від `mypy`.
diff --git a/docs/uk/docs/tutorial/index.md b/docs/uk/docs/tutorial/index.md
new file mode 100644
index 000000000..e5bae74bc
--- /dev/null
+++ b/docs/uk/docs/tutorial/index.md
@@ -0,0 +1,80 @@
+# Туторіал - Посібник користувача
+
+У цьому посібнику показано, як користуватися **FastAPI** з більшістю його функцій, крок за кроком.
+
+Кожен розділ поступово надбудовується на попередні, але він структурований на окремі теми, щоб ви могли перейти безпосередньо до будь-якої конкретної, щоб вирішити ваші конкретні потреби API.
+
+Він також створений як довідник для роботи у майбутньому.
+
+Тож ви можете повернутися і побачити саме те, що вам потрібно.
+
+## Запустіть код
+
+Усі блоки коду можна скопіювати та використовувати безпосередньо (це фактично перевірені файли Python).
+
+Щоб запустити будь-який із прикладів, скопіюйте код у файл `main.py` і запустіть `uvicorn` за допомогою:
+
+get
+
+!!! info Thông tin về "`@decorator`"
+ Cú pháp `@something` trong Python được gọi là một "decorator".
+
+ Bạn đặt nó trên một hàm. Giống như một chiếc mũ xinh xắn (Tôi ddonas đó là lí do mà thuật ngữ này ra đời).
+
+ Một "decorator" lấy một hàm bên dưới và thực hiện một vài thứ với nó.
+
+ Trong trường hợp của chúng ta, decorator này nói **FastAPI** rằng hàm bên dưới ứng với **đường dẫn** `/` và một **toán tử** `get`.
+
+ Nó là một "**decorator đường dẫn toán tử**".
+
+Bạn cũng có thể sử dụng với các toán tử khác:
+
+* `@app.post()`
+* `@app.put()`
+* `@app.delete()`
+
+Và nhiều hơn với các toán tử còn lại:
+
+* `@app.options()`
+* `@app.head()`
+* `@app.patch()`
+* `@app.trace()`
+
+!!! tip
+ Bạn thoải mái sử dụng mỗi toán tử (phương thức HTTP) như bạn mơ ước.
+
+ **FastAPI** không bắt buộc bất kì ý nghĩa cụ thể nào.
+
+ Thông tin ở đây được biểu thị như là một chỉ dẫn, không phải là một yêu cầu bắt buộc.
+
+ Ví dụ, khi sử dụng GraphQL bạn thông thường thực hiện tất cả các hành động chỉ bằng việc sử dụng các toán tử `POST`.
+
+### Step 4: Định nghĩa **hàm cho đường dẫn toán tử**
+
+Đây là "**hàm cho đường dẫn toán tử**":
+
+* **đường dẫn**: là `/`.
+* **toán tử**: là `get`.
+* **hàm**: là hàm bên dưới "decorator" (bên dưới `@app.get("/")`).
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Đây là một hàm Python.
+
+Nó sẽ được gọi bởi **FastAPI** bất cứ khi nào nó nhận một request tới URL "`/`" sử dụng một toán tử `GET`.
+
+Trong trường hợp này, nó là một hàm `async`.
+
+---
+
+Bạn cũng có thể định nghĩa nó như là một hàm thông thường thay cho `async def`:
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial003.py!}
+```
+
+!!! note
+ Nếu bạn không biết sự khác nhau, kiểm tra [Async: *"Trong khi vội vàng?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
+
+### Bước 5: Nội dung trả về
+
+```Python hl_lines="8"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Bạn có thể trả về một `dict`, `list`, một trong những giá trị đơn như `str`, `int`,...
+
+Bạn cũng có thể trả về Pydantic model (bạn sẽ thấy nhiều hơn về nó sau).
+
+Có nhiều object và model khác nhau sẽ được tự động chuyển đổi sang JSON (bao gồm cả ORM,...). Thử sử dụng loại ưa thích của bạn, nó có khả năng cao đã được hỗ trợ.
+
+## Tóm lại
+
+* Import `FastAPI`.
+* Tạo một `app` instance.
+* Viết một **decorator cho đường dẫn toán tử** (giống như `@app.get("/")`).
+* Viết một **hàm cho đường dẫn toán tử** (giống như `def root(): ...` ở trên).
+* Chạy server trong môi trường phát triển (giống như `uvicorn main:app --reload`).
diff --git a/docs/vi/docs/tutorial/index.md b/docs/vi/docs/tutorial/index.md
new file mode 100644
index 000000000..e8a93fe40
--- /dev/null
+++ b/docs/vi/docs/tutorial/index.md
@@ -0,0 +1,80 @@
+# Hướng dẫn sử dụng
+
+Hướng dẫn này cho bạn thấy từng bước cách sử dụng **FastAPI** đa số các tính năng của nó.
+
+Mỗi phần được xây dựng từ những phần trước đó, nhưng nó được cấu trúc thành các chủ đề riêng biệt, do đó bạn có thể xem trực tiếp từng phần cụ thể bất kì để giải quyết những API cụ thể mà bạn cần.
+
+Nó cũng được xây dựng để làm việc như một tham chiếu trong tương lai.
+
+Do đó bạn có thể quay lại và tìm chính xác những gì bạn cần.
+
+## Chạy mã
+
+Tất cả các code block có thể được sao chép và sử dụng trực tiếp (chúng thực chất là các tệp tin Python đã được kiểm thử).
+
+Để chạy bất kì ví dụ nào, sao chép code tới tệp tin `main.py`, và bắt đầu `uvicorn` với:
+
++ Ìlànà wẹ́ẹ́bù FastAPI, iṣẹ́ gíga, ó rọrùn láti kọ̀, o yára láti kóòdù, ó sì ṣetán fún iṣelọpọ ní lílo +
+ + +--- + +**Àkọsílẹ̀**: https://fastapi.tiangolo.com + +**Orisun Kóòdù**: https://github.com/tiangolo/fastapi + +--- + +FastAPI jẹ́ ìgbàlódé, tí ó yára (iṣẹ-giga), ìlànà wẹ́ẹ́bù fún kikọ àwọn API pẹ̀lú Python 3.7+ èyí tí ó da lori àwọn ìtọ́kasí àmì irúfẹ́ Python. + +Àwọn ẹya pàtàkì ni: + +* **Ó yára**: Iṣẹ tí ó ga púpọ̀, tí ó wa ni ibamu pẹ̀lú **NodeJS** àti **Go** (ọpẹ si Starlette àti Pydantic). [Ọkan nínú àwọn ìlànà Python ti o yára jùlọ ti o wa](#performance). +* **Ó yára láti kóòdù**: O mu iyara pọ si láti kọ àwọn ẹya tuntun kóòdù nipasẹ "Igba ìdá ọgọ́rùn-ún" (i.e. 200%) si "ọ̀ọ́dúrún ìdá ọgọ́rùn-ún" (i.e. 300%). +* **Àìtọ́ kékeré**: O n din aṣiṣe ku bi ọgbon ìdá ọgọ́rùn-ún (i.e. 40%) ti eda eniyan (oṣiṣẹ kóòdù) fa. * +* **Ọgbọ́n àti ìmọ̀**: Atilẹyin olootu nla. Ìparí nibi gbogbo. Àkókò díẹ̀ nipa wíwá ibi tí ìṣòro kóòdù wà. +* **Irọrun**: A kọ kí ó le rọrun láti lo àti láti kọ ẹkọ nínú rè. Ó máa fún ọ ní àkókò díẹ̀ látı ka àkọsílẹ. +* **Ó kúkurú ní kikọ**: Ó dín àtúnkọ àti àtúntò kóòdù kù. Ìkéde àṣàyàn kọ̀ọ̀kan nínú rẹ̀ ní ọ̀pọ̀lọpọ̀ àwọn ìlò. O ṣe iranlọwọ láti má ṣe ní ọ̀pọ̀lọpọ̀ àṣìṣe. +* **Ó lágbára**: Ó ń ṣe àgbéjáde kóòdù tí ó ṣetán fún ìṣelọ́pọ̀. Pẹ̀lú àkọsílẹ̀ tí ó máa ṣàlàyé ara rẹ̀ fún ẹ ní ìbáṣepọ̀ aládàáṣiṣẹ́ pẹ̀lú rè. +* **Ajohunše/Ìtọ́kasí**: Ó da lori (àti ibamu ni kikun pẹ̀lú) àwọn ìmọ ajohunše/ìtọ́kasí fún àwọn API: OpenAPI (èyí tí a mọ tẹlẹ si Swagger) àti JSON Schema. + +* iṣiro yi da lori àwọn idanwo tí ẹgbẹ ìdàgbàsókè FastAPI ṣe, nígbàtí wọn kọ àwọn ohun elo iṣelọpọ kóòdù pẹ̀lú rẹ. + +## Àwọn onígbọ̀wọ́ + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} +async def...uvicorn main:app --reload...email_validator - fún ifọwọsi ímeèlì.
+* pydantic-settings - fún ètò ìsàkóso.
+* pydantic-extra-types - fún àfikún oríṣi láti lọ pẹ̀lú Pydantic.
+
+Èyí tí Starlette ń lò:
+
+* httpx - Nílò tí ó bá fẹ́ láti lọ `TestClient`.
+* jinja2 - Nílò tí ó bá fẹ́ láti lọ iṣeto awoṣe aiyipada.
+* python-multipart - Nílò tí ó bá fẹ́ láti ṣe àtìlẹ́yìn fún "àyẹ̀wò" fọọmu, pẹ̀lú `request.form()`.
+* itsdangerous - Nílò fún àtìlẹ́yìn `SessionMiddleware`.
+* pyyaml - Nílò fún àtìlẹ́yìn Starlette's `SchemaGenerator` (ó ṣe ṣe kí ó má nílò rẹ̀ fún FastAPI).
+* ujson - Nílò tí ó bá fẹ́ láti lọ `UJSONResponse`.
+
+Èyí tí FastAPI / Starlette ń lò:
+
+* uvicorn - Fún olupin tí yóò sẹ́ àmúyẹ àti tí yóò ṣe ìpèsè fún iṣẹ́ rẹ tàbí ohun èlò rẹ.
+* orjson - Nílò tí ó bá fẹ́ láti lọ `ORJSONResponse`.
+
+Ó lè fi gbogbo àwọn wọ̀nyí sórí ẹrọ pẹ̀lú `pip install "fastapi[all]"`.
+
+## Iwe-aṣẹ
+
+Iṣẹ́ yìí ni iwe-aṣẹ lábẹ́ àwọn òfin tí iwe-aṣẹ MIT.
diff --git a/docs/yo/mkdocs.yml b/docs/yo/mkdocs.yml
new file mode 100644
index 000000000..de18856f4
--- /dev/null
+++ b/docs/yo/mkdocs.yml
@@ -0,0 +1 @@
+INHERIT: ../en/mkdocs.yml
diff --git a/docs/zh/docs/advanced/generate-clients.md b/docs/zh/docs/advanced/generate-clients.md
new file mode 100644
index 000000000..f3a58c062
--- /dev/null
+++ b/docs/zh/docs/advanced/generate-clients.md
@@ -0,0 +1,266 @@
+# 生成客户端
+
+因为 **FastAPI** 是基于OpenAPI规范的,自然您可以使用许多相匹配的工具,包括自动生成API文档 (由 Swagger UI 提供)。
+
+一个不太明显而又特别的优势是,你可以为你的API针对不同的**编程语言**来**生成客户端**(有时候被叫做 **SDKs** )。
+
+## OpenAPI 客户端生成
+
+有许多工具可以从**OpenAPI**生成客户端。
+
+一个常见的工具是 OpenAPI Generator。
+
+如果您正在开发**前端**,一个非常有趣的替代方案是 openapi-typescript-codegen。
+
+## 生成一个 TypeScript 前端客户端
+
+让我们从一个简单的 FastAPI 应用开始:
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="7-9 12-13 16-17 21"
+ {!> ../../../docs_src/generate_clients/tutorial001_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="9-11 14-15 18 19 23"
+ {!> ../../../docs_src/generate_clients/tutorial001.py!}
+ ```
+
+请注意,*路径操作* 定义了他们所用于请求数据和回应数据的模型,所使用的模型是`Item` 和 `ResponseMessage`。
+
+### API 文档
+
+如果您访问API文档,您将看到它具有在请求中发送和在响应中接收数据的**模式(schemas)**:
+
+
+
+您可以看到这些模式,因为它们是用程序中的模型声明的。
+
+那些信息可以在应用的 **OpenAPI模式** 被找到,然后显示在API文档中(通过Swagger UI)。
+
+OpenAPI中所包含的模型里有相同的信息可以用于 **生成客户端代码**。
+
+### 生成一个TypeScript 客户端
+
+现在我们有了带有模型的应用,我们可以为前端生成客户端代码。
+
+#### 安装 `openapi-typescript-codegen`
+
+您可以使用以下工具在前端代码中安装 `openapi-typescript-codegen`:
+
+
+
+您还将自动补全要发送的数据:
+
+
+
+!!! tip
+ 请注意, `name` 和 `price` 的自动补全,是通过其在`Item`模型(FastAPI)中的定义实现的。
+
+如果发送的数据字段不符,你也会看到编辑器的错误提示:
+
+
+
+响应(response)对象也拥有自动补全:
+
+
+
+## 带有标签的 FastAPI 应用
+
+在许多情况下,你的FastAPI应用程序会更复杂,你可能会使用标签来分隔不同组的*路径操作(path operations)*。
+
+例如,您可以有一个用 `items` 的部分和另一个用于 `users` 的部分,它们可以用标签来分隔:
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="21 26 34"
+ {!> ../../../docs_src/generate_clients/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="23 28 36"
+ {!> ../../../docs_src/generate_clients/tutorial002.py!}
+ ```
+
+### 生成带有标签的 TypeScript 客户端
+
+如果您使用标签为FastAPI应用生成客户端,它通常也会根据标签分割客户端代码。
+
+通过这种方式,您将能够为客户端代码进行正确地排序和分组:
+
+
+
+在这个案例中,您有:
+
+* `ItemsService`
+* `UsersService`
+
+### 客户端方法名称
+
+现在生成的方法名像 `createItemItemsPost` 看起来不太简洁:
+
+```TypeScript
+ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
+```
+
+...这是因为客户端生成器为每个 *路径操作* 使用OpenAPI的内部 **操作 ID(operation ID)**。
+
+OpenAPI要求每个操作 ID 在所有 *路径操作* 中都是唯一的,因此 FastAPI 使用**函数名**、**路径**和**HTTP方法/操作**来生成此操作ID,因为这样可以确保这些操作 ID 是唯一的。
+
+但接下来我会告诉你如何改进。 🤓
+
+## 自定义操作ID和更好的方法名
+
+您可以**修改**这些操作ID的**生成**方式,以使其更简洁,并在客户端中具有**更简洁的方法名称**。
+
+在这种情况下,您必须确保每个操作ID在其他方面是**唯一**的。
+
+例如,您可以确保每个*路径操作*都有一个标签,然后根据**标签**和*路径操作***名称**(函数名)来生成操作ID。
+
+### 自定义生成唯一ID函数
+
+FastAPI为每个*路径操作*使用一个**唯一ID**,它用于**操作ID**,也用于任何所需自定义模型的名称,用于请求或响应。
+
+你可以自定义该函数。它接受一个 `APIRoute` 对象作为输入,并输出一个字符串。
+
+例如,以下是一个示例,它使用第一个标签(你可能只有一个标签)和*路径操作*名称(函数名)。
+
+然后,你可以将这个自定义函数作为 `generate_unique_id_function` 参数传递给 **FastAPI**:
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="6-7 10"
+ {!> ../../../docs_src/generate_clients/tutorial003_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="8-9 12"
+ {!> ../../../docs_src/generate_clients/tutorial003.py!}
+ ```
+
+### 使用自定义操作ID生成TypeScript客户端
+
+现在,如果你再次生成客户端,你会发现它具有改善的方法名称:
+
+
+
+正如你所见,现在方法名称中只包含标签和函数名,不再包含URL路径和HTTP操作的信息。
+
+### 预处理用于客户端生成器的OpenAPI规范
+
+生成的代码仍然存在一些**重复的信息**。
+
+我们已经知道该方法与 **items** 相关,因为它在 `ItemsService` 中(从标签中获取),但方法名中仍然有标签名作为前缀。😕
+
+一般情况下对于OpenAPI,我们可能仍然希望保留它,因为这将确保操作ID是**唯一的**。
+
+但对于生成的客户端,我们可以在生成客户端之前**修改** OpenAPI 操作ID,以使方法名称更加美观和**简洁**。
+
+我们可以将 OpenAPI JSON 下载到一个名为`openapi.json`的文件中,然后使用以下脚本**删除此前缀的标签**:
+
+```Python
+{!../../../docs_src/generate_clients/tutorial004.py!}
+```
+
+通过这样做,操作ID将从类似于 `items-get_items` 的名称重命名为 `get_items` ,这样客户端生成器就可以生成更简洁的方法名称。
+
+### 使用预处理的OpenAPI生成TypeScript客户端
+
+现在,由于最终结果保存在文件openapi.json中,你可以修改 package.json 文件以使用此本地文件,例如:
+
+```JSON hl_lines="7"
+{
+ "name": "frontend-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "generate-client": "openapi --input ./openapi.json --output ./src/client --client axios"
+ },
+ "author": "",
+ "license": "",
+ "devDependencies": {
+ "openapi-typescript-codegen": "^0.20.1",
+ "typescript": "^4.6.2"
+ }
+}
+```
+
+生成新的客户端之后,你现在将拥有**清晰的方法名称**,具备**自动补全**、**错误提示**等功能:
+
+
+
+## 优点
+
+当使用自动生成的客户端时,你将获得以下的自动补全功能:
+
+* 方法。
+* 请求体中的数据、查询参数等。
+* 响应数据。
+
+你还将获得针对所有内容的错误提示。
+
+每当你更新后端代码并**重新生成**前端代码时,新的*路径操作*将作为方法可用,旧的方法将被删除,并且其他任何更改将反映在生成的代码中。 🤓
+
+这也意味着如果有任何更改,它将自动**反映**在客户端代码中。如果你**构建**客户端,在使用的数据上存在**不匹配**时,它将报错。
+
+因此,你将在开发周期的早期**检测到许多错误**,而不必等待错误在生产环境中向最终用户展示,然后尝试调试问题所在。 ✨
diff --git a/docs_src/python_types/tutorial011.py b/docs_src/python_types/tutorial011.py
index c8634cbff..297a84db6 100644
--- a/docs_src/python_types/tutorial011.py
+++ b/docs_src/python_types/tutorial011.py
@@ -6,7 +6,7 @@ from pydantic import BaseModel
class User(BaseModel):
id: int
- name = "John Doe"
+ name: str = "John Doe"
signup_ts: Union[datetime, None] = None
friends: List[int] = []
diff --git a/docs_src/python_types/tutorial011_py310.py b/docs_src/python_types/tutorial011_py310.py
index 7f173880f..842760c60 100644
--- a/docs_src/python_types/tutorial011_py310.py
+++ b/docs_src/python_types/tutorial011_py310.py
@@ -5,7 +5,7 @@ from pydantic import BaseModel
class User(BaseModel):
id: int
- name = "John Doe"
+ name: str = "John Doe"
signup_ts: datetime | None = None
friends: list[int] = []
diff --git a/docs_src/python_types/tutorial011_py39.py b/docs_src/python_types/tutorial011_py39.py
index 468496f51..4eb40b405 100644
--- a/docs_src/python_types/tutorial011_py39.py
+++ b/docs_src/python_types/tutorial011_py39.py
@@ -6,7 +6,7 @@ from pydantic import BaseModel
class User(BaseModel):
id: int
- name = "John Doe"
+ name: str = "John Doe"
signup_ts: Union[datetime, None] = None
friends: list[int] = []
diff --git a/docs_src/schema_extra_example/tutorial005.py b/docs_src/schema_extra_example/tutorial005.py
new file mode 100644
index 000000000..b8217c27e
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial005.py
@@ -0,0 +1,51 @@
+from typing import Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Item = Body(
+ openapi_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/schema_extra_example/tutorial005_an.py b/docs_src/schema_extra_example/tutorial005_an.py
new file mode 100644
index 000000000..4b2d9c662
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial005_an.py
@@ -0,0 +1,55 @@
+from typing import Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Annotated[
+ Item,
+ Body(
+ openapi_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/schema_extra_example/tutorial005_an_py310.py b/docs_src/schema_extra_example/tutorial005_an_py310.py
new file mode 100644
index 000000000..64dc2cf90
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial005_an_py310.py
@@ -0,0 +1,54 @@
+from typing import Annotated
+
+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: Annotated[
+ Item,
+ Body(
+ openapi_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/schema_extra_example/tutorial005_an_py39.py b/docs_src/schema_extra_example/tutorial005_an_py39.py
new file mode 100644
index 000000000..edeb1affc
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial005_an_py39.py
@@ -0,0 +1,54 @@
+from typing import Annotated, Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Annotated[
+ Item,
+ Body(
+ openapi_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/schema_extra_example/tutorial005_py310.py b/docs_src/schema_extra_example/tutorial005_py310.py
new file mode 100644
index 000000000..eef973343
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial005_py310.py
@@ -0,0 +1,49 @@
+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(
+ openapi_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/separate_openapi_schemas/tutorial001.py b/docs_src/separate_openapi_schemas/tutorial001.py
new file mode 100644
index 000000000..415eef8e2
--- /dev/null
+++ b/docs_src/separate_openapi_schemas/tutorial001.py
@@ -0,0 +1,28 @@
+from typing import List, Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+def create_item(item: Item):
+ return item
+
+
+@app.get("/items/")
+def read_items() -> List[Item]:
+ return [
+ Item(
+ name="Portal Gun",
+ description="Device to travel through the multi-rick-verse",
+ ),
+ Item(name="Plumbus"),
+ ]
diff --git a/docs_src/separate_openapi_schemas/tutorial001_py310.py b/docs_src/separate_openapi_schemas/tutorial001_py310.py
new file mode 100644
index 000000000..289cb54ed
--- /dev/null
+++ b/docs_src/separate_openapi_schemas/tutorial001_py310.py
@@ -0,0 +1,26 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+def create_item(item: Item):
+ return item
+
+
+@app.get("/items/")
+def read_items() -> list[Item]:
+ return [
+ Item(
+ name="Portal Gun",
+ description="Device to travel through the multi-rick-verse",
+ ),
+ Item(name="Plumbus"),
+ ]
diff --git a/docs_src/separate_openapi_schemas/tutorial001_py39.py b/docs_src/separate_openapi_schemas/tutorial001_py39.py
new file mode 100644
index 000000000..63cffd1e3
--- /dev/null
+++ b/docs_src/separate_openapi_schemas/tutorial001_py39.py
@@ -0,0 +1,28 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+def create_item(item: Item):
+ return item
+
+
+@app.get("/items/")
+def read_items() -> list[Item]:
+ return [
+ Item(
+ name="Portal Gun",
+ description="Device to travel through the multi-rick-verse",
+ ),
+ Item(name="Plumbus"),
+ ]
diff --git a/docs_src/separate_openapi_schemas/tutorial002.py b/docs_src/separate_openapi_schemas/tutorial002.py
new file mode 100644
index 000000000..7df93783b
--- /dev/null
+++ b/docs_src/separate_openapi_schemas/tutorial002.py
@@ -0,0 +1,28 @@
+from typing import List, Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+
+
+app = FastAPI(separate_input_output_schemas=False)
+
+
+@app.post("/items/")
+def create_item(item: Item):
+ return item
+
+
+@app.get("/items/")
+def read_items() -> List[Item]:
+ return [
+ Item(
+ name="Portal Gun",
+ description="Device to travel through the multi-rick-verse",
+ ),
+ Item(name="Plumbus"),
+ ]
diff --git a/docs_src/separate_openapi_schemas/tutorial002_py310.py b/docs_src/separate_openapi_schemas/tutorial002_py310.py
new file mode 100644
index 000000000..5db210872
--- /dev/null
+++ b/docs_src/separate_openapi_schemas/tutorial002_py310.py
@@ -0,0 +1,26 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+
+
+app = FastAPI(separate_input_output_schemas=False)
+
+
+@app.post("/items/")
+def create_item(item: Item):
+ return item
+
+
+@app.get("/items/")
+def read_items() -> list[Item]:
+ return [
+ Item(
+ name="Portal Gun",
+ description="Device to travel through the multi-rick-verse",
+ ),
+ Item(name="Plumbus"),
+ ]
diff --git a/docs_src/separate_openapi_schemas/tutorial002_py39.py b/docs_src/separate_openapi_schemas/tutorial002_py39.py
new file mode 100644
index 000000000..50d997d92
--- /dev/null
+++ b/docs_src/separate_openapi_schemas/tutorial002_py39.py
@@ -0,0 +1,28 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+
+
+app = FastAPI(separate_input_output_schemas=False)
+
+
+@app.post("/items/")
+def create_item(item: Item):
+ return item
+
+
+@app.get("/items/")
+def read_items() -> list[Item]:
+ return [
+ Item(
+ name="Portal Gun",
+ description="Device to travel through the multi-rick-verse",
+ ),
+ Item(name="Plumbus"),
+ ]
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index d8abf2103..329477e41 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.101.1"
+__version__ = "0.103.1"
from starlette import status as status
diff --git a/fastapi/_compat.py b/fastapi/_compat.py
index 9ffcaf409..eb55b08f2 100644
--- a/fastapi/_compat.py
+++ b/fastapi/_compat.py
@@ -181,9 +181,13 @@ if PYDANTIC_V2:
field_mapping: Dict[
Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
],
+ separate_input_output_schemas: bool = True,
) -> Dict[str, Any]:
+ override_mode: Union[Literal["validation"], None] = (
+ None if separate_input_output_schemas else "validation"
+ )
# This expects that GenerateJsonSchema was already used to generate the definitions
- json_schema = field_mapping[(field, field.mode)]
+ json_schema = field_mapping[(field, override_mode or field.mode)]
if "$ref" not in json_schema:
# TODO remove when deprecating Pydantic v1
# Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207
@@ -200,14 +204,19 @@ if PYDANTIC_V2:
fields: List[ModelField],
schema_generator: GenerateJsonSchema,
model_name_map: ModelNameMap,
+ separate_input_output_schemas: bool = True,
) -> Tuple[
Dict[
Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
],
Dict[str, Dict[str, Any]],
]:
+ override_mode: Union[Literal["validation"], None] = (
+ None if separate_input_output_schemas else "validation"
+ )
inputs = [
- (field, field.mode, field._type_adapter.core_schema) for field in fields
+ (field, override_mode or field.mode, field._type_adapter.core_schema)
+ for field in fields
]
field_mapping, definitions = schema_generator.generate_definitions(
inputs=inputs
@@ -429,6 +438,7 @@ else:
field_mapping: Dict[
Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
],
+ separate_input_output_schemas: bool = True,
) -> Dict[str, Any]:
# This expects that GenerateJsonSchema was already used to generate the definitions
return field_schema( # type: ignore[no-any-return]
@@ -444,6 +454,7 @@ else:
fields: List[ModelField],
schema_generator: GenerateJsonSchema,
model_name_map: ModelNameMap,
+ separate_input_output_schemas: bool = True,
) -> Tuple[
Dict[
Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
diff --git a/fastapi/applications.py b/fastapi/applications.py
index e32cfa03d..5cc568292 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -92,6 +92,7 @@ class FastAPI(Starlette):
generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
generate_unique_id
),
+ separate_input_output_schemas: bool = True,
**extra: Any,
) -> None:
self.debug = debug
@@ -111,6 +112,7 @@ class FastAPI(Starlette):
self.swagger_ui_init_oauth = swagger_ui_init_oauth
self.swagger_ui_parameters = swagger_ui_parameters
self.servers = servers or []
+ self.separate_input_output_schemas = separate_input_output_schemas
self.extra = extra
self.openapi_version = "3.1.0"
self.openapi_schema: Optional[Dict[str, Any]] = None
@@ -187,20 +189,20 @@ class FastAPI(Starlette):
# contextvars.
# This needs to happen after user middlewares because those create a
# new contextvars context copy by using a new AnyIO task group.
- # The initial part of dependencies with yield is executed in the
- # FastAPI code, inside all the middlewares, but the teardown part
- # (after yield) is executed in the AsyncExitStack in this middleware,
- # if the AsyncExitStack lived outside of the custom middlewares and
- # contextvars were set in a dependency with yield in that internal
+ # The initial part of dependencies with 'yield' is executed in the
+ # FastAPI code, inside all the middlewares. However, the teardown part
+ # (after 'yield') is executed in the AsyncExitStack in this middleware.
+ # If the AsyncExitStack lived outside of the custom middlewares and
+ # contextvars were set in a dependency with 'yield' in that internal
# contextvars context, the values would not be available in the
- # outside context of the AsyncExitStack.
- # By putting the middleware and the AsyncExitStack here, inside all
- # user middlewares, the code before and after yield in dependencies
- # with yield is executed in the same contextvars context, so all values
- # set in contextvars before yield is still available after yield as
- # would be expected.
+ # outer context of the AsyncExitStack.
+ # By placing the middleware and the AsyncExitStack here, inside all
+ # user middlewares, the code before and after 'yield' in dependencies
+ # with 'yield' is executed in the same contextvars context. Thus, all values
+ # set in contextvars before 'yield' are still available after 'yield,' as
+ # expected.
# Additionally, by having this AsyncExitStack here, after the
- # ExceptionMiddleware, now dependencies can catch handled exceptions,
+ # ExceptionMiddleware, dependencies can now catch handled exceptions,
# e.g. HTTPException, to customize the teardown code (e.g. DB session
# rollback).
Middleware(AsyncExitStackMiddleware),
@@ -227,6 +229,7 @@ class FastAPI(Starlette):
webhooks=self.webhooks.routes,
tags=self.openapi_tags,
servers=self.servers,
+ separate_input_output_schemas=self.separate_input_output_schemas,
)
return self.openapi_schema
diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py
index 2268dd229..3d982eb9a 100644
--- a/fastapi/openapi/models.py
+++ b/fastapi/openapi/models.py
@@ -11,7 +11,7 @@ from fastapi._compat import (
)
from fastapi.logger import logger
from pydantic import AnyUrl, BaseModel, Field
-from typing_extensions import Annotated, Literal
+from typing_extensions import Annotated, Literal, TypedDict
from typing_extensions import deprecated as typing_deprecated
try:
@@ -267,14 +267,14 @@ class Schema(BaseModel):
SchemaOrBool = Union[Schema, bool]
-class Example(BaseModel):
- summary: Optional[str] = None
- description: Optional[str] = None
- value: Optional[Any] = None
- externalValue: Optional[AnyUrl] = None
+class Example(TypedDict, total=False):
+ summary: Optional[str]
+ description: Optional[str]
+ value: Optional[Any]
+ externalValue: Optional[AnyUrl]
- if PYDANTIC_V2:
- model_config = {"extra": "allow"}
+ if PYDANTIC_V2: # type: ignore [misc]
+ __pydantic_config__ = {"extra": "allow"}
else:
diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
index 185ff9920..089bfa242 100644
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -95,6 +95,7 @@ def get_openapi_operation_parameters(
field_mapping: Dict[
Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
],
+ separate_input_output_schemas: bool = True,
) -> List[Dict[str, Any]]:
parameters = []
for param in all_route_params:
@@ -107,6 +108,7 @@ def get_openapi_operation_parameters(
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
+ separate_input_output_schemas=separate_input_output_schemas,
)
parameter = {
"name": param.alias,
@@ -116,7 +118,9 @@ def get_openapi_operation_parameters(
}
if field_info.description:
parameter["description"] = field_info.description
- if field_info.example != Undefined:
+ if field_info.openapi_examples:
+ parameter["examples"] = jsonable_encoder(field_info.openapi_examples)
+ elif field_info.example != Undefined:
parameter["example"] = jsonable_encoder(field_info.example)
if field_info.deprecated:
parameter["deprecated"] = field_info.deprecated
@@ -132,6 +136,7 @@ def get_openapi_operation_request_body(
field_mapping: Dict[
Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
],
+ separate_input_output_schemas: bool = True,
) -> Optional[Dict[str, Any]]:
if not body_field:
return None
@@ -141,6 +146,7 @@ def get_openapi_operation_request_body(
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
+ separate_input_output_schemas=separate_input_output_schemas,
)
field_info = cast(Body, body_field.field_info)
request_media_type = field_info.media_type
@@ -149,7 +155,11 @@ def get_openapi_operation_request_body(
if required:
request_body_oai["required"] = required
request_media_content: Dict[str, Any] = {"schema": body_schema}
- if field_info.example != Undefined:
+ if field_info.openapi_examples:
+ request_media_content["examples"] = jsonable_encoder(
+ field_info.openapi_examples
+ )
+ elif field_info.example != Undefined:
request_media_content["example"] = jsonable_encoder(field_info.example)
request_body_oai["content"] = {request_media_type: request_media_content}
return request_body_oai
@@ -211,6 +221,7 @@ def get_openapi_path(
field_mapping: Dict[
Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
],
+ separate_input_output_schemas: bool = True,
) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]:
path = {}
security_schemes: Dict[str, Any] = {}
@@ -242,6 +253,7 @@ def get_openapi_path(
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
+ separate_input_output_schemas=separate_input_output_schemas,
)
parameters.extend(operation_parameters)
if parameters:
@@ -263,6 +275,7 @@ def get_openapi_path(
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
+ separate_input_output_schemas=separate_input_output_schemas,
)
if request_body_oai:
operation["requestBody"] = request_body_oai
@@ -280,6 +293,7 @@ def get_openapi_path(
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
+ separate_input_output_schemas=separate_input_output_schemas,
)
callbacks[callback.name] = {callback.path: cb_path}
operation["callbacks"] = callbacks
@@ -310,6 +324,7 @@ def get_openapi_path(
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
+ separate_input_output_schemas=separate_input_output_schemas,
)
else:
response_schema = {}
@@ -343,6 +358,7 @@ def get_openapi_path(
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
+ separate_input_output_schemas=separate_input_output_schemas,
)
media_type = route_response_media_type or "application/json"
additional_schema = (
@@ -434,6 +450,7 @@ def get_openapi(
contact: Optional[Dict[str, Union[str, Any]]] = None,
license_info: Optional[Dict[str, Union[str, Any]]] = None,
prefix: str = "",
+ separate_input_output_schemas: bool = True,
) -> Dict[str, Any]:
info: Dict[str, Any] = {"title": title, "version": version}
if summary:
@@ -460,6 +477,7 @@ def get_openapi(
fields=all_fields,
schema_generator=schema_generator,
model_name_map=model_name_map,
+ separate_input_output_schemas=separate_input_output_schemas,
)
for route in routes or []:
if isinstance(route, routing.APIRoute):
@@ -469,6 +487,7 @@ def get_openapi(
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
+ separate_input_output_schemas=separate_input_output_schemas,
)
if result:
path, security_schemes, path_definitions = result
@@ -489,6 +508,7 @@ def get_openapi(
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
+ separate_input_output_schemas=separate_input_output_schemas,
)
if result:
path, security_schemes, path_definitions = result
diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py
index a43afaf31..63914d1d6 100644
--- a/fastapi/param_functions.py
+++ b/fastapi/param_functions.py
@@ -2,6 +2,7 @@ from typing import Any, Callable, Dict, List, Optional, Sequence, Union
from fastapi import params
from fastapi._compat import Undefined
+from fastapi.openapi.models import Example
from typing_extensions import Annotated, deprecated
_Unset: Any = Undefined
@@ -46,6 +47,7 @@ def Path( # noqa: N802
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -76,6 +78,7 @@ def Path( # noqa: N802
decimal_places=decimal_places,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
deprecated=deprecated,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
@@ -122,6 +125,7 @@ def Query( # noqa: N802
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -152,6 +156,7 @@ def Query( # noqa: N802
decimal_places=decimal_places,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
deprecated=deprecated,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
@@ -199,6 +204,7 @@ def Header( # noqa: N802
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -230,6 +236,7 @@ def Header( # noqa: N802
decimal_places=decimal_places,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
deprecated=deprecated,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
@@ -276,6 +283,7 @@ def Cookie( # noqa: N802
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -306,6 +314,7 @@ def Cookie( # noqa: N802
decimal_places=decimal_places,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
deprecated=deprecated,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
@@ -354,6 +363,7 @@ def Body( # noqa: N802
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -386,6 +396,7 @@ def Body( # noqa: N802
decimal_places=decimal_places,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
deprecated=deprecated,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
@@ -433,6 +444,7 @@ def Form( # noqa: N802
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -464,6 +476,7 @@ def Form( # noqa: N802
decimal_places=decimal_places,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
deprecated=deprecated,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
@@ -511,6 +524,7 @@ def File( # noqa: N802
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -542,6 +556,7 @@ def File( # noqa: N802
decimal_places=decimal_places,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
deprecated=deprecated,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
diff --git a/fastapi/params.py b/fastapi/params.py
index 2d8100650..b40944dba 100644
--- a/fastapi/params.py
+++ b/fastapi/params.py
@@ -2,6 +2,7 @@ import warnings
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Sequence, Union
+from fastapi.openapi.models import Example
from pydantic.fields import FieldInfo
from typing_extensions import Annotated, deprecated
@@ -61,6 +62,7 @@ class Param(FieldInfo):
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -75,6 +77,7 @@ class Param(FieldInfo):
)
self.example = example
self.include_in_schema = include_in_schema
+ self.openapi_examples = openapi_examples
kwargs = dict(
default=default,
default_factory=default_factory,
@@ -170,6 +173,7 @@ class Path(Param):
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -204,6 +208,7 @@ class Path(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
**extra,
@@ -254,6 +259,7 @@ class Query(Param):
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -286,6 +292,7 @@ class Query(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
**extra,
@@ -337,6 +344,7 @@ class Header(Param):
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -370,6 +378,7 @@ class Header(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
**extra,
@@ -420,6 +429,7 @@ class Cookie(Param):
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -452,6 +462,7 @@ class Cookie(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
**extra,
@@ -502,6 +513,7 @@ class Body(FieldInfo):
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -518,6 +530,7 @@ class Body(FieldInfo):
)
self.example = example
self.include_in_schema = include_in_schema
+ self.openapi_examples = openapi_examples
kwargs = dict(
default=default,
default_factory=default_factory,
@@ -613,6 +626,7 @@ class Form(Body):
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -647,6 +661,7 @@ class Form(Body):
deprecated=deprecated,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
**extra,
@@ -696,6 +711,7 @@ class File(Form):
"although still supported. Use examples instead."
),
] = _Unset,
+ openapi_examples: Optional[Dict[str, Example]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
json_schema_extra: Union[Dict[str, Any], None] = None,
@@ -729,6 +745,7 @@ class File(Form):
deprecated=deprecated,
example=example,
examples=examples,
+ openapi_examples=openapi_examples,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
**extra,
diff --git a/pyproject.toml b/pyproject.toml
index 9b7cca9c9..2870b31a5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -44,6 +44,8 @@ dependencies = [
"starlette>=0.27.0,<0.28.0",
"pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0",
"typing-extensions>=4.5.0",
+ # TODO: remove this pin after upgrading Starlette 0.31.1
+ "anyio>=3.7.1,<4.0.0",
]
dynamic = ["version"]
diff --git a/requirements.txt b/requirements.txt
index 7e746016a..ef25ec483 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,5 @@
-r requirements-docs.txt
uvicorn[standard] >=0.12.0,<0.23.0
pre-commit >=2.17.0,<4.0.0
+# For generating screenshots
+playwright
diff --git a/scripts/playwright/separate_openapi_schemas/image01.py b/scripts/playwright/separate_openapi_schemas/image01.py
new file mode 100644
index 000000000..0b40f3bbc
--- /dev/null
+++ b/scripts/playwright/separate_openapi_schemas/image01.py
@@ -0,0 +1,29 @@
+import subprocess
+
+from playwright.sync_api import Playwright, sync_playwright
+
+
+def run(playwright: Playwright) -> None:
+ browser = playwright.chromium.launch(headless=False)
+ context = browser.new_context(viewport={"width": 960, "height": 1080})
+ page = context.new_page()
+ page.goto("http://localhost:8000/docs")
+ page.get_by_text("POST/items/Create Item").click()
+ page.get_by_role("tab", name="Schema").first.click()
+ page.screenshot(
+ path="docs/en/docs/img/tutorial/separate-openapi-schemas/image01.png"
+ )
+
+ # ---------------------
+ context.close()
+ browser.close()
+
+
+process = subprocess.Popen(
+ ["uvicorn", "docs_src.separate_openapi_schemas.tutorial001:app"]
+)
+try:
+ with sync_playwright() as playwright:
+ run(playwright)
+finally:
+ process.terminate()
diff --git a/scripts/playwright/separate_openapi_schemas/image02.py b/scripts/playwright/separate_openapi_schemas/image02.py
new file mode 100644
index 000000000..f76af7ee2
--- /dev/null
+++ b/scripts/playwright/separate_openapi_schemas/image02.py
@@ -0,0 +1,30 @@
+import subprocess
+
+from playwright.sync_api import Playwright, sync_playwright
+
+
+def run(playwright: Playwright) -> None:
+ browser = playwright.chromium.launch(headless=False)
+ context = browser.new_context(viewport={"width": 960, "height": 1080})
+ page = context.new_page()
+ page.goto("http://localhost:8000/docs")
+ page.get_by_text("GET/items/Read Items").click()
+ page.get_by_role("button", name="Try it out").click()
+ page.get_by_role("button", name="Execute").click()
+ page.screenshot(
+ path="docs/en/docs/img/tutorial/separate-openapi-schemas/image02.png"
+ )
+
+ # ---------------------
+ context.close()
+ browser.close()
+
+
+process = subprocess.Popen(
+ ["uvicorn", "docs_src.separate_openapi_schemas.tutorial001:app"]
+)
+try:
+ with sync_playwright() as playwright:
+ run(playwright)
+finally:
+ process.terminate()
diff --git a/scripts/playwright/separate_openapi_schemas/image03.py b/scripts/playwright/separate_openapi_schemas/image03.py
new file mode 100644
index 000000000..127f5c428
--- /dev/null
+++ b/scripts/playwright/separate_openapi_schemas/image03.py
@@ -0,0 +1,30 @@
+import subprocess
+
+from playwright.sync_api import Playwright, sync_playwright
+
+
+def run(playwright: Playwright) -> None:
+ browser = playwright.chromium.launch(headless=False)
+ context = browser.new_context(viewport={"width": 960, "height": 1080})
+ page = context.new_page()
+ page.goto("http://localhost:8000/docs")
+ page.get_by_text("GET/items/Read Items").click()
+ page.get_by_role("tab", name="Schema").click()
+ page.get_by_label("Schema").get_by_role("button", name="Expand all").click()
+ page.screenshot(
+ path="docs/en/docs/img/tutorial/separate-openapi-schemas/image03.png"
+ )
+
+ # ---------------------
+ context.close()
+ browser.close()
+
+
+process = subprocess.Popen(
+ ["uvicorn", "docs_src.separate_openapi_schemas.tutorial001:app"]
+)
+try:
+ with sync_playwright() as playwright:
+ run(playwright)
+finally:
+ process.terminate()
diff --git a/scripts/playwright/separate_openapi_schemas/image04.py b/scripts/playwright/separate_openapi_schemas/image04.py
new file mode 100644
index 000000000..208eaf8a0
--- /dev/null
+++ b/scripts/playwright/separate_openapi_schemas/image04.py
@@ -0,0 +1,29 @@
+import subprocess
+
+from playwright.sync_api import Playwright, sync_playwright
+
+
+def run(playwright: Playwright) -> None:
+ browser = playwright.chromium.launch(headless=False)
+ context = browser.new_context(viewport={"width": 960, "height": 1080})
+ page = context.new_page()
+ page.goto("http://localhost:8000/docs")
+ page.get_by_role("button", name="Item-Input").click()
+ page.get_by_role("button", name="Item-Output").click()
+ page.set_viewport_size({"width": 960, "height": 820})
+ page.screenshot(
+ path="docs/en/docs/img/tutorial/separate-openapi-schemas/image04.png"
+ )
+ # ---------------------
+ context.close()
+ browser.close()
+
+
+process = subprocess.Popen(
+ ["uvicorn", "docs_src.separate_openapi_schemas.tutorial001:app"]
+)
+try:
+ with sync_playwright() as playwright:
+ run(playwright)
+finally:
+ process.terminate()
diff --git a/scripts/playwright/separate_openapi_schemas/image05.py b/scripts/playwright/separate_openapi_schemas/image05.py
new file mode 100644
index 000000000..83966b449
--- /dev/null
+++ b/scripts/playwright/separate_openapi_schemas/image05.py
@@ -0,0 +1,29 @@
+import subprocess
+
+from playwright.sync_api import Playwright, sync_playwright
+
+
+def run(playwright: Playwright) -> None:
+ browser = playwright.chromium.launch(headless=False)
+ context = browser.new_context(viewport={"width": 960, "height": 1080})
+ page = context.new_page()
+ page.goto("http://localhost:8000/docs")
+ page.get_by_role("button", name="Item", exact=True).click()
+ page.set_viewport_size({"width": 960, "height": 700})
+ page.screenshot(
+ path="docs/en/docs/img/tutorial/separate-openapi-schemas/image05.png"
+ )
+
+ # ---------------------
+ context.close()
+ browser.close()
+
+
+process = subprocess.Popen(
+ ["uvicorn", "docs_src.separate_openapi_schemas.tutorial002:app"]
+)
+try:
+ with sync_playwright() as playwright:
+ run(playwright)
+finally:
+ process.terminate()
diff --git a/tests/test_openapi_examples.py b/tests/test_openapi_examples.py
new file mode 100644
index 000000000..70664a8a4
--- /dev/null
+++ b/tests/test_openapi_examples.py
@@ -0,0 +1,458 @@
+from typing import Union
+
+from dirty_equals import IsDict
+from fastapi import Body, Cookie, FastAPI, Header, Path, Query
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ data: str
+
+
+@app.post("/examples/")
+def examples(
+ item: Item = Body(
+ examples=[
+ {"data": "Data in Body examples, example1"},
+ ],
+ openapi_examples={
+ "Example One": {
+ "summary": "Example One Summary",
+ "description": "Example One Description",
+ "value": {"data": "Data in Body examples, example1"},
+ },
+ "Example Two": {
+ "value": {"data": "Data in Body examples, example2"},
+ },
+ },
+ )
+):
+ return item
+
+
+@app.get("/path_examples/{item_id}")
+def path_examples(
+ item_id: str = Path(
+ examples=[
+ "json_schema_item_1",
+ "json_schema_item_2",
+ ],
+ openapi_examples={
+ "Path One": {
+ "summary": "Path One Summary",
+ "description": "Path One Description",
+ "value": "item_1",
+ },
+ "Path Two": {
+ "value": "item_2",
+ },
+ },
+ ),
+):
+ return item_id
+
+
+@app.get("/query_examples/")
+def query_examples(
+ data: Union[str, None] = Query(
+ default=None,
+ examples=[
+ "json_schema_query1",
+ "json_schema_query2",
+ ],
+ openapi_examples={
+ "Query One": {
+ "summary": "Query One Summary",
+ "description": "Query One Description",
+ "value": "query1",
+ },
+ "Query Two": {
+ "value": "query2",
+ },
+ },
+ ),
+):
+ return data
+
+
+@app.get("/header_examples/")
+def header_examples(
+ data: Union[str, None] = Header(
+ default=None,
+ examples=[
+ "json_schema_header1",
+ "json_schema_header2",
+ ],
+ openapi_examples={
+ "Header One": {
+ "summary": "Header One Summary",
+ "description": "Header One Description",
+ "value": "header1",
+ },
+ "Header Two": {
+ "value": "header2",
+ },
+ },
+ ),
+):
+ return data
+
+
+@app.get("/cookie_examples/")
+def cookie_examples(
+ data: Union[str, None] = Cookie(
+ default=None,
+ examples=["json_schema_cookie1", "json_schema_cookie2"],
+ openapi_examples={
+ "Cookie One": {
+ "summary": "Cookie One Summary",
+ "description": "Cookie One Description",
+ "value": "cookie1",
+ },
+ "Cookie Two": {
+ "value": "cookie2",
+ },
+ },
+ ),
+):
+ return data
+
+
+client = TestClient(app)
+
+
+def test_call_api():
+ response = client.post("/examples/", json={"data": "example1"})
+ assert response.status_code == 200, response.text
+
+ response = client.get("/path_examples/foo")
+ assert response.status_code == 200, response.text
+
+ response = client.get("/query_examples/")
+ assert response.status_code == 200, response.text
+
+ response = client.get("/header_examples/")
+ assert response.status_code == 200, response.text
+
+ response = client.get("/cookie_examples/")
+ assert response.status_code == 200, response.text
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/examples/": {
+ "post": {
+ "summary": "Examples",
+ "operationId": "examples_examples__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "allOf": [{"$ref": "#/components/schemas/Item"}],
+ "title": "Item",
+ "examples": [
+ {"data": "Data in Body examples, example1"}
+ ],
+ },
+ "examples": {
+ "Example One": {
+ "summary": "Example One Summary",
+ "description": "Example One Description",
+ "value": {
+ "data": "Data in Body examples, example1"
+ },
+ },
+ "Example Two": {
+ "value": {
+ "data": "Data in Body examples, example2"
+ }
+ },
+ },
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/path_examples/{item_id}": {
+ "get": {
+ "summary": "Path Examples",
+ "operationId": "path_examples_path_examples__item_id__get",
+ "parameters": [
+ {
+ "name": "item_id",
+ "in": "path",
+ "required": True,
+ "schema": {
+ "type": "string",
+ "examples": [
+ "json_schema_item_1",
+ "json_schema_item_2",
+ ],
+ "title": "Item Id",
+ },
+ "examples": {
+ "Path One": {
+ "summary": "Path One Summary",
+ "description": "Path One Description",
+ "value": "item_1",
+ },
+ "Path Two": {"value": "item_2"},
+ },
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/query_examples/": {
+ "get": {
+ "summary": "Query Examples",
+ "operationId": "query_examples_query_examples__get",
+ "parameters": [
+ {
+ "name": "data",
+ "in": "query",
+ "required": False,
+ "schema": IsDict(
+ {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "examples": [
+ "json_schema_query1",
+ "json_schema_query2",
+ ],
+ "title": "Data",
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "examples": [
+ "json_schema_query1",
+ "json_schema_query2",
+ ],
+ "type": "string",
+ "title": "Data",
+ }
+ ),
+ "examples": {
+ "Query One": {
+ "summary": "Query One Summary",
+ "description": "Query One Description",
+ "value": "query1",
+ },
+ "Query Two": {"value": "query2"},
+ },
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/header_examples/": {
+ "get": {
+ "summary": "Header Examples",
+ "operationId": "header_examples_header_examples__get",
+ "parameters": [
+ {
+ "name": "data",
+ "in": "header",
+ "required": False,
+ "schema": IsDict(
+ {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "examples": [
+ "json_schema_header1",
+ "json_schema_header2",
+ ],
+ "title": "Data",
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "type": "string",
+ "examples": [
+ "json_schema_header1",
+ "json_schema_header2",
+ ],
+ "title": "Data",
+ }
+ ),
+ "examples": {
+ "Header One": {
+ "summary": "Header One Summary",
+ "description": "Header One Description",
+ "value": "header1",
+ },
+ "Header Two": {"value": "header2"},
+ },
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/cookie_examples/": {
+ "get": {
+ "summary": "Cookie Examples",
+ "operationId": "cookie_examples_cookie_examples__get",
+ "parameters": [
+ {
+ "name": "data",
+ "in": "cookie",
+ "required": False,
+ "schema": IsDict(
+ {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "examples": [
+ "json_schema_cookie1",
+ "json_schema_cookie2",
+ ],
+ "title": "Data",
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "type": "string",
+ "examples": [
+ "json_schema_cookie1",
+ "json_schema_cookie2",
+ ],
+ "title": "Data",
+ }
+ ),
+ "examples": {
+ "Cookie One": {
+ "summary": "Cookie One Summary",
+ "description": "Cookie One Description",
+ "value": "cookie1",
+ },
+ "Cookie Two": {"value": "cookie2"},
+ },
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "Item": {
+ "properties": {"data": {"type": "string", "title": "Data"}},
+ "type": "object",
+ "required": ["data"],
+ "title": "Item",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
diff --git a/tests/test_openapi_separate_input_output_schemas.py b/tests/test_openapi_separate_input_output_schemas.py
new file mode 100644
index 000000000..70f4b90d7
--- /dev/null
+++ b/tests/test_openapi_separate_input_output_schemas.py
@@ -0,0 +1,490 @@
+from typing import List, Optional
+
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+from .utils import needs_pydanticv2
+
+
+class SubItem(BaseModel):
+ subname: str
+ sub_description: Optional[str] = None
+ tags: List[str] = []
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ sub: Optional[SubItem] = None
+
+
+def get_app_client(separate_input_output_schemas: bool = True) -> TestClient:
+ app = FastAPI(separate_input_output_schemas=separate_input_output_schemas)
+
+ @app.post("/items/")
+ def create_item(item: Item):
+ return item
+
+ @app.post("/items-list/")
+ def create_item_list(item: List[Item]):
+ return item
+
+ @app.get("/items/")
+ def read_items() -> List[Item]:
+ return [
+ Item(
+ name="Portal Gun",
+ description="Device to travel through the multi-rick-verse",
+ sub=SubItem(subname="subname"),
+ ),
+ Item(name="Plumbus"),
+ ]
+
+ client = TestClient(app)
+ return client
+
+
+def test_create_item():
+ client = get_app_client()
+ client_no = get_app_client(separate_input_output_schemas=False)
+ response = client.post("/items/", json={"name": "Plumbus"})
+ response2 = client_no.post("/items/", json={"name": "Plumbus"})
+ assert response.status_code == response2.status_code == 200, response.text
+ assert (
+ response.json()
+ == response2.json()
+ == {"name": "Plumbus", "description": None, "sub": None}
+ )
+
+
+def test_create_item_with_sub():
+ client = get_app_client()
+ client_no = get_app_client(separate_input_output_schemas=False)
+ data = {
+ "name": "Plumbus",
+ "sub": {"subname": "SubPlumbus", "sub_description": "Sub WTF"},
+ }
+ response = client.post("/items/", json=data)
+ response2 = client_no.post("/items/", json=data)
+ assert response.status_code == response2.status_code == 200, response.text
+ assert (
+ response.json()
+ == response2.json()
+ == {
+ "name": "Plumbus",
+ "description": None,
+ "sub": {"subname": "SubPlumbus", "sub_description": "Sub WTF", "tags": []},
+ }
+ )
+
+
+def test_create_item_list():
+ client = get_app_client()
+ client_no = get_app_client(separate_input_output_schemas=False)
+ data = [
+ {"name": "Plumbus"},
+ {
+ "name": "Portal Gun",
+ "description": "Device to travel through the multi-rick-verse",
+ },
+ ]
+ response = client.post("/items-list/", json=data)
+ response2 = client_no.post("/items-list/", json=data)
+ assert response.status_code == response2.status_code == 200, response.text
+ assert (
+ response.json()
+ == response2.json()
+ == [
+ {"name": "Plumbus", "description": None, "sub": None},
+ {
+ "name": "Portal Gun",
+ "description": "Device to travel through the multi-rick-verse",
+ "sub": None,
+ },
+ ]
+ )
+
+
+def test_read_items():
+ client = get_app_client()
+ client_no = get_app_client(separate_input_output_schemas=False)
+ response = client.get("/items/")
+ response2 = client_no.get("/items/")
+ assert response.status_code == response2.status_code == 200, response.text
+ assert (
+ response.json()
+ == response2.json()
+ == [
+ {
+ "name": "Portal Gun",
+ "description": "Device to travel through the multi-rick-verse",
+ "sub": {"subname": "subname", "sub_description": None, "tags": []},
+ },
+ {"name": "Plumbus", "description": None, "sub": None},
+ ]
+ )
+
+
+@needs_pydanticv2
+def test_openapi_schema():
+ client = get_app_client()
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "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": {
+ "items": {
+ "$ref": "#/components/schemas/Item-Output"
+ },
+ "type": "array",
+ "title": "Response Read Items Items Get",
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item-Input"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
+ "/items-list/": {
+ "post": {
+ "summary": "Create Item List",
+ "operationId": "create_item_list_items_list__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/Item-Input"
+ },
+ "type": "array",
+ "title": "Item",
+ }
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "Item-Input": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ "sub": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/SubItem-Input"},
+ {"type": "null"},
+ ]
+ },
+ },
+ "type": "object",
+ "required": ["name"],
+ "title": "Item",
+ },
+ "Item-Output": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ "sub": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/SubItem-Output"},
+ {"type": "null"},
+ ]
+ },
+ },
+ "type": "object",
+ "required": ["name", "description", "sub"],
+ "title": "Item",
+ },
+ "SubItem-Input": {
+ "properties": {
+ "subname": {"type": "string", "title": "Subname"},
+ "sub_description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Sub Description",
+ },
+ "tags": {
+ "items": {"type": "string"},
+ "type": "array",
+ "title": "Tags",
+ "default": [],
+ },
+ },
+ "type": "object",
+ "required": ["subname"],
+ "title": "SubItem",
+ },
+ "SubItem-Output": {
+ "properties": {
+ "subname": {"type": "string", "title": "Subname"},
+ "sub_description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Sub Description",
+ },
+ "tags": {
+ "items": {"type": "string"},
+ "type": "array",
+ "title": "Tags",
+ "default": [],
+ },
+ },
+ "type": "object",
+ "required": ["subname", "sub_description", "tags"],
+ "title": "SubItem",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
+
+
+@needs_pydanticv2
+def test_openapi_schema_no_separate():
+ client = get_app_client(separate_input_output_schemas=False)
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "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": {
+ "items": {"$ref": "#/components/schemas/Item"},
+ "type": "array",
+ "title": "Response Read Items Items Get",
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
+ "/items-list/": {
+ "post": {
+ "summary": "Create Item List",
+ "operationId": "create_item_list_items_list__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {"$ref": "#/components/schemas/Item"},
+ "type": "array",
+ "title": "Item",
+ }
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "Item": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ "sub": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/SubItem"},
+ {"type": "null"},
+ ]
+ },
+ },
+ "type": "object",
+ "required": ["name"],
+ "title": "Item",
+ },
+ "SubItem": {
+ "properties": {
+ "subname": {"type": "string", "title": "Subname"},
+ "sub_description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Sub Description",
+ },
+ "tags": {
+ "items": {"type": "string"},
+ "type": "array",
+ "title": "Tags",
+ "default": [],
+ },
+ },
+ "type": "object",
+ "required": ["subname"],
+ "title": "SubItem",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py
new file mode 100644
index 000000000..94a40ed5a
--- /dev/null
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py
@@ -0,0 +1,166 @@
+import pytest
+from dirty_equals import IsDict
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.schema_extra_example.tutorial005 import app
+
+ client = TestClient(app)
+ return client
+
+
+def test_post_body_example(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ )
+ assert response.status_code == 200
+
+
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "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": IsDict({"$ref": "#/components/schemas/Item"})
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "allOf": [
+ {"$ref": "#/components/schemas/Item"}
+ ],
+ "title": "Item",
+ }
+ ),
+ "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",
+ },
+ },
+ },
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": IsDict(
+ {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {"title": "Description", "type": "string"}
+ ),
+ "price": {"title": "Price", "type": "number"},
+ "tax": IsDict(
+ {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {"title": "Tax", "type": "number"}
+ ),
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py
new file mode 100644
index 000000000..da92f98f6
--- /dev/null
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py
@@ -0,0 +1,166 @@
+import pytest
+from dirty_equals import IsDict
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.schema_extra_example.tutorial005_an import app
+
+ client = TestClient(app)
+ return client
+
+
+def test_post_body_example(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ )
+ assert response.status_code == 200
+
+
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "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": IsDict({"$ref": "#/components/schemas/Item"})
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "allOf": [
+ {"$ref": "#/components/schemas/Item"}
+ ],
+ "title": "Item",
+ }
+ ),
+ "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",
+ },
+ },
+ },
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": IsDict(
+ {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {"title": "Description", "type": "string"}
+ ),
+ "price": {"title": "Price", "type": "number"},
+ "tax": IsDict(
+ {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {"title": "Tax", "type": "number"}
+ ),
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py
new file mode 100644
index 000000000..9109cb14e
--- /dev/null
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py
@@ -0,0 +1,170 @@
+import pytest
+from dirty_equals import IsDict
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.schema_extra_example.tutorial005_an_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_post_body_example(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ )
+ assert response.status_code == 200
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "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": IsDict({"$ref": "#/components/schemas/Item"})
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "allOf": [
+ {"$ref": "#/components/schemas/Item"}
+ ],
+ "title": "Item",
+ }
+ ),
+ "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",
+ },
+ },
+ },
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": IsDict(
+ {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {"title": "Description", "type": "string"}
+ ),
+ "price": {"title": "Price", "type": "number"},
+ "tax": IsDict(
+ {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {"title": "Tax", "type": "number"}
+ ),
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py
new file mode 100644
index 000000000..fd4ec0575
--- /dev/null
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py
@@ -0,0 +1,170 @@
+import pytest
+from dirty_equals import IsDict
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.schema_extra_example.tutorial005_an_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_post_body_example(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ )
+ assert response.status_code == 200
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "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": IsDict({"$ref": "#/components/schemas/Item"})
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "allOf": [
+ {"$ref": "#/components/schemas/Item"}
+ ],
+ "title": "Item",
+ }
+ ),
+ "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",
+ },
+ },
+ },
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": IsDict(
+ {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {"title": "Description", "type": "string"}
+ ),
+ "price": {"title": "Price", "type": "number"},
+ "tax": IsDict(
+ {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {"title": "Tax", "type": "number"}
+ ),
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py
new file mode 100644
index 000000000..05df53422
--- /dev/null
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py
@@ -0,0 +1,170 @@
+import pytest
+from dirty_equals import IsDict
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.schema_extra_example.tutorial005_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_post_body_example(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ )
+ assert response.status_code == 200
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "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": IsDict({"$ref": "#/components/schemas/Item"})
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "allOf": [
+ {"$ref": "#/components/schemas/Item"}
+ ],
+ "title": "Item",
+ }
+ ),
+ "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",
+ },
+ },
+ },
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": IsDict(
+ {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {"title": "Description", "type": "string"}
+ ),
+ "price": {"title": "Price", "type": "number"},
+ "tax": IsDict(
+ {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ }
+ )
+ | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {"title": "Tax", "type": "number"}
+ ),
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/__init__.py b/tests/test_tutorial/test_separate_openapi_schemas/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py
new file mode 100644
index 000000000..8079c1134
--- /dev/null
+++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py
@@ -0,0 +1,147 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client() -> TestClient:
+ from docs_src.separate_openapi_schemas.tutorial001 import app
+
+ client = TestClient(app)
+ return client
+
+
+def test_create_item(client: TestClient) -> None:
+ response = client.post("/items/", json={"name": "Foo"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "Foo", "description": None}
+
+
+def test_read_items(client: TestClient) -> None:
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {
+ "name": "Portal Gun",
+ "description": "Device to travel through the multi-rick-verse",
+ },
+ {"name": "Plumbus", "description": None},
+ ]
+
+
+@needs_pydanticv2
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "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": {
+ "items": {
+ "$ref": "#/components/schemas/Item-Output"
+ },
+ "type": "array",
+ "title": "Response Read Items Items Get",
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item-Input"}
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "Item-Input": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ },
+ "type": "object",
+ "required": ["name"],
+ "title": "Item",
+ },
+ "Item-Output": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ },
+ "type": "object",
+ "required": ["name", "description"],
+ "title": "Item",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py
new file mode 100644
index 000000000..4fa98ccbe
--- /dev/null
+++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py
@@ -0,0 +1,150 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310, needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client() -> TestClient:
+ from docs_src.separate_openapi_schemas.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_create_item(client: TestClient) -> None:
+ response = client.post("/items/", json={"name": "Foo"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "Foo", "description": None}
+
+
+@needs_py310
+def test_read_items(client: TestClient) -> None:
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {
+ "name": "Portal Gun",
+ "description": "Device to travel through the multi-rick-verse",
+ },
+ {"name": "Plumbus", "description": None},
+ ]
+
+
+@needs_py310
+@needs_pydanticv2
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "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": {
+ "items": {
+ "$ref": "#/components/schemas/Item-Output"
+ },
+ "type": "array",
+ "title": "Response Read Items Items Get",
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item-Input"}
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "Item-Input": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ },
+ "type": "object",
+ "required": ["name"],
+ "title": "Item",
+ },
+ "Item-Output": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ },
+ "type": "object",
+ "required": ["name", "description"],
+ "title": "Item",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py
new file mode 100644
index 000000000..ad36582ed
--- /dev/null
+++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py
@@ -0,0 +1,150 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39, needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client() -> TestClient:
+ from docs_src.separate_openapi_schemas.tutorial001_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_create_item(client: TestClient) -> None:
+ response = client.post("/items/", json={"name": "Foo"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "Foo", "description": None}
+
+
+@needs_py39
+def test_read_items(client: TestClient) -> None:
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {
+ "name": "Portal Gun",
+ "description": "Device to travel through the multi-rick-verse",
+ },
+ {"name": "Plumbus", "description": None},
+ ]
+
+
+@needs_py39
+@needs_pydanticv2
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "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": {
+ "items": {
+ "$ref": "#/components/schemas/Item-Output"
+ },
+ "type": "array",
+ "title": "Response Read Items Items Get",
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item-Input"}
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "Item-Input": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ },
+ "type": "object",
+ "required": ["name"],
+ "title": "Item",
+ },
+ "Item-Output": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ },
+ "type": "object",
+ "required": ["name", "description"],
+ "title": "Item",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py
new file mode 100644
index 000000000..d2cf7945b
--- /dev/null
+++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py
@@ -0,0 +1,133 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client() -> TestClient:
+ from docs_src.separate_openapi_schemas.tutorial002 import app
+
+ client = TestClient(app)
+ return client
+
+
+def test_create_item(client: TestClient) -> None:
+ response = client.post("/items/", json={"name": "Foo"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "Foo", "description": None}
+
+
+def test_read_items(client: TestClient) -> None:
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {
+ "name": "Portal Gun",
+ "description": "Device to travel through the multi-rick-verse",
+ },
+ {"name": "Plumbus", "description": None},
+ ]
+
+
+@needs_pydanticv2
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "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": {
+ "items": {"$ref": "#/components/schemas/Item"},
+ "type": "array",
+ "title": "Response Read Items Items Get",
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "Item": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ },
+ "type": "object",
+ "required": ["name"],
+ "title": "Item",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py
new file mode 100644
index 000000000..89c9ce977
--- /dev/null
+++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py
@@ -0,0 +1,136 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310, needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client() -> TestClient:
+ from docs_src.separate_openapi_schemas.tutorial002_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_create_item(client: TestClient) -> None:
+ response = client.post("/items/", json={"name": "Foo"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "Foo", "description": None}
+
+
+@needs_py310
+def test_read_items(client: TestClient) -> None:
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {
+ "name": "Portal Gun",
+ "description": "Device to travel through the multi-rick-verse",
+ },
+ {"name": "Plumbus", "description": None},
+ ]
+
+
+@needs_py310
+@needs_pydanticv2
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "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": {
+ "items": {"$ref": "#/components/schemas/Item"},
+ "type": "array",
+ "title": "Response Read Items Items Get",
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "Item": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ },
+ "type": "object",
+ "required": ["name"],
+ "title": "Item",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py
new file mode 100644
index 000000000..6ac3d8f79
--- /dev/null
+++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py
@@ -0,0 +1,136 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39, needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client() -> TestClient:
+ from docs_src.separate_openapi_schemas.tutorial002_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_create_item(client: TestClient) -> None:
+ response = client.post("/items/", json={"name": "Foo"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "Foo", "description": None}
+
+
+@needs_py39
+def test_read_items(client: TestClient) -> None:
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {
+ "name": "Portal Gun",
+ "description": "Device to travel through the multi-rick-verse",
+ },
+ {"name": "Plumbus", "description": None},
+ ]
+
+
+@needs_py39
+@needs_pydanticv2
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "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": {
+ "items": {"$ref": "#/components/schemas/Item"},
+ "type": "array",
+ "title": "Response Read Items Items Get",
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "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": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "Item": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ },
+ "type": "object",
+ "required": ["name"],
+ "title": "Item",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }