-
+
+requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/de/mkdocs.yml b/docs/de/mkdocs.yml
index 360fa8c4..1242af50 100644
--- a/docs/de/mkdocs.yml
+++ b/docs/de/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -98,6 +100,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -108,6 +112,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs/en/data/external_links.yml b/docs/en/data/external_links.yml
index 0850d978..b918c8fd 100644
--- a/docs/en/data/external_links.yml
+++ b/docs/en/data/external_links.yml
@@ -1,5 +1,13 @@
articles:
english:
+ - author: Silvan Melchior
+ author_link: https://github.com/silvanmelchior
+ link: https://blog.devgenius.io/seamless-fastapi-configuration-with-confz-90949c14ea12
+ title: Seamless FastAPI Configuration with ConfZ
+ - author: Kaustubh Gupta
+ author_link: https://medium.com/@kaustubhgupta1828/
+ link: https://levelup.gitconnected.com/5-advance-features-of-fastapi-you-should-try-7c0ac7eebb3e
+ title: 5 Advanced Features of FastAPI You Should Try
- author: Kaustubh Gupta
author_link: https://medium.com/@kaustubhgupta1828/
link: https://www.analyticsvidhya.com/blog/2021/06/deploying-ml-models-as-api-using-fastapi-and-heroku/
@@ -12,6 +20,10 @@ articles:
author_link: https://pystar.substack.com/
link: https://pystar.substack.com/p/how-to-create-a-fake-certificate
title: How to Create A Fake Certificate Authority And Generate TLS Certs for FastAPI
+ - author: Ben Gamble
+ author_link: https://uk.linkedin.com/in/bengamble7
+ link: https://ably.com/blog/realtime-ticket-booking-solution-kafka-fastapi-ably
+ title: Building a realtime ticket booking solution with Kafka, FastAPI, and Ably
- author: Shahriyar(Shako) Rzayev
author_link: https://www.linkedin.com/in/shahriyar-rzayev/
link: https://www.azepug.az/posts/fastapi/#building-simple-e-commerce-with-nuxtjs-and-fastapi-series
@@ -20,6 +32,10 @@ articles:
author_link: https://rodrigo-arenas.medium.com/
link: https://medium.com/analytics-vidhya/serve-a-machine-learning-model-using-sklearn-fastapi-and-docker-85aabf96729b
title: "Serve a machine learning model using Sklearn, FastAPI and Docker"
+ - author: Yashasvi Singh
+ author_link: https://hashnode.com/@aUnicornDev
+ link: https://aunicorndev.hashnode.dev/series/supafast-api
+ title: "Building an API with FastAPI and Supabase and Deploying on Deta"
- author: Navule Pavan Kumar Rao
author_link: https://www.linkedin.com/in/navule/
link: https://www.tutlinks.com/deploy-fastapi-on-ubuntu-gunicorn-caddy-2/
@@ -188,11 +204,19 @@ articles:
author_link: https://medium.com/@williamhayes
link: https://medium.com/@williamhayes/fastapi-starlette-debug-vs-prod-5f7561db3a59
title: FastAPI/Starlette debug vs prod
+ - author: Mukul Mantosh
+ author_link: https://twitter.com/MantoshMukul
+ link: https://www.jetbrains.com/pycharm/guide/tutorials/fastapi-aws-kubernetes/
+ title: Developing FastAPI Application using K8s & AWS
german:
- author: Nico Axtmann
author_link: https://twitter.com/_nicoax
link: https://blog.codecentric.de/2019/08/inbetriebnahme-eines-scikit-learn-modells-mit-onnx-und-fastapi/
title: Inbetriebnahme eines scikit-learn-Modells mit ONNX und FastAPI
+ - author: Felix Schürmeyer
+ author_link: https://hellocoding.de/autor/felix-schuermeyer/
+ link: https://hellocoding.de/blog/coding-language/python/fastapi
+ title: REST-API Programmieren mittels Python und dem FastAPI Modul
japanese:
- author: '@bee2'
author_link: https://qiita.com/bee2
diff --git a/docs/en/data/github_sponsors.yml b/docs/en/data/github_sponsors.yml
index 162a8dbe..db4a9acc 100644
--- a/docs/en/data/github_sponsors.yml
+++ b/docs/en/data/github_sponsors.yml
@@ -1,42 +1,75 @@
sponsors:
-- - login: jina-ai
+- - login: cryptapi
+ avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4
+ url: https://github.com/cryptapi
+ - login: jina-ai
avatarUrl: https://avatars.githubusercontent.com/u/60539444?v=4
url: https://github.com/jina-ai
+ - login: DropbaseHQ
+ avatarUrl: https://avatars.githubusercontent.com/u/85367855?v=4
+ url: https://github.com/DropbaseHQ
+- - login: sushi2all
+ avatarUrl: https://avatars.githubusercontent.com/u/1043732?v=4
+ url: https://github.com/sushi2all
+ - login: chaserowbotham
+ avatarUrl: https://avatars.githubusercontent.com/u/97751084?v=4
+ url: https://github.com/chaserowbotham
- - login: mikeckennedy
- avatarUrl: https://avatars.githubusercontent.com/u/2035561?v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/2035561?u=1bb18268bcd4d9249e1f783a063c27df9a84c05b&v=4
url: https://github.com/mikeckennedy
- - login: RodneyU215
- avatarUrl: https://avatars.githubusercontent.com/u/3329665?u=ec6a9adf8e7e8e306eed7d49687c398608d1604f&v=4
- url: https://github.com/RodneyU215
- login: Trivie
avatarUrl: https://avatars.githubusercontent.com/u/8161763?v=4
url: https://github.com/Trivie
- 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: investsuite
avatarUrl: https://avatars.githubusercontent.com/u/73833632?v=4
url: https://github.com/investsuite
- - login: vimsoHQ
- avatarUrl: https://avatars.githubusercontent.com/u/77627231?v=4
- url: https://github.com/vimsoHQ
-- - login: newrelic
- avatarUrl: https://avatars.githubusercontent.com/u/31739?v=4
- url: https://github.com/newrelic
+ - login: VincentParedes
+ avatarUrl: https://avatars.githubusercontent.com/u/103889729?v=4
+ url: https://github.com/VincentParedes
+- - login: plocher
+ avatarUrl: https://avatars.githubusercontent.com/u/1082871?v=4
+ url: https://github.com/plocher
+- - login: InesIvanova
+ avatarUrl: https://avatars.githubusercontent.com/u/22920417?u=409882ec1df6dbd77455788bb383a8de223dbf6f&v=4
+ url: https://github.com/InesIvanova
+- - login: SendCloud
+ avatarUrl: https://avatars.githubusercontent.com/u/7831959?v=4
+ url: https://github.com/SendCloud
- login: qaas
avatarUrl: https://avatars.githubusercontent.com/u/8503759?u=10a6b4391ad6ab4cf9487ce54e3fcb61322d1efc&v=4
url: https://github.com/qaas
+ - login: xoflare
+ avatarUrl: https://avatars.githubusercontent.com/u/74335107?v=4
+ url: https://github.com/xoflare
+ - login: Striveworks
+ avatarUrl: https://avatars.githubusercontent.com/u/45523576?v=4
+ url: https://github.com/Striveworks
+ - login: BoostryJP
+ avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4
+ url: https://github.com/BoostryJP
- - login: johnadjei
avatarUrl: https://avatars.githubusercontent.com/u/767860?v=4
url: https://github.com/johnadjei
+ - login: HiredScore
+ avatarUrl: https://avatars.githubusercontent.com/u/3908850?v=4
+ url: https://github.com/HiredScore
- login: wdwinslow
avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4
url: https://github.com/wdwinslow
-- - login: kamalgill
- avatarUrl: https://avatars.githubusercontent.com/u/133923?u=0df9181d97436ce330e9acf90ab8a54b7022efe7&v=4
- url: https://github.com/kamalgill
+- - login: moellenbeck
+ avatarUrl: https://avatars.githubusercontent.com/u/169372?v=4
+ url: https://github.com/moellenbeck
+ - login: RodneyU215
+ avatarUrl: https://avatars.githubusercontent.com/u/3329665?u=ec6a9adf8e7e8e306eed7d49687c398608d1604f&v=4
+ url: https://github.com/RodneyU215
- login: grillazz
- avatarUrl: https://avatars.githubusercontent.com/u/3415861?u=16d7d0ffa5dfb99f8834f8f76d90e138ba09b94a&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/3415861?u=0b32b7073ae1ab8b7f6d2db0188c2e1e357ff451&v=4
url: https://github.com/grillazz
- login: tizz98
avatarUrl: https://avatars.githubusercontent.com/u/5739698?u=f095a3659e3a8e7c69ccd822696990b521ea25f9&v=4
@@ -44,45 +77,57 @@ sponsors:
- login: jmaralc
avatarUrl: https://avatars.githubusercontent.com/u/21101214?u=b15a9f07b7cbf6c9dcdbcb6550bbd2c52f55aa50&v=4
url: https://github.com/jmaralc
- - login: AlexandruSimion
- avatarUrl: https://avatars.githubusercontent.com/u/71321732?v=4
- url: https://github.com/AlexandruSimion
-- - login: samuelcolvin
+ - login: marutoraman
+ avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4
+ url: https://github.com/marutoraman
+ - login: mainframeindustries
+ avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4
+ url: https://github.com/mainframeindustries
+ - login: A-Edge
+ avatarUrl: https://avatars.githubusercontent.com/u/59514131?v=4
+ url: https://github.com/A-Edge
+- - login: Kludex
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
+ url: https://github.com/Kludex
+ - login: samuelcolvin
avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=807390ba9cfe23906c3bf8a0d56aaca3cf2bfa0d&v=4
url: https://github.com/samuelcolvin
- login: jokull
avatarUrl: https://avatars.githubusercontent.com/u/701?u=0532b62166893d5160ef795c4c8b7512d971af05&v=4
url: https://github.com/jokull
+ - login: jefftriplett
+ avatarUrl: https://avatars.githubusercontent.com/u/50527?u=af1ddfd50f6afd6d99f333ba2ac8d0a5b245ea74&v=4
+ url: https://github.com/jefftriplett
+ - login: kamalgill
+ avatarUrl: https://avatars.githubusercontent.com/u/133923?u=0df9181d97436ce330e9acf90ab8a54b7022efe7&v=4
+ url: https://github.com/kamalgill
+ - login: jsutton
+ avatarUrl: https://avatars.githubusercontent.com/u/280777?v=4
+ url: https://github.com/jsutton
+ - login: deserat
+ avatarUrl: https://avatars.githubusercontent.com/u/299332?v=4
+ url: https://github.com/deserat
+ - login: ericof
+ avatarUrl: https://avatars.githubusercontent.com/u/306014?u=cf7c8733620397e6584a451505581c01c5d842d7&v=4
+ url: https://github.com/ericof
- login: wshayes
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
url: https://github.com/wshayes
- login: koxudaxi
avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4
url: https://github.com/koxudaxi
- - login: falkben
- avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4
- url: https://github.com/falkben
- login: jqueguiner
avatarUrl: https://avatars.githubusercontent.com/u/690878?u=e4835b2a985a0f2d52018e4926cb5a58c26a62e8&v=4
url: https://github.com/jqueguiner
- - login: Mazyod
- avatarUrl: https://avatars.githubusercontent.com/u/860511?v=4
- url: https://github.com/Mazyod
- login: ltieman
avatarUrl: https://avatars.githubusercontent.com/u/1084689?u=e69b17de17cb3ca141a17daa7ccbe173ceb1eb17&v=4
url: https://github.com/ltieman
- - login: mrmattwright
- avatarUrl: https://avatars.githubusercontent.com/u/1277725?v=4
- url: https://github.com/mrmattwright
- login: westonsteimel
avatarUrl: https://avatars.githubusercontent.com/u/1593939?u=0f2c0e3647f916fe295d62fa70da7a4c177115e3&v=4
url: https://github.com/westonsteimel
- - login: timdrijvers
- avatarUrl: https://avatars.githubusercontent.com/u/1694939?v=4
- url: https://github.com/timdrijvers
- - login: mrgnw
- avatarUrl: https://avatars.githubusercontent.com/u/2504532?u=7ec43837a6d0afa80f96f0788744ea6341b89f97&v=4
- url: https://github.com/mrgnw
+ - login: corleyma
+ avatarUrl: https://avatars.githubusercontent.com/u/2080732?u=aed2ff652294a87d666b1c3f6dbe98104db76d26&v=4
+ url: https://github.com/corleyma
- login: madisonmay
avatarUrl: https://avatars.githubusercontent.com/u/2645393?u=f22b93c6ea345a4d26a90a3834dfc7f0789fcb63&v=4
url: https://github.com/madisonmay
@@ -93,149 +138,194 @@ sponsors:
avatarUrl: https://avatars.githubusercontent.com/u/3148093?v=4
url: https://github.com/andre1sk
- login: Shark009
- avatarUrl: https://avatars.githubusercontent.com/u/3163309?v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/3163309?u=0c6f4091b0eda05c44c390466199826e6dc6e431&v=4
url: https://github.com/Shark009
+ - login: dblackrun
+ avatarUrl: https://avatars.githubusercontent.com/u/3528486?v=4
+ url: https://github.com/dblackrun
+ - login: zsinx6
+ avatarUrl: https://avatars.githubusercontent.com/u/3532625?u=ba75a5dc744d1116ccfeaaf30d41cb2fe81fe8dd&v=4
+ url: https://github.com/zsinx6
+ - login: anomaly
+ avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4
+ url: https://github.com/anomaly
- login: peterHoburg
avatarUrl: https://avatars.githubusercontent.com/u/3860655?u=f55f47eb2d6a9b495e806ac5a044e3ae01ccc1fa&v=4
url: https://github.com/peterHoburg
- login: jaredtrog
avatarUrl: https://avatars.githubusercontent.com/u/4381365?v=4
url: https://github.com/jaredtrog
+ - login: oliverxchen
+ avatarUrl: https://avatars.githubusercontent.com/u/4471774?u=534191f25e32eeaadda22dfab4b0a428733d5489&v=4
+ url: https://github.com/oliverxchen
- login: CINOAdam
- avatarUrl: https://avatars.githubusercontent.com/u/4728508?u=34c3d58cb900fed475d0172b436c66a94ad739ed&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/4728508?u=76ef23f06ae7c604e009873bc27cf0ea9ba738c9&v=4
url: https://github.com/CINOAdam
- - login: dudil
- avatarUrl: https://avatars.githubusercontent.com/u/4785835?u=58b7ea39123e0507f3b2996448a27256b16fd697&v=4
- url: https://github.com/dudil
+ - login: ScrimForever
+ avatarUrl: https://avatars.githubusercontent.com/u/5040124?u=091ec38bfe16d6e762099e91309b59f248616a65&v=4
+ url: https://github.com/ScrimForever
- login: ennui93
avatarUrl: https://avatars.githubusercontent.com/u/5300907?u=5b5452725ddb391b2caaebf34e05aba873591c3a&v=4
url: https://github.com/ennui93
- login: MacroPower
avatarUrl: https://avatars.githubusercontent.com/u/5648814?u=e13991efd1e03c44c911f919872e750530ded633&v=4
url: https://github.com/MacroPower
- - login: ginomempin
- avatarUrl: https://avatars.githubusercontent.com/u/6091865?v=4
- url: https://github.com/ginomempin
+ - login: Yaleesa
+ avatarUrl: https://avatars.githubusercontent.com/u/6135475?v=4
+ url: https://github.com/Yaleesa
- login: iwpnd
avatarUrl: https://avatars.githubusercontent.com/u/6152183?u=b2286006daafff5f991557344fee20b5da59639a&v=4
url: https://github.com/iwpnd
+ - login: simw
+ avatarUrl: https://avatars.githubusercontent.com/u/6322526?v=4
+ url: https://github.com/simw
+ - login: pkucmus
+ avatarUrl: https://avatars.githubusercontent.com/u/6347418?u=98f5918b32e214a168a2f5d59b0b8ebdf57dca0d&v=4
+ url: https://github.com/pkucmus
- login: s3ich4n
avatarUrl: https://avatars.githubusercontent.com/u/6926298?u=ba3025d698e1c986655e776ae383a3d60d9d578e&v=4
url: https://github.com/s3ich4n
- login: Rehket
avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4
url: https://github.com/Rehket
- - login: christippett
- avatarUrl: https://avatars.githubusercontent.com/u/7218120?u=434b9d29287d7de25772d94ddc74a9bd6d969284&v=4
- url: https://github.com/christippett
- - login: Kludex
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
- url: https://github.com/Kludex
+ - 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: cristeaadrian
- avatarUrl: https://avatars.githubusercontent.com/u/9112724?v=4
- url: https://github.com/cristeaadrian
- - login: otivvormes
- avatarUrl: https://avatars.githubusercontent.com/u/11317418?u=6de1edefb6afd0108c0ad2816bd6efc4464a9c44&v=4
- url: https://github.com/otivvormes
- - login: iambobmae
- avatarUrl: https://avatars.githubusercontent.com/u/12390270?u=c9a35c2ee5092a9b4135ebb1f91b7f521c467031&v=4
- url: https://github.com/iambobmae
- - login: ronaldnwilliams
- avatarUrl: https://avatars.githubusercontent.com/u/13632749?u=ac41a086d0728bf66a9d2bee9e5e377041ff44a4&v=4
- url: https://github.com/ronaldnwilliams
+ - login: Vikka
+ avatarUrl: https://avatars.githubusercontent.com/u/9381120?u=4bfc7032a824d1ed1994aa8256dfa597c8f187ad&v=4
+ url: https://github.com/Vikka
+ - login: Ge0f3
+ avatarUrl: https://avatars.githubusercontent.com/u/11887760?u=ccd80f1ac36dcb8517ef5c4e702e8cc5a80cad2f&v=4
+ url: https://github.com/Ge0f3
+ - login: gokulyc
+ avatarUrl: https://avatars.githubusercontent.com/u/13468848?u=269f269d3e70407b5fb80138c52daba7af783997&v=4
+ url: https://github.com/gokulyc
+ - login: dannywade
+ avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4
+ url: https://github.com/dannywade
- login: pablonnaoji
avatarUrl: https://avatars.githubusercontent.com/u/15187159?u=afc15bd5a4ba9c5c7206bbb1bcaeef606a0932e0&v=4
url: https://github.com/pablonnaoji
- - login: natenka
- avatarUrl: https://avatars.githubusercontent.com/u/15850513?u=00d1083c980d0b4ce32835dc07eee7f43f34fd2f&v=4
- url: https://github.com/natenka
- - login: la-mar
- avatarUrl: https://avatars.githubusercontent.com/u/16618300?u=7755c0521d2bb0d704f35a51464b15c1e2e6c4da&v=4
- url: https://github.com/la-mar
- login: robintully
avatarUrl: https://avatars.githubusercontent.com/u/17059673?u=862b9bb01513f5acd30df97433cb97a24dbfb772&v=4
url: https://github.com/robintully
- - login: ShaulAb
- avatarUrl: https://avatars.githubusercontent.com/u/18129076?u=2c8d48e47f2dbee15c3f89c3d17d4c356504386c&v=4
- url: https://github.com/ShaulAb
- login: wedwardbeck
avatarUrl: https://avatars.githubusercontent.com/u/19333237?u=1de4ae2bf8d59eb4c013f21d863cbe0f2010575f&v=4
url: https://github.com/wedwardbeck
- login: linusg
avatarUrl: https://avatars.githubusercontent.com/u/19366641?u=125e390abef8fff3b3b0d370c369cba5d7fd4c67&v=4
url: https://github.com/linusg
+ - login: stradivari96
+ avatarUrl: https://avatars.githubusercontent.com/u/19752586?u=255f5f06a768f518b20cebd6963e840ac49294fd&v=4
+ url: https://github.com/stradivari96
- login: RedCarpetUp
avatarUrl: https://avatars.githubusercontent.com/u/20360440?v=4
url: https://github.com/RedCarpetUp
- login: Filimoa
avatarUrl: https://avatars.githubusercontent.com/u/21352040?u=75e02d102d2ee3e3d793e555fa5c63045913ccb0&v=4
url: https://github.com/Filimoa
- - login: raminsj13
- avatarUrl: https://avatars.githubusercontent.com/u/24259406?u=d51f2a526312ebba150a06936ed187ca0727d329&v=4
- url: https://github.com/raminsj13
- - login: comoelcometa
- avatarUrl: https://avatars.githubusercontent.com/u/25950317?u=c6751efa038561b9bc5fa56d1033d5174e10cd65&v=4
- url: https://github.com/comoelcometa
+ - login: shuheng-liu
+ avatarUrl: https://avatars.githubusercontent.com/u/22414322?u=813c45f30786c6b511b21a661def025d8f7b609e&v=4
+ url: https://github.com/shuheng-liu
+ - login: cometa-haley
+ avatarUrl: https://avatars.githubusercontent.com/u/25950317?u=cec1a3e0643b785288ae8260cc295a85ab344995&v=4
+ url: https://github.com/cometa-haley
+ - login: LarryGF
+ avatarUrl: https://avatars.githubusercontent.com/u/26148349?u=431bb34d36d41c172466252242175281ae132152&v=4
+ url: https://github.com/LarryGF
- login: veprimk
avatarUrl: https://avatars.githubusercontent.com/u/29689749?u=f8cb5a15a286e522e5b189bc572d5a1a90217fb2&v=4
url: https://github.com/veprimk
- - login: orihomie
- avatarUrl: https://avatars.githubusercontent.com/u/29889683?u=6bc2135a52fcb3a49e69e7d50190796618185fda&v=4
- url: https://github.com/orihomie
- - login: SaltyCoco
- avatarUrl: https://avatars.githubusercontent.com/u/31451104?u=6ee4e17c07d21b7054f54a12fa9cc377a1b24ff9&v=4
- url: https://github.com/SaltyCoco
+ - login: meysam81
+ avatarUrl: https://avatars.githubusercontent.com/u/30233243?u=64dc9fc62d039892c6fb44d804251cad5537132b&v=4
+ url: https://github.com/meysam81
- login: mauroalejandrojm
avatarUrl: https://avatars.githubusercontent.com/u/31569442?u=cdada990a1527926a36e95f62c30a8b48bbc49a1&v=4
url: https://github.com/mauroalejandrojm
- - login: bulkw4r3
- avatarUrl: https://avatars.githubusercontent.com/u/35562532?u=0b812a14a02de14bf73d05fb2b2760a67bacffc2&v=4
- url: https://github.com/bulkw4r3
+ - login: Leay15
+ avatarUrl: https://avatars.githubusercontent.com/u/32212558?u=c4aa9c1737e515959382a5515381757b1fd86c53&v=4
+ url: https://github.com/Leay15
+ - login: AlrasheedA
+ avatarUrl: https://avatars.githubusercontent.com/u/33544979?u=7fe66bf62b47682612b222e3e8f4795ef3be769b&v=4
+ url: https://github.com/AlrasheedA
+ - login: ProteinQure
+ avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4
+ url: https://github.com/ProteinQure
+ - login: guligon90
+ avatarUrl: https://avatars.githubusercontent.com/u/35070513?u=b48c05f669d1ea1d329f90dc70e45f10b569ef55&v=4
+ url: https://github.com/guligon90
- login: ybressler
avatarUrl: https://avatars.githubusercontent.com/u/40807730?u=6621dc9ab53b697912ab2a32211bb29ae90a9112&v=4
url: https://github.com/ybressler
+ - login: iamkarshe
+ avatarUrl: https://avatars.githubusercontent.com/u/43641892?u=d08c901b359c931784501740610d416558ff3e24&v=4
+ url: https://github.com/iamkarshe
- login: dbanty
- avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=0cf33e4f40efc2ea206a1189fd63a11344eb88ed&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4
url: https://github.com/dbanty
+ - login: rafsaf
+ avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=be9f06b8ced2d2b677297decc781fa8ce4f7ddbd&v=4
+ url: https://github.com/rafsaf
- login: dudikbender
avatarUrl: https://avatars.githubusercontent.com/u/53487583?u=494f85229115076121b3639a3806bbac1c6ae7f6&v=4
url: https://github.com/dudikbender
+ - login: daisuke8000
+ avatarUrl: https://avatars.githubusercontent.com/u/55035595?u=5025e379cd3655ae1a96039efc85223a873d2e38&v=4
+ url: https://github.com/daisuke8000
+ - login: yakkonaut
+ avatarUrl: https://avatars.githubusercontent.com/u/60633704?u=90a71fd631aa998ba4a96480788f017c9904e07b&v=4
+ url: https://github.com/yakkonaut
- login: primer-io
avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4
url: https://github.com/primer-io
- - login: tkrestiankova
- avatarUrl: https://avatars.githubusercontent.com/u/67013045?v=4
- url: https://github.com/tkrestiankova
+ - login: around
+ avatarUrl: https://avatars.githubusercontent.com/u/62425723?v=4
+ url: https://github.com/around
+ - login: predictionmachine
+ avatarUrl: https://avatars.githubusercontent.com/u/63719559?v=4
+ url: https://github.com/predictionmachine
- login: daverin
avatarUrl: https://avatars.githubusercontent.com/u/70378377?u=6d1814195c0de7162820eaad95a25b423a3869c0&v=4
url: https://github.com/daverin
- login: anthonycepeda
avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=892f700c79f9732211bd5221bf16eec32356a732&v=4
url: https://github.com/anthonycepeda
- - login: an-tho-ny
- avatarUrl: https://avatars.githubusercontent.com/u/74874159?v=4
- url: https://github.com/an-tho-ny
+ - login: NinaHwang
+ avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4
+ url: https://github.com/NinaHwang
+ - login: dotlas
+ avatarUrl: https://avatars.githubusercontent.com/u/88832003?v=4
+ url: https://github.com/dotlas
+ - login: pyt3h
+ avatarUrl: https://avatars.githubusercontent.com/u/99658549?v=4
+ url: https://github.com/pyt3h
- - login: linux-china
avatarUrl: https://avatars.githubusercontent.com/u/46711?v=4
url: https://github.com/linux-china
+ - login: ddanier
+ avatarUrl: https://avatars.githubusercontent.com/u/113563?v=4
+ url: https://github.com/ddanier
- login: jhb
avatarUrl: https://avatars.githubusercontent.com/u/142217?v=4
url: https://github.com/jhb
+ - login: justinrmiller
+ avatarUrl: https://avatars.githubusercontent.com/u/143998?u=b507a940394d4fc2bc1c27cea2ca9c22538874bd&v=4
+ url: https://github.com/justinrmiller
+ - login: bryanculbertson
+ avatarUrl: https://avatars.githubusercontent.com/u/144028?u=defda4f90e93429221cc667500944abde60ebe4a&v=4
+ url: https://github.com/bryanculbertson
- login: yourkin
- avatarUrl: https://avatars.githubusercontent.com/u/178984?v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/178984?u=fa7c3503b47bf16405b96d21554bc59f07a65523&v=4
url: https://github.com/yourkin
- - login: jmagnusson
- avatarUrl: https://avatars.githubusercontent.com/u/190835?v=4
- url: https://github.com/jmagnusson
- - login: sakti
- avatarUrl: https://avatars.githubusercontent.com/u/196178?u=0110be74c4270244546f1b610334042cd16bb8ad&v=4
- url: https://github.com/sakti
- login: slafs
avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4
url: https://github.com/slafs
+ - login: assem-ch
+ avatarUrl: https://avatars.githubusercontent.com/u/315228?u=e0c5ab30726d3243a40974bb9bae327866e42d9b&v=4
+ url: https://github.com/assem-ch
- login: adamghill
avatarUrl: https://avatars.githubusercontent.com/u/317045?u=f1349d5ffe84a19f324e204777859fbf69ddf633&v=4
url: https://github.com/adamghill
@@ -245,21 +335,33 @@ sponsors:
- login: dmig
avatarUrl: https://avatars.githubusercontent.com/u/388564?v=4
url: https://github.com/dmig
- - login: hongqn
- avatarUrl: https://avatars.githubusercontent.com/u/405587?u=470b4c04832e45141fd5264d3354845cc9fc6466&v=4
- url: https://github.com/hongqn
- login: rinckd
avatarUrl: https://avatars.githubusercontent.com/u/546002?u=1fcc7e664dc86524a0af6837a0c222829c3fd4e5&v=4
url: https://github.com/rinckd
+ - login: securancy
+ avatarUrl: https://avatars.githubusercontent.com/u/606673?v=4
+ url: https://github.com/securancy
+ - login: falkben
+ avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4
+ url: https://github.com/falkben
- login: hardbyte
avatarUrl: https://avatars.githubusercontent.com/u/855189?u=aa29e92f34708814d6b67fcd47ca4cf2ce1c04ed&v=4
url: https://github.com/hardbyte
+ - login: janfilips
+ avatarUrl: https://avatars.githubusercontent.com/u/870699?u=6034d81731ecb41ae5c717e56a901ed46fc039a8&v=4
+ url: https://github.com/janfilips
+ - login: scari
+ avatarUrl: https://avatars.githubusercontent.com/u/964251?v=4
+ url: https://github.com/scari
- login: Pytlicek
avatarUrl: https://avatars.githubusercontent.com/u/1430522?u=169dba3bfbc04ed214a914640ff435969f19ddb3&v=4
url: https://github.com/Pytlicek
- - login: okken
- avatarUrl: https://avatars.githubusercontent.com/u/1568356?u=0a991a21bdc62e2bea9ad311652f2c45f453dc84&v=4
- url: https://github.com/okken
+ - login: Celeborn2BeAlive
+ avatarUrl: https://avatars.githubusercontent.com/u/1659465?u=944517e4db0f6df65070074e81cabdad9c8a434b&v=4
+ url: https://github.com/Celeborn2BeAlive
+ - login: WillHogan
+ avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=7036c064cf29781470573865264ec8e60b6b809f&v=4
+ url: https://github.com/WillHogan
- login: cbonoz
avatarUrl: https://avatars.githubusercontent.com/u/2351087?u=fd3e8030b2cc9fbfbb54a65e9890c548a016f58b&v=4
url: https://github.com/cbonoz
@@ -269,47 +371,47 @@ sponsors:
- login: rglsk
avatarUrl: https://avatars.githubusercontent.com/u/2768101?u=e349c88673f2155fe021331377c656a9d74bcc25&v=4
url: https://github.com/rglsk
- - login: Atem18
- avatarUrl: https://avatars.githubusercontent.com/u/2875254?v=4
- url: https://github.com/Atem18
- login: paul121
avatarUrl: https://avatars.githubusercontent.com/u/3116995?u=6e2d8691cc345e63ee02e4eb4d7cef82b1fcbedc&v=4
url: https://github.com/paul121
- login: igorcorrea
avatarUrl: https://avatars.githubusercontent.com/u/3438238?u=c57605077c31a8f7b2341fc4912507f91b4a5621&v=4
url: https://github.com/igorcorrea
- - login: anthcor
+ - login: anthonycorletti
avatarUrl: https://avatars.githubusercontent.com/u/3477132?v=4
- url: https://github.com/anthcor
- - login: zsinx6
- avatarUrl: https://avatars.githubusercontent.com/u/3532625?u=ba75a5dc744d1116ccfeaaf30d41cb2fe81fe8dd&v=4
- url: https://github.com/zsinx6
+ url: https://github.com/anthonycorletti
- login: pawamoy
avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4
url: https://github.com/pawamoy
- - login: spyker77
- avatarUrl: https://avatars.githubusercontent.com/u/4953435?u=03c724c6f8fbab5cd6575b810c0c91c652fa4f79&v=4
- url: https://github.com/spyker77
- - login: JonasKs
- avatarUrl: https://avatars.githubusercontent.com/u/5310116?u=98a049f3e1491bffb91e1feb7e93def6881a9389&v=4
- url: https://github.com/JonasKs
+ - login: Alisa-lisa
+ avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4
+ url: https://github.com/Alisa-lisa
+ - login: unredundant
+ avatarUrl: https://avatars.githubusercontent.com/u/5607577?u=57dd0023365bec03f4fc566df6b81bc0a264a47d&v=4
+ url: https://github.com/unredundant
- login: holec
avatarUrl: https://avatars.githubusercontent.com/u/6438041?u=f5af71ec85b3a9d7b8139cb5af0512b02fa9ab1e&v=4
url: https://github.com/holec
- - login: BartlomiejRasztabiga
- avatarUrl: https://avatars.githubusercontent.com/u/8852711?u=ed213d60f7a423df31ceb1004aa3ec60e612cb98&v=4
- url: https://github.com/BartlomiejRasztabiga
+ - login: moonape1226
+ avatarUrl: https://avatars.githubusercontent.com/u/8532038?u=d9f8b855a429fff9397c3833c2ff83849ebf989d&v=4
+ url: https://github.com/moonape1226
- login: davanstrien
avatarUrl: https://avatars.githubusercontent.com/u/8995957?u=fb2aad2b52bb4e7b56db6d7c8ecc9ae1eac1b984&v=4
url: https://github.com/davanstrien
- - login: and-semakin
- avatarUrl: https://avatars.githubusercontent.com/u/9129071?u=ea77ddf7de4bc375d546bf2825ed420eaddb7666&v=4
- url: https://github.com/and-semakin
+ - login: yenchenLiu
+ avatarUrl: https://avatars.githubusercontent.com/u/9199638?u=8cdf5ae507448430d90f6f3518d1665a23afe99b&v=4
+ url: https://github.com/yenchenLiu
- login: VivianSolide
- avatarUrl: https://avatars.githubusercontent.com/u/9358572?u=ffb2e2ec522a15dcd3f0af1f9fd1df4afe418afa&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/9358572?u=4a38ef72dd39e8b262bd5ab819992128b55c52b4&v=4
url: https://github.com/VivianSolide
+ - login: xncbf
+ avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=866a1311e4bd3ec5ae84185c4fcc99f397c883d7&v=4
+ url: https://github.com/xncbf
+ - login: DMantis
+ avatarUrl: https://avatars.githubusercontent.com/u/9536869?v=4
+ url: https://github.com/DMantis
- login: hard-coders
- avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=f2d3d2038c55d86d7f9348f4e6c5e30191e4ee8b&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
url: https://github.com/hard-coders
- login: satwikkansal
avatarUrl: https://avatars.githubusercontent.com/u/10217535?u=b12d6ef74ea297de9e46da6933b1a5b7ba9e6a61&v=4
@@ -317,63 +419,78 @@ sponsors:
- login: pheanex
avatarUrl: https://avatars.githubusercontent.com/u/10408624?u=5b6bab6ee174aa6e991333e06eb29f628741013d&v=4
url: https://github.com/pheanex
- - login: wotori
- avatarUrl: https://avatars.githubusercontent.com/u/10486621?u=0044c295b91694b8c9bccc0a805681f794250f7b&v=4
- url: https://github.com/wotori
- login: JimFawkes
avatarUrl: https://avatars.githubusercontent.com/u/12075115?u=dc58ecfd064d72887c34bf500ddfd52592509acd&v=4
url: https://github.com/JimFawkes
- login: logan-connolly
avatarUrl: https://avatars.githubusercontent.com/u/16244943?u=8ae66dfbba936463cc8aa0dd7a6d2b4c0cc757eb&v=4
url: https://github.com/logan-connolly
- - login: iPr0ger
- avatarUrl: https://avatars.githubusercontent.com/u/19322290?v=4
- url: https://github.com/iPr0ger
+ - login: cdsre
+ avatarUrl: https://avatars.githubusercontent.com/u/16945936?v=4
+ url: https://github.com/cdsre
+ - login: jangia
+ avatarUrl: https://avatars.githubusercontent.com/u/17927101?u=9261b9bb0c3e3bb1ecba43e8915dc58d8c9a077e&v=4
+ url: https://github.com/jangia
- login: ghandic
avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
url: https://github.com/ghandic
- - login: MoronVV
- avatarUrl: https://avatars.githubusercontent.com/u/24293616?v=4
- url: https://github.com/MoronVV
- login: fstau
avatarUrl: https://avatars.githubusercontent.com/u/24669867?u=60e7c8c09f8dafabee8fc3edcd6f9e19abbff918&v=4
url: https://github.com/fstau
- login: mertguvencli
avatarUrl: https://avatars.githubusercontent.com/u/29762151?u=16a906d90df96c8cff9ea131a575c4bc171b1523&v=4
url: https://github.com/mertguvencli
- - login: rgreen32
- avatarUrl: https://avatars.githubusercontent.com/u/35779241?u=c9d64ad1ab364b6a1ec8e3d859da9ca802d681d8&v=4
- url: https://github.com/rgreen32
- - login: askurihin
- avatarUrl: https://avatars.githubusercontent.com/u/37978981?v=4
- url: https://github.com/askurihin
- - login: JitPackJoyride
- avatarUrl: https://avatars.githubusercontent.com/u/40203625?u=9638bfeacfa5940358188f8205ce662bba022b53&v=4
- url: https://github.com/JitPackJoyride
- - login: es3n1n
- avatarUrl: https://avatars.githubusercontent.com/u/40367813?u=e881a3880f1e342d19a1ea7c8e1b6d76c52dc294&v=4
- url: https://github.com/es3n1n
+ - login: dwreeves
+ avatarUrl: https://avatars.githubusercontent.com/u/31971762?u=69732aba05aa5cf0780866349ebe109cf632b047&v=4
+ url: https://github.com/dwreeves
+ - login: kitaramu0401
+ avatarUrl: https://avatars.githubusercontent.com/u/33246506?u=929e6efa2c518033b8097ba524eb5347a069bb3b&v=4
+ url: https://github.com/kitaramu0401
+ - login: engineerjoe440
+ avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4
+ url: https://github.com/engineerjoe440
+ - login: declon
+ avatarUrl: https://avatars.githubusercontent.com/u/36180226?v=4
+ url: https://github.com/declon
+ - login: d-e-h-i-o
+ avatarUrl: https://avatars.githubusercontent.com/u/36816716?v=4
+ url: https://github.com/d-e-h-i-o
- login: ilias-ant
avatarUrl: https://avatars.githubusercontent.com/u/42189572?u=a2d6121bac4d125d92ec207460fa3f1842d37e66&v=4
url: https://github.com/ilias-ant
- login: arrrrrmin
- avatarUrl: https://avatars.githubusercontent.com/u/43553423?u=05600727f1cfe75f440bb3fddd49bfea84b1e894&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/43553423?u=fee5739394fea074cb0b66929d070114a5067aae&v=4
url: https://github.com/arrrrrmin
+ - login: Nephilim-Jack
+ avatarUrl: https://avatars.githubusercontent.com/u/48372168?u=6f2bb405238d7efc467536fe01f58df6779c58a9&v=4
+ url: https://github.com/Nephilim-Jack
- login: akanz1
avatarUrl: https://avatars.githubusercontent.com/u/51492342?u=2280f57134118714645e16b535c1a37adf6b369b&v=4
url: https://github.com/akanz1
-- - login: leogregianin
- avatarUrl: https://avatars.githubusercontent.com/u/1684053?u=94ddd387601bd1805034dbe83e6eba0491c15323&v=4
- url: https://github.com/leogregianin
+ - login: rooflexx
+ avatarUrl: https://avatars.githubusercontent.com/u/58993673?u=f8ba450460f1aea18430ed1e4a3889049a3b4dfa&v=4
+ url: https://github.com/rooflexx
+ - login: denisyao1
+ avatarUrl: https://avatars.githubusercontent.com/u/60019356?v=4
+ url: https://github.com/denisyao1
+ - login: apar-tiwari
+ avatarUrl: https://avatars.githubusercontent.com/u/61064197?v=4
+ url: https://github.com/apar-tiwari
+ - login: 0417taehyun
+ avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4
+ url: https://github.com/0417taehyun
+ - login: alessio-proietti
+ avatarUrl: https://avatars.githubusercontent.com/u/67370599?u=8ac73db1e18e946a7681f173abdb640516f88515&v=4
+ url: https://github.com/alessio-proietti
+- - login: backbord
+ avatarUrl: https://avatars.githubusercontent.com/u/6814946?v=4
+ url: https://github.com/backbord
- login: sadikkuzu
avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=765ed469c44c004560079210ccdad5b29938eaa9&v=4
url: https://github.com/sadikkuzu
- login: gabrielmbmb
- avatarUrl: https://avatars.githubusercontent.com/u/29572918?u=92084ed7242160dee4d20aece923a10c59758ee5&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/29572918?u=6d1e00b5d558e96718312ff910a2318f47cc3145&v=4
url: https://github.com/gabrielmbmb
- - login: starhype
- avatarUrl: https://avatars.githubusercontent.com/u/36908028?u=6df41f7b62f0f673f1ecbc87e9cbadaa4fcb0767&v=4
- url: https://github.com/starhype
- - login: pixel365
- avatarUrl: https://avatars.githubusercontent.com/u/53819609?u=9e0309c5420ec4624aececd3ca2d7105f7f68133&v=4
- url: https://github.com/pixel365
+ - login: danburonline
+ avatarUrl: https://avatars.githubusercontent.com/u/34251194?u=2cad4388c1544e539ecb732d656e42fb07b4ff2d&v=4
+ url: https://github.com/danburonline
diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml
index df088f39..92aab109 100644
--- a/docs/en/data/people.yml
+++ b/docs/en/data/people.yml
@@ -1,20 +1,20 @@
maintainers:
- login: tiangolo
- answers: 1230
- prs: 269
+ answers: 1243
+ prs: 300
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=5cad72c846b7aba2e960546af490edc7375dafc4&v=4
url: https://github.com/tiangolo
experts:
- login: Kludex
- count: 316
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
+ count: 335
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: dmontagu
count: 262
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
url: https://github.com/dmontagu
- login: ycd
- count: 219
+ count: 221
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
url: https://github.com/ycd
- login: Mause
@@ -29,14 +29,14 @@ experts:
count: 130
avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4
url: https://github.com/phy25
-- login: ArcLightSlavik
- count: 71
- avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4
- url: https://github.com/ArcLightSlavik
- login: raphaelauv
- count: 67
+ count: 71
avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
url: https://github.com/raphaelauv
+- login: ArcLightSlavik
+ count: 71
+ avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4
+ url: https://github.com/ArcLightSlavik
- login: falkben
count: 58
avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4
@@ -50,21 +50,33 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
url: https://github.com/insomnes
- login: Dustyposa
- count: 42
+ count: 43
avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
url: https://github.com/Dustyposa
- login: includeamin
- count: 38
+ count: 39
avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4
url: https://github.com/includeamin
+- login: jgould22
+ count: 38
+ avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
+ url: https://github.com/jgould22
+- login: STeveShary
+ count: 37
+ avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
+ url: https://github.com/STeveShary
+- login: adriangb
+ count: 36
+ avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4
+ url: https://github.com/adriangb
- login: prostomarkeloff
count: 33
avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4
url: https://github.com/prostomarkeloff
-- login: STeveShary
- count: 32
- avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
- url: https://github.com/STeveShary
+- login: frankie567
+ count: 31
+ avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4
+ url: https://github.com/frankie567
- login: krishnardt
count: 31
avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4
@@ -73,14 +85,14 @@ experts:
count: 29
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
url: https://github.com/wshayes
-- login: frankie567
- count: 29
- avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4
- url: https://github.com/frankie567
-- login: adriangb
+- login: chbndrhnns
count: 28
- avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4
- url: https://github.com/adriangb
+ avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
+ url: https://github.com/chbndrhnns
+- login: panla
+ count: 26
+ avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
+ url: https://github.com/panla
- login: ghandic
count: 25
avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
@@ -89,18 +101,10 @@ experts:
count: 25
avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4
url: https://github.com/dbanty
-- login: chbndrhnns
- count: 24
- avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
- url: https://github.com/chbndrhnns
- login: SirTelemak
count: 24
avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4
url: https://github.com/SirTelemak
-- login: panla
- count: 23
- avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
- url: https://github.com/panla
- login: acnebs
count: 22
avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=c27e50269f1ef8ea950cc6f0268c8ec5cebbe9c9&v=4
@@ -117,6 +121,10 @@ experts:
count: 19
avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4
url: https://github.com/retnikt
+- login: acidjunk
+ count: 18
+ avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
+ url: https://github.com/acidjunk
- login: Hultner
count: 18
avatarUrl: https://avatars.githubusercontent.com/u/2669034?u=115e53df959309898ad8dc9443fbb35fee71df07&v=4
@@ -129,26 +137,46 @@ experts:
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4
url: https://github.com/nkhitrov
+- login: harunyasar
+ count: 17
+ avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4
+ url: https://github.com/harunyasar
+- login: rafsaf
+ count: 17
+ avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=be9f06b8ced2d2b677297decc781fa8ce4f7ddbd&v=4
+ url: https://github.com/rafsaf
- login: waynerv
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
url: https://github.com/waynerv
-- login: acidjunk
- count: 15
- avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
- url: https://github.com/acidjunk
- login: dstlny
- count: 14
+ count: 16
avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4
url: https://github.com/dstlny
- login: haizaar
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/58201?u=4f1f9843d69433ca0d380d95146cfe119e5fdac4&v=4
url: https://github.com/haizaar
+- login: valentin994
+ count: 13
+ avatarUrl: https://avatars.githubusercontent.com/u/42819267?u=fdeeaa9242a59b243f8603496b00994f6951d5a2&v=4
+ url: https://github.com/valentin994
+- login: hellocoldworld
+ count: 12
+ avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4
+ url: https://github.com/hellocoldworld
- login: David-Lor
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4
url: https://github.com/David-Lor
+- login: yinziyan1206
+ count: 12
+ avatarUrl: https://avatars.githubusercontent.com/u/37829370?v=4
+ url: https://github.com/yinziyan1206
+- login: jonatasoli
+ count: 12
+ avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4
+ url: https://github.com/jonatasoli
- login: lowercase00
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/21188280?v=4
@@ -161,51 +189,35 @@ experts:
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4
url: https://github.com/juntatalor
-- login: valentin994
+- login: n8sty
count: 11
- avatarUrl: https://avatars.githubusercontent.com/u/42819267?u=fdeeaa9242a59b243f8603496b00994f6951d5a2&v=4
- url: https://github.com/valentin994
+ avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4
+ url: https://github.com/n8sty
- login: aalifadv
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/78442260?v=4
url: https://github.com/aalifadv
-- login: stefanondisponibile
- count: 10
- avatarUrl: https://avatars.githubusercontent.com/u/20441825?u=ee1e59446b98f8ec2363caeda4c17164d0d9cc7d&v=4
- url: https://github.com/stefanondisponibile
-- login: hellocoldworld
- count: 10
- avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4
- url: https://github.com/hellocoldworld
-- login: oligond
- count: 10
- avatarUrl: https://avatars.githubusercontent.com/u/2858306?u=1bb1182a5944e93624b7fb26585f22c8f7a9d76e&v=4
- url: https://github.com/oligond
last_month_active:
-- login: insomnes
- count: 10
- avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
- url: https://github.com/insomnes
-- login: raphaelauv
- count: 6
- avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
- url: https://github.com/raphaelauv
- login: jgould22
- count: 4
+ count: 15
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
url: https://github.com/jgould22
-- login: harunyasar
+- login: accelleon
+ count: 5
+ avatarUrl: https://avatars.githubusercontent.com/u/5001614?v=4
+ url: https://github.com/accelleon
+- login: jonatasoli
count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4
- url: https://github.com/harunyasar
+ avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4
+ url: https://github.com/jonatasoli
- login: Kludex
- count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
+ count: 4
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
-- login: panla
+- login: yinziyan1206
count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
- url: https://github.com/panla
+ avatarUrl: https://avatars.githubusercontent.com/u/37829370?v=4
+ url: https://github.com/yinziyan1206
top_contributors:
- login: waynerv
count: 25
@@ -219,14 +231,14 @@ top_contributors:
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
url: https://github.com/dmontagu
+- login: jaystone776
+ count: 15
+ avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
+ url: https://github.com/jaystone776
- login: euri10
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4
url: https://github.com/euri10
-- login: jaystone776
- count: 13
- avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
- url: https://github.com/jaystone776
- login: mariacamilagl
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
@@ -239,14 +251,14 @@ top_contributors:
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4
url: https://github.com/Serrones
+- login: Kludex
+ count: 8
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
+ url: https://github.com/Kludex
- login: RunningIkkyu
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=706e1ee3f248245f2d68b976d149d06fd5a2010d&v=4
url: https://github.com/RunningIkkyu
-- login: Kludex
- count: 7
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
- url: https://github.com/Kludex
- login: hard-coders
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
@@ -285,8 +297,8 @@ top_contributors:
url: https://github.com/NinaHwang
top_reviewers:
- login: Kludex
- count: 91
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
+ count: 93
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: waynerv
count: 47
@@ -301,29 +313,41 @@ top_reviewers:
avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
url: https://github.com/tokusumi
- login: ycd
- count: 44
+ count: 45
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
url: https://github.com/ycd
+- login: cikay
+ count: 41
+ avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4
+ url: https://github.com/cikay
+- login: BilalAlpaslan
+ count: 40
+ avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4
+ url: https://github.com/BilalAlpaslan
- login: AdrianDeAnda
count: 33
avatarUrl: https://avatars.githubusercontent.com/u/1024932?u=bb7f8a0d6c9de4e9d0320a9f271210206e202250&v=4
url: https://github.com/AdrianDeAnda
- login: ArcLightSlavik
count: 31
- avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4
url: https://github.com/ArcLightSlavik
+- login: cassiobotaro
+ count: 25
+ avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4
+ url: https://github.com/cassiobotaro
- login: dmontagu
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
url: https://github.com/dmontagu
-- login: cassiobotaro
- count: 22
- avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4
- url: https://github.com/cassiobotaro
- login: komtaki
count: 21
avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4
url: https://github.com/komtaki
+- login: yezz123
+ count: 19
+ avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4
+ url: https://github.com/yezz123
- login: hard-coders
count: 19
avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
@@ -332,10 +356,18 @@ top_reviewers:
count: 19
avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4
url: https://github.com/0417taehyun
+- login: zy7y
+ count: 17
+ avatarUrl: https://avatars.githubusercontent.com/u/67154681?u=5d634834cc514028ea3f9115f7030b99a1f4d5a4&v=4
+ url: https://github.com/zy7y
- login: yanever
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/21978760?v=4
url: https://github.com/yanever
+- login: lsglucas
+ count: 16
+ avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
+ url: https://github.com/lsglucas
- login: SwftAlpc
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4
@@ -356,12 +388,8 @@ top_reviewers:
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4
url: https://github.com/delhi09
-- login: lsglucas
- count: 14
- avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
- url: https://github.com/lsglucas
- login: rjNemo
- count: 13
+ count: 14
avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4
url: https://github.com/rjNemo
- login: RunningIkkyu
@@ -372,10 +400,6 @@ top_reviewers:
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4
url: https://github.com/sh0nk
-- login: yezz123
- count: 11
- avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4
- url: https://github.com/yezz123
- login: mariacamilagl
count: 10
avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
@@ -388,6 +412,10 @@ top_reviewers:
count: 10
avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4
url: https://github.com/maoyibo
+- login: solomein-sv
+ count: 10
+ avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=46acfb4aeefb1d7b9fdc5a8cbd9eb8744683c47a&v=4
+ url: https://github.com/solomein-sv
- login: graingert
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/413772?v=4
@@ -404,18 +432,26 @@ top_reviewers:
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4
url: https://github.com/bezaca
-- login: solomein-sv
- count: 9
- avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=46acfb4aeefb1d7b9fdc5a8cbd9eb8744683c47a&v=4
- url: https://github.com/solomein-sv
- login: blt232018
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/43393471?u=172b0e0391db1aa6c1706498d6dfcb003c8a4857&v=4
url: https://github.com/blt232018
+- login: rogerbrinkmann
+ count: 8
+ avatarUrl: https://avatars.githubusercontent.com/u/5690226?v=4
+ url: https://github.com/rogerbrinkmann
- login: ComicShrimp
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=b3e4d9a14d9a65d429ce62c566aef73178b7111d&v=4
url: https://github.com/ComicShrimp
+- login: NinaHwang
+ count: 8
+ avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4
+ url: https://github.com/NinaHwang
+- login: dimaqq
+ count: 8
+ avatarUrl: https://avatars.githubusercontent.com/u/662249?v=4
+ url: https://github.com/dimaqq
- login: Serrones
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4
@@ -428,10 +464,6 @@ top_reviewers:
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
url: https://github.com/raphaelauv
-- login: BilalAlpaslan
- count: 7
- avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4
- url: https://github.com/BilalAlpaslan
- login: NastasiaSaby
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/8245071?u=b3afd005f9e4bf080c219ef61a592b3a8004b764&v=4
@@ -440,6 +472,10 @@ top_reviewers:
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4
url: https://github.com/Mause
+- login: AlexandreBiguet
+ count: 7
+ avatarUrl: https://avatars.githubusercontent.com/u/1483079?u=ff926455cd4cab03c6c49441aa5dc2b21df3e266&v=4
+ url: https://github.com/AlexandreBiguet
- login: krocdort
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/34248814?v=4
@@ -448,35 +484,11 @@ top_reviewers:
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/21287303?u=b049eac3e51a4c0473c2efe66b4d28a7d8f2b572&v=4
url: https://github.com/jovicon
-- login: diogoduartec
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/31852339?u=b50fc11c531e9b77922e19edfc9e7233d4d7b92e&v=4
- url: https://github.com/diogoduartec
-- login: nimctl
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=e39b11d47188744ee07b2a1c7ce1a1bdf3c80760&v=4
- url: https://github.com/nimctl
-- login: israteneda
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/20668624?u=d7b2961d330aca65fbce5bdb26a0800a3d23ed2d&v=4
- url: https://github.com/israteneda
-- login: juntatalor
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4
- url: https://github.com/juntatalor
-- login: SnkSynthesis
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/63564282?u=0078826509dbecb2fdb543f4e881c9cd06157893&v=4
- url: https://github.com/SnkSynthesis
-- login: anthonycepeda
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=892f700c79f9732211bd5221bf16eec32356a732&v=4
- url: https://github.com/anthonycepeda
-- login: oandersonmagalhaes
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4
- url: https://github.com/oandersonmagalhaes
-- login: qysfblog
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/52229895?v=4
- url: https://github.com/qysfblog
+- login: LorhanSohaky
+ count: 6
+ avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4
+ url: https://github.com/LorhanSohaky
+- login: peidrao
+ count: 6
+ avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=88c2cb42a99e0f50cdeae3606992568184783ee5&v=4
+ url: https://github.com/peidrao
diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml
index b98e68b6..ac825193 100644
--- a/docs/en/data/sponsors.yml
+++ b/docs/en/data/sponsors.yml
@@ -5,6 +5,9 @@ gold:
- url: https://cryptapi.io/
title: "CryptAPI: Your easy to use, secure and privacy oriented payment gateway."
img: https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg
+ - url: https://classiq.link/n4s
+ title: Join the team building a new SaaS platform that will change the computing world
+ img: https://fastapi.tiangolo.com/img/sponsors/classiq.png
- url: https://www.dropbase.io/careers
title: Dropbase - seamlessly collect, clean, and centralize data.
img: https://fastapi.tiangolo.com/img/sponsors/dropbase.svg
@@ -15,10 +18,7 @@ silver:
- url: https://www.investsuite.com/jobs
title: Wealthtech jobs with FastAPI
img: https://fastapi.tiangolo.com/img/sponsors/investsuite.svg
- - url: https://www.vim.so/?utm_source=FastAPI
- title: We help you master vim with interactive exercises
- img: https://fastapi.tiangolo.com/img/sponsors/vimso.png
- - url: https://talkpython.fm/fastapi-sponsor
+ - url: https://training.talkpython.fm/fastapi-courses
title: FastAPI video courses on demand from people you trust
img: https://fastapi.tiangolo.com/img/sponsors/talkpython.png
- url: https://testdriven.io/courses/tdd-fastapi/
@@ -27,7 +27,16 @@ silver:
- url: https://github.com/deepset-ai/haystack/
title: Build powerful search from composable, open source building blocks
img: https://fastapi.tiangolo.com/img/sponsors/haystack-fastapi.svg
+ - url: https://www.udemy.com/course/fastapi-rest/
+ title: Learn FastAPI by building a complete project. Extend your knowledge on advanced web development-AWS, Payments, Emails.
+ img: https://fastapi.tiangolo.com/img/sponsors/ines-course.jpg
+ - url: https://careers.budget-insight.com/
+ title: Budget Insight is hiring!
+ img: https://fastapi.tiangolo.com/img/sponsors/budget-insight.svg
bronze:
- - url: https://calmcode.io
- title: Code. Simply. Clearly. Calmly.
- img: https://fastapi.tiangolo.com/img/sponsors/calmcode.jpg
+ - url: https://www.exoflare.com/open-source/?utm_source=FastAPI&utm_campaign=open_source
+ title: Biosecurity risk assessments made easy.
+ img: https://fastapi.tiangolo.com/img/sponsors/exoflare.png
+ - url: https://striveworks.us/careers?utm_source=fastapi&utm_medium=sponsor_banner&utm_campaign=feb_march#openings
+ title: https://striveworks.us/careers
+ img: https://fastapi.tiangolo.com/img/sponsors/striveworks.png
diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml
index 0c4e716d..1c8b0cde 100644
--- a/docs/en/data/sponsors_badge.yml
+++ b/docs/en/data/sponsors_badge.yml
@@ -2,9 +2,11 @@ logins:
- jina-ai
- deta
- investsuite
- - vimsoHQ
- mikeckennedy
- - koaning
- deepset-ai
- cryptapi
+ - Striveworks
+ - xoflare
+ - InesIvanova
- DropbaseHQ
+ - VincentParedes
diff --git a/docs/en/docs/advanced/generate-clients.md b/docs/en/docs/advanced/generate-clients.md
new file mode 100644
index 00000000..f31a248d
--- /dev/null
+++ b/docs/en/docs/advanced/generate-clients.md
@@ -0,0 +1,267 @@
+# Generate Clients
+
+As **FastAPI** is based on the OpenAPI specification, you get automatic compatibility with many tools, including the automatic API docs (provided by Swagger UI).
+
+One particular advantage that is not necessarily obvious is that you can **generate clients** (sometimes called **SDKs** ) for your API, for many different **programming languages**.
+
+## OpenAPI Client Generators
+
+There are many tools to generate clients from **OpenAPI**.
+
+A common tool is OpenAPI Generator.
+
+If you are building a **frontend**, a very interesting alternative is openapi-typescript-codegen.
+
+## Generate a TypeScript Frontend Client
+
+Let's start with a simple FastAPI application:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9-11 14-15 18 19 23"
+ {!> ../../../docs_src/generate_clients/tutorial001.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="7-9 12-13 16-17 21"
+ {!> ../../../docs_src/generate_clients/tutorial001_py39.py!}
+ ```
+
+Notice that the *path operations* define the models they use for request payload and response payload, using the models `Item` and `ResponseMessage`.
+
+### API Docs
+
+If you go to the API docs, you will see that it has the **schemas** for the data to be sent in requests and received in responses:
+
+
+
+You can see those schemas because they were declared with the models in the app.
+
+That information is available in the app's **OpenAPI schema**, and then shown in the API docs (by Swagger UI).
+
+And that same information from the models that is included in OpenAPI is what can be used to **generate the client code**.
+
+### Generate a TypeScript Client
+
+Now that we have the app with the models, we can generate the client code for the frontend.
+
+#### Install `openapi-typescript-codegen`
+
+You can install `openapi-typescript-codegen` in your frontend code with:
+
+
+
+You will also get autocompletion for the payload to send:
+
+
+
+!!! tip
+ Notice the autocompletion for `name` and `price`, that was defined in the FastAPI application, in the `Item` model.
+
+You will have inline errors for the data that you send:
+
+
+
+The response object will also have autocompletion:
+
+
+
+## FastAPI App with Tags
+
+In many cases your FastAPI app will be bigger, and you will probably use tags to separate different groups of *path operations*.
+
+For example, you could have a section for **items** and another section for **users**, and they could be separated by tags:
+
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="23 28 36"
+ {!> ../../../docs_src/generate_clients/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="21 26 34"
+ {!> ../../../docs_src/generate_clients/tutorial002_py39.py!}
+ ```
+
+### Generate a TypeScript Client with Tags
+
+If you generate a client for a FastAPI app using tags, it will normally also separate the client code based on the tags.
+
+This way you will be able to have things ordered and grouped correctly for the client code:
+
+
+
+In this case you have:
+
+* `ItemsService`
+* `UsersService`
+
+### Client Method Names
+
+Right now the generated method names like `createItemItemsPost` don't look very clean:
+
+```TypeScript
+ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
+```
+
+...that's because the client generator uses the OpenAPI internal **operation ID** for each *path operation*.
+
+OpenAPI requires that each operation ID is unique across all the *path operations*, so FastAPI uses the **function name**, the **path**, and the **HTTP method/operation** to generate that operation ID, because that way it can make sure that the operation IDs are unique.
+
+But I'll show you how to improve that next. 🤓
+
+## Custom Operation IDs and Better Method Names
+
+You can **modify** the way these operation IDs are **generated** to make them simpler and have **simpler method names** in the clients.
+
+In this case you will have to ensure that each operation ID is **unique** in some other way.
+
+For example, you could make sure that each *path operation* has a tag, and then generate the operation ID based on the **tag** and the *path operation* **name** (the function name).
+
+### Custom Generate Unique ID Function
+
+FastAPI uses a **unique ID** for each *path operation*, it is used for the **operation ID** and also for the names of any needed custom models, for requests or responses.
+
+You can customize that function. It takes an `APIRoute` and outputs a string.
+
+For example, here it is using the first tag (you will probably have only one tag) and the *path operation* name (the function name).
+
+You can then pass that custom function to **FastAPI** as the `generate_unique_id_function` parameter:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="8-9 12"
+ {!> ../../../docs_src/generate_clients/tutorial003.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="6-7 10"
+ {!> ../../../docs_src/generate_clients/tutorial003_py39.py!}
+ ```
+
+### Generate a TypeScript Client with Custom Operation IDs
+
+Now if you generate the client again, you will see that it has the improved method names:
+
+
+
+As you see, the method names now have the tag and then the function name, now they don't include information from the URL path and the HTTP operation.
+
+### Preprocess the OpenAPI Specification for the Client Generator
+
+The generated code still has some **duplicated information**.
+
+We already know that this method is related to the **items** because that word is in the `ItemsService` (taken from the tag), but we still have the tag name prefixed in the method name too. 😕
+
+We will probably still want to keep it for OpenAPI in general, as that will ensure that the operation IDs are **unique**.
+
+But for the generated client we could **modify** the OpenAPI operation IDs right before generating the clients, just to make those method names nicer and **cleaner**.
+
+We could download the OpenAPI JSON to a file `openapi.json` and then we could **remove that prefixed tag** with a script like this:
+
+```Python
+{!../../../docs_src/generate_clients/tutorial004.py!}
+```
+
+With that, the operation IDs would be renamed from things like `items-get_items` to just `get_items`, that way the client generator can generate simpler method names.
+
+### Generate a TypeScript Client with the Preprocessed OpenAPI
+
+Now as the end result is in a file `openapi.json`, you would modify the `package.json` to use that local file, for example:
+
+```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"
+ }
+}
+```
+
+After generating the new client, you would now have **clean method names**, with all the **autocompletion**, **inline errors**, etc:
+
+
+
+## Benefits
+
+When using the automatically generated clients you would **autocompletion** for:
+
+* Methods.
+* Request payloads in the body, query parameters, etc.
+* Response payloads.
+
+You would also have **inline errors** for everything.
+
+And whenever you update the backend code, and **regenerate** the frontend, it would have any new *path operations* available as methods, the old ones removed, and any other change would be reflected on the generated code. 🤓
+
+This also means that if something changed it will be **reflected** on the client code automatically. And if you **build** the client it will error out if you have any **mismatch** in the data used.
+
+So, you would **detect many errors** very early in the development cycle instead of having to wait for the errors to show up to your final users in production and then trying to debug where the problem is. ✨
diff --git a/docs/en/docs/async.md b/docs/en/docs/async.md
index 8194650f..71f2e750 100644
--- a/docs/en/docs/async.md
+++ b/docs/en/docs/async.md
@@ -116,7 +116,7 @@ The cashier 💁 gives you the number of your turn.
While you are waiting, you go with your crush 😍 and pick a table, you sit and talk with your crush 😍 for a long time (as your burgers are very fancy and take some time to prepare ✨🍔✨).
-As you are sitting on the table with your crush 😍, while you wait for the burgers 🍔, you can spend that time admiring how awesome, cute and smart your crush is ✨😍✨.
+As you are sitting at the table with your crush 😍, while you wait for the burgers 🍔, you can spend that time admiring how awesome, cute and smart your crush is ✨😍✨.
While waiting and talking to your crush 😍, from time to time, you check the number displayed on the counter to see if it's your turn already.
@@ -134,7 +134,7 @@ Then, when it's your turn, you do actual "productive" work 🤓, you process the
But then, even though you still don't have your burgers 🍔, your work with the cashier 💁 is "on pause" ⏸, because you have to wait 🕙 for your burgers to be ready.
-But as you go away from the counter and sit on the table with a number for your turn, you can switch 🔀 your attention to your crush 😍, and "work" ⏯ 🤓 on that. Then you are again doing something very "productive" 🤓, as is flirting with your crush 😍.
+But as you go away from the counter and sit at the table with a number for your turn, you can switch 🔀 your attention to your crush 😍, and "work" ⏯ 🤓 on that. Then you are again doing something very "productive" 🤓, as is flirting with your crush 😍.
Then the cashier 💁 says "I'm finished with doing the burgers" 🍔 by putting your number on the counter's display, but you don't jump like crazy immediately when the displayed number changes to your turn number. You know no one will steal your burgers 🍔 because you have the number of your turn, and they have theirs.
diff --git a/docs/en/docs/deployment/manually.md b/docs/en/docs/deployment/manually.md
index 7fd1f4d4..16286533 100644
--- a/docs/en/docs/deployment/manually.md
+++ b/docs/en/docs/deployment/manually.md
@@ -59,7 +59,7 @@ You can install an ASGI compatible server with:
## Run the Server Program
-You can then your application the same way you have done in the tutorials, but without the `--reload` option, e.g.:
+You can then run your application the same way you have done in the tutorials, but without the `--reload` option, e.g.:
=== "Uvicorn"
diff --git a/docs/en/docs/img/sponsors/budget-insight.svg b/docs/en/docs/img/sponsors/budget-insight.svg
new file mode 100644
index 00000000..d753727a
--- /dev/null
+++ b/docs/en/docs/img/sponsors/budget-insight.svg
@@ -0,0 +1,22 @@
+
diff --git a/docs/en/docs/img/sponsors/classiq-banner.png b/docs/en/docs/img/sponsors/classiq-banner.png
new file mode 100644
index 00000000..8a0d6ac7
Binary files /dev/null and b/docs/en/docs/img/sponsors/classiq-banner.png differ
diff --git a/docs/en/docs/img/sponsors/classiq.png b/docs/en/docs/img/sponsors/classiq.png
new file mode 100644
index 00000000..189c6bbe
Binary files /dev/null and b/docs/en/docs/img/sponsors/classiq.png differ
diff --git a/docs/en/docs/img/sponsors/exoflare.png b/docs/en/docs/img/sponsors/exoflare.png
new file mode 100644
index 00000000..b5977d80
Binary files /dev/null and b/docs/en/docs/img/sponsors/exoflare.png differ
diff --git a/docs/en/docs/img/sponsors/fastapi-course-bundle-banner.png b/docs/en/docs/img/sponsors/fastapi-course-bundle-banner.png
new file mode 100644
index 00000000..220d0863
Binary files /dev/null and b/docs/en/docs/img/sponsors/fastapi-course-bundle-banner.png differ
diff --git a/docs/en/docs/img/sponsors/ines-course.jpg b/docs/en/docs/img/sponsors/ines-course.jpg
new file mode 100644
index 00000000..05158b38
Binary files /dev/null and b/docs/en/docs/img/sponsors/ines-course.jpg differ
diff --git a/docs/en/docs/img/sponsors/striveworks-banner.png b/docs/en/docs/img/sponsors/striveworks-banner.png
new file mode 100644
index 00000000..5206744b
Binary files /dev/null and b/docs/en/docs/img/sponsors/striveworks-banner.png differ
diff --git a/docs/en/docs/img/sponsors/striveworks.png b/docs/en/docs/img/sponsors/striveworks.png
new file mode 100644
index 00000000..435ac536
Binary files /dev/null and b/docs/en/docs/img/sponsors/striveworks.png differ
diff --git a/docs/en/docs/img/tutorial/generate-clients/image01.png b/docs/en/docs/img/tutorial/generate-clients/image01.png
new file mode 100644
index 00000000..f23d5777
Binary files /dev/null and b/docs/en/docs/img/tutorial/generate-clients/image01.png differ
diff --git a/docs/en/docs/img/tutorial/generate-clients/image02.png b/docs/en/docs/img/tutorial/generate-clients/image02.png
new file mode 100644
index 00000000..f991352e
Binary files /dev/null and b/docs/en/docs/img/tutorial/generate-clients/image02.png differ
diff --git a/docs/en/docs/img/tutorial/generate-clients/image03.png b/docs/en/docs/img/tutorial/generate-clients/image03.png
new file mode 100644
index 00000000..e2514b04
Binary files /dev/null and b/docs/en/docs/img/tutorial/generate-clients/image03.png differ
diff --git a/docs/en/docs/img/tutorial/generate-clients/image04.png b/docs/en/docs/img/tutorial/generate-clients/image04.png
new file mode 100644
index 00000000..777a695b
Binary files /dev/null and b/docs/en/docs/img/tutorial/generate-clients/image04.png differ
diff --git a/docs/en/docs/img/tutorial/generate-clients/image05.png b/docs/en/docs/img/tutorial/generate-clients/image05.png
new file mode 100644
index 00000000..e1e53179
Binary files /dev/null and b/docs/en/docs/img/tutorial/generate-clients/image05.png differ
diff --git a/docs/en/docs/img/tutorial/generate-clients/image06.png b/docs/en/docs/img/tutorial/generate-clients/image06.png
new file mode 100644
index 00000000..0e9a100e
Binary files /dev/null and b/docs/en/docs/img/tutorial/generate-clients/image06.png differ
diff --git a/docs/en/docs/img/tutorial/generate-clients/image07.png b/docs/en/docs/img/tutorial/generate-clients/image07.png
new file mode 100644
index 00000000..27884960
Binary files /dev/null and b/docs/en/docs/img/tutorial/generate-clients/image07.png differ
diff --git a/docs/en/docs/img/tutorial/generate-clients/image08.png b/docs/en/docs/img/tutorial/generate-clients/image08.png
new file mode 100644
index 00000000..509fbd34
Binary files /dev/null and b/docs/en/docs/img/tutorial/generate-clients/image08.png differ
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 68b75e70..4d8bacf4 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -2,6 +2,198 @@
## Latest Changes
+
+## 0.77.1
+
+### Upgrades
+
+* ⬆ Upgrade Starlette from 0.19.0 to 0.19.1. PR [#4819](https://github.com/tiangolo/fastapi/pull/4819) by [@Kludex](https://github.com/Kludex).
+
+### Docs
+
+* 📝 Add link to german article: REST-API Programmieren mittels Python und dem FastAPI Modul. PR [#4624](https://github.com/tiangolo/fastapi/pull/4624) by [@fschuermeyer](https://github.com/fschuermeyer).
+* 📝 Add external link: PyCharm Guide to FastAPI. PR [#4512](https://github.com/tiangolo/fastapi/pull/4512) by [@mukulmantosh](https://github.com/mukulmantosh).
+* 📝 Add external link to article: Building an API with FastAPI and Supabase and Deploying on Deta. PR [#4440](https://github.com/tiangolo/fastapi/pull/4440) by [@aUnicornDev](https://github.com/aUnicornDev).
+* ✏ Fix small typo in `docs/en/docs/tutorial/security/first-steps.md`. PR [#4515](https://github.com/tiangolo/fastapi/pull/4515) by [@KikoIlievski](https://github.com/KikoIlievski).
+
+### Translations
+
+* 🌐 Add Polish translation for `docs/pl/docs/tutorial/index.md`. PR [#4516](https://github.com/tiangolo/fastapi/pull/4516) by [@MKaczkow](https://github.com/MKaczkow).
+* ✏ Fix typo in deployment. PR [#4629](https://github.com/tiangolo/fastapi/pull/4629) by [@raisulislam541](https://github.com/raisulislam541).
+* 🌐 Add Portuguese translation for `docs/pt/docs/help-fastapi.md`. PR [#4583](https://github.com/tiangolo/fastapi/pull/4583) by [@mateusjs](https://github.com/mateusjs).
+
+### Internal
+
+* 🔧 Add notifications in issue for Uzbek translations. PR [#4884](https://github.com/tiangolo/fastapi/pull/4884) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.77.0
+
+### Upgrades
+
+* ⬆ Upgrade Starlette from 0.18.0 to 0.19.0. PR [#4488](https://github.com/tiangolo/fastapi/pull/4488) by [@Kludex](https://github.com/Kludex).
+ * When creating an explicit `JSONResponse` the `content` argument is now required.
+
+### Docs
+
+* 📝 Add external link to article: Seamless FastAPI Configuration with ConfZ. PR [#4414](https://github.com/tiangolo/fastapi/pull/4414) by [@silvanmelchior](https://github.com/silvanmelchior).
+* 📝 Add external link to article: 5 Advanced Features of FastAPI You Should Try. PR [#4436](https://github.com/tiangolo/fastapi/pull/4436) by [@kaustubhgupta](https://github.com/kaustubhgupta).
+* ✏ Reword to improve legibility of docs about `TestClient`. PR [#4389](https://github.com/tiangolo/fastapi/pull/4389) by [@rgilton](https://github.com/rgilton).
+* 📝 Add external link to blog post about Kafka, FastAPI, and Ably. PR [#4044](https://github.com/tiangolo/fastapi/pull/4044) by [@Ugbot](https://github.com/Ugbot).
+* ✏ Fix typo in `docs/en/docs/tutorial/sql-databases.md`. PR [#4875](https://github.com/tiangolo/fastapi/pull/4875) by [@wpyoga](https://github.com/wpyoga).
+* ✏ Fix typo in `docs/en/docs/async.md`. PR [#4726](https://github.com/tiangolo/fastapi/pull/4726) by [@Prezu](https://github.com/Prezu).
+
+### Translations
+
+* 🌐 Update source example highlights for `docs/zh/docs/tutorial/query-params-str-validations.md`. PR [#4237](https://github.com/tiangolo/fastapi/pull/4237) by [@caimaoy](https://github.com/caimaoy).
+* 🌐 Remove translation docs references to aiofiles as it's no longer needed since AnyIO. PR [#3594](https://github.com/tiangolo/fastapi/pull/3594) by [@alonme](https://github.com/alonme).
+* ✏ 🌐 Fix typo in Portuguese translation for `docs/pt/docs/tutorial/path-params.md`. PR [#4722](https://github.com/tiangolo/fastapi/pull/4722) by [@CleoMenezesJr](https://github.com/CleoMenezesJr).
+* 🌐 Fix live docs server for translations for some languages. PR [#4729](https://github.com/tiangolo/fastapi/pull/4729) by [@wakabame](https://github.com/wakabame).
+* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/cookie-params.md`. PR [#4112](https://github.com/tiangolo/fastapi/pull/4112) by [@lbmendes](https://github.com/lbmendes).
+* 🌐 Fix French translation for `docs/tutorial/body.md`. PR [#4332](https://github.com/tiangolo/fastapi/pull/4332) by [@Smlep](https://github.com/Smlep).
+* 🌐 Add Japanese translation for `docs/ja/docs/advanced/conditional-openapi.md`. PR [#2631](https://github.com/tiangolo/fastapi/pull/2631) by [@sh0nk](https://github.com/sh0nk).
+* 🌐 Fix Japanese translation of `docs/ja/docs/tutorial/body.md`. PR [#3062](https://github.com/tiangolo/fastapi/pull/3062) by [@a-takahashi223](https://github.com/a-takahashi223).
+* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/background-tasks.md`. PR [#2170](https://github.com/tiangolo/fastapi/pull/2170) by [@izaguerreiro](https://github.com/izaguerreiro).
+* 🌐 Add Portuguese translation for `docs/deployment/deta.md`. PR [#4442](https://github.com/tiangolo/fastapi/pull/4442) by [@lsglucas](https://github.com/lsglucas).
+* 🌐 Add Russian translation for `docs/async.md`. PR [#4036](https://github.com/tiangolo/fastapi/pull/4036) by [@Winand](https://github.com/Winand).
+* 🌐 Add Portuguese translation for `docs/tutorial/body.md`. PR [#3960](https://github.com/tiangolo/fastapi/pull/3960) by [@leandrodesouzadev](https://github.com/leandrodesouzadev).
+* 🌐 Add Portuguese translation of `tutorial/extra-data-types.md`. PR [#4077](https://github.com/tiangolo/fastapi/pull/4077) by [@luccasmmg](https://github.com/luccasmmg).
+* 🌐 Update German translation for `docs/features.md`. PR [#3905](https://github.com/tiangolo/fastapi/pull/3905) by [@jomue](https://github.com/jomue).
+
+## 0.76.0
+
+### Upgrades
+
+* ⬆ Upgrade Starlette from 0.17.1 to 0.18.0. PR [#4483](https://github.com/tiangolo/fastapi/pull/4483) by [@Kludex](https://github.com/Kludex).
+
+### Internal
+
+* 👥 Update FastAPI People. PR [#4847](https://github.com/tiangolo/fastapi/pull/4847) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* 🔧 Add Budget Insight sponsor. PR [#4824](https://github.com/tiangolo/fastapi/pull/4824) by [@tiangolo](https://github.com/tiangolo).
+* 🍱 Update sponsor, ExoFlare badge. PR [#4822](https://github.com/tiangolo/fastapi/pull/4822) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Update sponsors, enable Dropbase again, update TalkPython link. PR [#4821](https://github.com/tiangolo/fastapi/pull/4821) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.75.2
+
+This release includes upgrades to third-party packages that handle security issues. Although there's a chance these issues don't affect you in particular, please upgrade as soon as possible.
+
+### Fixes
+
+* ✅ Fix new/recent tests with new fixed `ValidationError` JSON Schema. PR [#4806](https://github.com/tiangolo/fastapi/pull/4806) by [@tiangolo](https://github.com/tiangolo).
+* 🐛 Fix JSON Schema for `ValidationError` at field `loc`. PR [#3810](https://github.com/tiangolo/fastapi/pull/3810) by [@dconathan](https://github.com/dconathan).
+* 🐛 Fix support for prefix on APIRouter WebSockets. PR [#2640](https://github.com/tiangolo/fastapi/pull/2640) by [@Kludex](https://github.com/Kludex).
+
+### Upgrades
+
+* ⬆️ Update ujson ranges for CVE-2021-45958. PR [#4804](https://github.com/tiangolo/fastapi/pull/4804) by [@tiangolo](https://github.com/tiangolo).
+* ⬆️ Upgrade dependencies upper range for extras "all". PR [#4803](https://github.com/tiangolo/fastapi/pull/4803) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Upgrade Swagger UI - swagger-ui-dist@4. This handles a security issue in Swagger UI itself where it could be possible to inject HTML into Swagger UI. Please upgrade as soon as you can, in particular if you expose your Swagger UI (`/docs`) publicly to non-expert users. PR [#4347](https://github.com/tiangolo/fastapi/pull/4347) by [@RAlanWright](https://github.com/RAlanWright).
+
+### Internal
+
+* 🔧 Update sponsors, add: ExoFlare, Ines Course; remove: Dropbase, Vim.so, Calmcode; update: Striveworks, TalkPython and TestDriven.io. PR [#4805](https://github.com/tiangolo/fastapi/pull/4805) by [@tiangolo](https://github.com/tiangolo).
+* ⬆️ Upgrade Codecov GitHub Action. PR [#4801](https://github.com/tiangolo/fastapi/pull/4801) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.75.1
+
+### Translations
+
+* 🌐 Start Dutch translations. PR [#4703](https://github.com/tiangolo/fastapi/pull/4703) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Start Persian/Farsi translations. PR [#4243](https://github.com/tiangolo/fastapi/pull/4243) by [@aminalaee](https://github.com/aminalaee).
+* ✏ Reword sentence about handling errors. PR [#1993](https://github.com/tiangolo/fastapi/pull/1993) by [@khuhroproeza](https://github.com/khuhroproeza).
+
+### Internal
+
+* 👥 Update FastAPI People. PR [#4752](https://github.com/tiangolo/fastapi/pull/4752) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* ➖ Temporarily remove typer-cli from dependencies and upgrade Black to unblock Pydantic CI. PR [#4754](https://github.com/tiangolo/fastapi/pull/4754) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add configuration to notify Dutch translations. PR [#4702](https://github.com/tiangolo/fastapi/pull/4702) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI People. PR [#4699](https://github.com/tiangolo/fastapi/pull/4699) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* 🐛 Fix FastAPI People generation to include missing file in commit. PR [#4695](https://github.com/tiangolo/fastapi/pull/4695) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Update Classiq sponsor links. PR [#4688](https://github.com/tiangolo/fastapi/pull/4688) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add Classiq sponsor. PR [#4671](https://github.com/tiangolo/fastapi/pull/4671) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Add Jina's QA Bot to the docs to help people that want to ask quick questions. PR [#4655](https://github.com/tiangolo/fastapi/pull/4655) by [@tiangolo](https://github.com/tiangolo) based on original PR [#4626](https://github.com/tiangolo/fastapi/pull/4626) by [@hanxiao](https://github.com/hanxiao).
+
+## 0.75.0
+
+### Features
+
+* ✨ Add support for custom `generate_unique_id_function` and docs for generating clients. New docs: [Advanced - Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/). PR [#4650](https://github.com/tiangolo/fastapi/pull/4650) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.74.1
+
+### Features
+
+* ✨ Include route in scope to allow middleware and other tools to extract its information. PR [#4603](https://github.com/tiangolo/fastapi/pull/4603) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.74.0
+
+### Breaking Changes
+
+* ✨ Update internal `AsyncExitStack` to fix context for dependencies with `yield`. PR [#4575](https://github.com/tiangolo/fastapi/pull/4575) by [@tiangolo](https://github.com/tiangolo).
+
+Dependencies with `yield` can now catch `HTTPException` and custom exceptions. For example:
+
+```Python
+async def get_database():
+ with Session() as session:
+ try:
+ yield session
+ except HTTPException:
+ session.rollback()
+ raise
+ finally:
+ session.close()
+```
+
+After the dependency with `yield` handles the exception (or not) the exception is raised again. So that any exception handlers can catch it, or ultimately the default internal `ServerErrorMiddleware`.
+
+If you depended on exceptions not being received by dependencies with `yield`, and receiving an exception breaks the code after `yield`, you can use a block with `try` and `finally`:
+
+```Python
+async def do_something():
+ try:
+ yield something
+ finally:
+ some_cleanup()
+```
+
+...that way the `finally` block is run regardless of any exception that might happen.
+
+### Features
+
+* The same PR [#4575](https://github.com/tiangolo/fastapi/pull/4575) from above also fixes the `contextvars` context for the code before and after `yield`. This was the main objective of that PR.
+
+This means that now, if you set a value in a context variable before `yield`, the value would still be available after `yield` (as you would intuitively expect). And it also means that you can reset the context variable with a token afterwards.
+
+For example, this works correctly now:
+
+```Python
+from contextvars import ContextVar
+from typing import Any, Dict, Optional
+
+
+legacy_request_state_context_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
+ "legacy_request_state_context_var", default=None
+)
+
+async def set_up_request_state_dependency():
+ request_state = {"user": "deadpond"}
+ contextvar_token = legacy_request_state_context_var.set(request_state)
+ yield request_state
+ legacy_request_state_context_var.reset(contextvar_token)
+```
+
+...before this change it would raise an error when resetting the context variable, because the `contextvars` context was different, because of the way it was implemented.
+
+**Note**: You probably don't need `contextvars`, and you should probably avoid using them. But they are powerful and useful in some advanced scenarios, for example, migrating from code that used Flask's `g` semi-global variable.
+
+**Technical Details**: If you want to know more of the technical details you can check out the PR description [#4575](https://github.com/tiangolo/fastapi/pull/4575).
+
+### Internal
+
+* 🔧 Add Striveworks sponsor. PR [#4596](https://github.com/tiangolo/fastapi/pull/4596) by [@tiangolo](https://github.com/tiangolo).
+* 💚 Only build docs on push when on master to avoid duplicate runs from PRs. PR [#4564](https://github.com/tiangolo/fastapi/pull/4564) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI People. PR [#4502](https://github.com/tiangolo/fastapi/pull/4502) by [@github-actions[bot]](https://github.com/apps/github-actions).
+
## 0.73.0
### Features
diff --git a/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md
index 82553afa..ac2e9cb8 100644
--- a/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md
+++ b/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md
@@ -99,7 +99,7 @@ You saw that you can use dependencies with `yield` and have `try` blocks that ca
It might be tempting to raise an `HTTPException` or similar in the exit code, after the `yield`. But **it won't work**.
-The exit code in dependencies with `yield` is executed *after* [Exception Handlers](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. There's nothing catching exceptions thrown by your dependencies in the exit code (after the `yield`).
+The exit code in dependencies with `yield` is executed *after* the response is sent, so [Exception Handlers](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} will have already run. There's nothing catching exceptions thrown by your dependencies in the exit code (after the `yield`).
So, if you raise an `HTTPException` after the `yield`, the default (or any custom) exception handler that catches `HTTPException`s and returns an HTTP 400 response won't be there to catch that exception anymore.
@@ -138,9 +138,11 @@ participant tasks as Background tasks
end
dep ->> operation: Run dependency, e.g. DB session
opt raise
- operation -->> handler: Raise HTTPException
+ operation -->> dep: Raise HTTPException
+ dep -->> handler: Auto forward exception
handler -->> client: HTTP error response
operation -->> dep: Raise other exception
+ dep -->> handler: Auto forward exception
end
operation ->> client: Return response to client
Note over client,operation: Response is already sent, can't change it anymore
@@ -162,9 +164,9 @@ participant tasks as Background tasks
After one of those responses is sent, no other response can be sent.
!!! tip
- This diagram shows `HTTPException`, but you could also raise any other exception for which you create a [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. And that exception would be handled by that custom exception handler instead of the dependency exit code.
+ This diagram shows `HTTPException`, but you could also raise any other exception for which you create a [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
- But if you raise an exception that is not handled by the exception handlers, it will be handled by the exit code of the dependency.
+ If you raise any exception, it will be passed to the dependencies with yield, including `HTTPException`, and then **again** to the exception handlers. If there's no exception handler for that exception, it will then be handled by the default internal `ServerErrorMiddleware`, returning a 500 HTTP status code, to let the client know that there was an error in the server.
## Context Managers
diff --git a/docs/en/docs/tutorial/handling-errors.md b/docs/en/docs/tutorial/handling-errors.md
index 89f96176..82e16626 100644
--- a/docs/en/docs/tutorial/handling-errors.md
+++ b/docs/en/docs/tutorial/handling-errors.md
@@ -252,9 +252,7 @@ from starlette.exceptions import HTTPException as StarletteHTTPException
### Re-use **FastAPI**'s exception handlers
-You could also just want to use the exception somehow, but then use the same default exception handlers from **FastAPI**.
-
-You can import and re-use the default exception handlers from `fastapi.exception_handlers`:
+If you want to use the exception along with the same default exception handlers from **FastAPI**, You can import and re-use the default exception handlers from `fastapi.exception_handlers`:
```Python hl_lines="2-5 15 21"
{!../../../docs_src/handling_errors/tutorial006.py!}
diff --git a/docs/en/docs/tutorial/security/first-steps.md b/docs/en/docs/tutorial/security/first-steps.md
index 360d85ae..45ffaab9 100644
--- a/docs/en/docs/tutorial/security/first-steps.md
+++ b/docs/en/docs/tutorial/security/first-steps.md
@@ -121,7 +121,7 @@ When we create an instance of the `OAuth2PasswordBearer` class we pass in the `t
```
!!! tip
- here `tokenUrl="token"` refers to a relative URL `token` that we haven't created yet. As it's a relative URL, it's equivalent to `./token`.
+ Here `tokenUrl="token"` refers to a relative URL `token` that we haven't created yet. As it's a relative URL, it's equivalent to `./token`.
Because we are using a relative URL, if your API was located at `https://example.com/`, then it would refer to `https://example.com/token`. But if your API was located at `https://example.com/api/v1/`, then it would refer to `https://example.com/api/v1/token`.
diff --git a/docs/en/docs/tutorial/sql-databases.md b/docs/en/docs/tutorial/sql-databases.md
index 9dc2f64c..60c7fb06 100644
--- a/docs/en/docs/tutorial/sql-databases.md
+++ b/docs/en/docs/tutorial/sql-databases.md
@@ -491,7 +491,7 @@ You can find an example of Alembic in a FastAPI project in the templates from [P
### Create a dependency
-Now use the `SessionLocal` class we created in the `sql_app/databases.py` file to create a dependency.
+Now use the `SessionLocal` class we created in the `sql_app/database.py` file to create a dependency.
We need to have an independent database session/connection (`SessionLocal`) per request, use the same session through all the request and then close it after the request is finished.
diff --git a/docs/en/docs/tutorial/testing.md b/docs/en/docs/tutorial/testing.md
index 7e2ae84d..fea5a54f 100644
--- a/docs/en/docs/tutorial/testing.md
+++ b/docs/en/docs/tutorial/testing.md
@@ -10,7 +10,7 @@ With it, you can use
{% endblock %}
+{%- block scripts %}
+{{ super() }}
+
+
+
+requests - Requerido si quieres usar el `TestClient`.
-* aiofiles - Requerido si quieres usar `FileResponse` o `StaticFiles`.
* jinja2 - Requerido si quieres usar la configuración por defecto de templates.
* python-multipart - Requerido si quieres dar soporte a "parsing" de formularios, con `request.form()`.
* itsdangerous - Requerido para dar soporte a `SessionMiddleware`.
diff --git a/docs/es/mkdocs.yml b/docs/es/mkdocs.yml
index a4bc4115..eb7538cf 100644
--- a/docs/es/mkdocs.yml
+++ b/docs/es/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -107,6 +109,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -117,6 +121,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs/fa/docs/index.md b/docs/fa/docs/index.md
new file mode 100644
index 00000000..0070de17
--- /dev/null
+++ b/docs/fa/docs/index.md
@@ -0,0 +1,468 @@
+
+{!../../../docs/missing-translation.md!}
+
+
+
++ FastAPI framework, high performance, easy to learn, fast to code, ready for production +
+ + +--- + +**Documentation**: https://fastapi.tiangolo.com + +**Source Code**: https://github.com/tiangolo/fastapi + +--- + +FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. + +The key features are: + +* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). + +* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * +* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Easy**: Designed to be easy to use and learn. Less time reading docs. +* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. +* **Robust**: Get production-ready code. With automatic interactive documentation. +* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. + +* estimation based on tests on an internal development team, building production applications. + +## Sponsors + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} +async def...uvicorn main:app --reload...ujson - for faster JSON "parsing".
+* email_validator - for email validation.
+
+Used by Starlette:
+
+* requests - Required if you want to use the `TestClient`.
+* jinja2 - Required if you want to use the default template configuration.
+* python-multipart - Required if you want to support form "parsing", with `request.form()`.
+* itsdangerous - Required for `SessionMiddleware` support.
+* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
+* ujson - Required if you want to use `UJSONResponse`.
+
+Used by FastAPI / Starlette:
+
+* uvicorn - for the server that loads and serves your application.
+* orjson - Required if you want to use `ORJSONResponse`.
+
+You can install all of these with `pip install "fastapi[all]"`.
+
+## License
+
+This project is licensed under the terms of the MIT license.
diff --git a/docs/fa/mkdocs.yml b/docs/fa/mkdocs.yml
new file mode 100644
index 00000000..6fb3891b
--- /dev/null
+++ b/docs/fa/mkdocs.yml
@@ -0,0 +1,135 @@
+site_name: FastAPI
+site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production
+site_url: https://fastapi.tiangolo.com/fa/
+theme:
+ name: material
+ custom_dir: overrides
+ palette:
+ - scheme: default
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb
+ name: Switch to light mode
+ - scheme: slate
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb-outline
+ name: Switch to dark mode
+ features:
+ - search.suggest
+ - search.highlight
+ - content.tabs.link
+ icon:
+ repo: fontawesome/brands/github-alt
+ logo: https://fastapi.tiangolo.com/img/icon-white.svg
+ favicon: https://fastapi.tiangolo.com/img/favicon.png
+ language: fa
+repo_name: tiangolo/fastapi
+repo_url: https://github.com/tiangolo/fastapi
+edit_uri: ''
+plugins:
+- search
+- markdownextradata:
+ data: data
+nav:
+- FastAPI: index.md
+- Languages:
+ - en: /
+ - az: /az/
+ - de: /de/
+ - es: /es/
+ - fa: /fa/
+ - fr: /fr/
+ - id: /id/
+ - it: /it/
+ - ja: /ja/
+ - ko: /ko/
+ - nl: /nl/
+ - pl: /pl/
+ - pt: /pt/
+ - ru: /ru/
+ - sq: /sq/
+ - tr: /tr/
+ - uk: /uk/
+ - zh: /zh/
+markdown_extensions:
+- toc:
+ permalink: true
+- markdown.extensions.codehilite:
+ guess_lang: false
+- mdx_include:
+ base_path: docs
+- admonition
+- codehilite
+- extra
+- pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
+extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
+ social:
+ - icon: fontawesome/brands/github-alt
+ link: https://github.com/tiangolo/fastapi
+ - icon: fontawesome/brands/discord
+ link: https://discord.gg/VQjSZaeJmf
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/fastapi
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/tiangolo
+ - icon: fontawesome/brands/dev
+ link: https://dev.to/tiangolo
+ - icon: fontawesome/brands/medium
+ link: https://medium.com/@tiangolo
+ - icon: fontawesome/solid/globe
+ link: https://tiangolo.com
+ alternate:
+ - link: /
+ name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
+ - link: /es/
+ name: es - español
+ - link: /fa/
+ name: fa
+ - link: /fr/
+ name: fr - français
+ - link: /id/
+ name: id
+ - link: /it/
+ name: it - italiano
+ - link: /ja/
+ name: ja - 日本語
+ - link: /ko/
+ name: ko - 한국어
+ - link: /nl/
+ name: nl
+ - link: /pl/
+ name: pl
+ - link: /pt/
+ name: pt - português
+ - link: /ru/
+ name: ru - русский язык
+ - link: /sq/
+ name: sq - shqip
+ - link: /tr/
+ name: tr - Türkçe
+ - link: /uk/
+ name: uk - українська мова
+ - link: /zh/
+ name: zh - 汉语
+extra_css:
+- https://fastapi.tiangolo.com/css/termynal.css
+- https://fastapi.tiangolo.com/css/custom.css
+extra_javascript:
+- https://fastapi.tiangolo.com/js/termynal.js
+- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/fa/overrides/.gitignore b/docs/fa/overrides/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/fr/docs/fastapi-people.md b/docs/fr/docs/fastapi-people.md
index 9ec2718c..945f0794 100644
--- a/docs/fr/docs/fastapi-people.md
+++ b/docs/fr/docs/fastapi-people.md
@@ -114,6 +114,8 @@ Ce sont les **Sponsors**. 😎
Ils soutiennent mon travail avec **FastAPI** (et d'autres) avec GitHub Sponsors.
+{% if sponsors %}
+
{% if sponsors.gold %}
### Gold Sponsors
@@ -141,6 +143,7 @@ Ils soutiennent mon travail avec **FastAPI** (et d'autres) avec requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/fr/docs/tutorial/body.md b/docs/fr/docs/tutorial/body.md
index c0953f49..30458449 100644
--- a/docs/fr/docs/tutorial/body.md
+++ b/docs/fr/docs/tutorial/body.md
@@ -111,7 +111,7 @@ Mais vous auriez le même support de l'éditeur avec
!!! tip "Astuce"
- Si vous utilisez PyCharm comme éditeur, vous pouvez utiliser le Plugin PyCharm.
+ Si vous utilisez PyCharm comme éditeur, vous pouvez utiliser le Plugin Pydantic PyCharm Plugin.
Ce qui améliore le support pour les modèles Pydantic avec :
diff --git a/docs/fr/mkdocs.yml b/docs/fr/mkdocs.yml
index 02de2086..5960f675 100644
--- a/docs/fr/mkdocs.yml
+++ b/docs/fr/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -113,6 +115,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -123,6 +127,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs/id/docs/index.md b/docs/id/docs/index.md
index 95fb7ae2..a7af1478 100644
--- a/docs/id/docs/index.md
+++ b/docs/id/docs/index.md
@@ -447,7 +447,6 @@ Used by Pydantic:
Used by Starlette:
* requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/id/mkdocs.yml b/docs/id/mkdocs.yml
index d70d2b3c..0c60fecd 100644
--- a/docs/id/mkdocs.yml
+++ b/docs/id/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -97,6 +99,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -107,6 +111,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs/it/docs/index.md b/docs/it/docs/index.md
index c52f07e5..e9e42856 100644
--- a/docs/it/docs/index.md
+++ b/docs/it/docs/index.md
@@ -444,7 +444,6 @@ Used by Pydantic:
Used by Starlette:
* requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/it/mkdocs.yml b/docs/it/mkdocs.yml
index e6d01fbd..3bf3d739 100644
--- a/docs/it/mkdocs.yml
+++ b/docs/it/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -97,6 +99,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -107,6 +111,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs/ja/docs/advanced/conditional-openapi.md b/docs/ja/docs/advanced/conditional-openapi.md
new file mode 100644
index 00000000..b892ed6c
--- /dev/null
+++ b/docs/ja/docs/advanced/conditional-openapi.md
@@ -0,0 +1,58 @@
+# 条件付き OpenAPI
+
+必要であれば、設定と環境変数を利用して、環境に応じて条件付きでOpenAPIを構成することが可能です。また、完全にOpenAPIを無効にすることもできます。
+
+## セキュリティとAPI、およびドキュメントについて
+
+本番環境においてドキュメントのUIを非表示にすることによって、APIを保護しようと *すべきではありません*。
+
+それは、APIのセキュリティの強化にはならず、*path operations* は依然として利用可能です。
+
+もしセキュリティ上の欠陥がソースコードにあるならば、それは存在したままです。
+
+ドキュメンテーションを非表示にするのは、単にあなたのAPIへのアクセス方法を難解にするだけでなく、同時にあなた自身の本番環境でのAPIのデバッグを困難にしてしまう可能性があります。単純に、 Security through obscurity の一つの形態として考えられるでしょう。
+
+もしあなたのAPIのセキュリティを強化したいなら、いくつかのよりよい方法があります。例を示すと、
+
+* リクエストボディとレスポンスのためのPydanticモデルの定義を見直す。
+* 依存関係に基づきすべての必要なパーミッションとロールを設定する。
+* パスワードを絶対に平文で保存しない。パスワードハッシュのみを保存する。
+* PasslibやJWTトークンに代表される、よく知られた暗号化ツールを使って実装する。
+* そして必要なところでは、もっと細かいパーミッション制御をOAuth2スコープを使って行う。
+* など
+
+それでも、例えば本番環境のような特定の環境のみで、あるいは環境変数の設定によってAPIドキュメントをどうしても無効にしたいという、非常に特殊なユースケースがあるかもしれません。
+
+## 設定と環境変数による条件付き OpenAPI
+
+生成するOpenAPIとドキュメントUIの構成は、共通のPydanticの設定を使用して簡単に切り替えられます。
+
+例えば、
+
+```Python hl_lines="6 11"
+{!../../../docs_src/conditional_openapi/tutorial001.py!}
+```
+
+ここでは `openapi_url` の設定を、デフォルトの `"/openapi.json"` のまま宣言しています。
+
+そして、これを `FastAPI` appを作る際に使います。
+
+それから、以下のように `OPENAPI_URL` という環境変数を空文字列に設定することによってOpenAPI (UIドキュメントを含む) を無効化することができます。
+
+requests - `TestClient`を使用するために必要です。
-- aiofiles - `FileResponse` または `StaticFiles`を使用したい場合は必要です。
- jinja2 - デフォルトのテンプレート設定を使用する場合は必要です。
- python-multipart - "parsing"`request.form()`からの変換をサポートしたい場合は必要です。
- itsdangerous - `SessionMiddleware` サポートのためには必要です。
diff --git a/docs/ja/docs/tutorial/body.md b/docs/ja/docs/tutorial/body.md
index 9367ea25..59388d90 100644
--- a/docs/ja/docs/tutorial/body.md
+++ b/docs/ja/docs/tutorial/body.md
@@ -162,4 +162,4 @@ APIはほとんどの場合 **レスポンス** ボディを送らなければ
## Pydanticを使わない方法
-もしPydanticモデルを使用したくない場合は、**ボディ**パラメータが利用できます。[Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}を確認してください。
+もしPydanticモデルを使用したくない場合は、**Body**パラメータが利用できます。[Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}を確認してください。
diff --git a/docs/ja/docs/tutorial/static-files.md b/docs/ja/docs/tutorial/static-files.md
index fcc3ba92..1d9c434c 100644
--- a/docs/ja/docs/tutorial/static-files.md
+++ b/docs/ja/docs/tutorial/static-files.md
@@ -2,20 +2,6 @@
`StaticFiles` を使用して、ディレクトリから静的ファイルを自動的に提供できます。
-## `aiofiles` をインストール
-
-まず、`aiofiles` をインストールする必要があります:
-
-requests - `TestClient`를 사용하려면 필요.
-* aiofiles - `FileResponse` 또는 `StaticFiles`를 사용하려면 필요.
* jinja2 - 기본 템플릿 설정을 사용하려면 필요.
* python-multipart - `request.form()`과 함께 "parsing"의 지원을 원하면 필요.
* itsdangerous - `SessionMiddleware` 지원을 위해 필요.
diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml
index 1d4d3091..1e7d60db 100644
--- a/docs/ko/mkdocs.yml
+++ b/docs/ko/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -107,6 +109,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -117,6 +121,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs/nl/docs/index.md b/docs/nl/docs/index.md
new file mode 100644
index 00000000..0070de17
--- /dev/null
+++ b/docs/nl/docs/index.md
@@ -0,0 +1,468 @@
+
+{!../../../docs/missing-translation.md!}
+
+
+
++ FastAPI framework, high performance, easy to learn, fast to code, ready for production +
+ + +--- + +**Documentation**: https://fastapi.tiangolo.com + +**Source Code**: https://github.com/tiangolo/fastapi + +--- + +FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. + +The key features are: + +* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). + +* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * +* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Easy**: Designed to be easy to use and learn. Less time reading docs. +* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. +* **Robust**: Get production-ready code. With automatic interactive documentation. +* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. + +* estimation based on tests on an internal development team, building production applications. + +## Sponsors + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} +async def...uvicorn main:app --reload...ujson - for faster JSON "parsing".
+* email_validator - for email validation.
+
+Used by Starlette:
+
+* requests - Required if you want to use the `TestClient`.
+* jinja2 - Required if you want to use the default template configuration.
+* python-multipart - Required if you want to support form "parsing", with `request.form()`.
+* itsdangerous - Required for `SessionMiddleware` support.
+* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
+* ujson - Required if you want to use `UJSONResponse`.
+
+Used by FastAPI / Starlette:
+
+* uvicorn - for the server that loads and serves your application.
+* orjson - Required if you want to use `ORJSONResponse`.
+
+You can install all of these with `pip install "fastapi[all]"`.
+
+## License
+
+This project is licensed under the terms of the MIT license.
diff --git a/docs/nl/mkdocs.yml b/docs/nl/mkdocs.yml
new file mode 100644
index 00000000..c853216f
--- /dev/null
+++ b/docs/nl/mkdocs.yml
@@ -0,0 +1,135 @@
+site_name: FastAPI
+site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production
+site_url: https://fastapi.tiangolo.com/nl/
+theme:
+ name: material
+ custom_dir: overrides
+ palette:
+ - scheme: default
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb
+ name: Switch to light mode
+ - scheme: slate
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb-outline
+ name: Switch to dark mode
+ features:
+ - search.suggest
+ - search.highlight
+ - content.tabs.link
+ icon:
+ repo: fontawesome/brands/github-alt
+ logo: https://fastapi.tiangolo.com/img/icon-white.svg
+ favicon: https://fastapi.tiangolo.com/img/favicon.png
+ language: nl
+repo_name: tiangolo/fastapi
+repo_url: https://github.com/tiangolo/fastapi
+edit_uri: ''
+plugins:
+- search
+- markdownextradata:
+ data: data
+nav:
+- FastAPI: index.md
+- Languages:
+ - en: /
+ - az: /az/
+ - de: /de/
+ - es: /es/
+ - fa: /fa/
+ - fr: /fr/
+ - id: /id/
+ - it: /it/
+ - ja: /ja/
+ - ko: /ko/
+ - nl: /nl/
+ - pl: /pl/
+ - pt: /pt/
+ - ru: /ru/
+ - sq: /sq/
+ - tr: /tr/
+ - uk: /uk/
+ - zh: /zh/
+markdown_extensions:
+- toc:
+ permalink: true
+- markdown.extensions.codehilite:
+ guess_lang: false
+- mdx_include:
+ base_path: docs
+- admonition
+- codehilite
+- extra
+- pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
+extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
+ social:
+ - icon: fontawesome/brands/github-alt
+ link: https://github.com/tiangolo/fastapi
+ - icon: fontawesome/brands/discord
+ link: https://discord.gg/VQjSZaeJmf
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/fastapi
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/tiangolo
+ - icon: fontawesome/brands/dev
+ link: https://dev.to/tiangolo
+ - icon: fontawesome/brands/medium
+ link: https://medium.com/@tiangolo
+ - icon: fontawesome/solid/globe
+ link: https://tiangolo.com
+ alternate:
+ - link: /
+ name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
+ - link: /es/
+ name: es - español
+ - link: /fa/
+ name: fa
+ - link: /fr/
+ name: fr - français
+ - link: /id/
+ name: id
+ - link: /it/
+ name: it - italiano
+ - link: /ja/
+ name: ja - 日本語
+ - link: /ko/
+ name: ko - 한국어
+ - link: /nl/
+ name: nl
+ - link: /pl/
+ name: pl
+ - link: /pt/
+ name: pt - português
+ - link: /ru/
+ name: ru - русский язык
+ - link: /sq/
+ name: sq - shqip
+ - link: /tr/
+ name: tr - Türkçe
+ - link: /uk/
+ name: uk - українська мова
+ - link: /zh/
+ name: zh - 汉语
+extra_css:
+- https://fastapi.tiangolo.com/css/termynal.css
+- https://fastapi.tiangolo.com/css/custom.css
+extra_javascript:
+- https://fastapi.tiangolo.com/js/termynal.js
+- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/nl/overrides/.gitignore b/docs/nl/overrides/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/pl/docs/tutorial/index.md b/docs/pl/docs/tutorial/index.md
new file mode 100644
index 00000000..1a97214a
--- /dev/null
+++ b/docs/pl/docs/tutorial/index.md
@@ -0,0 +1,81 @@
+# Samouczek - Wprowadzenie
+
+Ten samouczek pokaże Ci, krok po kroku, jak używać większości funkcji **FastAPI**.
+
+Każda część korzysta z poprzednich, ale jest jednocześnie osobnym tematem. Możesz przejść bezpośrednio do każdego rozdziału, jeśli szukasz rozwiązania konkretnego problemu.
+
+Samouczek jest tak zbudowany, żeby służył jako punkt odniesienia w przyszłości.
+
+Możesz wracać i sprawdzać dokładnie to czego potrzebujesz.
+
+## Wykonywanie kodu
+
+Wszystkie fragmenty kodu mogą być skopiowane bezpośrednio i użyte (są poprawnymi i przetestowanymi plikami).
+
+Żeby wykonać każdy przykład skopiuj kod to pliku `main.py` i uruchom `uvicorn` za pomocą:
+
+
+
+## Permitir acesso público
+
+Por padrão, a Deta lidará com a autenticação usando cookies para sua conta.
+
+Mas quando estiver pronto, você pode torná-lo público com:
+
+
+
+## Saiba mais
+
+Em algum momento, você provavelmente desejará armazenar alguns dados para seu aplicativo de uma forma que persista ao longo do tempo. Para isso você pode usar Deta Base, que também tem um generoso **nível gratuito**.
+
+Você também pode ler mais na documentação da Deta.
+
+## Conceitos de implantação
+
+Voltando aos conceitos que discutimos em [Deployments Concepts](./concepts.md){.internal-link target=_blank}, veja como cada um deles seria tratado com a Deta:
+
+* **HTTPS**: Realizado pela Deta, eles fornecerão um subdomínio e lidarão com HTTPS automaticamente.
+* **Executando na inicialização**: Realizado pela Deta, como parte de seu serviço.
+* **Reinicialização**: Realizado pela Deta, como parte de seu serviço.
+* **Replicação**: Realizado pela Deta, como parte de seu serviço.
+* **Memória**: Limite predefinido pela Deta, você pode contatá-los para aumentá-lo.
+* **Etapas anteriores a inicialização**: Não suportado diretamente, você pode fazê-lo funcionar com o sistema Cron ou scripts adicionais.
+
+!!! note "Nota"
+ O Deta foi projetado para facilitar (e gratuitamente) a implantação rápida de aplicativos simples.
+
+ Ele pode simplificar vários casos de uso, mas, ao mesmo tempo, não suporta outros, como o uso de bancos de dados externos (além do próprio sistema de banco de dados NoSQL da Deta), máquinas virtuais personalizadas, etc.
+
+ Você pode ler mais detalhes na documentação da Deta para ver se é a escolha certa para você.
diff --git a/docs/pt/docs/help-fastapi.md b/docs/pt/docs/help-fastapi.md
new file mode 100644
index 00000000..086273a1
--- /dev/null
+++ b/docs/pt/docs/help-fastapi.md
@@ -0,0 +1,148 @@
+# Ajuda FastAPI - Obter Ajuda
+
+Você gosta do **FastAPI**?
+
+Você gostaria de ajudar o FastAPI, outros usários, e o autor?
+
+Ou você gostaria de obter ajuda relacionada ao **FastAPI**??
+
+Existem métodos muito simples de ajudar (A maioria das ajudas podem ser feitas com um ou dois cliques).
+
+E também existem vários modos de se conseguir ajuda.
+
+## Inscreva-se na newsletter
+
+Você pode se inscrever (pouco frequente) [**FastAPI e amigos** newsletter](/newsletter/){.internal-link target=_blank} para receber atualizações:
+
+* Notícias sobre FastAPI e amigos 🚀
+* Tutoriais 📝
+* Recursos ✨
+* Mudanças de última hora 🚨
+* Truques e dicas ✅
+
+## Siga o FastAPI no twitter
+
+Siga @fastapi no **Twitter** para receber as últimas notícias sobre o **FastAPI**. 🐦
+
+## Favorite o **FastAPI** no GitHub
+
+Você pode "favoritar" o FastAPI no GitHub (clicando na estrela no canto superior direito): https://github.com/tiangolo/fastapi. ⭐️
+
+Favoritando, outros usuários poderão encontrar mais facilmente e verão que já foi útil para muita gente.
+
+## Acompanhe novos updates no repositorio do GitHub
+
+Você pode "acompanhar" (watch) o FastAPI no GitHub (clicando no botão com um "olho" no canto superior direito): https://github.com/tiangolo/fastapi. 👀
+
+Podendo selecionar apenas "Novos Updates".
+
+Fazendo isto, serão enviadas notificações (em seu email) sempre que tiver novos updates (uma nova versão) com correções de bugs e novos recursos no **FastAPI**
+
+## Conect-se com o autor
+
+Você pode se conectar comigo (Sebastián Ramírez / `tiangolo`), o autor.
+
+Você pode:
+
+* Me siga no **GitHub**.
+ * Ver também outros projetos Open Source criados por mim que podem te ajudar.
+ * Me seguir para saber quando um novo projeto Open Source for criado.
+* Me siga no **Twitter**.
+ * Me dizer o motivo pelo o qual você está usando o FastAPI(Adoro ouvir esse tipo de comentário).
+ * Saber quando eu soltar novos anúncios ou novas ferramentas.
+ * Também é possivel seguir o @fastapi no Twitter (uma conta aparte).
+* Conect-se comigo no **Linkedin**.
+ * Saber quando eu fizer novos anúncios ou novas ferramentas (apesar de que uso o twitter com mais frequência 🤷♂).
+* Ler meus artigos (ou me seguir) no **Dev.to** ou no **Medium**.
+ * Ficar por dentro de novas ideias, artigos, e ferramentas criadas por mim.
+ * Me siga para saber quando eu publicar algo novo.
+
+## Tweete sobre **FastAPI**
+
+Tweete sobre o **FastAPI** e compartilhe comigo e com os outros o porque de gostar do FastAPI. 🎉
+
+Adoro ouvir sobre como o **FastAPI** é usado, o que você gosta nele, em qual projeto/empresa está sendo usado, etc.
+
+## Vote no FastAPI
+
+* Vote no **FastAPI** no Slant.
+* Vote no **FastAPI** no AlternativeTo.
+
+## Responda perguntas no GitHub
+
+Você pode acompanhar as perguntas existentes e tentar ajudar outros, . 🤓
+
+Ajudando a responder as questões de varias pessoas, você pode se tornar um [Expert em FastAPI](fastapi-people.md#experts){.internal-link target=_blank} oficial. 🎉
+
+## Acompanhe o repositório do GitHub
+
+Você pode "acompanhar" (watch) o FastAPI no GitHub (clicando no "olho" no canto superior direito): https://github.com/tiangolo/fastapi. 👀
+
+Se você selecionar "Acompanhando" (Watching) em vez de "Apenas Lançamentos" (Releases only) você receberá notificações quando alguém tiver uma nova pergunta.
+
+Assim podendo tentar ajudar a resolver essas questões.
+
+## Faça perguntas
+
+É possível criar uma nova pergunta no repositório do GitHub, por exemplo:
+
+* Faça uma **pergunta** ou pergunte sobre um **problema**.
+* Sugira novos **recursos**.
+
+**Nota**: Se você fizer uma pergunta, então eu gostaria de pedir que você também ajude os outros com suas respectivas perguntas. 😉
+
+## Crie um Pull Request
+
+É possível [contribuir](contributing.md){.internal-link target=_blank} no código fonte fazendo Pull Requests, por exemplo:
+
+* Para corrigir um erro de digitação que você encontrou na documentação.
+* Para compartilhar um artigo, video, ou podcast criados por você sobre o FastAPI editando este arquivo.
+ * Não se esqueça de adicionar o link no começo da seção correspondente.
+* Para ajudar [traduzir a documentação](contributing.md#translations){.internal-link target=_blank} para sua lingua.
+ * Também é possivel revisar as traduções já existentes.
+* Para propor novas seções na documentação.
+* Para corrigir um bug/questão.
+* Para adicionar um novo recurso.
+
+## Entre no chat
+
+Entre no 👥 server de conversa do Discord 👥 e conheça novas pessoas da comunidade
+do FastAPI.
+
+!!! dica
+ Para perguntas, pergunte nas questões do GitHub, lá tem um chance maior de você ser ajudado sobre o FastAPI [FastAPI Experts](fastapi-people.md#experts){.internal-link target=_blank}.
+
+ Use o chat apenas para outro tipo de assunto.
+
+Também existe o chat do Gitter, porém ele não possuí canais e recursos avançados, conversas são mais engessadas, por isso o Discord é mais recomendado.
+
+### Não faça perguntas no chat
+
+Tenha em mente que os chats permitem uma "conversa mais livre", dessa forma é muito fácil fazer perguntas que são muito genéricas e dificeís de responder, assim você pode acabar não sendo respondido.
+
+Nas questões do GitHub o template irá te guiar para que você faça a sua pergunta de um jeito mais correto, fazendo com que você receba respostas mais completas, e até mesmo que você mesmo resolva o problema antes de perguntar. E no GitHub eu garanto que sempre irei responder todas as perguntas, mesmo que leve um tempo. Eu pessoalmente não consigo fazer isso via chat. 😅
+
+Conversas no chat não são tão fáceis de serem encontrados quanto no GitHub, então questões e respostas podem se perder dentro da conversa. E apenas as que estão nas questões do GitHub contam para você se tornar um [Expert em FastAPI](fastapi-people.md#experts){.internal-link target=_blank}, então você receberá mais atenção nas questões do GitHub.
+
+Por outro lado, existem milhares de usuários no chat, então tem uma grande chance de você encontrar alguém para trocar uma idéia por lá em qualquer horário. 😄
+
+## Patrocine o autor
+
+Você também pode ajudar o autor financeiramente (eu) através do GitHub sponsors.
+
+Lá você pode me pagar um cafézinho ☕️ como agradecimento. 😄
+
+E você também pode se tornar um patrocinador Prata ou Ouro do FastAPI. 🏅🎉
+
+## Patrocine as ferramente que potencializam o FastAPI
+
+Como você viu na documentação, o FastAPI se apoia em nos gigantes, Starlette e Pydantic.
+
+Patrocine também:
+
+* Samuel Colvin (Pydantic)
+* Encode (Starlette, Uvicorn)
+
+---
+
+Muito Obrigado! 🚀
diff --git a/docs/pt/docs/index.md b/docs/pt/docs/index.md
index 848fff08..97044dd9 100644
--- a/docs/pt/docs/index.md
+++ b/docs/pt/docs/index.md
@@ -434,7 +434,6 @@ Usados por Pydantic:
Usados por Starlette:
* requests - Necessário se você quiser utilizar o `TestClient`.
-* aiofiles - Necessário se você quiser utilizar o `FileResponse` ou `StaticFiles`.
* jinja2 - Necessário se você quiser utilizar a configuração padrão de templates.
* python-multipart - Necessário se você quiser suporte com "parsing" de formulário, com `request.form()`.
* itsdangerous - Necessário para suporte a `SessionMiddleware`.
diff --git a/docs/pt/docs/tutorial/background-tasks.md b/docs/pt/docs/tutorial/background-tasks.md
new file mode 100644
index 00000000..625fa2b1
--- /dev/null
+++ b/docs/pt/docs/tutorial/background-tasks.md
@@ -0,0 +1,94 @@
+# Tarefas em segundo plano
+
+Você pode definir tarefas em segundo plano a serem executadas _ após _ retornar uma resposta.
+
+Isso é útil para operações que precisam acontecer após uma solicitação, mas que o cliente realmente não precisa esperar a operação ser concluída para receber a resposta.
+
+Isso inclui, por exemplo:
+
+- Envio de notificações por email após a realização de uma ação:
+ - Como conectar-se a um servidor de e-mail e enviar um e-mail tende a ser "lento" (vários segundos), você pode retornar a resposta imediatamente e enviar a notificação por e-mail em segundo plano.
+- Processando dados:
+ - Por exemplo, digamos que você receba um arquivo que deve passar por um processo lento, você pode retornar uma resposta de "Aceito" (HTTP 202) e processá-lo em segundo plano.
+
+## Usando `BackgroundTasks`
+
+Primeiro, importe `BackgroundTasks` e defina um parâmetro em sua _função de operação de caminho_ com uma declaração de tipo de `BackgroundTasks`:
+
+```Python hl_lines="1 13"
+{!../../../docs_src/background_tasks/tutorial001.py!}
+```
+
+O **FastAPI** criará o objeto do tipo `BackgroundTasks` para você e o passará como esse parâmetro.
+
+## Criar uma função de tarefa
+
+Crie uma função a ser executada como tarefa em segundo plano.
+
+É apenas uma função padrão que pode receber parâmetros.
+
+Pode ser uma função `async def` ou `def` normal, o **FastAPI** saberá como lidar com isso corretamente.
+
+Nesse caso, a função de tarefa gravará em um arquivo (simulando o envio de um e-mail).
+
+E como a operação de gravação não usa `async` e `await`, definimos a função com `def` normal:
+
+```Python hl_lines="6-9"
+{!../../../docs_src/background_tasks/tutorial001.py!}
+```
+
+## Adicionar a tarefa em segundo plano
+
+Dentro de sua _função de operação de caminho_, passe sua função de tarefa para o objeto _tarefas em segundo plano_ com o método `.add_task()`:
+
+```Python hl_lines="14"
+{!../../../docs_src/background_tasks/tutorial001.py!}
+```
+
+`.add_task()` recebe como argumentos:
+
+- Uma função de tarefa a ser executada em segundo plano (`write_notification`).
+- Qualquer sequência de argumentos que deve ser passada para a função de tarefa na ordem (`email`).
+- Quaisquer argumentos nomeados que devem ser passados para a função de tarefa (`mensagem = "alguma notificação"`).
+
+## Injeção de dependência
+
+Usar `BackgroundTasks` também funciona com o sistema de injeção de dependência, você pode declarar um parâmetro do tipo `BackgroundTasks` em vários níveis: em uma _função de operação de caminho_, em uma dependência (confiável), em uma subdependência, etc.
+
+O **FastAPI** sabe o que fazer em cada caso e como reutilizar o mesmo objeto, de forma que todas as tarefas em segundo plano sejam mescladas e executadas em segundo plano posteriormente:
+
+```Python hl_lines="13 15 22 25"
+{!../../../docs_src/background_tasks/tutorial002.py!}
+```
+
+Neste exemplo, as mensagens serão gravadas no arquivo `log.txt` _após_ o envio da resposta.
+
+Se houver uma consulta na solicitação, ela será gravada no log em uma tarefa em segundo plano.
+
+E então outra tarefa em segundo plano gerada na _função de operação de caminho_ escreverá uma mensagem usando o parâmetro de caminho `email`.
+
+## Detalhes técnicos
+
+A classe `BackgroundTasks` vem diretamente de `starlette.background`.
+
+Ela é importada/incluída diretamente no FastAPI para que você possa importá-la do `fastapi` e evitar a importação acidental da alternativa `BackgroundTask` (sem o `s` no final) de `starlette.background`.
+
+Usando apenas `BackgroundTasks` (e não `BackgroundTask`), é então possível usá-la como um parâmetro de _função de operação de caminho_ e deixar o **FastAPI** cuidar do resto para você, assim como ao usar o objeto `Request` diretamente.
+
+Ainda é possível usar `BackgroundTask` sozinho no FastAPI, mas você deve criar o objeto em seu código e retornar uma Starlette `Response` incluindo-o.
+
+Você pode ver mais detalhes na documentação oficiais da Starlette para tarefas em segundo plano .
+
+## Ressalva
+
+Se você precisa realizar cálculos pesados em segundo plano e não necessariamente precisa que seja executado pelo mesmo processo (por exemplo, você não precisa compartilhar memória, variáveis, etc), você pode se beneficiar do uso de outras ferramentas maiores, como Celery .
+
+Eles tendem a exigir configurações mais complexas, um gerenciador de fila de mensagens/tarefas, como RabbitMQ ou Redis, mas permitem que você execute tarefas em segundo plano em vários processos e, especialmente, em vários servidores.
+
+Para ver um exemplo, verifique os [Geradores de projeto](../project-generation.md){.internal-link target=\_blank}, todos incluem celery já configurado.
+
+Mas se você precisa acessar variáveis e objetos do mesmo aplicativo **FastAPI**, ou precisa realizar pequenas tarefas em segundo plano (como enviar uma notificação por e-mail), você pode simplesmente usar `BackgroundTasks`.
+
+## Recapitulando
+
+Importe e use `BackgroundTasks` com parâmetros em _funções de operação de caminho_ e dependências para adicionar tarefas em segundo plano.
diff --git a/docs/pt/docs/tutorial/body.md b/docs/pt/docs/tutorial/body.md
new file mode 100644
index 00000000..5891185f
--- /dev/null
+++ b/docs/pt/docs/tutorial/body.md
@@ -0,0 +1,165 @@
+# Corpo da Requisição
+
+Quando você precisa enviar dados de um cliente (como de um navegador web) para sua API, você o envia como um **corpo da requisição**.
+
+O corpo da **requisição** é a informação enviada pelo cliente para sua API. O corpo da **resposta** é a informação que sua API envia para o cliente.
+
+Sua API quase sempre irá enviar um corpo na **resposta**. Mas os clientes não necessariamente precisam enviar um corpo em toda **requisição**.
+
+Para declarar um corpo da **requisição**, você utiliza os modelos do Pydantic com todos os seus poderes e benefícios.
+
+!!! info "Informação"
+ Para enviar dados, você deve usar utilizar um dos métodos: `POST` (Mais comum), `PUT`, `DELETE` ou `PATCH`.
+
+ Enviar um corpo em uma requisição `GET` não tem um comportamento definido nas especificações, porém é suportado pelo FastAPI, apenas para casos de uso bem complexos/extremos.
+
+ Como é desencorajado, a documentação interativa com Swagger UI não irá mostrar a documentação para o corpo da requisição para um `GET`, e proxies que intermediarem podem não suportar o corpo da requisição.
+
+## Importe o `BaseModel` do Pydantic
+
+Primeiro, você precisa importar `BaseModel` do `pydantic`:
+
+```Python hl_lines="4"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+## Crie seu modelo de dados
+
+Então você declara seu modelo de dados como uma classe que herda `BaseModel`.
+
+Utilize os tipos Python padrão para todos os atributos:
+
+```Python hl_lines="7-11"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+Assim como quando declaramos parâmetros de consulta, quando um atributo do modelo possui um valor padrão, ele se torna opcional. Caso contrário, se torna obrigatório. Use `None` para torná-lo opcional.
+
+Por exemplo, o modelo acima declara um JSON "`object`" (ou `dict` no Python) como esse:
+
+```JSON
+{
+ "name": "Foo",
+ "description": "Uma descrição opcional",
+ "price": 45.2,
+ "tax": 3.5
+}
+```
+
+...como `description` e `tax` são opcionais (Com um valor padrão de `None`), esse JSON "`object`" também é válido:
+
+```JSON
+{
+ "name": "Foo",
+ "price": 45.2
+}
+```
+
+## Declare como um parâmetro
+
+Para adicionar o corpo na *função de operação de rota*, declare-o da mesma maneira que você declarou parâmetros de rota e consulta:
+
+```Python hl_lines="18"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+...E declare o tipo como o modelo que você criou, `Item`.
+
+## Resultados
+
+Apenas com esse declaração de tipos do Python, o **FastAPI** irá:
+
+* Ler o corpo da requisição como um JSON.
+* Converter os tipos correspondentes (se necessário).
+* Validar os dados.
+ * Se algum dados for inválido, irá retornar um erro bem claro, indicando exatamente onde e o que está incorreto.
+* Entregar a você a informação recebida no parâmetro `item`.
+ * Como você o declarou na função como do tipo `Item`, você também terá o suporte do editor (completação, etc) para todos os atributos e seus tipos.
+* Gerar um Esquema JSON com as definições do seu modelo, você também pode utilizá-lo em qualquer lugar que quiser, se fizer sentido para seu projeto.
+* Esses esquemas farão parte do esquema OpenAPI, e utilizados nas UIs de documentação automática.
+
+## Documentação automática
+
+Os esquemas JSON dos seus modelos farão parte do esquema OpenAPI gerado para sua aplicação, e aparecerão na documentação interativa da API:
+
+
+
+E também serão utilizados em cada *função de operação de rota* que utilizá-los:
+
+
+
+## Suporte do editor de texto:
+
+No seu editor de texto, dentro da função você receberá dicas de tipos e completação em todo lugar (isso não aconteceria se você recebesse um `dict` em vez de um modelo Pydantic):
+
+
+
+Você também poderá receber verificações de erros para operações de tipos incorretas:
+
+
+
+Isso não é por acaso, todo o framework foi construído em volta deste design.
+
+E foi imensamente testado na fase de design, antes de qualquer implementação, para garantir que funcionaria para todos os editores de texto.
+
+Houveram mudanças no próprio Pydantic para que isso fosse possível.
+
+As capturas de tela anteriores foram capturas no Visual Studio Code.
+
+Mas você terá o mesmo suporte do editor no PyCharm e na maioria dos editores Python:
+
+
+
+!!! tip "Dica"
+ Se você utiliza o PyCharm como editor, você pode utilizar o Plugin do Pydantic para o PyCharm .
+
+ Melhora o suporte do editor para seus modelos Pydantic com::
+
+ * completação automática
+ * verificação de tipos
+ * refatoração
+ * buscas
+ * inspeções
+
+## Use o modelo
+
+Dentro da função, você pode acessar todos os atributos do objeto do modelo diretamente:
+
+```Python hl_lines="21"
+{!../../../docs_src/body/tutorial002.py!}
+```
+
+## Corpo da requisição + parâmetros de rota
+
+Você pode declarar parâmetros de rota e corpo da requisição ao mesmo tempo.
+
+O **FastAPI** irá reconhecer que os parâmetros da função que combinam com parâmetros de rota devem ser **retirados da rota**, e parâmetros da função que são declarados como modelos Pydantic sejam **retirados do corpo da requisição**.
+
+```Python hl_lines="17-18"
+{!../../../docs_src/body/tutorial003.py!}
+```
+
+## Corpo da requisição + parâmetros de rota + parâmetros de consulta
+
+Você também pode declarar parâmetros de **corpo**, **rota** e **consulta**, ao mesmo tempo.
+
+O **FastAPI** irá reconhecer cada um deles e retirar a informação do local correto.
+
+```Python hl_lines="18"
+{!../../../docs_src/body/tutorial004.py!}
+```
+
+Os parâmetros da função serão reconhecidos conforme abaixo:
+
+* Se o parâmetro também é declarado na **rota**, será utilizado como um parâmetro de rota.
+* Se o parâmetro é de um **tipo único** (como `int`, `float`, `str`, `bool`, etc) será interpretado como um parâmetro de **consulta**.
+* Se o parâmetro é declarado como um **modelo Pydantic**, será interpretado como o **corpo** da requisição.
+
+!!! note "Observação"
+ O FastAPI saberá que o valor de `q` não é obrigatório por causa do valor padrão `= None`.
+
+ O `Optional` em `Optional[str]` não é utilizado pelo FastAPI, mas permite ao seu editor de texto lhe dar um suporte melhor e detectar erros.
+
+## Sem o Pydantic
+
+Se você não quer utilizar os modelos Pydantic, você também pode utilizar o parâmetro **Body**. Veja a documentação para [Body - Parâmetros múltiplos: Valores singulares no body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
diff --git a/docs/pt/docs/tutorial/cookie-params.md b/docs/pt/docs/tutorial/cookie-params.md
new file mode 100644
index 00000000..1a60e357
--- /dev/null
+++ b/docs/pt/docs/tutorial/cookie-params.md
@@ -0,0 +1,33 @@
+# Parâmetros de Cookie
+
+Você pode definir parâmetros de Cookie da mesma maneira que define paramêtros com `Query` e `Path`.
+
+## Importe `Cookie`
+
+Primeiro importe `Cookie`:
+
+```Python hl_lines="3"
+{!../../../docs_src/cookie_params/tutorial001.py!}
+```
+
+## Declare parâmetros de `Cookie`
+
+Então declare os paramêtros de cookie usando a mesma estrutura que em `Path` e `Query`.
+
+O primeiro valor é o valor padrão, você pode passar todas as validações adicionais ou parâmetros de anotação:
+
+```Python hl_lines="9"
+{!../../../docs_src/cookie_params/tutorial001.py!}
+```
+
+!!! note "Detalhes Técnicos"
+ `Cookie` é uma classe "irmã" de `Path` e `Query`. Ela também herda da mesma classe em comum `Param`.
+
+ Mas lembre-se que quando você importa `Query`, `Path`, `Cookie` e outras de `fastapi`, elas são na verdade funções que retornam classes especiais.
+
+!!! info "Informação"
+ Para declarar cookies, você precisa usar `Cookie`, caso contrário, os parâmetros seriam interpretados como parâmetros de consulta.
+
+## Recapitulando
+
+Declare cookies com `Cookie`, usando o mesmo padrão comum que utiliza-se em `Query` e `Path`.
diff --git a/docs/pt/docs/tutorial/extra-data-types.md b/docs/pt/docs/tutorial/extra-data-types.md
new file mode 100644
index 00000000..e4b9913d
--- /dev/null
+++ b/docs/pt/docs/tutorial/extra-data-types.md
@@ -0,0 +1,66 @@
+# Tipos de dados extras
+
+Até agora, você tem usado tipos de dados comuns, tais como:
+
+* `int`
+* `float`
+* `str`
+* `bool`
+
+Mas você também pode usar tipos de dados mais complexos.
+
+E você ainda terá os mesmos recursos que viu até agora:
+
+* Ótimo suporte do editor.
+* Conversão de dados das requisições recebidas.
+* Conversão de dados para os dados da resposta.
+* Validação de dados.
+* Anotação e documentação automáticas.
+
+## Outros tipos de dados
+
+Aqui estão alguns dos tipos de dados adicionais que você pode usar:
+
+* `UUID`:
+ * Um "Identificador Universalmente Único" padrão, comumente usado como ID em muitos bancos de dados e sistemas.
+ * Em requisições e respostas será representado como uma `str`.
+* `datetime.datetime`:
+ * O `datetime.datetime` do Python.
+ * Em requisições e respostas será representado como uma `str` no formato ISO 8601, exemplo: `2008-09-15T15:53:00+05:00`.
+* `datetime.date`:
+ * O `datetime.date` do Python.
+ * Em requisições e respostas será representado como uma `str` no formato ISO 8601, exemplo: `2008-09-15`.
+* `datetime.time`:
+ * O `datetime.time` do Python.
+ * Em requisições e respostas será representado como uma `str` no formato ISO 8601, exemplo: `14:23:55.003`.
+* `datetime.timedelta`:
+ * O `datetime.timedelta` do Python.
+ * Em requisições e respostas será representado como um `float` de segundos totais.
+ * O Pydantic também permite representá-lo como uma "codificação ISO 8601 diferença de tempo", cheque a documentação para mais informações.
+* `frozenset`:
+ * Em requisições e respostas, será tratado da mesma forma que um `set`:
+ * Nas requisições, uma lista será lida, eliminando duplicadas e convertendo-a em um `set`.
+ * Nas respostas, o `set` será convertido para uma `list`.
+ * O esquema gerado vai especificar que os valores do `set` são unicos (usando o `uniqueItems` do JSON Schema).
+* `bytes`:
+ * O `bytes` padrão do Python.
+ * Em requisições e respostas será representado como uma `str`.
+ * O esquema gerado vai especificar que é uma `str` com o "formato" `binary`.
+* `Decimal`:
+ * O `Decimal` padrão do Python.
+ * Em requisições e respostas será representado como um `float`.
+* Você pode checar todos os tipos de dados válidos do Pydantic aqui: Tipos de dados do Pydantic.
+
+## Exemplo
+
+Aqui está um exemplo de *operação de rota* com parâmetros utilizando-se de alguns dos tipos acima.
+
+```Python hl_lines="1 3 12-16"
+{!../../../docs_src/extra_data_types/tutorial001.py!}
+```
+
+Note que os parâmetros dentro da função tem seu tipo de dados natural, e você pode, por exemplo, realizar manipulações normais de data, como:
+
+```Python hl_lines="18-19"
+{!../../../docs_src/extra_data_types/tutorial001.py!}
+```
diff --git a/docs/pt/docs/tutorial/path-params.md b/docs/pt/docs/tutorial/path-params.md
index 20913a56..5de3756e 100644
--- a/docs/pt/docs/tutorial/path-params.md
+++ b/docs/pt/docs/tutorial/path-params.md
@@ -72,7 +72,7 @@ O mesmo erro apareceria se você tivesse fornecido um `float` ao invés de um `i
## Documentação
-Quando você abrir o seu navegador em http://127.0.0.1:8000/docs, você verá de forma automática e interativa a documtação da API como:
+Quando você abrir o seu navegador em http://127.0.0.1:8000/docs, você verá de forma automática e interativa a documentação da API como:
diff --git a/docs/pt/mkdocs.yml b/docs/pt/mkdocs.yml
index f202f306..25340080 100644
--- a/docs/pt/mkdocs.yml
+++ b/docs/pt/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -58,8 +60,11 @@ nav:
- tutorial/index.md
- tutorial/first-steps.md
- tutorial/path-params.md
+ - tutorial/body.md
- tutorial/body-fields.md
+ - tutorial/extra-data-types.md
- tutorial/query-params-str-validations.md
+ - tutorial/cookie-params.md
- Segurança:
- tutorial/security/index.md
- Guia de Usuário Avançado:
@@ -68,10 +73,12 @@ nav:
- deployment/index.md
- deployment/versions.md
- deployment/https.md
+ - deployment/deta.md
- alternatives.md
- history-design-future.md
- external-links.md
- benchmarks.md
+- help-fastapi.md
markdown_extensions:
- toc:
permalink: true
@@ -117,6 +124,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -127,6 +136,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs/ru/docs/async.md b/docs/ru/docs/async.md
new file mode 100644
index 00000000..fc5e4447
--- /dev/null
+++ b/docs/ru/docs/async.md
@@ -0,0 +1,505 @@
+# Конкурентность и async / await
+
+Здесь приведена подробная информация об использовании синтаксиса `async def` при написании *функций обработки пути*, а также рассмотрены основы асинхронного программирования, конкурентности и параллелизма.
+
+## Нет времени?
+
+TL;DR:
+
+Допустим, вы используете сторонюю библиотеку, которая требует вызова с ключевым словом `await`:
+
+```Python
+results = await some_library()
+```
+
+В этом случае *функции обработки пути* необходимо объявлять с использованием синтаксиса `async def`:
+
+```Python hl_lines="2"
+@app.get('/')
+async def read_results():
+ results = await some_library()
+ return results
+```
+
+!!! note
+ `await` можно использовать только внутри функций, объявленных с использованием `async def`.
+
+---
+
+Если вы обращаетесь к сторонней библиотеке, которая с чем-то взаимодействует
+(с базой данных, API, файловой системой и т. д.), и не имеет поддержки синтаксиса `await`
+(что относится сейчас к большинству библиотек для работы с базами данных), то
+объявляйте *функции обработки пути* обычным образом с помощью `def`, например:
+
+```Python hl_lines="2"
+@app.get('/')
+def results():
+ results = some_library()
+ return results
+```
+
+---
+
+Если вашему приложению (странным образом) не нужно ни с чем взаимодействовать и, соответственно,
+ожидать ответа, используйте `async def`.
+
+---
+
+Если вы не уверены, используйте обычный синтаксис `def`.
+
+---
+
+**Примечание**: при необходимости можно смешивать `def` и `async def` в *функциях обработки пути*
+и использовать в каждом случае наиболее подходящий синтаксис. А FastAPI сделает с этим всё, что нужно.
+
+В любом из описанных случаев FastAPI работает асинхронно и очень быстро.
+
+Однако придерживаясь указанных советов, можно получить дополнительную оптимизацию производительности.
+
+## Технические подробности
+
+Современные версии Python поддерживают разработку так называемого **"асинхронного кода"** посредством написания **"сопрограмм"** с использованием синтаксиса **`async` и `await`**.
+
+Ниже разберём эту фразу по частям:
+
+* **Асинхронный код**
+* **`async` и `await`**
+* **Сопрограммы**
+
+## Асинхронный код
+
+Асинхронный код означает, что в языке 💬 есть возможность сообщить машине / программе 🤖,
+что в определённой точке кода ей 🤖 нужно будет ожидать завершения выполнения *чего-то ещё* в другом месте. Допустим это *что-то ещё* называется "медленный файл" 📝.
+
+И пока мы ждём завершения работы с "медленным файлом" 📝, компьютер может переключиться для выполнения других задач.
+
+Но при каждой возможности компьютер / программа 🤖 будет возвращаться обратно. Например, если он 🤖 опять окажется в режиме ожидания, или когда закончит всю работу. В этом случае компьютер 🤖 проверяет, не завершена ли какая-нибудь из текущих задач.
+
+Потом он 🤖 берёт первую выполненную задачу (допустим, наш "медленный файл" 📝) и продолжает работу, производя с ней необходимые действия.
+
+Вышеупомянутое "что-то ещё", завершения которого приходится ожидать, обычно относится к достаточно "медленным" операциям I/O (по сравнению со скоростью работы процессора и оперативной памяти), например:
+
+* отправка данных от клиента по сети
+* получение клиентом данных, отправленных вашей программой по сети
+* чтение системой содержимого файла с диска и передача этих данных программе
+* запись на диск данных, которые программа передала системе
+* обращение к удалённому API
+* ожидание завершения операции с базой данных
+* получение результатов запроса к базе данных
+* и т. д.
+
+Поскольку в основном время тратится на ожидание выполнения операций I/O,
+их обычно называют операциями, ограниченными скоростью ввода-вывода.
+
+Код называют "асинхронным", потому что компьютеру / программе не требуется "синхронизироваться" с медленной задачей и,
+будучи в простое, ожидать момента её завершения, с тем чтобы забрать результат и продолжить работу.
+
+Вместо этого в "асинхронной" системе завершённая задача может немного подождать (буквально несколько микросекунд),
+пока компьютер / программа занимается другими важными вещами, с тем чтобы потом вернуться,
+забрать результаты выполнения и начать их обрабатывать.
+
+"Синхронное" исполнение (в противовес "асинхронному") также называют "последовательным",
+потому что компьютер / программа последовательно выполняет все требуемые шаги перед тем, как перейти к следующей задаче,
+даже если в процессе приходится ждать.
+
+### Конкурентность и бургеры
+
+Тот **асинхронный** код, о котором идёт речь выше, иногда называют **"конкурентностью"**. Она отличается от **"параллелизма"**.
+
+Да, **конкурентность** и **параллелизм** подразумевают, что разные вещи происходят примерно в одно время.
+
+Но внутреннее устройство **конкурентности** и **параллелизма** довольно разное.
+
+Чтобы это понять, представьте такую картину:
+
+### Конкурентные бургеры
+
+
+
+Вы идёте со своей возлюбленной 😍 в фастфуд 🍔 и становитесь в очередь, в это время кассир 💁 принимает заказы у посетителей перед вами.
+
+Когда наконец подходит очередь, вы заказываете парочку самых вкусных и навороченных бургеров 🍔, один для своей возлюбленной 😍, а другой себе.
+
+Отдаёте деньги 💸.
+
+Кассир 💁 что-то говорит поварам на кухне 👨🍳, теперь они знают, какие бургеры нужно будет приготовить 🍔
+(но пока они заняты бургерами предыдущих клиентов).
+
+Кассир 💁 отдаёт вам чек с номером заказа.
+
+В ожидании еды вы идёте со своей возлюбленной 😍 выбрать столик, садитесь и довольно продолжительное время общаетесь 😍
+(поскольку ваши бургеры самые навороченные, готовятся они не так быстро ✨🍔✨).
+
+Сидя за столиком с возлюбленной 😍 в ожидании бургеров 🍔, вы отлично проводите время,
+восхищаясь её великолепием, красотой и умом ✨😍✨.
+
+Всё ещё ожидая заказ и болтая со своей возлюбленной 😍, время от времени вы проверяете,
+какой номер горит над прилавком, и не подошла ли уже ваша очередь.
+
+И вот наконец настаёт этот момент, и вы идёте к стойке, чтобы забрать бургеры 🍔 и вернуться за столик.
+
+Вы со своей возлюбленной 😍 едите бургеры 🍔 и отлично проводите время ✨.
+
+---
+
+А теперь представьте, что в этой небольшой истории вы компьютер / программа 🤖.
+
+В очереди вы просто глазеете по сторонам 😴, ждёте и ничего особо "продуктивного" не делаете.
+Но очередь движется довольно быстро, поскольку кассир 💁 только принимает заказы (а не занимается приготовлением еды), так что ничего страшного.
+
+Когда подходит очередь вы наконец предпринимаете "продуктивные" действия 🤓: просматриваете меню, выбираете в нём что-то, узнаёте, что хочет ваша возлюбленная 😍, собираетесь оплатить 💸, смотрите, какую достали карту, проверяете, чтобы с вас списали верную сумму, и что в заказе всё верно и т. д.
+
+И хотя вы всё ещё не получили бургеры 🍔, ваша работа с кассиром 💁 ставится "на паузу" ⏸,
+поскольку теперь нужно ждать 🕙, когда заказ приготовят.
+
+Но отойдя с номерком от прилавка, вы садитесь за столик и можете переключить 🔀 внимание
+на свою возлюбленную 😍 и "работать" ⏯ 🤓 уже над этим. И вот вы снова очень
+"продуктивны" 🤓, мило болтаете вдвоём и всё такое 😍.
+
+В какой-то момент кассир 💁 поместит на табло ваш номер, подразумевая, что бургеры готовы 🍔, но вы не станете подскакивать как умалишённый, лишь только увидев на экране свою очередь. Вы уверены, что ваши бургеры 🍔 никто не утащит, ведь у вас свой номерок, а у других свой.
+
+Поэтому вы подождёте, пока возлюбленная 😍 закончит рассказывать историю (закончите текущую работу ⏯ / задачу в обработке 🤓),
+и мило улыбнувшись, скажете, что идёте забирать заказ ⏸.
+
+И вот вы подходите к стойке 🔀, к первоначальной задаче, которая уже завершена ⏯, берёте бургеры 🍔, говорите спасибо и относите заказ за столик. На этом заканчивается этап / задача взаимодействия с кассой ⏹.
+В свою очередь порождается задача "поедание бургеров" 🔀 ⏯, но предыдущая ("получение бургеров") завершена ⏹.
+
+### Параллельные бургеры
+
+Теперь представим, что вместо бургерной "Конкурентные бургеры" вы решили сходить в "Параллельные бургеры".
+
+И вот вы идёте со своей возлюбленной 😍 отведать параллельного фастфуда 🍔.
+
+Вы становитесь в очередь пока несколько (пусть будет 8) кассиров, которые по совместительству ещё и повары 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳, принимают заказы у посетителей перед вами.
+
+При этом клиенты не отходят от стойки и ждут 🕙 получения еды, поскольку каждый
+из 8 кассиров идёт на кухню готовить бургеры 🍔, а только потом принимает следующий заказ.
+
+Наконец настаёт ваша очередь, и вы просите два самых навороченных бургера 🍔, один для дамы сердца 😍, а другой себе.
+
+Ни о чём не жалея, расплачиваетесь 💸.
+
+И кассир уходит на кухню 👨🍳.
+
+Вам приходится ждать перед стойкой 🕙, чтобы никто по случайности не забрал ваши бургеры 🍔, ведь никаких номерков у вас нет.
+
+Поскольку вы с возлюбленной 😍 хотите получить заказ вовремя 🕙, и следите за тем, чтобы никто не вклинился в очередь,
+у вас не получается уделять должного внимание своей даме сердца 😞.
+
+Это "синхронная" работа, вы "синхронизированы" с кассиром/поваром 👨🍳. Приходится ждать 🕙 у стойки,
+когда кассир/повар 👨🍳 закончит делать бургеры 🍔 и вручит вам заказ, иначе его случайно может забрать кто-то другой.
+
+Наконец кассир/повар 👨🍳 возвращается с бургерами 🍔 после невыносимо долгого ожидания 🕙 за стойкой.
+
+Вы скорее забираете заказ 🍔 и идёте с возлюбленной 😍 за столик.
+
+Там вы просто едите эти бургеры, и на этом всё 🍔 ⏹.
+
+Вам не особо удалось пообщаться, потому что большую часть времени 🕙 пришлось провести у кассы 😞.
+
+---
+
+В описанном сценарии вы компьютер / программа 🤖 с двумя исполнителями (вы и ваша возлюбленная 😍),
+на протяжении долгого времени 🕙 вы оба уделяете всё внимание ⏯ задаче "ждать на кассе".
+
+В этом ресторане быстрого питания 8 исполнителей (кассиров/поваров) 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳.
+Хотя в бургерной конкурентного типа было всего два (один кассир и один повар) 💁 👨🍳.
+
+Несмотря на обилие работников, опыт в итоге получился не из лучших 😞.
+
+---
+
+Так бы выглядел аналог истории про бургерную 🍔 в "параллельном" мире.
+
+Вот более реалистичный пример. Представьте себе банк.
+
+До недавних пор в большинстве банков было несколько кассиров 👨💼👨💼👨💼👨💼 и длинные очереди 🕙🕙🕙🕙🕙🕙🕙🕙.
+
+Каждый кассир обслуживал одного клиента, потом следующего 👨💼⏯.
+
+Нужно было долгое время 🕙 стоять перед окошком вместе со всеми, иначе пропустишь свою очередь.
+
+Сомневаюсь, что у вас бы возникло желание прийти с возлюбленной 😍 в банк 🏦 оплачивать налоги.
+
+### Выводы о бургерах
+
+В нашей истории про поход в фастфуд за бургерами приходится много ждать 🕙,
+поэтому имеет смысл организовать конкурентную систему ⏸🔀⏯.
+
+И то же самое с большинством веб-приложений.
+
+Пользователей очень много, но ваш сервер всё равно вынужден ждать 🕙 запросы по их слабому интернет-соединению.
+
+Потом снова ждать 🕙, пока вернётся ответ.
+
+
+Это ожидание 🕙 измеряется микросекундами, но если всё сложить, то набегает довольно много времени.
+
+Вот почему есть смысл использовать асинхронное ⏸🔀⏯ программирование при построении веб-API.
+
+Большинство популярных фреймворков (включая Flask и Django) создавались
+до появления в Python новых возможностей асинхронного программирования. Поэтому
+их можно разворачивать с поддержкой параллельного исполнения или асинхронного
+программирования старого типа, которое не настолько эффективно.
+
+При том, что основная спецификация асинхронного взаимодействия Python с веб-сервером
+(ASGI)
+была разработана командой Django для внедрения поддержки веб-сокетов.
+
+Именно асинхронность сделала NodeJS таким популярным (несмотря на то, что он не параллельный),
+и в этом преимущество Go как языка программирования.
+
+И тот же уровень производительности даёт **FastAPI**.
+
+Поскольку можно использовать преимущества параллелизма и асинхронности вместе,
+вы получаете производительность лучше, чем у большинства протестированных NodeJS фреймворков
+и на уровне с Go, который является компилируемым языком близким к C (всё благодаря Starlette).
+
+### Получается, конкурентность лучше параллелизма?
+
+Нет! Мораль истории совсем не в этом.
+
+Конкурентность отличается от параллелизма. Она лучше в **конкретных** случаях, где много времени приходится на ожидание.
+Вот почему она зачастую лучше параллелизма при разработке веб-приложений. Но это не значит, что конкурентность лучше в любых сценариях.
+
+Давайте посмотрим с другой стороны, представьте такую картину:
+
+> Вам нужно убраться в большом грязном доме.
+
+*Да, это вся история*.
+
+---
+
+Тут не нужно нигде ждать 🕙, просто есть куча работы в разных частях дома.
+
+Можно организовать очередь как в примере с бургерами, сначала гостиная, потом кухня,
+но это ни на что не повлияет, поскольку вы нигде не ждёте 🕙, а просто трёте да моете.
+
+И понадобится одинаковое количество времени с очередью (конкурентностью) и без неё,
+и работы будет сделано тоже одинаковое количество.
+
+Однако в случае, если бы вы могли привести 8 бывших кассиров/поваров, а ныне уборщиков 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳,
+и каждый из них (вместе с вами) взялся бы за свой участок дома,
+с такой помощью вы бы закончили намного быстрее, делая всю работу **параллельно**.
+
+В описанном сценарии каждый уборщик (включая вас) был бы исполнителем, занятым на своём участке работы.
+
+И поскольку большую часть времени выполнения занимает реальная работа (а не ожидание),
+а работу в компьютере делает ЦП,
+такие задачи называют ограниченными производительностью процессора.
+
+---
+
+Ограничение по процессору проявляется в операциях, где требуется выполнять сложные математические вычисления.
+
+Например:
+
+* Обработка **звука** или **изображений**.
+* **Компьютерное зрение**: изображение состоит из миллионов пикселей, в каждом пикселе 3 составляющих цвета,
+обработка обычно требует проведения расчётов по всем пикселям сразу.
+* **Машинное обучение**: здесь обычно требуется умножение "матриц" и "векторов".
+Представьте гигантскую таблицу с числами в Экселе, и все их надо одновременно перемножить.
+* **Глубокое обучение**: это область *машинного обучения*, поэтому сюда подходит то же описание.
+Просто у вас будет не одна таблица в Экселе, а множество. В ряде случаев используется
+специальный процессор для создания и / или использования построенных таким образом моделей.
+
+### Конкурентность + параллелизм: Веб + машинное обучение
+
+**FastAPI** предоставляет возможности конкуретного программирования,
+которое очень распространено в веб-разработке (именно этим славится NodeJS).
+
+Кроме того вы сможете использовать все преимущества параллелизма и
+многопроцессорности (когда несколько процессов работают параллельно),
+если рабочая нагрузка предполагает **ограничение по процессору**,
+как, например, в системах машинного обучения.
+
+Необходимо также отметить, что Python является главным языком в области
+**дата-сайенс**,
+машинного обучения и, особенно, глубокого обучения. Всё это делает FastAPI
+отличным вариантом (среди многих других) для разработки веб-API и приложений
+в области дата-сайенс / машинного обучения.
+
+Как добиться такого параллелизма в эксплуатации описано в разделе [Развёртывание](deployment/index.md){.internal-link target=_blank}.
+
+## `async` и `await`
+
+В современных версиях Python разработка асинхронного кода реализована очень интуитивно.
+Он выглядит как обычный "последовательный" код и самостоятельно выполняет "ожидание", когда это необходимо.
+
+Если некая операция требует ожидания перед тем, как вернуть результат, и
+поддерживает современные возможности Python, код можно написать следующим образом:
+
+```Python
+burgers = await get_burgers(2)
+```
+
+Главное здесь слово `await`. Оно сообщает интерпретатору, что необходимо дождаться ⏸
+пока `get_burgers(2)` закончит свои дела 🕙, и только после этого сохранить результат в `burgers`.
+Зная это, Python может пока переключиться на выполнение других задач 🔀 ⏯
+(например получение следующего запроса).
+
+Чтобы ключевое слово `await` сработало, оно должно находиться внутри функции,
+которая поддерживает асинхронность. Для этого вам просто нужно объявить её как `async def`:
+
+```Python hl_lines="1"
+async def get_burgers(number: int):
+ # Готовим бургеры по специальному асинхронному рецепту
+ return burgers
+```
+
+...вместо `def`:
+
+```Python hl_lines="2"
+# Это не асинхронный код
+def get_sequential_burgers(number: int):
+ # Готовим бургеры последовательно по шагам
+ return burgers
+```
+
+Объявление `async def` указывает интерпретатору, что внутри этой функции
+следует ожидать выражений `await`, и что можно поставить выполнение такой функции на "паузу" ⏸ и
+переключиться на другие задачи 🔀, с тем чтобы вернуться сюда позже.
+
+Если вы хотите вызвать функцию с `async def`, вам нужно "ожидать" её.
+Поэтому такое не сработает:
+
+```Python
+# Это не заработает, поскольку get_burgers объявлена с использованием async def
+burgers = get_burgers(2)
+```
+
+---
+
+Если сторонняя библиотека требует вызывать её с ключевым словом `await`,
+необходимо писать *функции обработки пути* с использованием `async def`, например:
+
+```Python hl_lines="2-3"
+@app.get('/burgers')
+async def read_burgers():
+ burgers = await get_burgers(2)
+ return burgers
+```
+
+### Технические подробности
+
+Как вы могли заметить, `await` может применяться только в функциях, объявленных с использованием `async def`.
+
+
+Но выполнение такой функции необходимо "ожидать" с помощью `await`.
+Это означает, что её можно вызвать только из другой функции, которая тоже объявлена с `async def`.
+
+Но как же тогда появилась первая курица? В смысле... как нам вызвать первую асинхронную функцию?
+
+При работе с **FastAPI** просто не думайте об этом, потому что "первой" функцией является ваша *функция обработки пути*,
+и дальше с этим разберётся FastAPI.
+
+Кроме того, если хотите, вы можете использовать синтаксис `async` / `await` и без FastAPI.
+
+### Пишите свой асинхронный код
+
+Starlette (и **FastAPI**) основаны на AnyIO, что делает их совместимыми как со стандартной библиотекой asyncio в Python, так и с Trio.
+
+В частности, вы можете напрямую использовать AnyIO в тех проектах, где требуется более сложная логика работы с конкурентностью.
+
+Даже если вы не используете FastAPI, вы можете писать асинхронные приложения с помощью AnyIO, чтобы они были максимально совместимыми и получали его преимущества (например *структурную конкурентность*).
+
+### Другие виды асинхронного программирования
+
+Стиль написания кода с `async` и `await` появился в языке Python относительно недавно.
+
+Но он сильно облегчает работу с асинхронным кодом.
+
+Ровно такой же синтаксис (ну или почти такой же) недавно был включён в современные версии JavaScript (в браузере и NodeJS).
+
+До этого поддержка асинхронного кода была реализована намного сложнее, и его было труднее воспринимать.
+
+В предыдущих версиях Python для этого использовались потоки или Gevent. Но такой код намного сложнее понимать, отлаживать и мысленно представлять.
+
+Что касается JavaScript (в браузере и NodeJS), раньше там использовали для этой цели
+"обратные вызовы". Что выливалось в
+ад обратных вызовов.
+
+## Сопрограммы
+
+**Корути́на** (или же сопрограмма) — это крутое словечко для именования той сущности,
+которую возвращает функция `async def`. Python знает, что её можно запустить, как и обычную функцию,
+но кроме того сопрограмму можно поставить на паузу ⏸ в том месте, где встретится слово `await`.
+
+Всю функциональность асинхронного программирования с использованием `async` и `await`
+часто обобщают словом "корутины". Они аналогичны "горутинам", ключевой особенности
+языка Go.
+
+## Заключение
+
+В самом начале была такая фраза:
+
+> Современные версии Python поддерживают разработку так называемого
+**"асинхронного кода"** посредством написания **"сопрограмм"** с использованием
+синтаксиса **`async` и `await`**.
+
+Теперь всё должно звучать понятнее. ✨
+
+На этом основана работа FastAPI (посредством Starlette), и именно это
+обеспечивает его высокую производительность.
+
+## Очень технические подробности
+
+!!! warning
+ Этот раздел читать не обязательно.
+
+ Здесь приводятся подробности внутреннего устройства **FastAPI**.
+
+ Но если вы обладаете техническими знаниями (корутины, потоки, блокировка и т. д.)
+ и вам интересно, как FastAPI обрабатывает `async def` в отличие от обычных `def`,
+ читайте дальше.
+
+### Функции обработки пути
+
+Когда вы объявляете *функцию обработки пути* обычным образом с ключевым словом `def`
+вместо `async def`, FastAPI ожидает её выполнения, запустив функцию во внешнем
+пуле потоков, а не напрямую (это бы заблокировало сервер).
+
+Если ранее вы использовали другой асинхронный фреймворк, который работает иначе,
+и привыкли объявлять простые вычислительные *функции* через `def` ради
+незначительного прироста скорости (порядка 100 наносекунд), обратите внимание,
+что с **FastAPI** вы получите противоположный эффект. В таком случае больше подходит
+`async def`, если только *функция обработки пути* не использует код, приводящий
+к блокировке I/O.
+
+
+
+Но в любом случае велика вероятность, что **FastAPI** [окажется быстрее](/#performance){.internal-link target=_blank}
+другого фреймворка (или хотя бы на уровне с ним).
+
+### Зависимости
+
+То же относится к зависимостям. Если это обычная функция `def`, а не `async def`,
+она запускается во внешнем пуле потоков.
+
+### Подзависимости
+
+Вы можете объявить множество ссылающихся друг на друга зависимостей и подзависимостей
+(в виде параметров при определении функции). Какие-то будут созданы с помощью `async def`,
+другие обычным образом через `def`, и такая схема вполне работоспособна. Функции,
+объявленные с помощью `def` будут запускаться на внешнем потоке (из пула),
+а не с помощью `await`.
+
+### Другие служебные функции
+
+Любые другие служебные функции, которые вы вызываете напрямую, можно объявлять
+с использованием `def` или `async def`. FastAPI не будет влиять на то, как вы
+их запускаете.
+
+Этим они отличаются от функций, которые FastAPI вызывает самостоятельно:
+*функции обработки пути* и зависимости.
+
+Если служебная функция объявлена с помощью `def`, она будет вызвана напрямую
+(как вы и написали в коде), а не в отдельном потоке. Если же она объявлена с
+помощью `async def`, её вызов должен осуществляться с ожиданием через `await`.
+
+---
+
+
+Ещё раз повторим, что все эти технические подробности полезны, только если вы специально их искали.
+
+В противном случае просто ознакомьтесь с основными принципами в разделе выше: Нет времени?.
diff --git a/docs/ru/docs/index.md b/docs/ru/docs/index.md
index 0c2506b8..c0a958c3 100644
--- a/docs/ru/docs/index.md
+++ b/docs/ru/docs/index.md
@@ -447,7 +447,6 @@ Used by Pydantic:
Used by Starlette:
* requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml
index 6e17c287..0f8f0041 100644
--- a/docs/ru/mkdocs.yml
+++ b/docs/ru/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -52,6 +54,7 @@ nav:
- tr: /tr/
- uk: /uk/
- zh: /zh/
+- async.md
markdown_extensions:
- toc:
permalink: true
@@ -97,6 +100,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -107,6 +112,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs/sq/docs/index.md b/docs/sq/docs/index.md
index 95fb7ae2..a7af1478 100644
--- a/docs/sq/docs/index.md
+++ b/docs/sq/docs/index.md
@@ -447,7 +447,6 @@ Used by Pydantic:
Used by Starlette:
* requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/sq/mkdocs.yml b/docs/sq/mkdocs.yml
index d9c3dad4..a61f49bc 100644
--- a/docs/sq/mkdocs.yml
+++ b/docs/sq/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -97,6 +99,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -107,6 +111,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs/tr/docs/index.md b/docs/tr/docs/index.md
index 88660f7e..19f46fb4 100644
--- a/docs/tr/docs/index.md
+++ b/docs/tr/docs/index.md
@@ -455,7 +455,6 @@ Pydantic tarafında kullanılan:
Starlette tarafında kullanılan:
* requests - Eğer `TestClient` kullanmak istiyorsan gerekli.
-* aiofiles - `FileResponse` ya da `StaticFiles` kullanmak istiyorsan gerekli.
* jinja2 - Eğer kendine ait template konfigürasyonu oluşturmak istiyorsan gerekli
* python-multipart - Form kullanmak istiyorsan gerekli ("dönüşümü").
* itsdangerous - `SessionMiddleware` desteği için gerekli.
diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml
index f6ed7f5b..dd52d7fc 100644
--- a/docs/tr/mkdocs.yml
+++ b/docs/tr/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -100,6 +102,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -110,6 +114,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs/uk/docs/index.md b/docs/uk/docs/index.md
index 95fb7ae2..a7af1478 100644
--- a/docs/uk/docs/index.md
+++ b/docs/uk/docs/index.md
@@ -447,7 +447,6 @@ Used by Pydantic:
Used by Starlette:
* requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/uk/mkdocs.yml b/docs/uk/mkdocs.yml
index d0de8cc0..971a182d 100644
--- a/docs/uk/mkdocs.yml
+++ b/docs/uk/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -97,6 +99,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -107,6 +111,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs/zh/docs/fastapi-people.md b/docs/zh/docs/fastapi-people.md
index 75651592..5d7b0923 100644
--- a/docs/zh/docs/fastapi-people.md
+++ b/docs/zh/docs/fastapi-people.md
@@ -114,6 +114,8 @@ FastAPI 有一个非常棒的社区,它欢迎来自各个领域和背景的朋
他们主要通过GitHub Sponsors支持我在 **FastAPI** (和其他项目)的工作。
+{% if sponsors %}
+
{% if sponsors.gold %}
### 金牌赞助商
@@ -141,6 +143,8 @@ FastAPI 有一个非常棒的社区,它欢迎来自各个领域和背景的朋
{% endfor %}
{% endif %}
+{% endif %}
+
### 个人赞助
{% if github_sponsors %}
diff --git a/docs/zh/docs/index.md b/docs/zh/docs/index.md
index 85707e57..20755283 100644
--- a/docs/zh/docs/index.md
+++ b/docs/zh/docs/index.md
@@ -443,7 +443,6 @@ item: Item
用于 Starlette:
* requests - 使用 `TestClient` 时安装。
-* aiofiles - 使用 `FileResponse` 或 `StaticFiles` 时安装。
* jinja2 - 使用默认模板配置时安装。
* python-multipart - 需要通过 `request.form()` 对表单进行「解析」时安装。
* itsdangerous - 需要 `SessionMiddleware` 支持时安装。
diff --git a/docs/zh/docs/tutorial/query-params-str-validations.md b/docs/zh/docs/tutorial/query-params-str-validations.md
index 2a1d41a8..1d1d383d 100644
--- a/docs/zh/docs/tutorial/query-params-str-validations.md
+++ b/docs/zh/docs/tutorial/query-params-str-validations.md
@@ -26,7 +26,7 @@
现在,将 `Query` 用作查询参数的默认值,并将它的 `max_length` 参数设置为 50:
-```Python hl_lines="7"
+```Python hl_lines="9"
{!../../../docs_src/query_params_str_validations/tutorial002.py!}
```
@@ -58,7 +58,7 @@ q: str = Query(None, max_length=50)
你还可以添加 `min_length` 参数:
-```Python hl_lines="7"
+```Python hl_lines="9"
{!../../../docs_src/query_params_str_validations/tutorial003.py!}
```
@@ -66,7 +66,7 @@ q: str = Query(None, max_length=50)
你可以定义一个参数值必须匹配的正则表达式:
-```Python hl_lines="8"
+```Python hl_lines="10"
{!../../../docs_src/query_params_str_validations/tutorial004.py!}
```
@@ -211,13 +211,13 @@ http://localhost:8000/items/
你可以添加 `title`:
-```Python hl_lines="7"
+```Python hl_lines="10"
{!../../../docs_src/query_params_str_validations/tutorial007.py!}
```
以及 `description`:
-```Python hl_lines="11"
+```Python hl_lines="13"
{!../../../docs_src/query_params_str_validations/tutorial008.py!}
```
@@ -239,7 +239,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
这时你可以用 `alias` 参数声明一个别名,该别名将用于在 URL 中查找查询参数值:
-```Python hl_lines="7"
+```Python hl_lines="9"
{!../../../docs_src/query_params_str_validations/tutorial009.py!}
```
@@ -251,7 +251,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
那么将参数 `deprecated=True` 传入 `Query`:
-```Python hl_lines="16"
+```Python hl_lines="18"
{!../../../docs_src/query_params_str_validations/tutorial010.py!}
```
diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml
index 1d050fdd..40816648 100644
--- a/docs/zh/mkdocs.yml
+++ b/docs/zh/mkdocs.yml
@@ -40,11 +40,13 @@ nav:
- az: /az/
- de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
- id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -148,6 +150,8 @@ extra:
name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
- link: /id/
@@ -158,6 +162,8 @@ extra:
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
diff --git a/docs_src/generate_clients/tutorial001.py b/docs_src/generate_clients/tutorial001.py
new file mode 100644
index 00000000..2d1f91bc
--- /dev/null
+++ b/docs_src/generate_clients/tutorial001.py
@@ -0,0 +1,28 @@
+from typing import List
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+@app.post("/items/", response_model=ResponseMessage)
+async def create_item(item: Item):
+ return {"message": "item received"}
+
+
+@app.get("/items/", response_model=List[Item])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
diff --git a/docs_src/generate_clients/tutorial001_py39.py b/docs_src/generate_clients/tutorial001_py39.py
new file mode 100644
index 00000000..6a5ae232
--- /dev/null
+++ b/docs_src/generate_clients/tutorial001_py39.py
@@ -0,0 +1,26 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+@app.post("/items/", response_model=ResponseMessage)
+async def create_item(item: Item):
+ return {"message": "item received"}
+
+
+@app.get("/items/", response_model=list[Item])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
diff --git a/docs_src/generate_clients/tutorial002.py b/docs_src/generate_clients/tutorial002.py
new file mode 100644
index 00000000..bd80449a
--- /dev/null
+++ b/docs_src/generate_clients/tutorial002.py
@@ -0,0 +1,38 @@
+from typing import List
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+class User(BaseModel):
+ username: str
+ email: str
+
+
+@app.post("/items/", response_model=ResponseMessage, tags=["items"])
+async def create_item(item: Item):
+ return {"message": "Item received"}
+
+
+@app.get("/items/", response_model=List[Item], tags=["items"])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
+
+
+@app.post("/users/", response_model=ResponseMessage, tags=["users"])
+async def create_user(user: User):
+ return {"message": "User received"}
diff --git a/docs_src/generate_clients/tutorial002_py39.py b/docs_src/generate_clients/tutorial002_py39.py
new file mode 100644
index 00000000..83309760
--- /dev/null
+++ b/docs_src/generate_clients/tutorial002_py39.py
@@ -0,0 +1,36 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+class User(BaseModel):
+ username: str
+ email: str
+
+
+@app.post("/items/", response_model=ResponseMessage, tags=["items"])
+async def create_item(item: Item):
+ return {"message": "Item received"}
+
+
+@app.get("/items/", response_model=list[Item], tags=["items"])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
+
+
+@app.post("/users/", response_model=ResponseMessage, tags=["users"])
+async def create_user(user: User):
+ return {"message": "User received"}
diff --git a/docs_src/generate_clients/tutorial003.py b/docs_src/generate_clients/tutorial003.py
new file mode 100644
index 00000000..49eab73a
--- /dev/null
+++ b/docs_src/generate_clients/tutorial003.py
@@ -0,0 +1,44 @@
+from typing import List
+
+from fastapi import FastAPI
+from fastapi.routing import APIRoute
+from pydantic import BaseModel
+
+
+def custom_generate_unique_id(route: APIRoute):
+ return f"{route.tags[0]}-{route.name}"
+
+
+app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+class User(BaseModel):
+ username: str
+ email: str
+
+
+@app.post("/items/", response_model=ResponseMessage, tags=["items"])
+async def create_item(item: Item):
+ return {"message": "Item received"}
+
+
+@app.get("/items/", response_model=List[Item], tags=["items"])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
+
+
+@app.post("/users/", response_model=ResponseMessage, tags=["users"])
+async def create_user(user: User):
+ return {"message": "User received"}
diff --git a/docs_src/generate_clients/tutorial003_py39.py b/docs_src/generate_clients/tutorial003_py39.py
new file mode 100644
index 00000000..40722cf1
--- /dev/null
+++ b/docs_src/generate_clients/tutorial003_py39.py
@@ -0,0 +1,42 @@
+from fastapi import FastAPI
+from fastapi.routing import APIRoute
+from pydantic import BaseModel
+
+
+def custom_generate_unique_id(route: APIRoute):
+ return f"{route.tags[0]}-{route.name}"
+
+
+app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+class User(BaseModel):
+ username: str
+ email: str
+
+
+@app.post("/items/", response_model=ResponseMessage, tags=["items"])
+async def create_item(item: Item):
+ return {"message": "Item received"}
+
+
+@app.get("/items/", response_model=list[Item], tags=["items"])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
+
+
+@app.post("/users/", response_model=ResponseMessage, tags=["users"])
+async def create_user(user: User):
+ return {"message": "User received"}
diff --git a/docs_src/generate_clients/tutorial004.py b/docs_src/generate_clients/tutorial004.py
new file mode 100644
index 00000000..894dc7f8
--- /dev/null
+++ b/docs_src/generate_clients/tutorial004.py
@@ -0,0 +1,15 @@
+import json
+from pathlib import Path
+
+file_path = Path("./openapi.json")
+openapi_content = json.loads(file_path.read_text())
+
+for path_data in openapi_content["paths"].values():
+ for operation in path_data.values():
+ tag = operation["tags"][0]
+ operation_id = operation["operationId"]
+ to_remove = f"{tag}-"
+ new_operation_id = operation_id[len(to_remove) :]
+ operation["operationId"] = new_operation_id
+
+file_path.write_text(json.dumps(openapi_content))
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index 8718788f..1a4d0016 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.73.0"
+__version__ = "0.77.1"
from starlette import status as status
diff --git a/fastapi/applications.py b/fastapi/applications.py
index dbfd76fb..7530ddb9 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -1,8 +1,18 @@
from enum import Enum
-from typing import Any, Callable, Coroutine, Dict, List, Optional, Sequence, Type, Union
+from typing import (
+ Any,
+ Awaitable,
+ Callable,
+ Coroutine,
+ Dict,
+ List,
+ Optional,
+ Sequence,
+ Type,
+ Union,
+)
from fastapi import routing
-from fastapi.concurrency import AsyncExitStack
from fastapi.datastructures import Default, DefaultPlaceholder
from fastapi.encoders import DictIntStrAny, SetIntStr
from fastapi.exception_handlers import (
@@ -11,6 +21,7 @@ from fastapi.exception_handlers import (
)
from fastapi.exceptions import RequestValidationError
from fastapi.logger import logger
+from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_html,
@@ -19,10 +30,12 @@ from fastapi.openapi.docs import (
from fastapi.openapi.utils import get_openapi
from fastapi.params import Depends
from fastapi.types import DecoratedCallable
+from fastapi.utils import generate_unique_id
from starlette.applications import Starlette
from starlette.datastructures import State
-from starlette.exceptions import HTTPException
+from starlette.exceptions import ExceptionMiddleware, HTTPException
from starlette.middleware import Middleware
+from starlette.middleware.errors import ServerErrorMiddleware
from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse, Response
from starlette.routing import BaseRoute
@@ -67,10 +80,44 @@ class FastAPI(Starlette):
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
swagger_ui_parameters: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
**extra: Any,
) -> None:
self._debug: bool = debug
+ self.title = title
+ self.description = description
+ self.version = version
+ self.terms_of_service = terms_of_service
+ self.contact = contact
+ self.license_info = license_info
+ self.openapi_url = openapi_url
+ self.openapi_tags = openapi_tags
+ self.root_path_in_servers = root_path_in_servers
+ self.docs_url = docs_url
+ self.redoc_url = redoc_url
+ self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
+ self.swagger_ui_init_oauth = swagger_ui_init_oauth
+ self.swagger_ui_parameters = swagger_ui_parameters
+ self.servers = servers or []
+ self.extra = extra
+ self.openapi_version = "3.0.2"
+ self.openapi_schema: Optional[Dict[str, Any]] = None
+ if self.openapi_url:
+ assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'"
+ assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'"
+ # TODO: remove when discarding the openapi_prefix parameter
+ if openapi_prefix:
+ logger.warning(
+ '"openapi_prefix" has been deprecated in favor of "root_path", which '
+ "follows more closely the ASGI standard, is simpler, and more "
+ "automatic. Check the docs at "
+ "https://fastapi.tiangolo.com/advanced/sub-applications/"
+ )
+ self.root_path = root_path or openapi_prefix
self.state: State = State()
+ self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {}
self.router: routing.APIRouter = routing.APIRouter(
routes=routes,
dependency_overrides_provider=self,
@@ -82,13 +129,11 @@ class FastAPI(Starlette):
deprecated=deprecated,
include_in_schema=include_in_schema,
responses=responses,
+ generate_unique_id_function=generate_unique_id_function,
)
self.exception_handlers: Dict[
- Union[int, Type[Exception]],
- Callable[[Request, Any], Coroutine[Any, Any, Response]],
- ] = (
- {} if exception_handlers is None else dict(exception_handlers)
- )
+ Any, Callable[[Request, Any], Union[Response, Awaitable[Response]]]
+ ] = ({} if exception_handlers is None else dict(exception_handlers))
self.exception_handlers.setdefault(HTTPException, http_exception_handler)
self.exception_handlers.setdefault(
RequestValidationError, request_validation_exception_handler
@@ -98,42 +143,57 @@ class FastAPI(Starlette):
[] if middleware is None else list(middleware)
)
self.middleware_stack: ASGIApp = self.build_middleware_stack()
-
- self.title = title
- self.description = description
- self.version = version
- self.terms_of_service = terms_of_service
- self.contact = contact
- self.license_info = license_info
- self.servers = servers or []
- self.openapi_url = openapi_url
- self.openapi_tags = openapi_tags
- # TODO: remove when discarding the openapi_prefix parameter
- if openapi_prefix:
- logger.warning(
- '"openapi_prefix" has been deprecated in favor of "root_path", which '
- "follows more closely the ASGI standard, is simpler, and more "
- "automatic. Check the docs at "
- "https://fastapi.tiangolo.com/advanced/sub-applications/"
- )
- self.root_path = root_path or openapi_prefix
- self.root_path_in_servers = root_path_in_servers
- self.docs_url = docs_url
- self.redoc_url = redoc_url
- self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
- self.swagger_ui_init_oauth = swagger_ui_init_oauth
- self.swagger_ui_parameters = swagger_ui_parameters
- self.extra = extra
- self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {}
-
- self.openapi_version = "3.0.2"
-
- if self.openapi_url:
- assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'"
- assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'"
- self.openapi_schema: Optional[Dict[str, Any]] = None
self.setup()
+ def build_middleware_stack(self) -> ASGIApp:
+ # Duplicate/override from Starlette to add AsyncExitStackMiddleware
+ # inside of ExceptionMiddleware, inside of custom user middlewares
+ debug = self.debug
+ error_handler = None
+ exception_handlers = {}
+
+ for key, value in self.exception_handlers.items():
+ if key in (500, Exception):
+ error_handler = value
+ else:
+ exception_handlers[key] = value
+
+ middleware = (
+ [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
+ + self.user_middleware
+ + [
+ Middleware(
+ ExceptionMiddleware, handlers=exception_handlers, debug=debug
+ ),
+ # Add FastAPI-specific AsyncExitStackMiddleware for dependencies with
+ # 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
+ # 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.
+ # Additionally, by having this AsyncExitStack here, after the
+ # ExceptionMiddleware, now dependencies can catch handled exceptions,
+ # e.g. HTTPException, to customize the teardown code (e.g. DB session
+ # rollback).
+ Middleware(AsyncExitStackMiddleware),
+ ]
+ )
+
+ app = self.router
+ for cls, options in reversed(middleware):
+ app = cls(app=app, **options)
+ return app
+
def openapi(self) -> Dict[str, Any]:
if not self.openapi_schema:
self.openapi_schema = get_openapi(
@@ -206,12 +266,7 @@ class FastAPI(Starlette):
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if self.root_path:
scope["root_path"] = self.root_path
- if AsyncExitStack:
- async with AsyncExitStack() as stack:
- scope["fastapi_astack"] = stack
- await super().__call__(scope, receive, send)
- else:
- await super().__call__(scope, receive, send) # pragma: no cover
+ await super().__call__(scope, receive, send)
def add_api_route(
self,
@@ -241,6 +296,9 @@ class FastAPI(Starlette):
),
name: Optional[str] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> None:
self.router.add_api_route(
path,
@@ -266,6 +324,7 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def api_route(
@@ -293,6 +352,9 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
def decorator(func: DecoratedCallable) -> DecoratedCallable:
self.router.add_api_route(
@@ -319,6 +381,7 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
return func
@@ -350,6 +413,9 @@ class FastAPI(Starlette):
include_in_schema: bool = True,
default_response_class: Type[Response] = Default(JSONResponse),
callbacks: Optional[List[BaseRoute]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> None:
self.router.include_router(
router,
@@ -361,6 +427,7 @@ class FastAPI(Starlette):
include_in_schema=include_in_schema,
default_response_class=default_response_class,
callbacks=callbacks,
+ generate_unique_id_function=generate_unique_id_function,
)
def get(
@@ -388,6 +455,9 @@ class FastAPI(Starlette):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.get(
path,
@@ -412,6 +482,7 @@ class FastAPI(Starlette):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def put(
@@ -439,6 +510,9 @@ class FastAPI(Starlette):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.put(
path,
@@ -463,6 +537,7 @@ class FastAPI(Starlette):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def post(
@@ -490,6 +565,9 @@ class FastAPI(Starlette):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.post(
path,
@@ -514,6 +592,7 @@ class FastAPI(Starlette):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def delete(
@@ -541,6 +620,9 @@ class FastAPI(Starlette):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.delete(
path,
@@ -565,6 +647,7 @@ class FastAPI(Starlette):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def options(
@@ -592,6 +675,9 @@ class FastAPI(Starlette):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.options(
path,
@@ -616,6 +702,7 @@ class FastAPI(Starlette):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def head(
@@ -643,6 +730,9 @@ class FastAPI(Starlette):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.head(
path,
@@ -667,6 +757,7 @@ class FastAPI(Starlette):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def patch(
@@ -694,6 +785,9 @@ class FastAPI(Starlette):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.patch(
path,
@@ -718,6 +812,7 @@ class FastAPI(Starlette):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def trace(
@@ -745,6 +840,9 @@ class FastAPI(Starlette):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.trace(
path,
@@ -769,4 +867,5 @@ class FastAPI(Starlette):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
diff --git a/fastapi/concurrency.py b/fastapi/concurrency.py
index 04382c69..becac3f3 100644
--- a/fastapi/concurrency.py
+++ b/fastapi/concurrency.py
@@ -25,7 +25,7 @@ async def contextmanager_in_threadpool(
try:
yield await run_in_threadpool(cm.__enter__)
except Exception as e:
- ok = await run_in_threadpool(cm.__exit__, type(e), e, None)
+ ok: bool = await run_in_threadpool(cm.__exit__, type(e), e, None)
if not ok:
raise e
else:
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index d4028d06..9dccd354 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -462,13 +462,10 @@ async def solve_dependencies(
]:
values: Dict[str, Any] = {}
errors: List[ErrorWrapper] = []
- response = response or Response(
- content=None,
- status_code=None, # type: ignore
- headers=None, # type: ignore # in Starlette
- media_type=None, # type: ignore # in Starlette
- background=None, # type: ignore # in Starlette
- )
+ if response is None:
+ response = Response()
+ del response.headers["content-length"]
+ response.status_code = None # type: ignore
dependency_cache = dependency_cache or {}
sub_dependant: Dependant
for sub_dependant in dependant.dependencies:
diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py
index f4a837bb..0f50acc6 100644
--- a/fastapi/exceptions.py
+++ b/fastapi/exceptions.py
@@ -12,8 +12,7 @@ class HTTPException(StarletteHTTPException):
detail: Any = None,
headers: Optional[Dict[str, Any]] = None,
) -> None:
- super().__init__(status_code=status_code, detail=detail)
- self.headers = headers
+ super().__init__(status_code=status_code, detail=detail, headers=headers)
RequestErrorModel: Type[BaseModel] = create_model("Request")
diff --git a/fastapi/middleware/asyncexitstack.py b/fastapi/middleware/asyncexitstack.py
new file mode 100644
index 00000000..503a68ac
--- /dev/null
+++ b/fastapi/middleware/asyncexitstack.py
@@ -0,0 +1,28 @@
+from typing import Optional
+
+from fastapi.concurrency import AsyncExitStack
+from starlette.types import ASGIApp, Receive, Scope, Send
+
+
+class AsyncExitStackMiddleware:
+ def __init__(self, app: ASGIApp, context_name: str = "fastapi_astack") -> None:
+ self.app = app
+ self.context_name = context_name
+
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
+ if AsyncExitStack:
+ dependency_exception: Optional[Exception] = None
+ async with AsyncExitStack() as stack:
+ scope[self.context_name] = stack
+ try:
+ await self.app(scope, receive, send)
+ except Exception as e:
+ dependency_exception = e
+ raise e
+ if dependency_exception:
+ # This exception was possibly handled by the dependency but it should
+ # still bubble up so that the ServerErrorMiddleware can return a 500
+ # or the ExceptionMiddleware can catch and handle any other exceptions
+ raise dependency_exception
+ else:
+ await self.app(scope, receive, send) # pragma: no cover
diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py
index 1be90d18..d6af17a8 100644
--- a/fastapi/openapi/docs.py
+++ b/fastapi/openapi/docs.py
@@ -17,8 +17,8 @@ def get_swagger_ui_html(
*,
openapi_url: str,
title: str,
- swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js",
- swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css",
+ swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui-bundle.js",
+ swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui.css",
swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
oauth2_redirect_url: Optional[str] = None,
init_oauth: Optional[Dict[str, Any]] = None,
diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
index aff76b15..4eb727bd 100644
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -1,5 +1,6 @@
import http.client
import inspect
+import warnings
from enum import Enum
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, cast
@@ -37,7 +38,11 @@ validation_error_definition = {
"title": "ValidationError",
"type": "object",
"properties": {
- "loc": {"title": "Location", "type": "array", "items": {"type": "string"}},
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
@@ -140,7 +145,15 @@ def get_openapi_operation_request_body(
return request_body_oai
-def generate_operation_id(*, route: routing.APIRoute, method: str) -> str:
+def generate_operation_id(
+ *, route: routing.APIRoute, method: str
+) -> str: # pragma: nocover
+ warnings.warn(
+ "fastapi.openapi.utils.generate_operation_id() was deprecated, "
+ "it is not used internally, and will be removed soon",
+ DeprecationWarning,
+ stacklevel=2,
+ )
if route.operation_id:
return route.operation_id
path: str = route.path_format
@@ -154,7 +167,7 @@ def generate_operation_summary(*, route: routing.APIRoute, method: str) -> str:
def get_openapi_operation_metadata(
- *, route: routing.APIRoute, method: str
+ *, route: routing.APIRoute, method: str, operation_ids: Set[str]
) -> Dict[str, Any]:
operation: Dict[str, Any] = {}
if route.tags:
@@ -162,14 +175,25 @@ def get_openapi_operation_metadata(
operation["summary"] = generate_operation_summary(route=route, method=method)
if route.description:
operation["description"] = route.description
- operation["operationId"] = generate_operation_id(route=route, method=method)
+ operation_id = route.operation_id or route.unique_id
+ if operation_id in operation_ids:
+ message = (
+ f"Duplicate Operation ID {operation_id} for function "
+ + f"{route.endpoint.__name__}"
+ )
+ file_name = getattr(route.endpoint, "__globals__", {}).get("__file__")
+ if file_name:
+ message += f" at {file_name}"
+ warnings.warn(message)
+ operation_ids.add(operation_id)
+ operation["operationId"] = operation_id
if route.deprecated:
operation["deprecated"] = route.deprecated
return operation
def get_openapi_path(
- *, route: routing.APIRoute, model_name_map: Dict[type, str]
+ *, route: routing.APIRoute, model_name_map: Dict[type, str], operation_ids: Set[str]
) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]:
path = {}
security_schemes: Dict[str, Any] = {}
@@ -183,7 +207,9 @@ def get_openapi_path(
route_response_media_type: Optional[str] = current_response_class.media_type
if route.include_in_schema:
for method in route.methods:
- operation = get_openapi_operation_metadata(route=route, method=method)
+ operation = get_openapi_operation_metadata(
+ route=route, method=method, operation_ids=operation_ids
+ )
parameters: List[Dict[str, Any]] = []
flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True)
security_definitions, operation_security = get_openapi_security_definitions(
@@ -217,7 +243,9 @@ def get_openapi_path(
cb_security_schemes,
cb_definitions,
) = get_openapi_path(
- route=callback, model_name_map=model_name_map
+ route=callback,
+ model_name_map=model_name_map,
+ operation_ids=operation_ids,
)
callbacks[callback.name] = {callback.path: cb_path}
operation["callbacks"] = callbacks
@@ -384,6 +412,7 @@ def get_openapi(
output["servers"] = servers
components: Dict[str, Dict[str, Any]] = {}
paths: Dict[str, Dict[str, Any]] = {}
+ operation_ids: Set[str] = set()
flat_models = get_flat_models_from_routes(routes)
model_name_map = get_model_name_map(flat_models)
definitions = get_model_definitions(
@@ -391,7 +420,9 @@ def get_openapi(
)
for route in routes:
if isinstance(route, routing.APIRoute):
- result = get_openapi_path(route=route, model_name_map=model_name_map)
+ result = get_openapi_path(
+ route=route, model_name_map=model_name_map, operation_ids=operation_ids
+ )
if result:
path, security_schemes, path_definitions = result
if path:
diff --git a/fastapi/routing.py b/fastapi/routing.py
index f6d5370d..db39d3ff 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -13,6 +13,7 @@ from typing import (
Optional,
Sequence,
Set,
+ Tuple,
Type,
Union,
)
@@ -33,7 +34,7 @@ from fastapi.types import DecoratedCallable
from fastapi.utils import (
create_cloned_field,
create_response_field,
- generate_operation_id_for_path,
+ generate_unique_id,
get_value_or_default,
)
from pydantic import BaseModel
@@ -44,7 +45,7 @@ from starlette.concurrency import run_in_threadpool
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
-from starlette.routing import BaseRoute
+from starlette.routing import BaseRoute, Match
from starlette.routing import Mount as Mount # noqa
from starlette.routing import (
compile_path,
@@ -53,7 +54,7 @@ from starlette.routing import (
websocket_session,
)
from starlette.status import WS_1008_POLICY_VIOLATION
-from starlette.types import ASGIApp
+from starlette.types import ASGIApp, Scope
from starlette.websockets import WebSocket
@@ -126,7 +127,7 @@ async def serialize_response(
if is_coroutine:
value, errors_ = field.validate(response_content, {}, loc=("response",))
else:
- value, errors_ = await run_in_threadpool(
+ value, errors_ = await run_in_threadpool( # type: ignore[misc]
field.validate, response_content, {}, loc=("response",)
)
if isinstance(errors_, ErrorWrapper):
@@ -296,6 +297,12 @@ class APIWebSocketRoute(routing.WebSocketRoute):
)
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
+ def matches(self, scope: Scope) -> Tuple[Match, Scope]:
+ match, child_scope = super().matches(scope)
+ if match != Match.NONE:
+ child_scope["route"] = self
+ return match, child_scope
+
class APIRoute(routing.Route):
def __init__(
@@ -328,21 +335,47 @@ class APIRoute(routing.Route):
dependency_overrides_provider: Optional[Any] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Union[
+ Callable[["APIRoute"], str], DefaultPlaceholder
+ ] = Default(generate_unique_id),
) -> None:
- # normalise enums e.g. http.HTTPStatus
- if isinstance(status_code, IntEnum):
- status_code = int(status_code)
self.path = path
self.endpoint = endpoint
+ self.response_model = response_model
+ self.summary = summary
+ self.response_description = response_description
+ self.deprecated = deprecated
+ self.operation_id = operation_id
+ self.response_model_include = response_model_include
+ self.response_model_exclude = response_model_exclude
+ self.response_model_by_alias = response_model_by_alias
+ self.response_model_exclude_unset = response_model_exclude_unset
+ self.response_model_exclude_defaults = response_model_exclude_defaults
+ self.response_model_exclude_none = response_model_exclude_none
+ self.include_in_schema = include_in_schema
+ self.response_class = response_class
+ self.dependency_overrides_provider = dependency_overrides_provider
+ self.callbacks = callbacks
+ self.openapi_extra = openapi_extra
+ self.generate_unique_id_function = generate_unique_id_function
+ self.tags = tags or []
+ self.responses = responses or {}
self.name = get_name(endpoint) if name is None else name
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
if methods is None:
methods = ["GET"]
self.methods: Set[str] = set([method.upper() for method in methods])
- self.unique_id = generate_operation_id_for_path(
- name=self.name, path=self.path_format, method=list(methods)[0]
- )
- self.response_model = response_model
+ if isinstance(generate_unique_id_function, DefaultPlaceholder):
+ current_generate_unique_id: Callable[
+ ["APIRoute"], str
+ ] = generate_unique_id_function.value
+ else:
+ current_generate_unique_id = generate_unique_id_function
+ self.unique_id = self.operation_id or current_generate_unique_id(self)
+ # normalize enums e.g. http.HTTPStatus
+ if isinstance(status_code, IntEnum):
+ status_code = int(status_code)
+ self.status_code = status_code
if self.response_model:
assert (
status_code not in STATUS_CODES_WITH_NO_BODY
@@ -364,19 +397,14 @@ class APIRoute(routing.Route):
else:
self.response_field = None # type: ignore
self.secure_cloned_response_field = None
- self.status_code = status_code
- self.tags = tags or []
if dependencies:
self.dependencies = list(dependencies)
else:
self.dependencies = []
- self.summary = summary
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
# if a "form feed" character (page break) is found in the description text,
# truncate description text to the content preceding the first "form feed"
self.description = self.description.split("\f")[0]
- self.response_description = response_description
- self.responses = responses or {}
response_fields = {}
for additional_status_code, response in self.responses.items():
assert isinstance(response, dict), "An additional response must be a dict"
@@ -392,16 +420,6 @@ class APIRoute(routing.Route):
self.response_fields: Dict[Union[int, str], ModelField] = response_fields
else:
self.response_fields = {}
- self.deprecated = deprecated
- self.operation_id = operation_id
- self.response_model_include = response_model_include
- self.response_model_exclude = response_model_exclude
- self.response_model_by_alias = response_model_by_alias
- self.response_model_exclude_unset = response_model_exclude_unset
- self.response_model_exclude_defaults = response_model_exclude_defaults
- self.response_model_exclude_none = response_model_exclude_none
- self.include_in_schema = include_in_schema
- self.response_class = response_class
assert callable(endpoint), "An endpoint must be a callable"
self.dependant = get_dependant(path=self.path_format, call=self.endpoint)
@@ -411,10 +429,7 @@ class APIRoute(routing.Route):
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
)
self.body_field = get_body_field(dependant=self.dependant, name=self.unique_id)
- self.dependency_overrides_provider = dependency_overrides_provider
- self.callbacks = callbacks
self.app = request_response(self.get_route_handler())
- self.openapi_extra = openapi_extra
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
return get_request_handler(
@@ -432,6 +447,12 @@ class APIRoute(routing.Route):
dependency_overrides_provider=self.dependency_overrides_provider,
)
+ def matches(self, scope: Scope) -> Tuple[Match, Scope]:
+ match, child_scope = super().matches(scope)
+ if match != Match.NONE:
+ child_scope["route"] = self
+ return match, child_scope
+
class APIRouter(routing.Router):
def __init__(
@@ -452,13 +473,16 @@ class APIRouter(routing.Router):
on_shutdown: Optional[Sequence[Callable[[], Any]]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> None:
super().__init__(
- routes=routes, # type: ignore # in Starlette
+ routes=routes,
redirect_slashes=redirect_slashes,
- default=default, # type: ignore # in Starlette
- on_startup=on_startup, # type: ignore # in Starlette
- on_shutdown=on_shutdown, # type: ignore # in Starlette
+ default=default,
+ on_startup=on_startup,
+ on_shutdown=on_shutdown,
)
if prefix:
assert prefix.startswith("/"), "A path prefix must start with '/'"
@@ -475,6 +499,7 @@ class APIRouter(routing.Router):
self.dependency_overrides_provider = dependency_overrides_provider
self.route_class = route_class
self.default_response_class = default_response_class
+ self.generate_unique_id_function = generate_unique_id_function
def add_api_route(
self,
@@ -506,6 +531,9 @@ class APIRouter(routing.Router):
route_class_override: Optional[Type[APIRoute]] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Union[
+ Callable[[APIRoute], str], DefaultPlaceholder
+ ] = Default(generate_unique_id),
) -> None:
route_class = route_class_override or self.route_class
responses = responses or {}
@@ -522,6 +550,9 @@ class APIRouter(routing.Router):
current_callbacks = self.callbacks.copy()
if callbacks:
current_callbacks.extend(callbacks)
+ current_generate_unique_id = get_value_or_default(
+ generate_unique_id_function, self.generate_unique_id_function
+ )
route = route_class(
self.prefix + path,
endpoint=endpoint,
@@ -548,6 +579,7 @@ class APIRouter(routing.Router):
dependency_overrides_provider=self.dependency_overrides_provider,
callbacks=current_callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=current_generate_unique_id,
)
self.routes.append(route)
@@ -577,6 +609,9 @@ class APIRouter(routing.Router):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
def decorator(func: DecoratedCallable) -> DecoratedCallable:
self.add_api_route(
@@ -604,6 +639,7 @@ class APIRouter(routing.Router):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
return func
@@ -613,7 +649,7 @@ class APIRouter(routing.Router):
self, path: str, endpoint: Callable[..., Any], name: Optional[str] = None
) -> None:
route = APIWebSocketRoute(
- path,
+ self.prefix + path,
endpoint=endpoint,
name=name,
dependency_overrides_provider=self.dependency_overrides_provider,
@@ -641,6 +677,9 @@ class APIRouter(routing.Router):
callbacks: Optional[List[BaseRoute]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> None:
if prefix:
assert prefix.startswith("/"), "A path prefix must start with '/'"
@@ -681,6 +720,12 @@ class APIRouter(routing.Router):
current_callbacks.extend(callbacks)
if route.callbacks:
current_callbacks.extend(route.callbacks)
+ current_generate_unique_id = get_value_or_default(
+ route.generate_unique_id_function,
+ router.generate_unique_id_function,
+ generate_unique_id_function,
+ self.generate_unique_id_function,
+ )
self.add_api_route(
prefix + route.path,
route.endpoint,
@@ -709,9 +754,10 @@ class APIRouter(routing.Router):
route_class_override=type(route),
callbacks=current_callbacks,
openapi_extra=route.openapi_extra,
+ generate_unique_id_function=current_generate_unique_id,
)
elif isinstance(route, routing.Route):
- methods = list(route.methods or []) # type: ignore # in Starlette
+ methods = list(route.methods or [])
self.add_route(
prefix + route.path,
route.endpoint,
@@ -757,6 +803,9 @@ class APIRouter(routing.Router):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -782,6 +831,7 @@ class APIRouter(routing.Router):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def put(
@@ -809,6 +859,9 @@ class APIRouter(routing.Router):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -834,6 +887,7 @@ class APIRouter(routing.Router):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def post(
@@ -861,6 +915,9 @@ class APIRouter(routing.Router):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -886,6 +943,7 @@ class APIRouter(routing.Router):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def delete(
@@ -913,6 +971,9 @@ class APIRouter(routing.Router):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -938,6 +999,7 @@ class APIRouter(routing.Router):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def options(
@@ -965,6 +1027,9 @@ class APIRouter(routing.Router):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -990,6 +1055,7 @@ class APIRouter(routing.Router):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def head(
@@ -1017,6 +1083,9 @@ class APIRouter(routing.Router):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -1042,6 +1111,7 @@ class APIRouter(routing.Router):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def patch(
@@ -1069,6 +1139,9 @@ class APIRouter(routing.Router):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -1094,6 +1167,7 @@ class APIRouter(routing.Router):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def trace(
@@ -1121,6 +1195,9 @@ class APIRouter(routing.Router):
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
@@ -1147,4 +1224,5 @@ class APIRouter(routing.Router):
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
diff --git a/fastapi/utils.py b/fastapi/utils.py
index 8913d85b..b9301499 100644
--- a/fastapi/utils.py
+++ b/fastapi/utils.py
@@ -1,8 +1,9 @@
import functools
import re
+import warnings
from dataclasses import is_dataclass
from enum import Enum
-from typing import Any, Dict, Optional, Set, Type, Union, cast
+from typing import TYPE_CHECKING, Any, Dict, Optional, Set, Type, Union, cast
import fastapi
from fastapi.datastructures import DefaultPlaceholder, DefaultType
@@ -13,6 +14,9 @@ from pydantic.fields import FieldInfo, ModelField, UndefinedType
from pydantic.schema import model_process_schema
from pydantic.utils import lenient_issubclass
+if TYPE_CHECKING: # pragma: nocover
+ from .routing import APIRoute
+
def get_model_definitions(
*,
@@ -119,13 +123,29 @@ def create_cloned_field(
return new_field
-def generate_operation_id_for_path(*, name: str, path: str, method: str) -> str:
+def generate_operation_id_for_path(
+ *, name: str, path: str, method: str
+) -> str: # pragma: nocover
+ warnings.warn(
+ "fastapi.utils.generate_operation_id_for_path() was deprecated, "
+ "it is not used internally, and will be removed soon",
+ DeprecationWarning,
+ stacklevel=2,
+ )
operation_id = name + path
operation_id = re.sub("[^0-9a-zA-Z_]", "_", operation_id)
operation_id = operation_id + "_" + method.lower()
return operation_id
+def generate_unique_id(route: "APIRoute") -> str:
+ operation_id = route.name + route.path_format
+ operation_id = re.sub("[^0-9a-zA-Z_]", "_", operation_id)
+ assert route.methods
+ operation_id = operation_id + "_" + list(route.methods)[0].lower()
+ return operation_id
+
+
def deep_dict_update(main_dict: Dict[Any, Any], update_dict: Dict[Any, Any]) -> None:
for key in update_dict:
if (
diff --git a/pyproject.toml b/pyproject.toml
index 77c01322..b23d0db0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -35,7 +35,7 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP",
]
requires = [
- "starlette ==0.17.1",
+ "starlette==0.19.1",
"pydantic >=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0",
]
description-file = "README.md"
@@ -50,7 +50,7 @@ test = [
"pytest-cov >=2.12.0,<4.0.0",
"mypy ==0.910",
"flake8 >=3.8.3,<4.0.0",
- "black ==21.9b0",
+ "black == 22.3.0",
"isort >=5.0.6,<6.0.0",
"requests >=2.24.0,<3.0.0",
"httpx >=0.14.0,<0.19.0",
@@ -59,41 +59,43 @@ test = [
"peewee >=3.13.3,<4.0.0",
"databases[sqlite] >=0.3.2,<0.6.0",
"orjson >=3.2.1,<4.0.0",
- "ujson >=4.0.1,<5.0.0",
+ "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0",
"python-multipart >=0.0.5,<0.0.6",
"flask >=1.1.2,<3.0.0",
"anyio[trio] >=3.2.1,<4.0.0",
# types
- "types-ujson ==0.1.1",
- "types-orjson ==3.6.0",
- "types-dataclasses ==0.1.7; python_version<'3.7'",
+ "types-ujson ==4.2.1",
+ "types-orjson ==3.6.2",
+ "types-dataclasses ==0.6.5; python_version<'3.7'",
]
doc = [
"mkdocs >=1.1.2,<2.0.0",
"mkdocs-material >=8.1.4,<9.0.0",
"mdx-include >=1.4.1,<2.0.0",
"mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0",
- "typer-cli >=0.0.12,<0.0.13",
- "pyyaml >=5.3.1,<6.0.0"
+ # TODO: upgrade and enable typer-cli once it supports Click 8.x.x
+ # "typer-cli >=0.0.12,<0.0.13",
+ "typer >=0.4.1,<0.5.0",
+ "pyyaml >=5.3.1,<7.0.0",
]
dev = [
"python-jose[cryptography] >=3.3.0,<4.0.0",
"passlib[bcrypt] >=1.7.2,<2.0.0",
"autoflake >=1.4.0,<2.0.0",
"flake8 >=3.8.3,<4.0.0",
- "uvicorn[standard] >=0.12.0,<0.16.0",
+ "uvicorn[standard] >=0.12.0,<0.18.0",
]
all = [
"requests >=2.24.0,<3.0.0",
"jinja2 >=2.11.2,<4.0.0",
"python-multipart >=0.0.5,<0.0.6",
"itsdangerous >=1.1.0,<3.0.0",
- "pyyaml >=5.3.1,<6.0.0",
- "ujson >=4.0.1,<5.0.0",
+ "pyyaml >=5.3.1,<7.0.0",
+ "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0",
"orjson >=3.2.1,<4.0.0",
"email_validator >=1.1.1,<2.0.0",
- "uvicorn[standard] >=0.12.0,<0.16.0",
+ "uvicorn[standard] >=0.12.0,<0.18.0",
]
[tool.isort]
@@ -138,4 +140,7 @@ filterwarnings = [
"error",
# TODO: needed by asyncio in Python 3.9.7 https://bugs.python.org/issue45097, try to remove on 3.9.8
'ignore:The loop argument is deprecated since Python 3\.8, and scheduled for removal in Python 3\.10:DeprecationWarning:asyncio',
+ 'ignore:starlette.middleware.wsgi is deprecated and will be removed in a future release\..*:DeprecationWarning:starlette',
+ # TODO: remove after dropping support for Python 3.6
+ 'ignore:Python 3.6 is no longer supported by the Python core team. Therefore, support for it is deprecated in cryptography and will be removed in a future release.:UserWarning:jose',
]
diff --git a/tests/test_additional_properties.py b/tests/test_additional_properties.py
index 9e15e6ed..016c1f73 100644
--- a/tests/test_additional_properties.py
+++ b/tests/test_additional_properties.py
@@ -76,7 +76,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_additional_responses_custom_model_in_callback.py b/tests/test_additional_responses_custom_model_in_callback.py
index 36dd0d6d..a1072cc5 100644
--- a/tests/test_additional_responses_custom_model_in_callback.py
+++ b/tests/test_additional_responses_custom_model_in_callback.py
@@ -119,7 +119,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_additional_responses_default_validationerror.py b/tests/test_additional_responses_default_validationerror.py
index 6ea372ce..cabb536d 100644
--- a/tests/test_additional_responses_default_validationerror.py
+++ b/tests/test_additional_responses_default_validationerror.py
@@ -54,7 +54,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_application.py b/tests/test_application.py
index 5ba73730..d9194c15 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -1101,7 +1101,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_dependency_contextmanager.py b/tests/test_dependency_contextmanager.py
index 3e42b47f..03ef56c4 100644
--- a/tests/test_dependency_contextmanager.py
+++ b/tests/test_dependency_contextmanager.py
@@ -235,7 +235,16 @@ def test_sync_raise_other():
assert "/sync_raise" not in errors
-def test_async_raise():
+def test_async_raise_raises():
+ with pytest.raises(AsyncDependencyError):
+ client.get("/async_raise")
+ assert state["/async_raise"] == "asyncgen raise finalized"
+ assert "/async_raise" in errors
+ errors.clear()
+
+
+def test_async_raise_server_error():
+ client = TestClient(app, raise_server_exceptions=False)
response = client.get("/async_raise")
assert response.status_code == 500, response.text
assert state["/async_raise"] == "asyncgen raise finalized"
@@ -270,7 +279,16 @@ def test_background_tasks():
assert state["bg"] == "bg set - b: started b - a: started a"
-def test_sync_raise():
+def test_sync_raise_raises():
+ with pytest.raises(SyncDependencyError):
+ client.get("/sync_raise")
+ assert state["/sync_raise"] == "generator raise finalized"
+ assert "/sync_raise" in errors
+ errors.clear()
+
+
+def test_sync_raise_server_error():
+ client = TestClient(app, raise_server_exceptions=False)
response = client.get("/sync_raise")
assert response.status_code == 500, response.text
assert state["/sync_raise"] == "generator raise finalized"
@@ -306,7 +324,16 @@ def test_sync_sync_raise_other():
assert "/sync_raise" not in errors
-def test_sync_async_raise():
+def test_sync_async_raise_raises():
+ with pytest.raises(AsyncDependencyError):
+ client.get("/sync_async_raise")
+ assert state["/async_raise"] == "asyncgen raise finalized"
+ assert "/async_raise" in errors
+ errors.clear()
+
+
+def test_sync_async_raise_server_error():
+ client = TestClient(app, raise_server_exceptions=False)
response = client.get("/sync_async_raise")
assert response.status_code == 500, response.text
assert state["/async_raise"] == "asyncgen raise finalized"
@@ -314,7 +341,16 @@ def test_sync_async_raise():
errors.clear()
-def test_sync_sync_raise():
+def test_sync_sync_raise_raises():
+ with pytest.raises(SyncDependencyError):
+ client.get("/sync_sync_raise")
+ assert state["/sync_raise"] == "generator raise finalized"
+ assert "/sync_raise" in errors
+ errors.clear()
+
+
+def test_sync_sync_raise_server_error():
+ client = TestClient(app, raise_server_exceptions=False)
response = client.get("/sync_sync_raise")
assert response.status_code == 500, response.text
assert state["/sync_raise"] == "generator raise finalized"
diff --git a/tests/test_dependency_contextvars.py b/tests/test_dependency_contextvars.py
new file mode 100644
index 00000000..076802df
--- /dev/null
+++ b/tests/test_dependency_contextvars.py
@@ -0,0 +1,51 @@
+from contextvars import ContextVar
+from typing import Any, Awaitable, Callable, Dict, Optional
+
+from fastapi import Depends, FastAPI, Request, Response
+from fastapi.testclient import TestClient
+
+legacy_request_state_context_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
+ "legacy_request_state_context_var", default=None
+)
+
+app = FastAPI()
+
+
+async def set_up_request_state_dependency():
+ request_state = {"user": "deadpond"}
+ contextvar_token = legacy_request_state_context_var.set(request_state)
+ yield request_state
+ legacy_request_state_context_var.reset(contextvar_token)
+
+
+@app.middleware("http")
+async def custom_middleware(
+ request: Request, call_next: Callable[[Request], Awaitable[Response]]
+):
+ response = await call_next(request)
+ response.headers["custom"] = "foo"
+ return response
+
+
+@app.get("/user", dependencies=[Depends(set_up_request_state_dependency)])
+def get_user():
+ request_state = legacy_request_state_context_var.get()
+ assert request_state
+ return request_state["user"]
+
+
+client = TestClient(app)
+
+
+def test_dependency_contextvars():
+ """
+ Check that custom middlewares don't affect the contextvar context for dependencies.
+
+ The code before yield and the code after yield should be run in the same contextvar
+ context, so that request_state_context_var.reset(contextvar_token).
+
+ If they are run in a different context, that raises an error.
+ """
+ response = client.get("/user")
+ assert response.json() == "deadpond"
+ assert response.headers["custom"] == "foo"
diff --git a/tests/test_dependency_duplicates.py b/tests/test_dependency_duplicates.py
index 5e15812b..33899134 100644
--- a/tests/test_dependency_duplicates.py
+++ b/tests/test_dependency_duplicates.py
@@ -177,7 +177,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_dependency_normal_exceptions.py b/tests/test_dependency_normal_exceptions.py
new file mode 100644
index 00000000..49a19f46
--- /dev/null
+++ b/tests/test_dependency_normal_exceptions.py
@@ -0,0 +1,71 @@
+import pytest
+from fastapi import Body, Depends, FastAPI, HTTPException
+from fastapi.testclient import TestClient
+
+initial_fake_database = {"rick": "Rick Sanchez"}
+
+fake_database = initial_fake_database.copy()
+
+initial_state = {"except": False, "finally": False}
+
+state = initial_state.copy()
+
+app = FastAPI()
+
+
+async def get_database():
+ temp_database = fake_database.copy()
+ try:
+ yield temp_database
+ fake_database.update(temp_database)
+ except HTTPException:
+ state["except"] = True
+ finally:
+ state["finally"] = True
+
+
+@app.put("/invalid-user/{user_id}")
+def put_invalid_user(
+ user_id: str, name: str = Body(...), db: dict = Depends(get_database)
+):
+ db[user_id] = name
+ raise HTTPException(status_code=400, detail="Invalid user")
+
+
+@app.put("/user/{user_id}")
+def put_user(user_id: str, name: str = Body(...), db: dict = Depends(get_database)):
+ db[user_id] = name
+ return {"message": "OK"}
+
+
+@pytest.fixture(autouse=True)
+def reset_state_and_db():
+ global fake_database
+ global state
+ fake_database = initial_fake_database.copy()
+ state = initial_state.copy()
+
+
+client = TestClient(app)
+
+
+def test_dependency_gets_exception():
+ assert state["except"] is False
+ assert state["finally"] is False
+ response = client.put("/invalid-user/rick", json="Morty")
+ assert response.status_code == 400, response.text
+ assert response.json() == {"detail": "Invalid user"}
+ assert state["except"] is True
+ assert state["finally"] is True
+ assert fake_database["rick"] == "Rick Sanchez"
+
+
+def test_dependency_no_exception():
+ assert state["except"] is False
+ assert state["finally"] is False
+ response = client.put("/user/rick", json="Morty")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "OK"}
+ assert state["except"] is False
+ assert state["finally"] is True
+ assert fake_database["rick"] == "Morty"
diff --git a/tests/test_exception_handlers.py b/tests/test_exception_handlers.py
index 6153f7ab..67a4bece 100644
--- a/tests/test_exception_handlers.py
+++ b/tests/test_exception_handlers.py
@@ -1,3 +1,4 @@
+import pytest
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.testclient import TestClient
@@ -12,10 +13,15 @@ def request_validation_exception_handler(request, exception):
return JSONResponse({"exception": "request-validation"})
+def server_error_exception_handler(request, exception):
+ return JSONResponse(status_code=500, content={"exception": "server-error"})
+
+
app = FastAPI(
exception_handlers={
HTTPException: http_exception_handler,
RequestValidationError: request_validation_exception_handler,
+ Exception: server_error_exception_handler,
}
)
@@ -32,6 +38,11 @@ def route_with_request_validation_exception(param: int):
pass # pragma: no cover
+@app.get("/server-error")
+def route_with_server_error():
+ raise RuntimeError("Oops!")
+
+
def test_override_http_exception():
response = client.get("/http-exception")
assert response.status_code == 200
@@ -42,3 +53,15 @@ def test_override_request_validation_exception():
response = client.get("/request-validation/invalid")
assert response.status_code == 200
assert response.json() == {"exception": "request-validation"}
+
+
+def test_override_server_error_exception_raises():
+ with pytest.raises(RuntimeError):
+ client.get("/server-error")
+
+
+def test_override_server_error_exception_response():
+ client = TestClient(app, raise_server_exceptions=False)
+ response = client.get("/server-error")
+ assert response.status_code == 500
+ assert response.json() == {"exception": "server-error"}
diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py
index 6aba3e8d..491ba61c 100644
--- a/tests/test_extra_routes.py
+++ b/tests/test_extra_routes.py
@@ -32,12 +32,12 @@ def delete_item(item_id: str, item: Item):
@app.head("/items/{item_id}")
def head_item(item_id: str):
- return JSONResponse(headers={"x-fastapi-item-id": item_id})
+ return JSONResponse(None, headers={"x-fastapi-item-id": item_id})
@app.options("/items/{item_id}")
def options_item(item_id: str):
- return JSONResponse(headers={"x-fastapi-item-id": item_id})
+ return JSONResponse(None, headers={"x-fastapi-item-id": item_id})
@app.patch("/items/{item_id}")
@@ -47,7 +47,7 @@ def patch_item(item_id: str, item: Item):
@app.trace("/items/{item_id}")
def trace_item(item_id: str):
- return JSONResponse(media_type="message/http")
+ return JSONResponse(None, media_type="message/http")
client = TestClient(app)
@@ -292,7 +292,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_filter_pydantic_sub_model.py b/tests/test_filter_pydantic_sub_model.py
index 90a37297..8814356a 100644
--- a/tests/test_filter_pydantic_sub_model.py
+++ b/tests/test_filter_pydantic_sub_model.py
@@ -116,7 +116,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_generate_unique_id_function.py b/tests/test_generate_unique_id_function.py
new file mode 100644
index 00000000..0b519f85
--- /dev/null
+++ b/tests/test_generate_unique_id_function.py
@@ -0,0 +1,1631 @@
+import warnings
+from typing import List
+
+from fastapi import APIRouter, FastAPI
+from fastapi.routing import APIRoute
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+
+def custom_generate_unique_id(route: APIRoute):
+ return f"foo_{route.name}"
+
+
+def custom_generate_unique_id2(route: APIRoute):
+ return f"bar_{route.name}"
+
+
+def custom_generate_unique_id3(route: APIRoute):
+ return f"baz_{route.name}"
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class Message(BaseModel):
+ title: str
+ description: str
+
+
+def test_top_level_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter()
+
+ @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ app.include_router(router)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "foo_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "foo_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_foo_post_root": {
+ "title": "Body_foo_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_router": {
+ "title": "Body_foo_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ },
+ },
+ "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_router_overrides_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ app.include_router(router)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "foo_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "bar_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_bar_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Bar Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Bar Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_bar_post_router": {
+ "title": "Body_bar_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_root": {
+ "title": "Body_foo_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ },
+ },
+ "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_router_include_overrides_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ app.include_router(router, generate_unique_id_function=custom_generate_unique_id3)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "foo_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "bar_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_bar_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Bar Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Bar Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_bar_post_router": {
+ "title": "Body_bar_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_root": {
+ "title": "Body_foo_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ },
+ },
+ "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_subrouter_top_level_include_overrides_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter()
+ sub_router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @sub_router.post(
+ "/subrouter",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ )
+ def post_subrouter(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ router.include_router(sub_router)
+ app.include_router(router, generate_unique_id_function=custom_generate_unique_id3)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "foo_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "baz_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_baz_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Baz Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Baz Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/subrouter": {
+ "post": {
+ "summary": "Post Subrouter",
+ "operationId": "bar_post_subrouter",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_bar_post_subrouter"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Bar Post Subrouter",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Bar Post Subrouter",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_bar_post_subrouter": {
+ "title": "Body_bar_post_subrouter",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_baz_post_router": {
+ "title": "Body_baz_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_root": {
+ "title": "Body_foo_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ },
+ },
+ "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_router_path_operation_overrides_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ generate_unique_id_function=custom_generate_unique_id3,
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ app.include_router(router)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "foo_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "baz_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_baz_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Baz Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Baz Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_baz_post_router": {
+ "title": "Body_baz_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_root": {
+ "title": "Body_foo_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ },
+ },
+ "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_app_path_operation_overrides_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @app.post(
+ "/",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ generate_unique_id_function=custom_generate_unique_id3,
+ )
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ app.include_router(router)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "baz_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_baz_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Baz Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Baz Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "bar_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_bar_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Bar Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Bar Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_bar_post_router": {
+ "title": "Body_bar_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_baz_post_root": {
+ "title": "Body_baz_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ },
+ },
+ "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_callback_override_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ callback_router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @callback_router.post(
+ "/post-callback",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ generate_unique_id_function=custom_generate_unique_id3,
+ )
+ def post_callback(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @app.post(
+ "/",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ generate_unique_id_function=custom_generate_unique_id3,
+ callbacks=callback_router.routes,
+ )
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @app.post(
+ "/tocallback",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ )
+ def post_with_callback(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "baz_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_baz_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Baz Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Baz Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "callbacks": {
+ "post_callback": {
+ "/post-callback": {
+ "post": {
+ "summary": "Post Callback",
+ "operationId": "baz_post_callback",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_baz_post_callback"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Baz Post Callback",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Item"
+ },
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Baz Post Callback",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ }
+ },
+ }
+ },
+ "/tocallback": {
+ "post": {
+ "summary": "Post With Callback",
+ "operationId": "foo_post_with_callback",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_with_callback"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post With Callback",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post With Callback",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_baz_post_callback": {
+ "title": "Body_baz_post_callback",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_baz_post_root": {
+ "title": "Body_baz_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_with_callback": {
+ "title": "Body_foo_post_with_callback",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ },
+ },
+ "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_warn_duplicate_operation_id():
+ def broken_operation_id(route: APIRoute):
+ return "foo"
+
+ app = FastAPI(generate_unique_id_function=broken_operation_id)
+
+ @app.post("/")
+ def post_root(item1: Item):
+ return item1 # pragma: nocover
+
+ @app.post("/second")
+ def post_second(item1: Item):
+ return item1 # pragma: nocover
+
+ @app.post("/third")
+ def post_third(item1: Item):
+ return item1 # pragma: nocover
+
+ client = TestClient(app)
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ client.get("/openapi.json")
+ assert len(w) == 2
+ assert issubclass(w[-1].category, UserWarning)
+ assert "Duplicate Operation ID" in str(w[-1].message)
diff --git a/tests/test_get_request_body.py b/tests/test_get_request_body.py
index b12f499e..88b9d839 100644
--- a/tests/test_get_request_body.py
+++ b/tests/test_get_request_body.py
@@ -85,7 +85,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_include_router_defaults_overrides.py b/tests/test_include_router_defaults_overrides.py
index c46cb670..ccb6c722 100644
--- a/tests/test_include_router_defaults_overrides.py
+++ b/tests/test_include_router_defaults_overrides.py
@@ -1,3 +1,5 @@
+import warnings
+
import pytest
from fastapi import APIRouter, Depends, FastAPI, Response
from fastapi.responses import JSONResponse
@@ -343,7 +345,11 @@ client = TestClient(app)
def test_openapi():
client = TestClient(app)
- response = client.get("/openapi.json")
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ response = client.get("/openapi.json")
+ assert issubclass(w[-1].category, UserWarning)
+ assert "Duplicate Operation ID" in str(w[-1].message)
assert response.json() == openapi_schema
@@ -6606,7 +6612,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_modules_same_name_body/test_main.py b/tests/test_modules_same_name_body/test_main.py
index b0d3330c..8b1aea03 100644
--- a/tests/test_modules_same_name_body/test_main.py
+++ b/tests/test_modules_same_name_body/test_main.py
@@ -101,7 +101,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py
index c1be8280..31308ea8 100644
--- a/tests/test_multi_body_errors.py
+++ b/tests/test_multi_body_errors.py
@@ -79,7 +79,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_multi_query_errors.py b/tests/test_multi_query_errors.py
index 69ea87a9..0a15833f 100644
--- a/tests/test_multi_query_errors.py
+++ b/tests/test_multi_query_errors.py
@@ -63,7 +63,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_param_in_path_and_dependency.py b/tests/test_param_in_path_and_dependency.py
index 0a94c215..4d85afbc 100644
--- a/tests/test_param_in_path_and_dependency.py
+++ b/tests/test_param_in_path_and_dependency.py
@@ -71,7 +71,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py
index 4eaac72d..26aa6389 100644
--- a/tests/test_param_include_in_schema.py
+++ b/tests/test_param_include_in_schema.py
@@ -149,7 +149,7 @@ openapi_shema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_put_no_body.py b/tests/test_put_no_body.py
index 1c2cfac8..3da294cc 100644
--- a/tests/test_put_no_body.py
+++ b/tests/test_put_no_body.py
@@ -57,7 +57,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_repeated_dependency_schema.py b/tests/test_repeated_dependency_schema.py
index fd616e12..00441694 100644
--- a/tests/test_repeated_dependency_schema.py
+++ b/tests/test_repeated_dependency_schema.py
@@ -36,7 +36,7 @@ schema = {
"ValidationError": {
"properties": {
"loc": {
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
"title": "Location",
"type": "array",
},
diff --git a/tests/test_route_scope.py b/tests/test_route_scope.py
new file mode 100644
index 00000000..a188e9a5
--- /dev/null
+++ b/tests/test_route_scope.py
@@ -0,0 +1,50 @@
+import pytest
+from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
+from fastapi.routing import APIRoute, APIWebSocketRoute
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.get("/users/{user_id}")
+async def get_user(user_id: str, request: Request):
+ route: APIRoute = request.scope["route"]
+ return {"user_id": user_id, "path": route.path}
+
+
+@app.websocket("/items/{item_id}")
+async def websocket_item(item_id: str, websocket: WebSocket):
+ route: APIWebSocketRoute = websocket.scope["route"]
+ await websocket.accept()
+ await websocket.send_json({"item_id": item_id, "path": route.path})
+
+
+client = TestClient(app)
+
+
+def test_get():
+ response = client.get("/users/rick")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"user_id": "rick", "path": "/users/{user_id}"}
+
+
+def test_invalid_method_doesnt_match():
+ response = client.post("/users/rick")
+ assert response.status_code == 405, response.text
+
+
+def test_invalid_path_doesnt_match():
+ response = client.post("/usersx/rick")
+ assert response.status_code == 404, response.text
+
+
+def test_websocket():
+ with client.websocket_connect("/items/portal-gun") as websocket:
+ data = websocket.receive_json()
+ assert data == {"item_id": "portal-gun", "path": "/items/{item_id}"}
+
+
+def test_websocket_invalid_path_doesnt_match():
+ with pytest.raises(WebSocketDisconnect):
+ with client.websocket_connect("/itemsx/portal-gun") as websocket:
+ websocket.receive_json()
diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py
index 3e0d846c..444e350a 100644
--- a/tests/test_schema_extra_examples.py
+++ b/tests/test_schema_extra_examples.py
@@ -830,7 +830,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py
index b7ada7ca..b9ac488e 100644
--- a/tests/test_security_oauth2.py
+++ b/tests/test_security_oauth2.py
@@ -117,7 +117,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py
index ecc76651..a5fd49b8 100644
--- a/tests/test_security_oauth2_optional.py
+++ b/tests/test_security_oauth2_optional.py
@@ -121,7 +121,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py
index 011db65e..171f96b7 100644
--- a/tests/test_security_oauth2_optional_description.py
+++ b/tests/test_security_oauth2_optional_description.py
@@ -122,7 +122,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_starlette_exception.py b/tests/test_starlette_exception.py
index 5759a93f..859169d3 100644
--- a/tests/test_starlette_exception.py
+++ b/tests/test_starlette_exception.py
@@ -102,7 +102,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_sub_callbacks.py b/tests/test_sub_callbacks.py
index 16644b55..7574d6fb 100644
--- a/tests/test_sub_callbacks.py
+++ b/tests/test_sub_callbacks.py
@@ -256,7 +256,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tuples.py b/tests/test_tuples.py
index 4cd5ee3a..2085dc36 100644
--- a/tests/test_tuples.py
+++ b/tests/test_tuples.py
@@ -200,7 +200,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial001.py b/tests/test_tutorial/test_additional_responses/test_tutorial001.py
index 8342dd78..1a8acb52 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial001.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial001.py
@@ -76,7 +76,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
index 57f87797..2adcf15d 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
@@ -72,7 +72,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial003.py b/tests/test_tutorial/test_additional_responses/test_tutorial003.py
index 37190b36..8b2167de 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial003.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial003.py
@@ -77,7 +77,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
index c44a18f6..990d5235 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
@@ -75,7 +75,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py
index 90feb017..1ad625db 100644
--- a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py
+++ b/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py
@@ -88,7 +88,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py
index 7eb67517..cd6d7b5c 100644
--- a/tests/test_tutorial/test_bigger_applications/test_main.py
+++ b/tests/test_tutorial/test_bigger_applications/test_main.py
@@ -323,7 +323,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py
index 7bf62c90..8dbaf15d 100644
--- a/tests/test_tutorial/test_body/test_tutorial001.py
+++ b/tests/test_tutorial/test_body/test_tutorial001.py
@@ -63,7 +63,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body/test_tutorial001_py310.py b/tests/test_tutorial/test_body/test_tutorial001_py310.py
index e292b534..dd9d9911 100644
--- a/tests/test_tutorial/test_body/test_tutorial001_py310.py
+++ b/tests/test_tutorial/test_body/test_tutorial001_py310.py
@@ -61,7 +61,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001.py b/tests/test_tutorial/test_body_fields/test_tutorial001.py
index 9de4907c..fe5a270f 100644
--- a/tests/test_tutorial/test_body_fields/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_fields/test_tutorial001.py
@@ -87,7 +87,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py b/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py
index d7a525ea..993e2a91 100644
--- a/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py
+++ b/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py
@@ -84,7 +84,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
index b11ecdda..8dc710d7 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
@@ -79,7 +79,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py
index 85ba41ce..5114ccea 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py
@@ -77,7 +77,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
index d98e3e41..64aa9c43 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
@@ -90,7 +90,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py
index f896f7bf..fc019d8b 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py
@@ -88,7 +88,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
index 8eb0ad13..c56d41b5 100644
--- a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
@@ -53,7 +53,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py
index 17ca29ce..5b8d8286 100644
--- a/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py
@@ -52,7 +52,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py
index 5e92ef7e..efd0e467 100644
--- a/tests/test_tutorial/test_body_updates/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py
@@ -109,7 +109,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py
index ca1d8c58..49279b32 100644
--- a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py
@@ -108,7 +108,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py
index f2b184c4..872530bc 100644
--- a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py
@@ -108,7 +108,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
index 3451dc19..edccffec 100644
--- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
@@ -50,7 +50,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py
index 587a328d..5caa5c44 100644
--- a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py
+++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py
@@ -48,7 +48,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py
index 3e3fc9ac..bf156419 100644
--- a/tests/test_tutorial/test_dataclasses/test_tutorial001.py
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py
@@ -71,7 +71,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py
index dd0f1f2c..2d86f7b9 100644
--- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py
@@ -118,7 +118,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001.py b/tests/test_tutorial/test_dependencies/test_tutorial001.py
index 8b53157c..c3bca5d5 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial001.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial001.py
@@ -104,7 +104,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py
index a7991170..32a61c82 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py
@@ -102,7 +102,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial004.py
index eb21f652..f2b1878d 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial004.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial004.py
@@ -62,7 +62,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py
index f66a36a9..e3ae0c74 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py
@@ -60,7 +60,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006.py b/tests/test_tutorial/test_dependencies/test_tutorial006.py
index c08992ec..2916577a 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial006.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial006.py
@@ -55,7 +55,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012.py b/tests/test_tutorial/test_dependencies/test_tutorial012.py
index ada83c62..e4e07395 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial012.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial012.py
@@ -102,7 +102,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_events/test_tutorial001.py b/tests/test_tutorial/test_events/test_tutorial001.py
index e3587a0e..d52dd1a0 100644
--- a/tests/test_tutorial/test_events/test_tutorial001.py
+++ b/tests/test_tutorial/test_events/test_tutorial001.py
@@ -47,7 +47,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
index 68b7d61d..8522d7b9 100644
--- a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
+++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
@@ -89,7 +89,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py
index 3d4c1d07..4efdecc5 100644
--- a/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py
+++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py
@@ -87,7 +87,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003.py b/tests/test_tutorial/test_extra_models/test_tutorial003.py
index a2a325c7..f1433470 100644
--- a/tests/test_tutorial/test_extra_models/test_tutorial003.py
+++ b/tests/test_tutorial/test_extra_models/test_tutorial003.py
@@ -78,7 +78,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py
index 185bc3a3..56fd83ad 100644
--- a/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py
+++ b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py
@@ -77,7 +77,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_generate_clients/__init__.py b/tests/test_tutorial/test_generate_clients/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial003.py b/tests/test_tutorial/test_generate_clients/test_tutorial003.py
new file mode 100644
index 00000000..128fcea3
--- /dev/null
+++ b/tests/test_tutorial/test_generate_clients/test_tutorial003.py
@@ -0,0 +1,188 @@
+from fastapi.testclient import TestClient
+
+from docs_src.generate_clients.tutorial003 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "tags": ["items"],
+ "summary": "Get Items",
+ "operationId": "items-get_items",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Items-Get Items",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "tags": ["items"],
+ "summary": "Create Item",
+ "operationId": "items-create_item",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResponseMessage"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
+ "/users/": {
+ "post": {
+ "tags": ["users"],
+ "summary": "Create User",
+ "operationId": "users-create_user",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResponseMessage"
+ }
+ }
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "ResponseMessage": {
+ "title": "ResponseMessage",
+ "required": ["message"],
+ "type": "object",
+ "properties": {"message": {"title": "Message", "type": "string"}},
+ },
+ "User": {
+ "title": "User",
+ "required": ["username", "email"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {"title": "Email", "type": "string"},
+ },
+ },
+ "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"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi():
+ with client:
+ response = client.get("/openapi.json")
+
+ assert response.json() == openapi_schema
+
+
+def test_post_items():
+ response = client.post("/items/", json={"name": "Foo", "price": 5})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Item received"}
+
+
+def test_post_users():
+ response = client.post(
+ "/users/", json={"username": "Foo", "email": "foo@example.com"}
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "User received"}
+
+
+def test_get_items():
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial001.py b/tests/test_tutorial/test_handling_errors/test_tutorial001.py
index 6b62293d..ffd79ccf 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial001.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial001.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial002.py b/tests/test_tutorial/test_handling_errors/test_tutorial002.py
index d2ce0bf9..e678499c 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial002.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial002.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial003.py b/tests/test_tutorial/test_handling_errors/test_tutorial003.py
index ca9d94e3..a01726dc 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial003.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial003.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial004.py b/tests/test_tutorial/test_handling_errors/test_tutorial004.py
index d95debf3..0b5f7479 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial004.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial004.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial005.py b/tests/test_tutorial/test_handling_errors/test_tutorial005.py
index cedcaae7..253f3d00 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial005.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial005.py
@@ -69,7 +69,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial006.py b/tests/test_tutorial/test_handling_errors/test_tutorial006.py
index 8b6c1e7e..21233d7b 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial006.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial006.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_header_params/test_tutorial001.py b/tests/test_tutorial/test_header_params/test_tutorial001.py
index 0f05b9e8..273cf324 100644
--- a/tests/test_tutorial/test_header_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_header_params/test_tutorial001.py
@@ -51,7 +51,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_header_params/test_tutorial001_py310.py b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py
index f5ee1742..77a60eb9 100644
--- a/tests/test_tutorial/test_header_params/test_tutorial001_py310.py
+++ b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py
@@ -48,7 +48,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
index b30427d0..e773e7f8 100644
--- a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
+++ b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
@@ -143,7 +143,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
index f2ec2c7e..456e509d 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
@@ -72,7 +72,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
index d2164094..e587519a 100644
--- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
@@ -72,7 +72,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py
index 1f617da7..43a7a610 100644
--- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py
@@ -71,7 +71,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py
index ffdf0508..62aa73ac 100644
--- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py
@@ -71,7 +71,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_path_params/test_tutorial004.py b/tests/test_tutorial/test_path_params/test_tutorial004.py
index 131bf773..7f0227ec 100644
--- a/tests/test_tutorial/test_path_params/test_tutorial004.py
+++ b/tests/test_tutorial/test_path_params/test_tutorial004.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_path_params/test_tutorial005.py b/tests/test_tutorial/test_path_params/test_tutorial005.py
index ed9d2032..eae3637b 100644
--- a/tests/test_tutorial/test_path_params/test_tutorial005.py
+++ b/tests/test_tutorial/test_path_params/test_tutorial005.py
@@ -54,7 +54,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
@@ -138,7 +138,7 @@ openapi_schema2 = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params/test_tutorial005.py b/tests/test_tutorial/test_query_params/test_tutorial005.py
index aabc0af4..07178f8a 100644
--- a/tests/test_tutorial/test_query_params/test_tutorial005.py
+++ b/tests/test_tutorial/test_query_params/test_tutorial005.py
@@ -56,7 +56,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params/test_tutorial006.py b/tests/test_tutorial/test_query_params/test_tutorial006.py
index 042a0e1f..73c5302e 100644
--- a/tests/test_tutorial/test_query_params/test_tutorial006.py
+++ b/tests/test_tutorial/test_query_params/test_tutorial006.py
@@ -68,7 +68,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params/test_tutorial006_py310.py b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py
index 1986d27d..141525f1 100644
--- a/tests/test_tutorial/test_query_params/test_tutorial006_py310.py
+++ b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py
@@ -66,7 +66,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py
index 709bf695..f8d7f85c 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py
@@ -59,7 +59,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py
index 66b24017..298b5d61 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py
@@ -57,7 +57,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
index 6ae10296..ad3645f3 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
@@ -53,7 +53,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py
index 8894ee1b..9330037e 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py
@@ -52,7 +52,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py
index b10e70af..11f23be2 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py
@@ -52,7 +52,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
index 724c975f..d69139dd 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
@@ -54,7 +54,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py
index a9cbce02..b25bb284 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py
@@ -53,7 +53,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
index ad559791..1b2e3635 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
@@ -54,7 +54,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
index 98ae5a68..57b8b9d9 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
@@ -53,7 +53,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py
index 33f3d5f7..fe54fc08 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py
@@ -51,7 +51,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py
index c1537f44..166014c7 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial001.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial001.py
@@ -99,7 +99,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
@@ -162,7 +162,7 @@ def test_post_file(tmp_path):
def test_post_large_file(tmp_path):
- default_pydantic_max_size = 2 ** 16
+ default_pydantic_max_size = 2**16
path = tmp_path / "test.txt"
path.write_bytes(b"x" * (default_pydantic_max_size + 1))
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py
index e852a1b3..a254bf3e 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py
@@ -106,7 +106,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py
index 62e9f98d..15b6a8d5 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py
@@ -107,7 +107,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py
index ec7509ea..c34165f1 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py
@@ -120,7 +120,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py
index 4e33ef46..73d1179a 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial002.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial002.py
@@ -119,7 +119,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_request_files/test_tutorial002_py39.py b/tests/test_tutorial/test_request_files/test_tutorial002_py39.py
index bbdf25cd..de412705 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial002_py39.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial002_py39.py
@@ -119,7 +119,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_request_files/test_tutorial003.py b/tests/test_tutorial/test_request_files/test_tutorial003.py
index 943b235a..83aea66c 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial003.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial003.py
@@ -132,7 +132,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_request_files/test_tutorial003_py39.py b/tests/test_tutorial/test_request_files/test_tutorial003_py39.py
index d5fbd788..56aeb54c 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial003_py39.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial003_py39.py
@@ -132,7 +132,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_request_forms/test_tutorial001.py b/tests/test_tutorial/test_request_forms/test_tutorial001.py
index 3d271b53..215260ff 100644
--- a/tests/test_tutorial/test_request_forms/test_tutorial001.py
+++ b/tests/test_tutorial/test_request_forms/test_tutorial001.py
@@ -61,7 +61,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py
index 10cce5e6..09e232b8 100644
--- a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py
+++ b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py
@@ -61,7 +61,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003.py b/tests/test_tutorial/test_response_model/test_tutorial003.py
index 44f2fb7c..e1bde5d1 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial003.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial003.py
@@ -74,7 +74,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_py310.py
index ffba1166..9827dab8 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial003_py310.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_py310.py
@@ -73,7 +73,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_response_model/test_tutorial004.py b/tests/test_tutorial/test_response_model/test_tutorial004.py
index 19303982..8c98c6de 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial004.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial004.py
@@ -71,7 +71,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_response_model/test_tutorial004_py310.py b/tests/test_tutorial/test_response_model/test_tutorial004_py310.py
index f1508a05..7fc86faf 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial004_py310.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial004_py310.py
@@ -69,7 +69,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_response_model/test_tutorial004_py39.py b/tests/test_tutorial/test_response_model/test_tutorial004_py39.py
index e5d9c8b5..405fe79f 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial004_py39.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial004_py39.py
@@ -69,7 +69,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_response_model/test_tutorial005.py b/tests/test_tutorial/test_response_model/test_tutorial005.py
index 9ca5463e..476b172d 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial005.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial005.py
@@ -98,7 +98,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_response_model/test_tutorial005_py310.py b/tests/test_tutorial/test_response_model/test_tutorial005_py310.py
index 6d7366f1..389a302e 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial005_py310.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial005_py310.py
@@ -97,7 +97,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_response_model/test_tutorial006.py b/tests/test_tutorial/test_response_model/test_tutorial006.py
index 25eb6e33..38eb31e5 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial006.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial006.py
@@ -98,7 +98,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_response_model/test_tutorial006_py310.py b/tests/test_tutorial/test_response_model/test_tutorial006_py310.py
index a3d8d204..f870f392 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial006_py310.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial006_py310.py
@@ -97,7 +97,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "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_tutorial004.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py
index 89f5b66f..badf66b3 100644
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py
@@ -103,7 +103,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "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_tutorial004_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py
index 4f9a2ff5..d326a5a0 100644
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py
@@ -102,7 +102,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_security/test_tutorial003.py b/tests/test_tutorial/test_security/test_tutorial003.py
index 3fc7f5f4..59510783 100644
--- a/tests/test_tutorial/test_security/test_tutorial003.py
+++ b/tests/test_tutorial/test_security/test_tutorial003.py
@@ -81,7 +81,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_security/test_tutorial003_py310.py b/tests/test_tutorial/test_security/test_tutorial003_py310.py
index e621bcd4..26f5c097 100644
--- a/tests/test_tutorial/test_security/test_tutorial003_py310.py
+++ b/tests/test_tutorial/test_security/test_tutorial003_py310.py
@@ -80,7 +80,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_security/test_tutorial005.py b/tests/test_tutorial/test_security/test_tutorial005.py
index a37f2d60..e8697339 100644
--- a/tests/test_tutorial/test_security/test_tutorial005.py
+++ b/tests/test_tutorial/test_security/test_tutorial005.py
@@ -141,7 +141,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_security/test_tutorial005_py310.py b/tests/test_tutorial/test_security/test_tutorial005_py310.py
index 0c9372e2..3144a236 100644
--- a/tests/test_tutorial/test_security/test_tutorial005_py310.py
+++ b/tests/test_tutorial/test_security/test_tutorial005_py310.py
@@ -134,7 +134,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_security/test_tutorial005_py39.py b/tests/test_tutorial/test_security/test_tutorial005_py39.py
index 099ab252..290136e1 100644
--- a/tests/test_tutorial/test_security/test_tutorial005_py39.py
+++ b/tests/test_tutorial/test_security/test_tutorial005_py39.py
@@ -134,7 +134,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases.py b/tests/test_tutorial/test_sql_databases/test_sql_databases.py
index c88fd0bc..09304ff8 100644
--- a/tests/test_tutorial/test_sql_databases/test_sql_databases.py
+++ b/tests/test_tutorial/test_sql_databases/test_sql_databases.py
@@ -261,7 +261,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py
index b02e1c89..fbaa8938 100644
--- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py
+++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py
@@ -260,7 +260,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py
index 1d0442eb..d131b4b6 100644
--- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py
+++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py
@@ -263,7 +263,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py
index 8764d07a..470fb52f 100644
--- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py
+++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py
@@ -263,7 +263,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py
index f7e73dea..dc6a1db1 100644
--- a/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py
+++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py
@@ -263,7 +263,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py
index c194c85a..ebf55ed0 100644
--- a/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py
+++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py
@@ -263,7 +263,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py b/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py
index 2ebc31b9..d28ea5e7 100644
--- a/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py
+++ b/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py
@@ -318,7 +318,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_union_body.py b/tests/test_union_body.py
index d1dfd5ef..3e424de0 100644
--- a/tests/test_union_body.py
+++ b/tests/test_union_body.py
@@ -84,7 +84,7 @@ item_openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_union_inherited_body.py b/tests/test_union_inherited_body.py
index e3d0acc9..60b327eb 100644
--- a/tests/test_union_inherited_body.py
+++ b/tests/test_union_inherited_body.py
@@ -96,7 +96,7 @@ inherited_item_openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_ws_router.py b/tests/test_ws_router.py
index bd7c3c53..fbca104a 100644
--- a/tests/test_ws_router.py
+++ b/tests/test_ws_router.py
@@ -3,6 +3,7 @@ from fastapi.testclient import TestClient
router = APIRouter()
prefix_router = APIRouter()
+native_prefix_route = APIRouter(prefix="/native")
app = FastAPI()
@@ -47,8 +48,16 @@ async def router_ws_decorator_depends(
await websocket.close()
+@native_prefix_route.websocket("/")
+async def router_native_prefix_ws(websocket: WebSocket):
+ await websocket.accept()
+ await websocket.send_text("Hello, router with native prefix!")
+ await websocket.close()
+
+
app.include_router(router)
app.include_router(prefix_router, prefix="/prefix")
+app.include_router(native_prefix_route)
def test_app():
@@ -72,6 +81,13 @@ def test_prefix_router():
assert data == "Hello, router with prefix!"
+def test_native_prefix_router():
+ client = TestClient(app)
+ with client.websocket_connect("/native/") as websocket:
+ data = websocket.receive_text()
+ assert data == "Hello, router with native prefix!"
+
+
def test_router2():
client = TestClient(app)
with client.websocket_connect("/router2") as websocket: