mirror of https://github.com/tiangolo/fastapi.git
Merge branch 'master' into fix-duplicate-special-dependency-handling
This commit is contained in:
commit
2b04a5abee
|
|
@ -0,0 +1,52 @@
|
|||
name: FastAPI People Sponsors
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 6 1 * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug_enabled:
|
||||
description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
env:
|
||||
UV_SYSTEM_PYTHON: 1
|
||||
|
||||
jobs:
|
||||
job:
|
||||
if: github.repository_owner == 'fastapi'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Dump GitHub context
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
version: "0.4.15"
|
||||
enable-cache: true
|
||||
cache-dependency-glob: |
|
||||
requirements**.txt
|
||||
pyproject.toml
|
||||
- name: Install Dependencies
|
||||
run: uv pip install -r requirements-github-actions.txt
|
||||
# Allow debugging with tmate
|
||||
- name: Setup tmate session
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
- name: FastAPI People Sponsors
|
||||
run: python ./scripts/sponsors.py
|
||||
env:
|
||||
SPONSORS_TOKEN: ${{ secrets.SPONSORS_TOKEN }}
|
||||
PR_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}
|
||||
|
|
@ -2,57 +2,60 @@ sponsors:
|
|||
- - login: bump-sh
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/33217836?v=4
|
||||
url: https://github.com/bump-sh
|
||||
- login: porter-dev
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/62078005?v=4
|
||||
url: https://github.com/porter-dev
|
||||
- login: Nixtla
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/79945230?v=4
|
||||
url: https://github.com/Nixtla
|
||||
- login: andrew-propelauth
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/89474256?u=1188c27cb744bbec36447a2cfd4453126b2ddb5c&v=4
|
||||
url: https://github.com/andrew-propelauth
|
||||
- login: liblaber
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/100821118?v=4
|
||||
url: https://github.com/liblaber
|
||||
- login: zanfaruqui
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/104461687?v=4
|
||||
url: https://github.com/zanfaruqui
|
||||
- login: Alek99
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/38776361?u=bd6c163fe787c2de1a26c881598e54b67e2482dd&v=4
|
||||
url: https://github.com/Alek99
|
||||
- login: cryptapi
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4
|
||||
url: https://github.com/cryptapi
|
||||
- login: Kong
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/962416?v=4
|
||||
url: https://github.com/Kong
|
||||
- login: codacy
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/1834093?v=4
|
||||
url: https://github.com/codacy
|
||||
- login: blockbee-io
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/115143449?u=1b8620c2d6567c4df2111a371b85a51f448f9b85&v=4
|
||||
url: https://github.com/blockbee-io
|
||||
- login: zuplo
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/85497839?v=4
|
||||
url: https://github.com/zuplo
|
||||
- login: render-sponsorships
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/189296666?v=4
|
||||
url: https://github.com/render-sponsorships
|
||||
- login: porter-dev
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/62078005?v=4
|
||||
url: https://github.com/porter-dev
|
||||
- login: scalar
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/301879?v=4
|
||||
url: https://github.com/scalar
|
||||
- - login: ObliviousAI
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/65656077?v=4
|
||||
url: https://github.com/ObliviousAI
|
||||
- - login: databento
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4
|
||||
url: https://github.com/databento
|
||||
- login: svix
|
||||
- - login: svix
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4
|
||||
url: https://github.com/svix
|
||||
- login: deepset-ai
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/51827949?v=4
|
||||
url: https://github.com/deepset-ai
|
||||
- login: mikeckennedy
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/2035561?u=ce6165b799ea3164cb6f5ff54ea08042057442af&v=4
|
||||
url: https://github.com/mikeckennedy
|
||||
- login: ndimares
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/6267663?u=cfb27efde7a7212be8142abb6c058a1aeadb41b1&v=4
|
||||
url: https://github.com/ndimares
|
||||
- - login: takashi-yoneya
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4
|
||||
url: https://github.com/takashi-yoneya
|
||||
- login: stainless-api
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/88061651?v=4
|
||||
url: https://github.com/stainless-api
|
||||
- login: speakeasy-api
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/91446104?v=4
|
||||
url: https://github.com/speakeasy-api
|
||||
- login: databento
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4
|
||||
url: https://github.com/databento
|
||||
- - login: mercedes-benz
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/34240465?v=4
|
||||
url: https://github.com/mercedes-benz
|
||||
- login: xoflare
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/74335107?v=4
|
||||
url: https://github.com/xoflare
|
||||
- login: marvin-robot
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/41086007?u=b9fcab402d0cd0aec738b6574fe60855cb0cd36d&v=4
|
||||
url: https://github.com/marvin-robot
|
||||
- login: Ponte-Energy-Partners
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/114745848?v=4
|
||||
url: https://github.com/Ponte-Energy-Partners
|
||||
- login: BoostryJP
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4
|
||||
url: https://github.com/BoostryJP
|
||||
|
|
@ -62,42 +65,63 @@ sponsors:
|
|||
- - login: Trivie
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/8161763?v=4
|
||||
url: https://github.com/Trivie
|
||||
- - login: americanair
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/12281813?v=4
|
||||
url: https://github.com/americanair
|
||||
- - login: takashi-yoneya
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4
|
||||
url: https://github.com/takashi-yoneya
|
||||
- - login: mainframeindustries
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4
|
||||
url: https://github.com/mainframeindustries
|
||||
- login: CanoaPBC
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/64223768?v=4
|
||||
url: https://github.com/CanoaPBC
|
||||
- login: mainframeindustries
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4
|
||||
url: https://github.com/mainframeindustries
|
||||
- login: mangualero
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/3422968?u=c59272d7b5a912d6126fd6c6f17db71e20f506eb&v=4
|
||||
url: https://github.com/mangualero
|
||||
- login: birkjernstrom
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/281715?u=4be14b43f76b4bd497b1941309bb390250b405e6&v=4
|
||||
url: https://github.com/birkjernstrom
|
||||
- login: yasyf
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/709645?u=f36736b3c6a85f578886ecc42a740e7b436e7a01&v=4
|
||||
url: https://github.com/yasyf
|
||||
- - login: genzou9201
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/42960762?u=1ca6c18c59e8b327ae584c545b72de31ebc05275&v=4
|
||||
url: https://github.com/genzou9201
|
||||
- - login: primer-io
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4
|
||||
url: https://github.com/primer-io
|
||||
- login: povilasb
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/1213442?u=b11f58ed6ceea6e8297c9b310030478ebdac894d&v=4
|
||||
url: https://github.com/povilasb
|
||||
- - login: jhundman
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/24263908?v=4
|
||||
url: https://github.com/jhundman
|
||||
- login: upciti
|
||||
- - login: upciti
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/43346262?v=4
|
||||
url: https://github.com/upciti
|
||||
- login: freddiev4
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/8339018?u=1aad5b4f5a04cb750852b843d5e1d8f4ce339c2e&v=4
|
||||
url: https://github.com/freddiev4
|
||||
- - login: samuelcolvin
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4
|
||||
url: https://github.com/samuelcolvin
|
||||
- login: Kludex
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
|
||||
url: https://github.com/Kludex
|
||||
- login: vincentkoc
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/25068?u=cbf098fc04c0473523d373b0dd2145b4ec99ef93&v=4
|
||||
url: https://github.com/vincentkoc
|
||||
- login: ProteinQure
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4
|
||||
url: https://github.com/ProteinQure
|
||||
- login: ddilidili
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/42176885?u=c0a849dde06987434653197b5f638d3deb55fc6c&v=4
|
||||
url: https://github.com/ddilidili
|
||||
- login: otosky
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/42260747?u=69d089387c743d89427aa4ad8740cfb34045a9e0&v=4
|
||||
url: https://github.com/otosky
|
||||
- login: mjohnsey
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4
|
||||
url: https://github.com/mjohnsey
|
||||
- login: ashi-agrawal
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4
|
||||
url: https://github.com/ashi-agrawal
|
||||
- login: sepsi77
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/18682303?v=4
|
||||
url: https://github.com/sepsi77
|
||||
- login: RaamEEIL
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4
|
||||
url: https://github.com/RaamEEIL
|
||||
- login: jhundman
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/24263908?v=4
|
||||
url: https://github.com/jhundman
|
||||
- login: b-rad-c
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/25362581?u=5bb10629f4015b62bec1f9a366675d5085551af9&v=4
|
||||
url: https://github.com/b-rad-c
|
||||
|
|
@ -105,7 +129,7 @@ sponsors:
|
|||
avatarUrl: https://avatars.githubusercontent.com/u/25950317?u=cec1a3e0643b785288ae8260cc295a85ab344995&v=4
|
||||
url: https://github.com/ehaca
|
||||
- login: raphaellaude
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/28026311?u=9ae4b158c0d2cb29ebd46df6b6edb7de08a67566&v=4
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/28026311?u=28faad3e62250ef91a0c3c5d0faba39592d9ab39&v=4
|
||||
url: https://github.com/raphaellaude
|
||||
- login: timlrx
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/28362229?u=9a745ca31372ee324af682715ae88ce8522f9094&v=4
|
||||
|
|
@ -116,78 +140,51 @@ sponsors:
|
|||
- login: ygorpontelo
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/32963605?u=35f7103f9c4c4c2589ae5737ee882e9375ef072e&v=4
|
||||
url: https://github.com/ygorpontelo
|
||||
- login: ProteinQure
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4
|
||||
url: https://github.com/ProteinQure
|
||||
- login: catherinenelson1
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/11951946?u=e714b957185b8cf3d301cced7fc3ad2842122c6a&v=4
|
||||
url: https://github.com/catherinenelson1
|
||||
- login: jsoques
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4
|
||||
url: https://github.com/jsoques
|
||||
- login: joeds13
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/13631604?u=628eb122e08bef43767b3738752b883e8e7f6259&v=4
|
||||
url: https://github.com/joeds13
|
||||
- login: dannywade
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4
|
||||
url: https://github.com/dannywade
|
||||
- login: khadrawy
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4
|
||||
url: https://github.com/khadrawy
|
||||
- login: mjohnsey
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4
|
||||
url: https://github.com/mjohnsey
|
||||
- login: ashi-agrawal
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4
|
||||
url: https://github.com/ashi-agrawal
|
||||
- login: sepsi77
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/18682303?v=4
|
||||
url: https://github.com/sepsi77
|
||||
- login: wedwardbeck
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/19333237?u=1de4ae2bf8d59eb4c013f21d863cbe0f2010575f&v=4
|
||||
url: https://github.com/wedwardbeck
|
||||
- login: RaamEEIL
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4
|
||||
url: https://github.com/RaamEEIL
|
||||
- login: anthonycepeda
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4
|
||||
url: https://github.com/anthonycepeda
|
||||
- login: patricioperezv
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4
|
||||
url: https://github.com/patricioperezv
|
||||
- login: chickenandstats
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/79477966?v=4
|
||||
url: https://github.com/chickenandstats
|
||||
- login: kaoru0310
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/80977929?u=1b61d10142b490e56af932ddf08a390fae8ee94f&v=4
|
||||
url: https://github.com/kaoru0310
|
||||
- login: DelfinaCare
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/83734439?v=4
|
||||
url: https://github.com/DelfinaCare
|
||||
- login: Eruditis
|
||||
- login: Karine-Bauch
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/90465103?u=7feb1018abb1a5631cfd9a91fea723d1ceb5f49b&v=4
|
||||
url: https://github.com/Karine-Bauch
|
||||
- login: eruditis
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/95244703?v=4
|
||||
url: https://github.com/Eruditis
|
||||
url: https://github.com/eruditis
|
||||
- login: jugeeem
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/116043716?u=ae590d79c38ac79c91b9c5caa6887d061e865a3d&v=4
|
||||
url: https://github.com/jugeeem
|
||||
- login: apitally
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/138365043?v=4
|
||||
url: https://github.com/apitally
|
||||
- login: logic-automation
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/144732884?v=4
|
||||
url: https://github.com/logic-automation
|
||||
- login: ddilidili
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/42176885?u=c0a849dde06987434653197b5f638d3deb55fc6c&v=4
|
||||
url: https://github.com/ddilidili
|
||||
- login: Torqsight-Labs
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/169598176?v=4
|
||||
url: https://github.com/Torqsight-Labs
|
||||
- login: ramonalmeidam
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/45269580?u=3358750b3a5854d7c3ed77aaca7dd20a0f529d32&v=4
|
||||
url: https://github.com/ramonalmeidam
|
||||
- login: roboflow
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/53104118?v=4
|
||||
url: https://github.com/roboflow
|
||||
- login: dudikbender
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/53487583?u=3a57542938ebfd57579a0111db2b297e606d9681&v=4
|
||||
url: https://github.com/dudikbender
|
||||
- login: prodhype
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/60444672?u=3f278cff25ea37ead487d7861d4a984795de819e&v=4
|
||||
url: https://github.com/prodhype
|
||||
- login: patsatsia
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/61111267?u=3271b85f7a37b479c8d0ae0a235182e83c166edf&v=4
|
||||
url: https://github.com/patsatsia
|
||||
- login: anthonycepeda
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4
|
||||
url: https://github.com/anthonycepeda
|
||||
- login: patricioperezv
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4
|
||||
url: https://github.com/patricioperezv
|
||||
- login: mintuhouse
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4
|
||||
url: https://github.com/mintuhouse
|
||||
- login: tcsmith
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/989034?u=7d8d741552b3279e8f4d3878679823a705a46f8f&v=4
|
||||
url: https://github.com/tcsmith
|
||||
|
|
@ -200,9 +197,6 @@ sponsors:
|
|||
- login: knallgelb
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/2358812?u=c48cb6362b309d74cbf144bd6ad3aed3eb443e82&v=4
|
||||
url: https://github.com/knallgelb
|
||||
- login: johannquerne
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/2736484?u=9b3381546a25679913a2b08110e4373c98840821&v=4
|
||||
url: https://github.com/johannquerne
|
||||
- login: Shark009
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/3163309?u=0c6f4091b0eda05c44c390466199826e6dc6e431&v=4
|
||||
url: https://github.com/Shark009
|
||||
|
|
@ -215,15 +209,18 @@ sponsors:
|
|||
- login: kennywakeland
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/3631417?u=7c8f743f1ae325dfadea7c62bbf1abd6a824fc55&v=4
|
||||
url: https://github.com/kennywakeland
|
||||
- login: simw
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/6322526?v=4
|
||||
url: https://github.com/simw
|
||||
- login: koconder
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/25068?u=582657b23622aaa3dfe68bd028a780f272f456fa&v=4
|
||||
url: https://github.com/koconder
|
||||
- login: aacayaco
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4
|
||||
url: https://github.com/aacayaco
|
||||
- login: anomaly
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4
|
||||
url: https://github.com/anomaly
|
||||
- login: jstanden
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/63288?u=c3658d57d2862c607a0e19c2101c3c51876e36ad&v=4
|
||||
url: https://github.com/jstanden
|
||||
- login: paulcwatts
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/150269?u=1819e145d573b44f0ad74b87206d21cd60331d4e&v=4
|
||||
url: https://github.com/paulcwatts
|
||||
- login: andreaso
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/285964?u=837265cc7562c0685f25b2d81cd9de0434fe107c&v=4
|
||||
url: https://github.com/andreaso
|
||||
|
|
@ -239,36 +236,36 @@ sponsors:
|
|||
- login: wshayes
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
|
||||
url: https://github.com/wshayes
|
||||
- login: gaetanBloch
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/583199?u=50c49e83d6b4feb78a091901ea02ead1462f442b&v=4
|
||||
url: https://github.com/gaetanBloch
|
||||
- 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=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4
|
||||
url: https://github.com/falkben
|
||||
- login: mintuhouse
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4
|
||||
url: https://github.com/mintuhouse
|
||||
- login: Rehket
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4
|
||||
url: https://github.com/Rehket
|
||||
- login: hiancdtrsnm
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4
|
||||
url: https://github.com/hiancdtrsnm
|
||||
- login: TrevorBenson
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/9167887?u=afdd1766fdb79e04e59094cc6a54cd011ee7f686&v=4
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/9167887?u=dccbea3327a57750923333d8ebf1a0b3f1948949&v=4
|
||||
url: https://github.com/TrevorBenson
|
||||
- login: wdwinslow
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4
|
||||
url: https://github.com/wdwinslow
|
||||
- login: aacayaco
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4
|
||||
url: https://github.com/aacayaco
|
||||
- login: anomaly
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4
|
||||
url: https://github.com/anomaly
|
||||
- login: jgreys
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/4136890?u=096820d1ef89877d57d0f68e669ead8b0fde84df&v=4
|
||||
url: https://github.com/jgreys
|
||||
- login: catherinenelson1
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/11951946?u=fe11bc35d36b6038cd46a946e4e46ef8aa5688ab&v=4
|
||||
url: https://github.com/catherinenelson1
|
||||
- login: jsoques
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4
|
||||
url: https://github.com/jsoques
|
||||
- login: joeds13
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/13631604?u=628eb122e08bef43767b3738752b883e8e7f6259&v=4
|
||||
url: https://github.com/joeds13
|
||||
- login: dannywade
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4
|
||||
url: https://github.com/dannywade
|
||||
- login: khadrawy
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4
|
||||
url: https://github.com/khadrawy
|
||||
- login: Ryandaydev
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=48f68868db8886fce31a1d802c1003914c6cd7c6&v=4
|
||||
url: https://github.com/Ryandaydev
|
||||
|
|
@ -290,12 +287,42 @@ sponsors:
|
|||
- login: FernandoCelmer
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/6262214?u=d29fff3fd862fda4ca752079f13f32e84c762ea4&v=4
|
||||
url: https://github.com/FernandoCelmer
|
||||
- - login: getsentry
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/1396951?v=4
|
||||
url: https://github.com/getsentry
|
||||
- login: simw
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/6322526?v=4
|
||||
url: https://github.com/simw
|
||||
- login: Rehket
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4
|
||||
url: https://github.com/Rehket
|
||||
- login: hiancdtrsnm
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4
|
||||
url: https://github.com/hiancdtrsnm
|
||||
- - login: pawamoy
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4
|
||||
url: https://github.com/pawamoy
|
||||
- login: engineerjoe440
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4
|
||||
url: https://github.com/engineerjoe440
|
||||
- login: bnkc
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=db5e6f4f87836cad26c2aa90ce390ce49041c5a9&v=4
|
||||
url: https://github.com/bnkc
|
||||
- login: petercool
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/37613029?u=81c525232bb35780945a68e88afd96bb2cdad9c4&v=4
|
||||
url: https://github.com/petercool
|
||||
- login: siavashyj
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/43583410?u=562005ddc7901cd27a1219a118a2363817b14977&v=4
|
||||
url: https://github.com/siavashyj
|
||||
- login: mobyw
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/44370805?v=4
|
||||
url: https://github.com/mobyw
|
||||
- login: ArtyomVancyan
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/44609997?v=4
|
||||
url: https://github.com/ArtyomVancyan
|
||||
- login: TheR1D
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4
|
||||
url: https://github.com/TheR1D
|
||||
- login: joshuatz
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4
|
||||
url: https://github.com/joshuatz
|
||||
- login: SebTota
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/25122511?v=4
|
||||
url: https://github.com/SebTota
|
||||
|
|
@ -311,87 +338,30 @@ sponsors:
|
|||
- login: rlnchow
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/28018479?u=a93ca9cf1422b9ece155784a72d5f2fdbce7adff&v=4
|
||||
url: https://github.com/rlnchow
|
||||
- login: engineerjoe440
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4
|
||||
url: https://github.com/engineerjoe440
|
||||
- login: bnkc
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=db5e6f4f87836cad26c2aa90ce390ce49041c5a9&v=4
|
||||
url: https://github.com/bnkc
|
||||
- login: DevOpsKev
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/36336550?u=6ccd5978fdaab06f37e22f2a14a7439341df7f67&v=4
|
||||
url: https://github.com/DevOpsKev
|
||||
- login: petercool
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/37613029?u=81c525232bb35780945a68e88afd96bb2cdad9c4&v=4
|
||||
url: https://github.com/petercool
|
||||
- login: JimFawkes
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/12075115?u=dc58ecfd064d72887c34bf500ddfd52592509acd&v=4
|
||||
url: https://github.com/JimFawkes
|
||||
- login: artempronevskiy
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/12235104?u=03df6e1e55c9c6fe5d230adabb8dd7d43d8bbe8f&v=4
|
||||
url: https://github.com/artempronevskiy
|
||||
- login: TheR1D
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4
|
||||
url: https://github.com/TheR1D
|
||||
- login: joshuatz
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4
|
||||
url: https://github.com/joshuatz
|
||||
- login: jangia
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/17927101?u=9261b9bb0c3e3bb1ecba43e8915dc58d8c9a077e&v=4
|
||||
url: https://github.com/jangia
|
||||
- login: jackleeio
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/20477587?u=c5184dab6d021733d10c8f975b20e391856303d6&v=4
|
||||
url: https://github.com/jackleeio
|
||||
- login: shuheng-liu
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/22414322?u=813c45f30786c6b511b21a661def025d8f7b609e&v=4
|
||||
url: https://github.com/shuheng-liu
|
||||
- login: pers0n4
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/24864600?u=f211a13a7b572cbbd7779b9c8d8cb428cc7ba07e&v=4
|
||||
url: https://github.com/pers0n4
|
||||
- login: curegit
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/37978051?u=1733c322079118c0cdc573c03d92813f50a9faec&v=4
|
||||
url: https://github.com/curegit
|
||||
- login: fernandosmither
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/66154723?u=f79753eb207d01cca5bbb91ac62db6123e7622d1&v=4
|
||||
url: https://github.com/fernandosmither
|
||||
- login: PunRabbit
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4
|
||||
url: https://github.com/PunRabbit
|
||||
- login: PelicanQ
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4
|
||||
url: https://github.com/PelicanQ
|
||||
- login: tahmarrrr23
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/138208610?u=465a46b0ff72a74252d3e3a71ac7d2f1919cda28&v=4
|
||||
url: https://github.com/tahmarrrr23
|
||||
- login: zk-Call
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/147117264?v=4
|
||||
url: https://github.com/zk-Call
|
||||
- login: kristiangronberg
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/42678548?v=4
|
||||
url: https://github.com/kristiangronberg
|
||||
- login: leonardo-holguin
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/43093055?u=b59013d52fb6c4e0954aaaabc0882bd844985b38&v=4
|
||||
url: https://github.com/leonardo-holguin
|
||||
- login: arrrrrmin
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/43553423?u=36a3880a6eb29309c19e6cadbb173bafbe91deb1&v=4
|
||||
url: https://github.com/arrrrrmin
|
||||
- login: mobyw
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/44370805?v=4
|
||||
url: https://github.com/mobyw
|
||||
- login: ArtyomVancyan
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/44609997?v=4
|
||||
url: https://github.com/ArtyomVancyan
|
||||
- login: harol97
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/49042862?u=2b18e115ab73f5f09a280be2850f93c58a12e3d2&v=4
|
||||
url: https://github.com/harol97
|
||||
- login: dvlpjrs
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/32254642?u=fbd6ad0324d4f1eb6231cf775be1c7bd4404e961&v=4
|
||||
url: https://github.com/dvlpjrs
|
||||
- login: caviri
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/45425937?u=4e14bd64282bad8f385eafbdb004b5a279366d6e&v=4
|
||||
url: https://github.com/caviri
|
||||
- login: hgalytoby
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/50397689?u=62c7ff3519858423579676cd0efbd7e3f1ffe63a&v=4
|
||||
url: https://github.com/hgalytoby
|
||||
- login: conservative-dude
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/55538308?u=f250c44942ea6e73a6bd90739b381c470c192c11&v=4
|
||||
url: https://github.com/conservative-dude
|
||||
- login: Joaopcamposs
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/57376574?u=699d5ba5ee66af1d089df6b5e532b97169e73650&v=4
|
||||
url: https://github.com/Joaopcamposs
|
||||
- login: CR1337
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/62649536?u=57a6aab10d2421a497306da8bcded01b826c54ae&v=4
|
||||
url: https://github.com/CR1337
|
||||
- login: PunRabbit
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4
|
||||
url: https://github.com/PunRabbit
|
||||
- login: PelicanQ
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4
|
||||
url: https://github.com/PelicanQ
|
||||
- login: tochikuji
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4
|
||||
url: https://github.com/tochikuji
|
||||
- login: browniebroke
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/861044?u=5abfca5588f3e906b31583d7ee62f6de4b68aa24&v=4
|
||||
url: https://github.com/browniebroke
|
||||
|
|
@ -407,9 +377,12 @@ sponsors:
|
|||
- login: leobiscassi
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/1977418?u=f9f82445a847ab479bd7223debd677fcac6c49a0&v=4
|
||||
url: https://github.com/leobiscassi
|
||||
- login: cbonoz
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/2351087?u=fd3e8030b2cc9fbfbb54a65e9890c548a016f58b&v=4
|
||||
url: https://github.com/cbonoz
|
||||
- login: Alisa-lisa
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4
|
||||
url: https://github.com/Alisa-lisa
|
||||
- login: Graeme22
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/4185684?u=498182a42300d7bcd4de1215190cb17eb501136c&v=4
|
||||
url: https://github.com/Graeme22
|
||||
- login: ddanier
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/113563?u=ed1dc79de72f93bd78581f88ebc6952b62f472da&v=4
|
||||
url: https://github.com/ddanier
|
||||
|
|
@ -419,33 +392,15 @@ sponsors:
|
|||
- login: slafs
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4
|
||||
url: https://github.com/slafs
|
||||
- login: adamghill
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/317045?u=f1349d5ffe84a19f324e204777859fbf69ddf633&v=4
|
||||
url: https://github.com/adamghill
|
||||
- login: ceb10n
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
|
||||
url: https://github.com/ceb10n
|
||||
- login: eteq
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/346587?v=4
|
||||
url: https://github.com/eteq
|
||||
- login: dmig
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/388564?v=4
|
||||
url: https://github.com/dmig
|
||||
- login: securancy
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/606673?v=4
|
||||
url: https://github.com/securancy
|
||||
- login: tochikuji
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4
|
||||
url: https://github.com/tochikuji
|
||||
- login: KentShikama
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4
|
||||
url: https://github.com/KentShikama
|
||||
- login: katnoria
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4
|
||||
url: https://github.com/katnoria
|
||||
- login: harsh183
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4
|
||||
url: https://github.com/harsh183
|
||||
- login: hcristea
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7814406?u=61d7a4fcf846983a4606788eac25e1c6c1209ba8&v=4
|
||||
url: https://github.com/hcristea
|
||||
- login: moonape1226
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/8532038?u=d9f8b855a429fff9397c3833c2ff83849ebf989d&v=4
|
||||
url: https://github.com/moonape1226
|
||||
|
|
@ -453,7 +408,7 @@ sponsors:
|
|||
avatarUrl: https://avatars.githubusercontent.com/u/9369632?u=8c988f1b008a3f601385a3616f9327820f66e3a5&v=4
|
||||
url: https://github.com/msehnout
|
||||
- login: xncbf
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=2ef1ede118a72c170805f50b9ad07341fd16a354&v=4
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=a80a7bb349555b277645632ed66639ff43400614&v=4
|
||||
url: https://github.com/xncbf
|
||||
- login: DMantis
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/9536869?v=4
|
||||
|
|
@ -464,9 +419,6 @@ sponsors:
|
|||
- login: supdann
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/9986994?u=9671810f4ae9504c063227fee34fd47567ff6954&v=4
|
||||
url: https://github.com/supdann
|
||||
- login: satwikkansal
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/10217535?u=b12d6ef74ea297de9e46da6933b1a5b7ba9e6a61&v=4
|
||||
url: https://github.com/satwikkansal
|
||||
- login: mntolia
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/10390224?v=4
|
||||
url: https://github.com/mntolia
|
||||
|
|
@ -479,17 +431,14 @@ sponsors:
|
|||
- login: Zuzah
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/10934846?u=1ef43e075ddc87bd1178372bf4d95ee6175cae27&v=4
|
||||
url: https://github.com/Zuzah
|
||||
- login: Alisa-lisa
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4
|
||||
url: https://github.com/Alisa-lisa
|
||||
- login: Graeme22
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/4185684?u=498182a42300d7bcd4de1215190cb17eb501136c&v=4
|
||||
url: https://github.com/Graeme22
|
||||
- login: artempronevskiy
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/12235104?u=03df6e1e55c9c6fe5d230adabb8dd7d43d8bbe8f&v=4
|
||||
url: https://github.com/artempronevskiy
|
||||
- login: danielunderwood
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4
|
||||
url: https://github.com/danielunderwood
|
||||
- login: rangulvers
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/5235430?v=4
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/5235430?u=e254d4af4ace5a05fa58372ae677c7d26f0d5a53&v=4
|
||||
url: https://github.com/rangulvers
|
||||
- login: sdevkota
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/5250987?u=4ed9a120c89805a8aefda1cbdc0cf6512e64d1b4&v=4
|
||||
|
|
@ -500,33 +449,42 @@ sponsors:
|
|||
- login: Baghdady92
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/5708590?v=4
|
||||
url: https://github.com/Baghdady92
|
||||
- login: jakeecolution
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/5884696?u=4a7c7883fb064b593b50cb6697b54687e6f7aafe&v=4
|
||||
url: https://github.com/jakeecolution
|
||||
- login: stephane-rbn
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/5939522?u=eb7ffe768fa3bcbcd04de14fe4a47444cc00ec4c&v=4
|
||||
url: https://github.com/stephane-rbn
|
||||
- - login: danburonline
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/34251194?u=94935cccfbec58083ab1e535212d54f1bf2c978a&v=4
|
||||
url: https://github.com/danburonline
|
||||
- login: AliYmn
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/18416653?u=0de5a262e8b4dc0a08d065f30f7a39941e246530&v=4
|
||||
url: https://github.com/AliYmn
|
||||
- login: sadikkuzu
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=d179c06bb9f65c4167fcab118526819f8e0dac17&v=4
|
||||
url: https://github.com/sadikkuzu
|
||||
- login: tran-hai-long
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/119793901?u=3b173a845dcf099b275bdc9713a69cbbc36040ce&v=4
|
||||
url: https://github.com/tran-hai-long
|
||||
- login: KentShikama
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4
|
||||
url: https://github.com/KentShikama
|
||||
- login: katnoria
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4
|
||||
url: https://github.com/katnoria
|
||||
- login: harsh183
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4
|
||||
url: https://github.com/harsh183
|
||||
- login: hcristea
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7814406?u=61d7a4fcf846983a4606788eac25e1c6c1209ba8&v=4
|
||||
url: https://github.com/hcristea
|
||||
- - login: larsyngvelundin
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/34173819?u=74958599695bf83ac9f1addd935a51548a10c6b0&v=4
|
||||
url: https://github.com/larsyngvelundin
|
||||
- login: andrecorumba
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/37807517?u=9b9be3b41da9bda60957da9ef37b50dbf65baa61&v=4
|
||||
url: https://github.com/andrecorumba
|
||||
- login: rwxd
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/40308458?u=cd04a39e3655923be4f25c2ba8a5a07b3da3230a&v=4
|
||||
url: https://github.com/rwxd
|
||||
- login: sadikkuzu
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=d179c06bb9f65c4167fcab118526819f8e0dac17&v=4
|
||||
url: https://github.com/sadikkuzu
|
||||
- login: Olegt0rr
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/25399456?u=3e87b5239a2f4600975ba13be73054f8567c6060&v=4
|
||||
url: https://github.com/Olegt0rr
|
||||
- login: FabulousCodingFox
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/78906517?u=924a27cbee3db7e0ece5cc1509921402e1445e74&v=4
|
||||
url: https://github.com/FabulousCodingFox
|
||||
- login: anqorithm
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/61029571?u=468256fa4e2d9ce2870b608299724bebb7a33f18&v=4
|
||||
url: https://github.com/anqorithm
|
||||
- login: ssbarnea
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/102495?u=c2efbf6fea2737e21dfc6b1113c4edc9644e9eaa&v=4
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/102495?u=c7bd9ddf127785286fc939dd18cb02db0a453bce&v=4
|
||||
url: https://github.com/ssbarnea
|
||||
- login: yuawn
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/5111198?u=5315576f3fe1a70fd2d0f02181588f4eea5d353d&v=4
|
||||
url: https://github.com/yuawn
|
||||
- login: dongzhenye
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/5765843?u=fe420c9a4c41e5b060faaf44029f5485616b470d&v=4
|
||||
url: https://github.com/dongzhenye
|
||||
- login: andreagrandi
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/636391?u=13d90cb8ec313593a5b71fbd4e33b78d6da736f5&v=4
|
||||
url: https://github.com/andreagrandi
|
||||
|
|
|
|||
|
|
@ -9,6 +9,24 @@ hide:
|
|||
|
||||
### Refactors
|
||||
|
||||
* ✅ Simplify tests for websockets. PR [#13202](https://github.com/fastapi/fastapi/pull/13202) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for request_form_models . PR [#13183](https://github.com/fastapi/fastapi/pull/13183) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for separate_openapi_schemas. PR [#13201](https://github.com/fastapi/fastapi/pull/13201) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for security. PR [#13200](https://github.com/fastapi/fastapi/pull/13200) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for schema_extra_example. PR [#13197](https://github.com/fastapi/fastapi/pull/13197) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for request_model. PR [#13195](https://github.com/fastapi/fastapi/pull/13195) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for request_forms_and_files. PR [#13185](https://github.com/fastapi/fastapi/pull/13185) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for request_forms. PR [#13184](https://github.com/fastapi/fastapi/pull/13184) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for path_query_params. PR [#13181](https://github.com/fastapi/fastapi/pull/13181) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for path_operation_configurations. PR [#13180](https://github.com/fastapi/fastapi/pull/13180) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for header_params. PR [#13179](https://github.com/fastapi/fastapi/pull/13179) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for extra_models. PR [#13178](https://github.com/fastapi/fastapi/pull/13178) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for extra_data_types. PR [#13177](https://github.com/fastapi/fastapi/pull/13177) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for cookie_params. PR [#13176](https://github.com/fastapi/fastapi/pull/13176) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for dependencies. PR [#13174](https://github.com/fastapi/fastapi/pull/13174) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for body_updates. PR [#13172](https://github.com/fastapi/fastapi/pull/13172) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for body_nested_models. PR [#13171](https://github.com/fastapi/fastapi/pull/13171) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for body_multiple_params. PR [#13170](https://github.com/fastapi/fastapi/pull/13170) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for body_fields. PR [#13169](https://github.com/fastapi/fastapi/pull/13169) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for body. PR [#13168](https://github.com/fastapi/fastapi/pull/13168) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✅ Simplify tests for bigger_applications. PR [#13167](https://github.com/fastapi/fastapi/pull/13167) by [@alejsdev](https://github.com/alejsdev).
|
||||
|
|
@ -31,6 +49,13 @@ hide:
|
|||
|
||||
### Translations
|
||||
|
||||
* 🌐 Update Portuguese translation for `docs/pt/docs/advanced/settings.md`. PR [#13209](https://github.com/fastapi/fastapi/pull/13209) by [@ceb10n](https://github.com/ceb10n).
|
||||
* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/security/oauth2-jwt.md`. PR [#13205](https://github.com/fastapi/fastapi/pull/13205) by [@ceb10n](https://github.com/ceb10n).
|
||||
* 🌐 Add Indonesian translation for `docs/id/docs/index.md`. PR [#13191](https://github.com/fastapi/fastapi/pull/13191) by [@gerry-sabar](https://github.com/gerry-sabar).
|
||||
* 🌐 Add Indonesian translation for `docs/id/docs/tutorial/static-files.md`. PR [#13092](https://github.com/fastapi/fastapi/pull/13092) by [@guspan-tanadi](https://github.com/guspan-tanadi).
|
||||
* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/security/get-current-user.md`. PR [#13188](https://github.com/fastapi/fastapi/pull/13188) by [@ceb10n](https://github.com/ceb10n).
|
||||
* 🌐 Remove Wrong Portuguese translations location for `docs/pt/docs/advanced/benchmarks.md`. PR [#13187](https://github.com/fastapi/fastapi/pull/13187) by [@ceb10n](https://github.com/ceb10n).
|
||||
* 🌐 Update Portuguese translations. PR [#13156](https://github.com/fastapi/fastapi/pull/13156) by [@nillvitor](https://github.com/nillvitor).
|
||||
* 🌐 Update Russian translation for `docs/ru/docs/tutorial/security/first-steps.md`. PR [#13159](https://github.com/fastapi/fastapi/pull/13159) by [@Yarous](https://github.com/Yarous).
|
||||
* ✏️ Delete unnecessary backspace in `docs/ja/docs/tutorial/path-params-numeric-validations.md`. PR [#12238](https://github.com/fastapi/fastapi/pull/12238) by [@FakeDocument](https://github.com/FakeDocument).
|
||||
* 🌐 Update Chinese translation for `docs/zh/docs/fastapi-cli.md`. PR [#13102](https://github.com/fastapi/fastapi/pull/13102) by [@Zhongheng-Cheng](https://github.com/Zhongheng-Cheng).
|
||||
|
|
@ -78,6 +103,10 @@ hide:
|
|||
|
||||
### Internal
|
||||
|
||||
* 👥 Update FastAPI People - Sponsors. PR [#13231](https://github.com/fastapi/fastapi/pull/13231) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Refactor FastAPI People Sponsors to use 2 tokens. PR [#13228](https://github.com/fastapi/fastapi/pull/13228) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Update token for FastAPI People - Sponsors. PR [#13225](https://github.com/fastapi/fastapi/pull/13225) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Add independent CI automation for FastAPI People - Sponsors. PR [#13221](https://github.com/fastapi/fastapi/pull/13221) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Add retries to Smokeshow. PR [#13151](https://github.com/fastapi/fastapi/pull/13151) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🔧 Update Speakeasy sponsor graphic. PR [#13147](https://github.com/fastapi/fastapi/pull/13147) by [@chailandau](https://github.com/chailandau).
|
||||
* 👥 Update FastAPI GitHub topic repositories. PR [#13146](https://github.com/fastapi/fastapi/pull/13146) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,495 @@
|
|||
# FastAPI
|
||||
|
||||
<style>
|
||||
.md-content .md-typeset h1 { display: none; }
|
||||
</style>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<em>FastAPI, framework performa tinggi, mudah dipelajari, cepat untuk coding, siap untuk pengembangan</em>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/fastapi/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank">
|
||||
<img src="https://github.com/fastapi/fastapi/workflows/Test/badge.svg?event=push&branch=master" alt="Test">
|
||||
</a>
|
||||
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/fastapi" target="_blank">
|
||||
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/fastapi.svg" alt="Coverage">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/fastapi" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/fastapi" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/pyversions/fastapi.svg?color=%2334D058" alt="Supported Python versions">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
**Dokumentasi**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
|
||||
|
||||
**Kode Sumber**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a>
|
||||
|
||||
---
|
||||
|
||||
FastAPI adalah *framework* *web* moderen, cepat (performa-tinggi) untuk membangun API dengan Python berdasarkan tipe petunjuk Python.
|
||||
|
||||
Fitur utama FastAPI:
|
||||
|
||||
* **Cepat**: Performa sangat tinggi, setara **NodeJS** dan **Go** (berkat Starlette dan Pydantic). [Salah satu *framework* Python tercepat yang ada](#performa).
|
||||
* **Cepat untuk coding**: Meningkatkan kecepatan pengembangan fitur dari 200% sampai 300%. *
|
||||
* **Sedikit bug**: Mengurangi hingga 40% kesalahan dari manusia (pemrogram). *
|
||||
* **Intuitif**: Dukungan editor hebat. <abbr title="juga dikenal otomatis-lengkap, pelengkapan otomatis, kecerdasan">Penyelesaian</abbr> di mana pun. Lebih sedikit *debugging*.
|
||||
* **Mudah**: Dibuat mudah digunakan dan dipelajari. Sedikit waktu membaca dokumentasi.
|
||||
* **Ringkas**: Mengurasi duplikasi kode. Beragam fitur dari setiap deklarasi parameter. Lebih sedikit *bug*.
|
||||
* **Handal**: Dapatkan kode siap-digunakan. Dengan dokumentasi otomatis interaktif.
|
||||
* **Standar-resmi**: Berdasarkan (kompatibel dengan ) standar umum untuk API: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (sebelumnya disebut Swagger) dan <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
|
||||
|
||||
<small>* estimasi berdasarkan pengujian tim internal pengembangan applikasi siap pakai.</small>
|
||||
|
||||
## Sponsor
|
||||
|
||||
<!-- sponsors -->
|
||||
|
||||
{% if sponsors %}
|
||||
{% for sponsor in sponsors.gold -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor -%}
|
||||
{%- for sponsor in sponsors.silver -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<!-- /sponsors -->
|
||||
|
||||
<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">Sponsor lainnya</a>
|
||||
|
||||
## Opini
|
||||
|
||||
"_[...] Saya banyak menggunakan **FastAPI** sekarang ini. [...] Saya berencana menggunakannya di semua tim servis ML Microsoft. Beberapa dari mereka sudah mengintegrasikan dengan produk inti *Windows** dan sebagian produk **Office**._"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/fastapi/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_Kami adopsi library **FastAPI** untuk membuat server **REST** yang melakukan kueri untuk menghasilkan **prediksi**. [untuk Ludwig]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_**Netflix** dengan bangga mengumumkan rilis open-source orkestrasi framework **manajemen krisis** : **Dispatch**! [dibuat dengan **FastAPI**]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kevin Glisson, Marc Vilanova, Forest Monsen - <strong>Netflix</strong> <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_Saya sangat senang dengan **FastAPI**. Sangat menyenangkan!_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> podcast host</strong> <a href="https://twitter.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_Jujur, apa yang anda buat sangat solid dan berkualitas. Ini adalah yang saya inginkan di **Hug** - sangat menginspirasi melihat seseorang membuat ini._"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_Jika anda ingin mempelajari **framework moderen** untuk membangun REST API, coba **FastAPI** [...] cepat, mudah digunakan dan dipelajari [...]_"
|
||||
|
||||
"_Kami sudah pindah ke **FastAPI** untuk **API** kami [...] Saya pikir kamu juga akan suka [...]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> founders - <a href="https://spacy.io" target="_blank">spaCy</a> creators</strong> <a href="https://twitter.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://twitter.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
"_Jika anda ingin membuat API Python siap pakai, saya merekomendasikan **FastAPI**. FastAPI **didesain indah**, **mudah digunakan** dan **sangat scalable**, FastAPI adalah **komponen kunci** di strategi pengembangan API pertama kami dan mengatur banyak otomatisasi dan service seperti TAC Engineer kami._"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
## **Typer**, CLI FastAPI
|
||||
|
||||
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
|
||||
|
||||
Jika anda mengembangkan app <abbr title="Command Line Interface">CLI</abbr> yang digunakan di terminal bukan sebagai API web, kunjungi <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
|
||||
|
||||
**Typer** adalah saudara kecil FastAPI. Dan ditujukan sebagai **CLI FastAPI**. ⌨️ 🚀
|
||||
|
||||
## Prayarat
|
||||
|
||||
FastAPI berdiri di pundak raksasa:
|
||||
|
||||
* <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> untuk bagian web.
|
||||
* <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> untuk bagian data.
|
||||
|
||||
## Instalasi
|
||||
|
||||
Buat dan aktifkan <a href="https://fastapi.tiangolo.com/virtual-environments/" class="external-link" target="_blank">virtual environment</a> kemudian *install* FastAPI:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "fastapi[standard]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
**Catatan**: Pastikan anda menulis `"fastapi[standard]"` dengan tanda petik untuk memastikan bisa digunakan di semua *terminal*.
|
||||
|
||||
## Contoh
|
||||
|
||||
### Buat app
|
||||
|
||||
* Buat file `main.py` dengan:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
<details markdown="1">
|
||||
<summary>Atau gunakan <code>async def</code>...</summary>
|
||||
|
||||
Jika kode anda menggunakan `async` / `await`, gunakan `async def`:
|
||||
|
||||
```Python hl_lines="9 14"
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(item_id: int, q: Union[str, None] = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
**Catatan**:
|
||||
|
||||
Jika anda tidak paham, kunjungi _"Panduan cepat"_ bagian <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">`async` dan `await` di dokumentasi</a>.
|
||||
|
||||
</details>
|
||||
|
||||
### Jalankan
|
||||
|
||||
Jalankan *server* dengan:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi dev main.py
|
||||
|
||||
╭────────── FastAPI CLI - Development mode ───────────╮
|
||||
│ │
|
||||
│ Serving at: http://127.0.0.1:8000 │
|
||||
│ │
|
||||
│ API docs: http://127.0.0.1:8000/docs │
|
||||
│ │
|
||||
│ Running in development mode, for production use: │
|
||||
│ │
|
||||
│ fastapi run │
|
||||
│ │
|
||||
╰─────────────────────────────────────────────────────╯
|
||||
|
||||
INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
|
||||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
INFO: Started reloader process [2248755] using WatchFiles
|
||||
INFO: Started server process [2248757]
|
||||
INFO: Waiting for application startup.
|
||||
INFO: Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<details markdown="1">
|
||||
<summary>Mengenai perintah <code>fastapi dev main.py</code>...</summary>
|
||||
|
||||
Perintah `fastapi dev` membaca file `main.py`, memeriksa app **FastAPI** di dalamnya, dan menjalan server dengan <a href="https://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a>.
|
||||
|
||||
Secara otomatis, `fastapi dev` akan mengaktifkan *auto-reload* untuk pengembangan lokal.
|
||||
|
||||
Informasi lebih lanjut kunjungi <a href="https://fastapi.tiangolo.com/fastapi-cli/" target="_blank">Dokumen FastAPI CLI</a>.
|
||||
|
||||
</details>
|
||||
|
||||
### Periksa
|
||||
|
||||
Buka *browser* di <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>.
|
||||
|
||||
Anda akan melihat respon JSON berikut:
|
||||
|
||||
```JSON
|
||||
{"item_id": 5, "q": "somequery"}
|
||||
```
|
||||
|
||||
Anda telah membuat API:
|
||||
|
||||
* Menerima permintaan HTTP di _path_ `/` dan `/items/{item_id}`.
|
||||
* Kedua _paths_ menerima <em>operasi</em> `GET` (juga disebut _metode_ HTTP).
|
||||
* _path_ `/items/{item_id}` memiliki _parameter path_ `item_id` yang harus berjenis `int`.
|
||||
* _path_ `/items/{item_id}` memiliki _query parameter_ `q` berjenis `str`.
|
||||
|
||||
### Dokumentasi API interaktif
|
||||
|
||||
Sekarang kunjungi <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Anda akan melihat dokumentasi API interaktif otomatis (dibuat oleh <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
|
||||

|
||||
|
||||
### Dokumentasi API alternatif
|
||||
|
||||
Kemudian kunjungi <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
|
||||
Anda akan melihat dokumentasi alternatif otomatis (dibuat oleh <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
|
||||
|
||||

|
||||
|
||||
## Contoh upgrade
|
||||
|
||||
Sekarang ubah `main.py` untuk menerima struktur permintaan `PUT`.
|
||||
|
||||
Deklarasikan struktur menggunakan tipe standar Python, berkat Pydantic.
|
||||
|
||||
```Python hl_lines="4 9-12 25-27"
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: float
|
||||
is_offer: Union[bool, None] = None
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
def update_item(item_id: int, item: Item):
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
Server `fastapi dev` akan otomatis memuat kembali.
|
||||
|
||||
### Upgrade dokumentasi API interaktif
|
||||
|
||||
Kunjungi <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
* Dokumentasi API interaktif akan otomatis diperbarui, termasuk kode yang baru:
|
||||
|
||||

|
||||
|
||||
* Klik tombol "Try it out", anda dapat mengisi parameter dan langsung berinteraksi dengan API:
|
||||
|
||||

|
||||
|
||||
* Kemudian klik tombol "Execute", tampilan pengguna akan berkomunikasi dengan API, mengirim parameter, mendapatkan dan menampilkan hasil ke layar:
|
||||
|
||||

|
||||
|
||||
### Upgrade dokumentasi API alternatif
|
||||
|
||||
Kunjungi <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
|
||||
* Dokumentasi alternatif akan menampilkan parameter *query* dan struktur *request*:
|
||||
|
||||

|
||||
|
||||
### Ringkasan
|
||||
|
||||
Singkatnya, anda mendeklarasikan **sekali** jenis parameter, struktur, dll. sebagai parameter fungsi.
|
||||
|
||||
Anda melakukannya dengan tipe standar moderen Python.
|
||||
|
||||
Anda tidak perlu belajar sintaksis, metode, *classs* baru dari *library* tertentu, dll.
|
||||
|
||||
Cukup **Python** standar.
|
||||
|
||||
Sebagai contoh untuk `int`:
|
||||
|
||||
```Python
|
||||
item_id: int
|
||||
```
|
||||
|
||||
atau untuk model lebih rumit `Item`:
|
||||
|
||||
```Python
|
||||
item: Item
|
||||
```
|
||||
|
||||
...dengan sekali deklarasi anda mendapatkan:
|
||||
|
||||
* Dukungan editor, termasuk:
|
||||
* Pelengkapan kode.
|
||||
* Pengecekan tipe.
|
||||
* Validasi data:
|
||||
* Kesalahan otomatis dan jelas ketika data tidak sesuai.
|
||||
* Validasi hingga untuk object JSON bercabang mendalam.
|
||||
* <abbr title="juga disebut: serialization, parsing, marshalling">Konversi</abbr> input data: berasal dari jaringan ke data dan tipe Python. Membaca dari:
|
||||
* JSON.
|
||||
* Parameter path.
|
||||
* Parameter query.
|
||||
* Cookie.
|
||||
* Header.
|
||||
* Form.
|
||||
* File.
|
||||
* <abbr title="juga disebut: serialization, parsing, marshalling">Konversi</abbr> output data: konversi data Python ke tipe jaringan data (seperti JSON):
|
||||
* Konversi tipe Python (`str`, `int`, `float`, `bool`, `list`, dll).
|
||||
* Objek `datetime`.
|
||||
* Objek `UUID`.
|
||||
* Model database.
|
||||
* ...dan banyak lagi.
|
||||
* Dokumentasi interaktif otomatis, termasuk 2 alternatif tampilan pengguna:
|
||||
* Swagger UI.
|
||||
* ReDoc.
|
||||
|
||||
---
|
||||
|
||||
Kembali ke kode contoh sebelumnya, **FastAPI** akan:
|
||||
|
||||
* Validasi apakah terdapat `item_id` di *path* untuk permintaan `GET` dan `PUT` requests.
|
||||
* Validasi apakah `item_id` berjenit `int` untuk permintaan `GET` dan `PUT`.
|
||||
* Jika tidak, klien akan melihat pesan kesalahan jelas.
|
||||
* Periksa jika ada parameter *query* opsional bernama `q` (seperti `http://127.0.0.1:8000/items/foo?q=somequery`) untuk permintaan `GET`.
|
||||
* Karena parameter `q` dideklarasikan dengan `= None`, maka bersifat opsional.
|
||||
* Tanpa `None` maka akan menjadi wajib ada (seperti struktur di kondisi dengan `PUT`).
|
||||
* Untuk permintaan `PUT` `/items/{item_id}`, membaca struktur sebagai JSON:
|
||||
* Memeriksa terdapat atribut wajib `name` harus berjenis `str`.
|
||||
* Memeriksa terdapat atribut wajib`price` harus berjenis `float`.
|
||||
* Memeriksa atribut opsional `is_offer`, harus berjenis `bool`, jika ada.
|
||||
* Semua ini juga sama untuk objek json yang bersarang mendalam.
|
||||
* Konversi dari dan ke JSON secara otomatis.
|
||||
* Dokumentasi segalanya dengan OpenAPI, dengan menggunakan:
|
||||
* Sistem dokumentasi interaktif.
|
||||
* Sistem otomatis penghasil kode, untuk banyak bahasa.
|
||||
* Menyediakan 2 tampilan dokumentasi web interaktif dengan langsung.
|
||||
|
||||
---
|
||||
|
||||
Kita baru menyentuh permukaannya saja, tetapi anda sudah mulai paham gambaran besar cara kerjanya.
|
||||
|
||||
Coba ubah baris:
|
||||
|
||||
```Python
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
...dari:
|
||||
|
||||
```Python
|
||||
... "item_name": item.name ...
|
||||
```
|
||||
|
||||
...menjadi:
|
||||
|
||||
```Python
|
||||
... "item_price": item.price ...
|
||||
```
|
||||
|
||||
...anda akan melihat kode editor secara otomatis melengkapi atributnya dan tahu tipe nya:
|
||||
|
||||

|
||||
|
||||
Untuk contoh lengkap termasuk fitur lainnya, kunjungi <a href="https://fastapi.tiangolo.com/tutorial/">Tutorial - Panduan Pengguna</a>.
|
||||
|
||||
**Peringatan spoiler**: tutorial - panduan pengguna termasuk:
|
||||
|
||||
* Deklarasi **parameter** dari tempat berbeda seperti: **header**, **cookie**, **form field** and **file**.
|
||||
* Bagaimana mengatur **batasan validasi** seperti `maximum_length`atau `regex`.
|
||||
* Sistem **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** yang hebat dan mudah digunakan.
|
||||
* Keamanan dan autentikasi, termasuk dukungan ke **OAuth2** dengan **JWT token** dan autentikasi **HTTP Basic**.
|
||||
* Teknik lebih aju (tetapi mudah dipakai untuk deklarasi **model JSON bersarang ke dalam** (berkat Pydantic).
|
||||
* Integrasi **GraphQL** dengan <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> dan library lainnya.
|
||||
* Fitur lainnya (berkat Starlette) seperti:
|
||||
* **WebSocket**
|
||||
* Test yang sangat mudah berdasarkan HTTPX dan `pytest`
|
||||
* **CORS**
|
||||
* **Cookie Session**
|
||||
* ...dan lainnya.
|
||||
|
||||
## Performa
|
||||
|
||||
Tolok ukur Independent TechEmpower mendapati aplikasi **FastAPI** berjalan menggunakan Uvicorn sebagai <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">salah satu framework Python tercepat yang ada</a>, hanya di bawah Starlette dan Uvicorn itu sendiri (digunakan di internal FastAPI). (*)
|
||||
|
||||
Penjelasan lebih lanjut, lihat bagian <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">Tolok ukur</a>.
|
||||
|
||||
## Dependensi
|
||||
|
||||
FastAPI bergantung pada Pydantic dan Starlette.
|
||||
|
||||
### Dependensi `standar`
|
||||
|
||||
Ketika anda meng-*install* FastAPI dengan `pip install "fastapi[standard]"`, maka FastAPI akan menggunakan sekumpulan dependensi opsional `standar`:
|
||||
|
||||
Digunakan oleh Pydantic:
|
||||
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> - untuk validasi email.
|
||||
|
||||
Digunakan oleh Starlette:
|
||||
|
||||
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - Dibutuhkan jika anda menggunakan `TestClient`.
|
||||
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Dibutuhkan jika anda menggunakan konfigurasi template bawaan.
|
||||
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Dibutuhkan jika anda menggunakan form dukungan <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>, dengan `request.form()`.
|
||||
|
||||
Digunakan oleh FastAPI / Starlette:
|
||||
|
||||
* <a href="https://www.uvicorn.org" target="_blank"><code>uvicorn</code></a> - untuk server yang memuat dan melayani aplikasi anda. Termasuk `uvicorn[standard]`, yang memasukan sejumlah dependensi (misal `uvloop`) untuk needed melayani dengan performa tinggi.
|
||||
* `fastapi-cli` - untuk menyediakan perintah `fastapi`.
|
||||
|
||||
### Tanpda dependensi `standard`
|
||||
|
||||
Jika anda tidak ingin menambahkan dependensi opsional `standard`, anda dapat menggunakan `pip install fastapi` daripada `pip install "fastapi[standard]"`.
|
||||
|
||||
### Dependensi Opsional Tambahan
|
||||
|
||||
Ada beberapa dependensi opsional yang bisa anda install.
|
||||
|
||||
Dependensi opsional tambahan Pydantic:
|
||||
|
||||
* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> - untuk manajemen setting.
|
||||
* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> - untuk tipe tambahan yang digunakan dengan Pydantic.
|
||||
|
||||
Dependensi tambahan opsional FastAPI:
|
||||
|
||||
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - Diperlukan jika anda akan menggunakan`ORJSONResponse`.
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - Diperlukan jika anda akan menggunakan `UJSONResponse`.
|
||||
|
||||
## Lisensi
|
||||
|
||||
Project terlisensi dengan lisensi MIT.
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# Berkas Statis
|
||||
|
||||
Anda dapat menyajikan berkas statis secara otomatis dari sebuah direktori menggunakan `StaticFiles`.
|
||||
|
||||
## Penggunaan `StaticFiles`
|
||||
|
||||
* Mengimpor `StaticFiles`.
|
||||
* "Mount" representatif `StaticFiles()` di jalur spesifik.
|
||||
|
||||
{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
|
||||
|
||||
/// note | Detail Teknis
|
||||
|
||||
Anda dapat pula menggunakan `from starlette.staticfiles import StaticFiles`.
|
||||
|
||||
**FastAPI** menyediakan `starlette.staticfiles` sama seperti `fastapi.staticfiles` sebagai kemudahan pada Anda, yaitu para pengembang. Tetapi ini asli berasal langsung dari Starlette.
|
||||
|
||||
///
|
||||
|
||||
### Apa itu "Mounting"
|
||||
|
||||
"Mounting" dimaksud menambah aplikasi "independen" secara lengkap di jalur spesifik, kemudian menangani seluruh sub-jalur.
|
||||
|
||||
Hal ini berbeda dari menggunakan `APIRouter` karena aplikasi yang dimount benar-benar independen. OpenAPI dan dokumentasi dari aplikasi utama Anda tak akan menyertakan apa pun dari aplikasi yang dimount, dst.
|
||||
|
||||
Anda dapat mempelajari mengenai ini dalam [Panduan Pengguna Lanjutan](../advanced/index.md){.internal-link target=_blank}.
|
||||
|
||||
## Detail
|
||||
|
||||
Terhadap `"/static"` pertama mengacu pada sub-jalur yang akan menjadi tempat "sub-aplikasi" ini akan "dimount". Maka, jalur apa pun yang dimulai dengan `"/static"` akan ditangani oleh sub-jalur tersebut.
|
||||
|
||||
Terhadap `directory="static"` mengacu pada nama direktori yang berisi berkas statis Anda.
|
||||
|
||||
Terhadap `name="static"` ialah nama yang dapat digunakan secara internal oleh **FastAPI**.
|
||||
|
||||
Seluruh parameter ini dapat berbeda dari sekadar "`static`", sesuaikan parameter dengan keperluan dan detail spesifik akan aplikasi Anda.
|
||||
|
||||
## Info lanjutan
|
||||
|
||||
Sebagai detail dan opsi tambahan lihat <a href="https://www.starlette.io/staticfiles/" class="external-link" target="_blank">dokumentasi Starlette perihal Berkas Statis</a>.
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
# Benchmarks
|
||||
|
||||
Benchmarks independentes da TechEmpower mostram que aplicações **FastAPI** rodando com o Uvicorn como <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">um dos frameworks Python mais rápidos disponíveis</a>, ficando atrás apenas do Starlette e Uvicorn (utilizado internamente pelo FastAPI).
|
||||
|
||||
Porém, ao verificar benchmarks e comparações você deve prestar atenção ao seguinte:
|
||||
|
||||
## Benchmarks e velocidade
|
||||
|
||||
Quando você verifica os benchmarks, é comum ver diversas ferramentas de diferentes tipos comparados como se fossem equivalentes.
|
||||
|
||||
Especificamente, para ver o Uvicorn, Starlette e FastAPI comparados entre si (entre diversas outras ferramentas).
|
||||
|
||||
Quanto mais simples o problema resolvido pela ferramenta, melhor será a performance. E a maioria das análises não testa funcionalidades adicionais que são oferecidas pela ferramenta.
|
||||
|
||||
A hierarquia é:
|
||||
|
||||
* **Uvicorn**: um servidor ASGI
|
||||
* **Starlette**: (utiliza Uvicorn) um microframework web
|
||||
* **FastAPI**: (utiliza Starlette) um microframework para APIs com diversas funcionalidades adicionais para a construção de APIs, com validação de dados, etc.
|
||||
|
||||
* **Uvicorn**:
|
||||
* Terá a melhor performance, pois não possui muito código além do próprio servidor.
|
||||
* Você não escreveria uma aplicação utilizando o Uvicorn diretamente. Isso significaria que o seu código teria que incluir pelo menos todo o código fornecido pelo Starlette (ou o **FastAPI**). E caso você fizesse isso, a sua aplicação final teria a mesma sobrecarga que teria se utilizasse um framework, minimizando o código e os bugs.
|
||||
* Se você está comparando o Uvicorn, compare com os servidores de aplicação Daphne, Hypercorn, uWSGI, etc.
|
||||
* **Starlette**:
|
||||
* Terá o melhor desempenho, depois do Uvicorn. Na verdade, o Starlette utiliza o Uvicorn para rodar. Portanto, ele pode ficar mais "devagar" que o Uvicorn apenas por ter que executar mais código.
|
||||
* Mas ele fornece as ferramentas para construir aplicações web simples, com roteamento baseado em caminhos, etc.
|
||||
* Se você está comparando o Starlette, compare-o com o Sanic, Flask, Django, etc. Frameworks web (ou microframeworks).
|
||||
* **FastAPI**:
|
||||
* Da mesma forma que o Starlette utiliza o Uvicorn e não consegue ser mais rápido que ele, o **FastAPI** utiliza o Starlette, portanto, ele não consegue ser mais rápido que ele.
|
||||
* O FastAPI provê mais funcionalidades em cima do Starlette. Funcionalidades que você quase sempre precisará quando estiver construindo APIs, como validação de dados e serialização. E ao utilizá-lo, você obtém documentação automática sem custo nenhum (a documentação automática sequer adiciona sobrecarga nas aplicações rodando, pois ela é gerada na inicialização).
|
||||
* Caso você não utilize o FastAPI e faz uso do Starlette diretamente (ou outra ferramenta, como o Sanic, Flask, Responder, etc) você mesmo teria que implementar toda a validação de dados e serialização. Então, a sua aplicação final ainda teria a mesma sobrecarga caso estivesse usando o FastAPI. E em muitos casos, validação de dados e serialização é a maior parte do código escrito em aplicações.
|
||||
* Então, ao utilizar o FastAPI, você está economizando tempo de programação, evitando bugs, linhas de código, e provavelmente terá a mesma performance (ou até melhor) do que teria caso você não o utilizasse (já que você teria que implementar tudo no seu código).
|
||||
* Se você está comparando o FastAPI, compare-o com frameworks de aplicações web (ou conjunto de ferramentas) que oferecem validação de dados, serialização e documentação, como por exemplo o Flask-apispec, NestJS, Molten, etc. Frameworks que possuem validação integrada de dados, serialização e documentação.
|
||||
|
|
@ -8,7 +8,7 @@ Por isso é comum prover essas configurações como variáveis de ambiente que s
|
|||
|
||||
## Variáveis de Ambiente
|
||||
|
||||
/// dica
|
||||
/// tip | Dica
|
||||
|
||||
Se você já sabe o que são variáveis de ambiente e como utilizá-las, sinta-se livre para avançar para o próximo tópico.
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ name = os.getenv("MY_NAME", "World")
|
|||
print(f"Hello {name} from Python")
|
||||
```
|
||||
|
||||
/// dica
|
||||
/// tip | Dica
|
||||
|
||||
O segundo parâmetro em <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> é o valor padrão para o retorno.
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ Hello World from Python
|
|||
|
||||
</div>
|
||||
|
||||
/// dica
|
||||
/// tip | Dica
|
||||
|
||||
Você pode ler mais sobre isso em: <a href="https://12factor.net/pt_br/config" class="external-link" target="_blank">The Twelve-Factor App: Configurações</a>.
|
||||
|
||||
|
|
@ -196,7 +196,7 @@ Na versão 1 do Pydantic você importaria `BaseSettings` diretamente do módulo
|
|||
|
||||
////
|
||||
|
||||
/// dica
|
||||
/// tip | Dica
|
||||
|
||||
Se você quiser algo pronto para copiar e colar na sua aplicação, não use esse exemplo, mas sim o exemplo abaixo.
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p
|
|||
|
||||
</div>
|
||||
|
||||
/// dica
|
||||
/// tip | Dica
|
||||
|
||||
Para definir múltiplas variáveis de ambiente para um único comando basta separá-las utilizando espaços, e incluir todas elas antes do comando.
|
||||
|
||||
|
|
@ -250,7 +250,7 @@ E utilizar essa configuração em `main.py`:
|
|||
|
||||
{* ../../docs_src/settings/app01/main.py hl[3,11:13] *}
|
||||
|
||||
/// dica
|
||||
/// tip | Dica
|
||||
|
||||
Você também precisa incluir um arquivo `__init__.py` como visto em [Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=\_blank}.
|
||||
|
||||
|
|
@ -276,7 +276,7 @@ Agora criamos a dependência que retorna um novo objeto `config.Settings()`.
|
|||
|
||||
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *}
|
||||
|
||||
/// dica
|
||||
/// tip | Dica
|
||||
|
||||
Vamos discutir sobre `@lru_cache` logo mais.
|
||||
|
||||
|
|
@ -304,7 +304,7 @@ Se você tiver muitas configurações que variem bastante, talvez em ambientes d
|
|||
|
||||
Essa prática é tão comum que possui um nome, essas variáveis de ambiente normalmente são colocadas em um arquivo `.env`, e esse arquivo é chamado de "dotenv".
|
||||
|
||||
/// dica
|
||||
/// tip | Dica
|
||||
|
||||
Um arquivo iniciando com um ponto final (`.`) é um arquivo oculto em sistemas baseados em Unix, como Linux e MacOS.
|
||||
|
||||
|
|
@ -314,7 +314,7 @@ Mas um arquivo dotenv não precisa ter esse nome exato.
|
|||
|
||||
Pydantic suporta a leitura desses tipos de arquivos utilizando uma biblioteca externa. Você pode ler mais em <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>.
|
||||
|
||||
/// dica
|
||||
/// tip | Dica
|
||||
|
||||
Para que isso funcione você precisa executar `pip install python-dotenv`.
|
||||
|
||||
|
|
@ -337,7 +337,7 @@ E então adicionar o seguinte código em `config.py`:
|
|||
|
||||
{* ../../docs_src/settings/app03_an/config.py hl[9] *}
|
||||
|
||||
/// dica
|
||||
/// tip | Dica
|
||||
|
||||
O atributo `model_config` é usado apenas para configuração do Pydantic. Você pode ler mais em <a href="https://docs.pydantic.dev/latest/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.
|
||||
|
||||
|
|
@ -349,7 +349,7 @@ O atributo `model_config` é usado apenas para configuração do Pydantic. Você
|
|||
|
||||
{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *}
|
||||
|
||||
/// dica
|
||||
/// tip | Dica
|
||||
|
||||
A classe `Config` é usada apenas para configuração do Pydantic. Você pode ler mais em <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,45 +7,33 @@ Em resumo, utilize o comando `fastapi run` para inicializar sua aplicação Fast
|
|||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:single">main.py</u>
|
||||
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
|
||||
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
|
||||
│ │
|
||||
│ 🐍 main.py │
|
||||
│ │
|
||||
╰──────────────────────╯
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀
|
||||
|
||||
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
|
||||
<font color="#3465A4">INFO </font> Found importable FastAPI app
|
||||
Searching for package file structure from directories
|
||||
with <font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
|
||||
│ │
|
||||
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
|
||||
│ │
|
||||
╰──────────────────────────╯
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
|
||||
the following code:
|
||||
|
||||
<font color="#4E9A06">╭─────────── FastAPI CLI - Production mode ───────────╮</font>
|
||||
<font color="#4E9A06">│ │</font>
|
||||
<font color="#4E9A06">│ Serving at: http://0.0.0.0:8000 │</font>
|
||||
<font color="#4E9A06">│ │</font>
|
||||
<font color="#4E9A06">│ API docs: http://0.0.0.0:8000/docs │</font>
|
||||
<font color="#4E9A06">│ │</font>
|
||||
<font color="#4E9A06">│ Running in production mode, for development use: │</font>
|
||||
<font color="#4E9A06">│ │</font>
|
||||
<font color="#4E9A06">│ </font><font color="#8AE234"><b>fastapi dev</b></font><font color="#4E9A06"> │</font>
|
||||
<font color="#4E9A06">│ │</font>
|
||||
<font color="#4E9A06">╰─────────────────────────────────────────────────────╯</font>
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2306215</font>]
|
||||
<font color="#4E9A06">INFO</font>: Waiting for application startup.
|
||||
<font color="#4E9A06">INFO</font>: Application startup complete.
|
||||
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8000</b> (Press CTRL+C to quit)
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>2306215</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C
|
||||
to quit<b>)</b>
|
||||
```
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,56 +36,43 @@ Se você usar o comando `fastapi`:
|
|||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ <pre> <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:single">main.py</u>
|
||||
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
|
||||
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
$ <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
|
||||
│ │
|
||||
│ 🐍 main.py │
|
||||
│ │
|
||||
╰──────────────────────╯
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀
|
||||
|
||||
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
|
||||
<font color="#3465A4">INFO </font> Found importable FastAPI app
|
||||
Searching for package file structure from directories with
|
||||
<font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
|
||||
│ │
|
||||
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
|
||||
│ │
|
||||
╰──────────────────────────╯
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
|
||||
following code:
|
||||
|
||||
<font color="#4E9A06">╭─────────── FastAPI CLI - Production mode ───────────╮</font>
|
||||
<font color="#4E9A06">│ │</font>
|
||||
<font color="#4E9A06">│ Serving at: http://0.0.0.0:8000 │</font>
|
||||
<font color="#4E9A06">│ │</font>
|
||||
<font color="#4E9A06">│ API docs: http://0.0.0.0:8000/docs │</font>
|
||||
<font color="#4E9A06">│ │</font>
|
||||
<font color="#4E9A06">│ Running in production mode, for development use: │</font>
|
||||
<font color="#4E9A06">│ │</font>
|
||||
<font color="#4E9A06">│ </font><font color="#8AE234"><b>fastapi dev</b></font><font color="#4E9A06"> │</font>
|
||||
<font color="#4E9A06">│ │</font>
|
||||
<font color="#4E9A06">╰─────────────────────────────────────────────────────╯</font>
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8000</b> (Press CTRL+C to quit)
|
||||
<font color="#4E9A06">INFO</font>: Started parent process [<font color="#34E2E2"><b>27365</b></font>]
|
||||
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27368</font>]
|
||||
<font color="#4E9A06">INFO</font>: Waiting for application startup.
|
||||
<font color="#4E9A06">INFO</font>: Application startup complete.
|
||||
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27369</font>]
|
||||
<font color="#4E9A06">INFO</font>: Waiting for application startup.
|
||||
<font color="#4E9A06">INFO</font>: Application startup complete.
|
||||
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27370</font>]
|
||||
<font color="#4E9A06">INFO</font>: Waiting for application startup.
|
||||
<font color="#4E9A06">INFO</font>: Application startup complete.
|
||||
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27367</font>]
|
||||
<font color="#4E9A06">INFO</font>: Waiting for application startup.
|
||||
<font color="#4E9A06">INFO</font>: Application startup complete.
|
||||
</pre>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C to
|
||||
quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started parent process <b>[</b><font color="#34E2E2"><b>27365</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27368</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27369</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27370</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27367</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,27 +11,43 @@ Execute o servidor:
|
|||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
<span style="color: green;">INFO</span>: Started reloader process [28720]
|
||||
<span style="color: green;">INFO</span>: Started server process [28722]
|
||||
<span style="color: green;">INFO</span>: Waiting for application startup.
|
||||
<span style="color: green;">INFO</span>: Application startup complete.
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
|
||||
|
||||
Searching for package file structure from directories
|
||||
with <font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
|
||||
the following code:
|
||||
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
|
||||
<b>fastapi run</b>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
|
||||
<b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
|
||||
to quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// note | Nota
|
||||
|
||||
O comando `uvicorn main:app` se refere a:
|
||||
|
||||
* `main`: o arquivo `main.py` (o "módulo" Python).
|
||||
* `app`: o objeto criado no arquivo `main.py` com a linha `app = FastAPI()`.
|
||||
* `--reload`: faz o servidor reiniciar após mudanças de código. Use apenas para desenvolvimento.
|
||||
|
||||
///
|
||||
|
||||
Na saída, temos:
|
||||
|
||||
```hl_lines="4"
|
||||
|
|
@ -151,34 +167,6 @@ Aqui, a variável `app` será uma "instância" da classe `FastAPI`.
|
|||
|
||||
Este será o principal ponto de interação para criar toda a sua API.
|
||||
|
||||
Este `app` é o mesmo referenciado por `uvicorn` no comando:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Se você criar a sua aplicação como:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial002.py hl[3] *}
|
||||
|
||||
E colocar em um arquivo `main.py`, você iria chamar o `uvicorn` assim:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:my_awesome_api --reload
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### Passo 3: crie uma *rota*
|
||||
|
||||
#### Rota
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ Esse tutorial mostra como usar o **FastAPI** com a maior parte de seus recursos,
|
|||
|
||||
Cada seção constrói, gradualmente, sobre as anteriores, mas sua estrutura são tópicos separados, para que você possa ir a qualquer um específico e resolver suas necessidades específicas de API.
|
||||
|
||||
Ele também foi feito como referência futura.
|
||||
|
||||
Então você poderá voltar e ver exatamente o que precisar.
|
||||
Ele também foi construído para servir como uma referência futura, então você pode voltar e ver exatamente o que você precisa.
|
||||
|
||||
## Rode o código
|
||||
|
||||
|
|
@ -17,13 +15,39 @@ Para rodar qualquer um dos exemplos, copie o codigo para um arquivo `main.py`, e
|
|||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
<span style="color: green;">INFO</span>: Started reloader process [28720]
|
||||
<span style="color: green;">INFO</span>: Started server process [28722]
|
||||
<span style="color: green;">INFO</span>: Waiting for application startup.
|
||||
<span style="color: green;">INFO</span>: Application startup complete.
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
|
||||
|
||||
Searching for package file structure from directories
|
||||
with <font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
|
||||
the following code:
|
||||
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
|
||||
<b>fastapi run</b>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
|
||||
<b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
|
||||
to quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
|
@ -43,32 +67,18 @@ Para o tutorial, você deve querer instalá-lo com todas as dependências e recu
|
|||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "fastapi[all]"
|
||||
$ pip install "fastapi[standard]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
...isso também inclui o `uvicorn`, que você pode usar como o servidor que rodará seu código.
|
||||
|
||||
/// note | Nota
|
||||
|
||||
Você também pode instalar parte por parte.
|
||||
Quando você instala com pip install "fastapi[standard]", ele vem com algumas dependências opcionais padrão.
|
||||
|
||||
Isso é provavelmente o que você faria quando você quisesse lançar sua aplicação em produção:
|
||||
|
||||
```
|
||||
pip install fastapi
|
||||
```
|
||||
|
||||
Também instale o `uvicorn` para funcionar como servidor:
|
||||
|
||||
```
|
||||
pip install "uvicorn[standard]"
|
||||
```
|
||||
|
||||
E o mesmo para cada dependência opcional que você quiser usar.
|
||||
Se você não quiser ter essas dependências opcionais, pode instalar pip install fastapi em vez disso.
|
||||
|
||||
///
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
# Obter Usuário Atual
|
||||
|
||||
No capítulo anterior, o sistema de segurança (que é baseado no sistema de injeção de dependências) estava fornecendo à *função de operação de rota* um `token` como uma `str`:
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
|
||||
|
||||
Mas isso ainda não é tão útil.
|
||||
|
||||
Vamos fazer com que ele nos forneça o usuário atual.
|
||||
|
||||
## Criar um modelo de usuário
|
||||
|
||||
Primeiro, vamos criar um modelo de usuário com Pydantic.
|
||||
|
||||
Da mesma forma que usamos o Pydantic para declarar corpos, podemos usá-lo em qualquer outro lugar:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *}
|
||||
|
||||
## Criar uma dependência `get_current_user`
|
||||
|
||||
Vamos criar uma dependência chamada `get_current_user`.
|
||||
|
||||
Lembra que as dependências podem ter subdependências?
|
||||
|
||||
`get_current_user` terá uma dependência com o mesmo `oauth2_scheme` que criamos antes.
|
||||
|
||||
Da mesma forma que estávamos fazendo antes diretamente na *operação de rota*, a nossa nova dependência `get_current_user` receberá um `token` como uma `str` da subdependência `oauth2_scheme`:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *}
|
||||
|
||||
## Obter o usuário
|
||||
|
||||
`get_current_user` usará uma função utilitária (falsa) que criamos, que recebe um token como uma `str` e retorna nosso modelo Pydantic `User`:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *}
|
||||
|
||||
## Injetar o usuário atual
|
||||
|
||||
Então agora nós podemos usar o mesmo `Depends` com nosso `get_current_user` na *operação de rota*:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *}
|
||||
|
||||
Observe que nós declaramos o tipo de `current_user` como o modelo Pydantic `User`.
|
||||
|
||||
Isso nos ajudará dentro da função com todo o preenchimento automático e verificações de tipo.
|
||||
|
||||
/// tip | Dica
|
||||
|
||||
Você pode se lembrar que corpos de requisição também são declarados com modelos Pydantic.
|
||||
|
||||
Aqui, o **FastAPI** não ficará confuso porque você está usando `Depends`.
|
||||
|
||||
///
|
||||
|
||||
/// check | Verifique
|
||||
|
||||
A forma como esse sistema de dependências foi projetado nos permite ter diferentes dependências (diferentes "dependables") que retornam um modelo `User`.
|
||||
|
||||
Não estamos restritos a ter apenas uma dependência que possa retornar esse tipo de dado.
|
||||
|
||||
///
|
||||
|
||||
## Outros modelos
|
||||
|
||||
Agora você pode obter o usuário atual diretamente nas *funções de operação de rota* e lidar com os mecanismos de segurança no nível da **Injeção de Dependências**, usando `Depends`.
|
||||
|
||||
E você pode usar qualquer modelo ou dado para os requisitos de segurança (neste caso, um modelo Pydantic `User`).
|
||||
|
||||
Mas você não está restrito a usar um modelo de dados, classe ou tipo específico.
|
||||
|
||||
Você quer ter apenas um `id` e `email`, sem incluir nenhum `username` no modelo? Claro. Você pode usar essas mesmas ferramentas.
|
||||
|
||||
Você quer ter apenas uma `str`? Ou apenas um `dict`? Ou uma instância de modelo de classe de banco de dados diretamente? Tudo funciona da mesma forma.
|
||||
|
||||
Na verdade, você não tem usuários que fazem login no seu aplicativo, mas sim robôs, bots ou outros sistemas, que possuem apenas um token de acesso? Novamente, tudo funciona da mesma forma.
|
||||
|
||||
Apenas use qualquer tipo de modelo, qualquer tipo de classe, qualquer tipo de banco de dados que você precise para a sua aplicação. O **FastAPI** cobre tudo com o sistema de injeção de dependências.
|
||||
|
||||
## Tamanho do código
|
||||
|
||||
Este exemplo pode parecer verboso. Lembre-se de que estamos misturando segurança, modelos de dados, funções utilitárias e *operações de rota* no mesmo arquivo.
|
||||
|
||||
Mas aqui está o ponto principal.
|
||||
|
||||
O código relacionado à segurança e à injeção de dependências é escrito apenas uma vez.
|
||||
|
||||
E você pode torná-lo tão complexo quanto quiser. E ainda assim, tê-lo escrito apenas uma vez, em um único lugar. Com toda a flexibilidade.
|
||||
|
||||
Mas você pode ter milhares de endpoints (*operações de rota*) usando o mesmo sistema de segurança.
|
||||
|
||||
E todos eles (ou qualquer parte deles que você desejar) podem aproveitar o reuso dessas dependências ou de quaisquer outras dependências que você criar.
|
||||
|
||||
E todos esses milhares de *operações de rota* podem ter apenas 3 linhas:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
|
||||
|
||||
## Recapitulação
|
||||
|
||||
Agora você pode obter o usuário atual diretamente na sua *função de operação de rota*.
|
||||
|
||||
Já estamos na metade do caminho.
|
||||
|
||||
Só precisamos adicionar uma *operação de rota* para que o usuário/cliente realmente envie o `username` e `password`.
|
||||
|
||||
Isso vem a seguir.
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
# OAuth2 com Senha (e hashing), Bearer com tokens JWT
|
||||
|
||||
Agora que temos todo o fluxo de segurança, vamos tornar a aplicação realmente segura, usando tokens <abbr title="JSON Web Tokens">JWT</abbr> e hashing de senhas seguras.
|
||||
|
||||
Este código é algo que você pode realmente usar na sua aplicação, salvar os hashes das senhas no seu banco de dados, etc.
|
||||
|
||||
Vamos começar de onde paramos no capítulo anterior e incrementá-lo.
|
||||
|
||||
## Sobre o JWT
|
||||
|
||||
JWT significa "JSON Web Tokens".
|
||||
|
||||
É um padrão para codificar um objeto JSON em uma string longa e densa sem espaços. Ele se parece com isso:
|
||||
|
||||
```
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
||||
```
|
||||
|
||||
Ele não é criptografado, então qualquer pessoa pode recuperar as informações do seu conteúdo.
|
||||
|
||||
Mas ele é assinado. Assim, quando você recebe um token que você emitiu, você pode verificar que foi realmente você quem o emitiu.
|
||||
|
||||
Dessa forma, você pode criar um token com um prazo de expiração, digamos, de 1 semana. E então, quando o usuário voltar no dia seguinte com o token, você sabe que ele ainda está logado no seu sistema.
|
||||
|
||||
Depois de uma semana, o token expirará e o usuário não estará autorizado, precisando fazer login novamente para obter um novo token. E se o usuário (ou uma terceira parte) tentar modificar o token para alterar a expiração, você seria capaz de descobrir isso, pois as assinaturas não iriam corresponder.
|
||||
|
||||
Se você quiser brincar com tokens JWT e ver como eles funcionam, visite <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>.
|
||||
|
||||
## Instalar `PyJWT`
|
||||
|
||||
Nós precisamos instalar o `PyJWT` para criar e verificar os tokens JWT em Python.
|
||||
|
||||
Certifique-se de criar um [ambiente virtual](../../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar o `pyjwt`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install pyjwt
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// info | Informação
|
||||
|
||||
Se você pretente utilizar algoritmos de assinatura digital como o RSA ou o ECDSA, você deve instalar a dependência da biblioteca de criptografia `pyjwt[crypto]`.
|
||||
|
||||
Você pode ler mais sobre isso na <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">documentação de instalação do PyJWT</a>.
|
||||
|
||||
///
|
||||
|
||||
## Hashing de senhas
|
||||
|
||||
"Hashing" significa converter algum conteúdo (uma senha neste caso) em uma sequência de bytes (apenas uma string) que parece um monte de caracteres sem sentido.
|
||||
|
||||
Sempre que você passar exatamente o mesmo conteúdo (exatamente a mesma senha), você obterá exatamente o mesmo resultado.
|
||||
|
||||
Mas não é possível converter os caracteres sem sentido de volta para a senha original.
|
||||
|
||||
### Por que usar hashing de senhas
|
||||
|
||||
Se o seu banco de dados for roubado, o invasor não terá as senhas em texto puro dos seus usuários, apenas os hashes.
|
||||
|
||||
Então, o invasor não poderá tentar usar essas senhas em outro sistema (como muitos usuários utilizam a mesma senha em vários lugares, isso seria perigoso).
|
||||
|
||||
## Instalar o `passlib`
|
||||
|
||||
O PassLib é uma excelente biblioteca Python para lidar com hashes de senhas.
|
||||
|
||||
Ele suporta muitos algoritmos de hashing seguros e utilitários para trabalhar com eles.
|
||||
|
||||
O algoritmo recomendado é o "Bcrypt".
|
||||
|
||||
Certifique-se de criar um [ambiente virtual](../../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar o PassLib com Bcrypt:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "passlib[bcrypt]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | Dica
|
||||
|
||||
Com o `passlib`, você poderia até configurá-lo para ser capaz de ler senhas criadas pelo **Django**, um plug-in de segurança do **Flask** ou muitos outros.
|
||||
|
||||
Assim, você poderia, por exemplo, compartilhar os mesmos dados de um aplicativo Django em um banco de dados com um aplicativo FastAPI. Ou migrar gradualmente uma aplicação Django usando o mesmo banco de dados.
|
||||
|
||||
E seus usuários poderiam fazer login tanto pela sua aplicação Django quanto pela sua aplicação **FastAPI**, ao mesmo tempo.
|
||||
|
||||
///
|
||||
|
||||
## Criar o hash e verificar as senhas
|
||||
|
||||
Importe as ferramentas que nós precisamos de `passlib`.
|
||||
|
||||
Crie um "contexto" do PassLib. Este será usado para criar o hash e verificar as senhas.
|
||||
|
||||
/// tip | Dica
|
||||
|
||||
O contexto do PassLib também possui funcionalidades para usar diferentes algoritmos de hashing, incluindo algoritmos antigos que estão obsoletos, apenas para permitir verificá-los, etc.
|
||||
|
||||
Por exemplo, você poderia usá-lo para ler e verificar senhas geradas por outro sistema (como Django), mas criar o hash de novas senhas com um algoritmo diferente, como o Bcrypt.
|
||||
|
||||
E ser compatível com todos eles ao mesmo tempo.
|
||||
|
||||
///
|
||||
|
||||
Crie uma função utilitária para criar o hash de uma senha fornecida pelo usuário.
|
||||
|
||||
E outra função utilitária para verificar se uma senha recebida corresponde ao hash armazenado.
|
||||
|
||||
E outra para autenticar e retornar um usuário.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *}
|
||||
|
||||
/// note | Nota
|
||||
|
||||
Se você verificar o novo banco de dados (falso) `fake_users_db`, você verá como o hash da senha se parece agora: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`.
|
||||
|
||||
///
|
||||
|
||||
## Manipular tokens JWT
|
||||
|
||||
Importe os módulos instalados.
|
||||
|
||||
Crie uma chave secreta aleatória que será usada para assinar os tokens JWT.
|
||||
|
||||
Para gerar uma chave secreta aleatória e segura, use o comando:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ openssl rand -hex 32
|
||||
|
||||
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
E copie a saída para a variável `SECRET_KEY` (não use a do exemplo).
|
||||
|
||||
Crie uma variável `ALGORITHM` com o algoritmo usado para assinar o token JWT e defina como `"HS256"`.
|
||||
|
||||
Crie uma variável para a expiração do token.
|
||||
|
||||
Defina um modelo Pydantic que será usado no endpoint de token para a resposta.
|
||||
|
||||
Crie uma função utilitária para gerar um novo token de acesso.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
|
||||
|
||||
## Atualize as dependências
|
||||
|
||||
Atualize `get_current_user` para receber o mesmo token de antes, mas desta vez, usando tokens JWT.
|
||||
|
||||
Decodifique o token recebido, verifique-o e retorne o usuário atual.
|
||||
|
||||
Se o token for inválido, retorne um erro HTTP imediatamente.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *}
|
||||
|
||||
## Atualize a *operação de rota* `/token`
|
||||
|
||||
Crie um `timedelta` com o tempo de expiração do token.
|
||||
|
||||
Crie um token de acesso JWT real e o retorne.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
|
||||
|
||||
### Detalhes técnicos sobre o "sujeito" `sub` do JWT
|
||||
|
||||
A especificação JWT diz que existe uma chave `sub`, com o sujeito do token.
|
||||
|
||||
É opcional usá-la, mas é onde você colocaria a identificação do usuário, então nós estamos usando aqui.
|
||||
|
||||
O JWT pode ser usado para outras coisas além de identificar um usuário e permitir que ele execute operações diretamente na sua API.
|
||||
|
||||
Por exemplo, você poderia identificar um "carro" ou uma "postagem de blog".
|
||||
|
||||
Depois, você poderia adicionar permissões sobre essa entidade, como "dirigir" (para o carro) ou "editar" (para o blog).
|
||||
|
||||
E então, poderia dar esse token JWT para um usuário (ou bot), e ele poderia usá-lo para realizar essas ações (dirigir o carro ou editar o blog) sem sequer precisar ter uma conta, apenas com o token JWT que sua API gerou para isso.
|
||||
|
||||
Usando essas ideias, o JWT pode ser usado para cenários muito mais sofisticados.
|
||||
|
||||
Nesses casos, várias dessas entidades poderiam ter o mesmo ID, digamos `foo` (um usuário `foo`, um carro `foo` e uma postagem de blog `foo`).
|
||||
|
||||
Então, para evitar colisões de ID, ao criar o token JWT para o usuário, você poderia prefixar o valor da chave `sub`, por exemplo, com `username:`. Assim, neste exemplo, o valor de `sub` poderia ser: `username:johndoe`.
|
||||
|
||||
O importante a se lembrar é que a chave `sub` deve ter um identificador único em toda a aplicação e deve ser uma string.
|
||||
|
||||
## Testando
|
||||
|
||||
Execute o servidor e vá para a documentação: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Você verá a interface de usuário assim:
|
||||
|
||||
<img src="/img/tutorial/security/image07.png">
|
||||
|
||||
Autorize a aplicação da mesma maneira que antes.
|
||||
|
||||
Usando as credenciais:
|
||||
|
||||
Username: `johndoe`
|
||||
Password: `secret`
|
||||
|
||||
/// check | Verifique
|
||||
|
||||
Observe que em nenhuma parte do código está a senha em texto puro "`secret`", nós temos apenas o hash.
|
||||
|
||||
///
|
||||
|
||||
<img src="/img/tutorial/security/image08.png">
|
||||
|
||||
Chame o endpoint `/users/me/`, você receberá o retorno como:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"username": "johndoe",
|
||||
"email": "johndoe@example.com",
|
||||
"full_name": "John Doe",
|
||||
"disabled": false
|
||||
}
|
||||
```
|
||||
|
||||
<img src="/img/tutorial/security/image09.png">
|
||||
|
||||
Se você abrir as ferramentas de desenvolvedor, poderá ver que os dados enviados incluem apenas o token. A senha é enviada apenas na primeira requisição para autenticar o usuário e obter o token de acesso, mas não é enviada nas próximas requisições:
|
||||
|
||||
<img src="/img/tutorial/security/image10.png">
|
||||
|
||||
/// note | Nota
|
||||
|
||||
Perceba que o cabeçalho `Authorization`, com o valor que começa com `Bearer `.
|
||||
|
||||
///
|
||||
|
||||
## Uso avançado com `scopes`
|
||||
|
||||
O OAuth2 tem a noção de "scopes" (escopos).
|
||||
|
||||
Você pode usá-los para adicionar um conjunto específico de permissões a um token JWT.
|
||||
|
||||
Então, você pode dar este token diretamente a um usuário ou a uma terceira parte para interagir com sua API com um conjunto de restrições.
|
||||
|
||||
Você pode aprender como usá-los e como eles são integrados ao **FastAPI** mais adiante no **Guia Avançado do Usuário**.
|
||||
|
||||
|
||||
## Recapitulação
|
||||
|
||||
Com o que você viu até agora, você pode configurar uma aplicação **FastAPI** segura usando padrões como OAuth2 e JWT.
|
||||
|
||||
Em quase qualquer framework, lidar com a segurança se torna rapidamente um assunto bastante complexo.
|
||||
|
||||
Muitos pacotes que simplificam bastante isso precisam fazer muitas concessões com o modelo de dados, o banco de dados e os recursos disponíveis. E alguns desses pacotes que simplificam demais na verdade têm falhas de segurança subjacentes.
|
||||
|
||||
---
|
||||
|
||||
O **FastAPI** não faz nenhuma concessão com nenhum banco de dados, modelo de dados ou ferramenta.
|
||||
|
||||
Ele oferece toda a flexibilidade para você escolher as opções que melhor se ajustam ao seu projeto.
|
||||
|
||||
E você pode usar diretamente muitos pacotes bem mantidos e amplamente utilizados, como `passlib` e `PyJWT`, porque o **FastAPI** não exige mecanismos complexos para integrar pacotes externos.
|
||||
|
||||
Mas ele fornece as ferramentas para simplificar o processo o máximo possível, sem comprometer a flexibilidade, robustez ou segurança.
|
||||
|
||||
E você pode usar e implementar protocolos padrão seguros, como o OAuth2, de uma maneira relativamente simples.
|
||||
|
||||
Você pode aprender mais no **Guia Avançado do Usuário** sobre como usar os "scopes" do OAuth2 para um sistema de permissões mais refinado, seguindo esses mesmos padrões. O OAuth2 com scopes é o mecanismo usado por muitos provedores grandes de autenticação, como o Facebook, Google, GitHub, Microsoft, Twitter, etc. para autorizar aplicativos de terceiros a interagir com suas APIs em nome de seus usuários.
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
import logging
|
||||
import secrets
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import yaml
|
||||
from github import Github
|
||||
from pydantic import BaseModel, SecretStr
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
github_graphql_url = "https://api.github.com/graphql"
|
||||
|
||||
|
||||
sponsors_query = """
|
||||
query Q($after: String) {
|
||||
user(login: "tiangolo") {
|
||||
sponsorshipsAsMaintainer(first: 100, after: $after) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
sponsorEntity {
|
||||
... on Organization {
|
||||
login
|
||||
avatarUrl
|
||||
url
|
||||
}
|
||||
... on User {
|
||||
login
|
||||
avatarUrl
|
||||
url
|
||||
}
|
||||
}
|
||||
tier {
|
||||
name
|
||||
monthlyPriceInDollars
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class SponsorEntity(BaseModel):
|
||||
login: str
|
||||
avatarUrl: str
|
||||
url: str
|
||||
|
||||
|
||||
class Tier(BaseModel):
|
||||
name: str
|
||||
monthlyPriceInDollars: float
|
||||
|
||||
|
||||
class SponsorshipAsMaintainerNode(BaseModel):
|
||||
sponsorEntity: SponsorEntity
|
||||
tier: Tier
|
||||
|
||||
|
||||
class SponsorshipAsMaintainerEdge(BaseModel):
|
||||
cursor: str
|
||||
node: SponsorshipAsMaintainerNode
|
||||
|
||||
|
||||
class SponsorshipAsMaintainer(BaseModel):
|
||||
edges: list[SponsorshipAsMaintainerEdge]
|
||||
|
||||
|
||||
class SponsorsUser(BaseModel):
|
||||
sponsorshipsAsMaintainer: SponsorshipAsMaintainer
|
||||
|
||||
|
||||
class SponsorsResponseData(BaseModel):
|
||||
user: SponsorsUser
|
||||
|
||||
|
||||
class SponsorsResponse(BaseModel):
|
||||
data: SponsorsResponseData
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
sponsors_token: SecretStr
|
||||
pr_token: SecretStr
|
||||
github_repository: str
|
||||
httpx_timeout: int = 30
|
||||
|
||||
|
||||
def get_graphql_response(
|
||||
*,
|
||||
settings: Settings,
|
||||
query: str,
|
||||
after: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
headers = {"Authorization": f"token {settings.sponsors_token.get_secret_value()}"}
|
||||
variables = {"after": after}
|
||||
response = httpx.post(
|
||||
github_graphql_url,
|
||||
headers=headers,
|
||||
timeout=settings.httpx_timeout,
|
||||
json={"query": query, "variables": variables, "operationName": "Q"},
|
||||
)
|
||||
if response.status_code != 200:
|
||||
logging.error(f"Response was not 200, after: {after}")
|
||||
logging.error(response.text)
|
||||
raise RuntimeError(response.text)
|
||||
data = response.json()
|
||||
if "errors" in data:
|
||||
logging.error(f"Errors in response, after: {after}")
|
||||
logging.error(data["errors"])
|
||||
logging.error(response.text)
|
||||
raise RuntimeError(response.text)
|
||||
return data
|
||||
|
||||
|
||||
def get_graphql_sponsor_edges(
|
||||
*, settings: Settings, after: str | None = None
|
||||
) -> list[SponsorshipAsMaintainerEdge]:
|
||||
data = get_graphql_response(settings=settings, query=sponsors_query, after=after)
|
||||
graphql_response = SponsorsResponse.model_validate(data)
|
||||
return graphql_response.data.user.sponsorshipsAsMaintainer.edges
|
||||
|
||||
|
||||
def get_individual_sponsors(
|
||||
settings: Settings,
|
||||
) -> defaultdict[float, dict[str, SponsorEntity]]:
|
||||
nodes: list[SponsorshipAsMaintainerNode] = []
|
||||
edges = get_graphql_sponsor_edges(settings=settings)
|
||||
|
||||
while edges:
|
||||
for edge in edges:
|
||||
nodes.append(edge.node)
|
||||
last_edge = edges[-1]
|
||||
edges = get_graphql_sponsor_edges(settings=settings, after=last_edge.cursor)
|
||||
|
||||
tiers: defaultdict[float, dict[str, SponsorEntity]] = defaultdict(dict)
|
||||
for node in nodes:
|
||||
tiers[node.tier.monthlyPriceInDollars][node.sponsorEntity.login] = (
|
||||
node.sponsorEntity
|
||||
)
|
||||
return tiers
|
||||
|
||||
|
||||
def update_content(*, content_path: Path, new_content: Any) -> bool:
|
||||
old_content = content_path.read_text(encoding="utf-8")
|
||||
|
||||
new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True)
|
||||
if old_content == new_content:
|
||||
logging.info(f"The content hasn't changed for {content_path}")
|
||||
return False
|
||||
content_path.write_text(new_content, encoding="utf-8")
|
||||
logging.info(f"Updated {content_path}")
|
||||
return True
|
||||
|
||||
|
||||
def main() -> None:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
settings = Settings()
|
||||
logging.info(f"Using config: {settings.model_dump_json()}")
|
||||
g = Github(settings.pr_token.get_secret_value())
|
||||
repo = g.get_repo(settings.github_repository)
|
||||
|
||||
tiers = get_individual_sponsors(settings=settings)
|
||||
keys = list(tiers.keys())
|
||||
keys.sort(reverse=True)
|
||||
sponsors = []
|
||||
for key in keys:
|
||||
sponsor_group = []
|
||||
for login, sponsor in tiers[key].items():
|
||||
sponsor_group.append(
|
||||
{"login": login, "avatarUrl": sponsor.avatarUrl, "url": sponsor.url}
|
||||
)
|
||||
sponsors.append(sponsor_group)
|
||||
github_sponsors = {
|
||||
"sponsors": sponsors,
|
||||
}
|
||||
|
||||
# For local development
|
||||
# github_sponsors_path = Path("../docs/en/data/github_sponsors.yml")
|
||||
github_sponsors_path = Path("./docs/en/data/github_sponsors.yml")
|
||||
updated = update_content(
|
||||
content_path=github_sponsors_path, new_content=github_sponsors
|
||||
)
|
||||
|
||||
if not updated:
|
||||
logging.info("The data hasn't changed, finishing.")
|
||||
return
|
||||
|
||||
logging.info("Setting up GitHub Actions git user")
|
||||
subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
|
||||
subprocess.run(
|
||||
["git", "config", "user.email", "github-actions@github.com"], check=True
|
||||
)
|
||||
branch_name = f"fastapi-people-sponsors-{secrets.token_hex(4)}"
|
||||
logging.info(f"Creating a new branch {branch_name}")
|
||||
subprocess.run(["git", "checkout", "-b", branch_name], check=True)
|
||||
logging.info("Adding updated file")
|
||||
subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"add",
|
||||
str(github_sponsors_path),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
logging.info("Committing updated file")
|
||||
message = "👥 Update FastAPI People - Sponsors"
|
||||
subprocess.run(["git", "commit", "-m", message], check=True)
|
||||
logging.info("Pushing branch")
|
||||
subprocess.run(["git", "push", "origin", branch_name], check=True)
|
||||
logging.info("Creating PR")
|
||||
pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
|
||||
logging.info(f"Created PR: {pr.number}")
|
||||
logging.info("Finished")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,13 +1,26 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39, needs_py310
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.body_multiple_params.tutorial001 import app
|
||||
|
||||
client = TestClient(app)
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial001",
|
||||
pytest.param("tutorial001_py310", marks=needs_py310),
|
||||
"tutorial001_an",
|
||||
pytest.param("tutorial001_an_py39", marks=needs_py39),
|
||||
pytest.param("tutorial001_an_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,206 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.body_multiple_params.tutorial001_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
def test_post_body_q_bar_content(client: TestClient):
|
||||
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"item_id": 5,
|
||||
"item": {
|
||||
"name": "Foo",
|
||||
"price": 50.5,
|
||||
"description": None,
|
||||
"tax": None,
|
||||
},
|
||||
"q": "bar",
|
||||
}
|
||||
|
||||
|
||||
def test_post_no_body_q_bar(client: TestClient):
|
||||
response = client.put("/items/5?q=bar", json=None)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item_id": 5, "q": "bar"}
|
||||
|
||||
|
||||
def test_post_no_body(client: TestClient):
|
||||
response = client.put("/items/5", json=None)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item_id": 5}
|
||||
|
||||
|
||||
def test_post_id_foo(client: TestClient):
|
||||
response = client.put("/items/foo", json=None)
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "int_parsing",
|
||||
"loc": ["path", "item_id"],
|
||||
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||
"input": "foo",
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["path", "item_id"],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Update Item",
|
||||
"operationId": "update_item_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "The ID of the item to get",
|
||||
"maximum": 1000.0,
|
||||
"minimum": 0.0,
|
||||
"type": "integer",
|
||||
},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/Item"},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "Item",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"$ref": "#/components/schemas/Item"}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": ["name", "price"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"description": IsDict(
|
||||
{
|
||||
"title": "Description",
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Description", "type": "string"}
|
||||
),
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"tax": IsDict(
|
||||
{
|
||||
"title": "Tax",
|
||||
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Tax", "type": "number"}
|
||||
),
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.body_multiple_params.tutorial001_an_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_post_body_q_bar_content(client: TestClient):
|
||||
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"item_id": 5,
|
||||
"item": {
|
||||
"name": "Foo",
|
||||
"price": 50.5,
|
||||
"description": None,
|
||||
"tax": None,
|
||||
},
|
||||
"q": "bar",
|
||||
}
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_post_no_body_q_bar(client: TestClient):
|
||||
response = client.put("/items/5?q=bar", json=None)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item_id": 5, "q": "bar"}
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_post_no_body(client: TestClient):
|
||||
response = client.put("/items/5", json=None)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item_id": 5}
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_post_id_foo(client: TestClient):
|
||||
response = client.put("/items/foo", json=None)
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "int_parsing",
|
||||
"loc": ["path", "item_id"],
|
||||
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||
"input": "foo",
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["path", "item_id"],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Update Item",
|
||||
"operationId": "update_item_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "The ID of the item to get",
|
||||
"maximum": 1000.0,
|
||||
"minimum": 0.0,
|
||||
"type": "integer",
|
||||
},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/Item"},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "Item",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"$ref": "#/components/schemas/Item"}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": ["name", "price"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"description": IsDict(
|
||||
{
|
||||
"title": "Description",
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Description", "type": "string"}
|
||||
),
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"tax": IsDict(
|
||||
{
|
||||
"title": "Tax",
|
||||
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Tax", "type": "number"}
|
||||
),
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.body_multiple_params.tutorial001_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_q_bar_content(client: TestClient):
|
||||
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"item_id": 5,
|
||||
"item": {
|
||||
"name": "Foo",
|
||||
"price": 50.5,
|
||||
"description": None,
|
||||
"tax": None,
|
||||
},
|
||||
"q": "bar",
|
||||
}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_no_body_q_bar(client: TestClient):
|
||||
response = client.put("/items/5?q=bar", json=None)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item_id": 5, "q": "bar"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_no_body(client: TestClient):
|
||||
response = client.put("/items/5", json=None)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item_id": 5}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_id_foo(client: TestClient):
|
||||
response = client.put("/items/foo", json=None)
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "int_parsing",
|
||||
"loc": ["path", "item_id"],
|
||||
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||
"input": "foo",
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["path", "item_id"],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Update Item",
|
||||
"operationId": "update_item_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "The ID of the item to get",
|
||||
"maximum": 1000.0,
|
||||
"minimum": 0.0,
|
||||
"type": "integer",
|
||||
},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/Item"},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "Item",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"$ref": "#/components/schemas/Item"}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": ["name", "price"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"description": IsDict(
|
||||
{
|
||||
"title": "Description",
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Description", "type": "string"}
|
||||
),
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"tax": IsDict(
|
||||
{
|
||||
"title": "Tax",
|
||||
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Tax", "type": "number"}
|
||||
),
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.body_multiple_params.tutorial001_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_post_body_q_bar_content(client: TestClient):
|
||||
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"item_id": 5,
|
||||
"item": {
|
||||
"name": "Foo",
|
||||
"price": 50.5,
|
||||
"description": None,
|
||||
"tax": None,
|
||||
},
|
||||
"q": "bar",
|
||||
}
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_post_no_body_q_bar(client: TestClient):
|
||||
response = client.put("/items/5?q=bar", json=None)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item_id": 5, "q": "bar"}
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_post_no_body(client: TestClient):
|
||||
response = client.put("/items/5", json=None)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item_id": 5}
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_post_id_foo(client: TestClient):
|
||||
response = client.put("/items/foo", json=None)
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "int_parsing",
|
||||
"loc": ["path", "item_id"],
|
||||
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||
"input": "foo",
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["path", "item_id"],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Update Item",
|
||||
"operationId": "update_item_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "The ID of the item to get",
|
||||
"maximum": 1000.0,
|
||||
"minimum": 0.0,
|
||||
"type": "integer",
|
||||
},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/Item"},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "Item",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"$ref": "#/components/schemas/Item"}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": ["name", "price"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"description": IsDict(
|
||||
{
|
||||
"title": "Description",
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Description", "type": "string"}
|
||||
),
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"tax": IsDict(
|
||||
{
|
||||
"title": "Tax",
|
||||
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Tax", "type": "number"}
|
||||
),
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,13 +1,23 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.body_nested_models.tutorial009 import app
|
||||
|
||||
client = TestClient(app)
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial009",
|
||||
pytest.param("tutorial009_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,128 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.body_nested_models.tutorial009_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body(client: TestClient):
|
||||
data = {"2": 2.2, "3": 3.3}
|
||||
response = client.post("/index-weights/", json=data)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == data
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_invalid_body(client: TestClient):
|
||||
data = {"foo": 2.2, "3": 3.3}
|
||||
response = client.post("/index-weights/", json=data)
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "int_parsing",
|
||||
"loc": ["body", "foo", "[key]"],
|
||||
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||
"input": "foo",
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "__key__"],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/index-weights/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create Index Weights",
|
||||
"operationId": "create_index_weights_index_weights__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Weights",
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "number"},
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,14 +1,23 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_pydanticv1, needs_pydanticv2
|
||||
from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.body_updates.tutorial001 import app
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial001",
|
||||
pytest.param("tutorial001_py310", marks=needs_py310),
|
||||
pytest.param("tutorial001_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.body_updates.{request.param}")
|
||||
|
||||
client = TestClient(app)
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,317 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.body_updates.tutorial001_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_get(client: TestClient):
|
||||
response = client.get("/items/baz")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"name": "Baz",
|
||||
"description": None,
|
||||
"price": 50.2,
|
||||
"tax": 10.5,
|
||||
"tags": [],
|
||||
}
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_put(client: TestClient):
|
||||
response = client.put(
|
||||
"/items/bar", json={"name": "Barz", "price": 3, "description": None}
|
||||
)
|
||||
assert response.json() == {
|
||||
"name": "Barz",
|
||||
"description": None,
|
||||
"price": 3,
|
||||
"tax": 10.5,
|
||||
"tags": [],
|
||||
}
|
||||
|
||||
|
||||
@needs_py310
|
||||
@needs_pydanticv2
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
},
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Update Item",
|
||||
"operationId": "update_item_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"type": "object",
|
||||
"title": "Item",
|
||||
"properties": {
|
||||
"name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Name",
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Description",
|
||||
},
|
||||
"price": {
|
||||
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||
"title": "Price",
|
||||
},
|
||||
"tax": {"title": "Tax", "type": "number", "default": 10.5},
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"default": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_py310
|
||||
@needs_pydanticv1
|
||||
def test_openapi_schema_pv1(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
},
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Update Item",
|
||||
"operationId": "update_item_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"description": {"title": "Description", "type": "string"},
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"tax": {"title": "Tax", "type": "number", "default": 10.5},
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"default": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,317 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.body_updates.tutorial001_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get(client: TestClient):
|
||||
response = client.get("/items/baz")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"name": "Baz",
|
||||
"description": None,
|
||||
"price": 50.2,
|
||||
"tax": 10.5,
|
||||
"tags": [],
|
||||
}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_put(client: TestClient):
|
||||
response = client.put(
|
||||
"/items/bar", json={"name": "Barz", "price": 3, "description": None}
|
||||
)
|
||||
assert response.json() == {
|
||||
"name": "Barz",
|
||||
"description": None,
|
||||
"price": 3,
|
||||
"tax": 10.5,
|
||||
"tags": [],
|
||||
}
|
||||
|
||||
|
||||
@needs_py39
|
||||
@needs_pydanticv2
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
},
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Update Item",
|
||||
"operationId": "update_item_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"type": "object",
|
||||
"title": "Item",
|
||||
"properties": {
|
||||
"name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Name",
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Description",
|
||||
},
|
||||
"price": {
|
||||
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||
"title": "Price",
|
||||
},
|
||||
"tax": {"title": "Tax", "type": "number", "default": 10.5},
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"default": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_py39
|
||||
@needs_pydanticv1
|
||||
def test_openapi_schema_pv1(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
},
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Update Item",
|
||||
"operationId": "update_item_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"description": {"title": "Description", "type": "string"},
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"tax": {"title": "Tax", "type": "number", "default": 10.5},
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"default": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,8 +1,27 @@
|
|||
import importlib
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.cookie_params.tutorial001 import app
|
||||
from ...utils import needs_py39, needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
name="mod",
|
||||
params=[
|
||||
"tutorial001",
|
||||
pytest.param("tutorial001_py310", marks=needs_py310),
|
||||
"tutorial001_an",
|
||||
pytest.param("tutorial001_an_py39", marks=needs_py39),
|
||||
pytest.param("tutorial001_an_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_mod(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.cookie_params.{request.param}")
|
||||
|
||||
return mod
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -19,15 +38,15 @@ from docs_src.cookie_params.tutorial001 import app
|
|||
("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
|
||||
],
|
||||
)
|
||||
def test(path, cookies, expected_status, expected_response):
|
||||
client = TestClient(app, cookies=cookies)
|
||||
def test(path, cookies, expected_status, expected_response, mod: ModuleType):
|
||||
client = TestClient(mod.app, cookies=cookies)
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
client = TestClient(app)
|
||||
def test_openapi_schema(mod: ModuleType):
|
||||
client = TestClient(mod.app)
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.cookie_params.tutorial001_an import app
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path,cookies,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"ads_id": None}),
|
||||
("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}),
|
||||
(
|
||||
"/items",
|
||||
{"ads_id": "ads_track", "session": "cookiesession"},
|
||||
200,
|
||||
{"ads_id": "ads_track"},
|
||||
),
|
||||
("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
|
||||
],
|
||||
)
|
||||
def test(path, cookies, expected_status, expected_response):
|
||||
client = TestClient(app, cookies=cookies)
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
client = TestClient(app)
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Ads Id",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Ads Id", "type": "string"}
|
||||
),
|
||||
"name": "ads_id",
|
||||
"in": "cookie",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,cookies,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"ads_id": None}),
|
||||
("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}),
|
||||
(
|
||||
"/items",
|
||||
{"ads_id": "ads_track", "session": "cookiesession"},
|
||||
200,
|
||||
{"ads_id": "ads_track"},
|
||||
),
|
||||
("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
|
||||
],
|
||||
)
|
||||
def test(path, cookies, expected_status, expected_response):
|
||||
from docs_src.cookie_params.tutorial001_an_py310 import app
|
||||
|
||||
client = TestClient(app, cookies=cookies)
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema():
|
||||
from docs_src.cookie_params.tutorial001_an_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Ads Id",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Ads Id", "type": "string"}
|
||||
),
|
||||
"name": "ads_id",
|
||||
"in": "cookie",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@needs_py39
|
||||
@pytest.mark.parametrize(
|
||||
"path,cookies,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"ads_id": None}),
|
||||
("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}),
|
||||
(
|
||||
"/items",
|
||||
{"ads_id": "ads_track", "session": "cookiesession"},
|
||||
200,
|
||||
{"ads_id": "ads_track"},
|
||||
),
|
||||
("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
|
||||
],
|
||||
)
|
||||
def test(path, cookies, expected_status, expected_response):
|
||||
from docs_src.cookie_params.tutorial001_an_py39 import app
|
||||
|
||||
client = TestClient(app, cookies=cookies)
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema():
|
||||
from docs_src.cookie_params.tutorial001_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Ads Id",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Ads Id", "type": "string"}
|
||||
),
|
||||
"name": "ads_id",
|
||||
"in": "cookie",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,cookies,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"ads_id": None}),
|
||||
("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}),
|
||||
(
|
||||
"/items",
|
||||
{"ads_id": "ads_track", "session": "cookiesession"},
|
||||
200,
|
||||
{"ads_id": "ads_track"},
|
||||
),
|
||||
("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
|
||||
],
|
||||
)
|
||||
def test(path, cookies, expected_status, expected_response):
|
||||
from docs_src.cookie_params.tutorial001_py310 import app
|
||||
|
||||
client = TestClient(app, cookies=cookies)
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema():
|
||||
from docs_src.cookie_params.tutorial001_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Ads Id",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Ads Id", "type": "string"}
|
||||
),
|
||||
"name": "ads_id",
|
||||
"in": "cookie",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,10 +1,27 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dependencies.tutorial001 import app
|
||||
from ...utils import needs_py39, needs_py310
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial001",
|
||||
pytest.param("tutorial001_py310", marks=needs_py310),
|
||||
"tutorial001_an",
|
||||
pytest.param("tutorial001_an_py39", marks=needs_py39),
|
||||
pytest.param("tutorial001_an_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -17,13 +34,13 @@ client = TestClient(app)
|
|||
("/users", 200, {"q": None, "skip": 0, "limit": 100}),
|
||||
],
|
||||
)
|
||||
def test_get(path, expected_status, expected_response):
|
||||
def test_get(path, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,183 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dependencies.tutorial001_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path,expected_status,expected_response",
|
||||
[
|
||||
("/items", 200, {"q": None, "skip": 0, "limit": 100}),
|
||||
("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
|
||||
("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
|
||||
("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
|
||||
("/users", 200, {"q": None, "skip": 0, "limit": 100}),
|
||||
],
|
||||
)
|
||||
def test_get(path, expected_status, expected_response):
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
"/users/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Users",
|
||||
"operationId": "read_users_users__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial001_an_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,expected_status,expected_response",
|
||||
[
|
||||
("/items", 200, {"q": None, "skip": 0, "limit": 100}),
|
||||
("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
|
||||
("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
|
||||
("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
|
||||
("/users", 200, {"q": None, "skip": 0, "limit": 100}),
|
||||
],
|
||||
)
|
||||
def test_get(path, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
"/users/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Users",
|
||||
"operationId": "read_users_users__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial001_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
@pytest.mark.parametrize(
|
||||
"path,expected_status,expected_response",
|
||||
[
|
||||
("/items", 200, {"q": None, "skip": 0, "limit": 100}),
|
||||
("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
|
||||
("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
|
||||
("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
|
||||
("/users", 200, {"q": None, "skip": 0, "limit": 100}),
|
||||
],
|
||||
)
|
||||
def test_get(path, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
"/users/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Users",
|
||||
"operationId": "read_users_users__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial001_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,expected_status,expected_response",
|
||||
[
|
||||
("/items", 200, {"q": None, "skip": 0, "limit": 100}),
|
||||
("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
|
||||
("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
|
||||
("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
|
||||
("/users", 200, {"q": None, "skip": 0, "limit": 100}),
|
||||
],
|
||||
)
|
||||
def test_get(path, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
"/users/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Users",
|
||||
"operationId": "read_users_users__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,10 +1,27 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dependencies.tutorial004 import app
|
||||
from ...utils import needs_py39, needs_py310
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial004",
|
||||
pytest.param("tutorial004_py310", marks=needs_py310),
|
||||
"tutorial004_an",
|
||||
pytest.param("tutorial004_an_py39", marks=needs_py39),
|
||||
pytest.param("tutorial004_an_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -55,13 +72,13 @@ client = TestClient(app)
|
|||
),
|
||||
],
|
||||
)
|
||||
def test_get(path, expected_status, expected_response):
|
||||
def test_get(path, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,162 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dependencies.tutorial004_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path,expected_status,expected_response",
|
||||
[
|
||||
(
|
||||
"/items",
|
||||
200,
|
||||
{
|
||||
"items": [
|
||||
{"item_name": "Foo"},
|
||||
{"item_name": "Bar"},
|
||||
{"item_name": "Baz"},
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
"/items?q=foo",
|
||||
200,
|
||||
{
|
||||
"items": [
|
||||
{"item_name": "Foo"},
|
||||
{"item_name": "Bar"},
|
||||
{"item_name": "Baz"},
|
||||
],
|
||||
"q": "foo",
|
||||
},
|
||||
),
|
||||
(
|
||||
"/items?q=foo&skip=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
|
||||
),
|
||||
(
|
||||
"/items?q=bar&limit=2",
|
||||
200,
|
||||
{"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
(
|
||||
"/items?q=bar&skip=1&limit=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
(
|
||||
"/items?limit=1&q=bar&skip=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get(path, expected_status, expected_response):
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial004_an_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,expected_status,expected_response",
|
||||
[
|
||||
(
|
||||
"/items",
|
||||
200,
|
||||
{
|
||||
"items": [
|
||||
{"item_name": "Foo"},
|
||||
{"item_name": "Bar"},
|
||||
{"item_name": "Baz"},
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
"/items?q=foo",
|
||||
200,
|
||||
{
|
||||
"items": [
|
||||
{"item_name": "Foo"},
|
||||
{"item_name": "Bar"},
|
||||
{"item_name": "Baz"},
|
||||
],
|
||||
"q": "foo",
|
||||
},
|
||||
),
|
||||
(
|
||||
"/items?q=foo&skip=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
|
||||
),
|
||||
(
|
||||
"/items?q=bar&limit=2",
|
||||
200,
|
||||
{"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
(
|
||||
"/items?q=bar&skip=1&limit=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
(
|
||||
"/items?limit=1&q=bar&skip=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get(path, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial004_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
@pytest.mark.parametrize(
|
||||
"path,expected_status,expected_response",
|
||||
[
|
||||
(
|
||||
"/items",
|
||||
200,
|
||||
{
|
||||
"items": [
|
||||
{"item_name": "Foo"},
|
||||
{"item_name": "Bar"},
|
||||
{"item_name": "Baz"},
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
"/items?q=foo",
|
||||
200,
|
||||
{
|
||||
"items": [
|
||||
{"item_name": "Foo"},
|
||||
{"item_name": "Bar"},
|
||||
{"item_name": "Baz"},
|
||||
],
|
||||
"q": "foo",
|
||||
},
|
||||
),
|
||||
(
|
||||
"/items?q=foo&skip=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
|
||||
),
|
||||
(
|
||||
"/items?q=bar&limit=2",
|
||||
200,
|
||||
{"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
(
|
||||
"/items?q=bar&skip=1&limit=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
(
|
||||
"/items?limit=1&q=bar&skip=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get(path, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial004_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,expected_status,expected_response",
|
||||
[
|
||||
(
|
||||
"/items",
|
||||
200,
|
||||
{
|
||||
"items": [
|
||||
{"item_name": "Foo"},
|
||||
{"item_name": "Bar"},
|
||||
{"item_name": "Baz"},
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
"/items?q=foo",
|
||||
200,
|
||||
{
|
||||
"items": [
|
||||
{"item_name": "Foo"},
|
||||
{"item_name": "Bar"},
|
||||
{"item_name": "Baz"},
|
||||
],
|
||||
"q": "foo",
|
||||
},
|
||||
),
|
||||
(
|
||||
"/items?q=foo&skip=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
|
||||
),
|
||||
(
|
||||
"/items?q=bar&limit=2",
|
||||
200,
|
||||
{"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
(
|
||||
"/items?q=bar&skip=1&limit=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
(
|
||||
"/items?limit=1&q=bar&skip=1",
|
||||
200,
|
||||
{"items": [{"item_name": "Bar"}], "q": "bar"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get(path, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Q",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Q", "type": "string"}
|
||||
),
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
},
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,12 +1,28 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dependencies.tutorial006 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
def test_get_no_headers():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial006",
|
||||
"tutorial006_an",
|
||||
pytest.param("tutorial006_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_get_no_headers(client: TestClient):
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
|
|
@ -45,13 +61,13 @@ def test_get_no_headers():
|
|||
)
|
||||
|
||||
|
||||
def test_get_invalid_one_header():
|
||||
def test_get_invalid_one_header(client: TestClient):
|
||||
response = client.get("/items/", headers={"X-Token": "invalid"})
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Token header invalid"}
|
||||
|
||||
|
||||
def test_get_invalid_second_header():
|
||||
def test_get_invalid_second_header(client: TestClient):
|
||||
response = client.get(
|
||||
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
|
||||
)
|
||||
|
|
@ -59,7 +75,7 @@ def test_get_invalid_second_header():
|
|||
assert response.json() == {"detail": "X-Key header invalid"}
|
||||
|
||||
|
||||
def test_get_valid_headers():
|
||||
def test_get_valid_headers(client: TestClient):
|
||||
response = client.get(
|
||||
"/items/",
|
||||
headers={
|
||||
|
|
@ -71,7 +87,7 @@ def test_get_valid_headers():
|
|||
assert response.json() == [{"item": "Foo"}, {"item": "Bar"}]
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,149 +0,0 @@
|
|||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dependencies.tutorial006_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_get_no_headers():
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_get_invalid_one_header():
|
||||
response = client.get("/items/", headers={"X-Token": "invalid"})
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Token header invalid"}
|
||||
|
||||
|
||||
def test_get_invalid_second_header():
|
||||
response = client.get(
|
||||
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
|
||||
)
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Key header invalid"}
|
||||
|
||||
|
||||
def test_get_valid_headers():
|
||||
response = client.get(
|
||||
"/items/",
|
||||
headers={
|
||||
"X-Token": "fake-super-secret-token",
|
||||
"X-Key": "fake-super-secret-key",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [{"item": "Foo"}, {"item": "Bar"}]
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Token", "type": "string"},
|
||||
"name": "x-token",
|
||||
"in": "header",
|
||||
},
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Key", "type": "string"},
|
||||
"name": "x-key",
|
||||
"in": "header",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial006_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_no_headers(client: TestClient):
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_invalid_one_header(client: TestClient):
|
||||
response = client.get("/items/", headers={"X-Token": "invalid"})
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Token header invalid"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_invalid_second_header(client: TestClient):
|
||||
response = client.get(
|
||||
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
|
||||
)
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Key header invalid"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_valid_headers(client: TestClient):
|
||||
response = client.get(
|
||||
"/items/",
|
||||
headers={
|
||||
"X-Token": "fake-super-secret-token",
|
||||
"X-Key": "fake-super-secret-key",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [{"item": "Foo"}, {"item": "Bar"}]
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Token", "type": "string"},
|
||||
"name": "x-token",
|
||||
"in": "header",
|
||||
},
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Key", "type": "string"},
|
||||
"name": "x-key",
|
||||
"in": "header",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,23 +1,39 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dependencies.tutorial008b import app
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
def test_get_no_item():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial008b",
|
||||
"tutorial008b_an",
|
||||
pytest.param("tutorial008b_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_get_no_item(client: TestClient):
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.json() == {"detail": "Item not found"}
|
||||
|
||||
|
||||
def test_owner_error():
|
||||
def test_owner_error(client: TestClient):
|
||||
response = client.get("/items/plumbus")
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "Owner error: Rick"}
|
||||
|
||||
|
||||
def test_get_item():
|
||||
def test_get_item(client: TestClient):
|
||||
response = client.get("/items/portal-gun")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"description": "Gun to create portals", "owner": "Rick"}
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dependencies.tutorial008b_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_get_no_item():
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.json() == {"detail": "Item not found"}
|
||||
|
||||
|
||||
def test_owner_error():
|
||||
response = client.get("/items/plumbus")
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "Owner error: Rick"}
|
||||
|
||||
|
||||
def test_get_item():
|
||||
response = client.get("/items/portal-gun")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"description": "Gun to create portals", "owner": "Rick"}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial008b_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_no_item(client: TestClient):
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.json() == {"detail": "Item not found"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_owner_error(client: TestClient):
|
||||
response = client.get("/items/plumbus")
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "Owner error: Rick"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_item(client: TestClient):
|
||||
response = client.get("/items/portal-gun")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"description": "Gun to create portals", "owner": "Rick"}
|
||||
|
|
@ -1,38 +1,50 @@
|
|||
import importlib
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
from fastapi.exceptions import FastAPIError
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial008c import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
def test_get_no_item(client: TestClient):
|
||||
@pytest.fixture(
|
||||
name="mod",
|
||||
params=[
|
||||
"tutorial008c",
|
||||
"tutorial008c_an",
|
||||
pytest.param("tutorial008c_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_mod(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
|
||||
|
||||
return mod
|
||||
|
||||
|
||||
def test_get_no_item(mod: ModuleType):
|
||||
client = TestClient(mod.app)
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
|
||||
|
||||
|
||||
def test_get(client: TestClient):
|
||||
def test_get(mod: ModuleType):
|
||||
client = TestClient(mod.app)
|
||||
response = client.get("/items/plumbus")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == "plumbus"
|
||||
|
||||
|
||||
def test_fastapi_error(client: TestClient):
|
||||
def test_fastapi_error(mod: ModuleType):
|
||||
client = TestClient(mod.app)
|
||||
with pytest.raises(FastAPIError) as exc_info:
|
||||
client.get("/items/portal-gun")
|
||||
assert "No response object was returned" in exc_info.value.args[0]
|
||||
|
||||
|
||||
def test_internal_server_error():
|
||||
from docs_src.dependencies.tutorial008c import app
|
||||
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
def test_internal_server_error(mod: ModuleType):
|
||||
client = TestClient(mod.app, raise_server_exceptions=False)
|
||||
response = client.get("/items/portal-gun")
|
||||
assert response.status_code == 500, response.text
|
||||
assert response.text == "Internal Server Error"
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.exceptions import FastAPIError
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial008c_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
def test_get_no_item(client: TestClient):
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
|
||||
|
||||
|
||||
def test_get(client: TestClient):
|
||||
response = client.get("/items/plumbus")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == "plumbus"
|
||||
|
||||
|
||||
def test_fastapi_error(client: TestClient):
|
||||
with pytest.raises(FastAPIError) as exc_info:
|
||||
client.get("/items/portal-gun")
|
||||
assert "No response object was returned" in exc_info.value.args[0]
|
||||
|
||||
|
||||
def test_internal_server_error():
|
||||
from docs_src.dependencies.tutorial008c_an import app
|
||||
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
response = client.get("/items/portal-gun")
|
||||
assert response.status_code == 500, response.text
|
||||
assert response.text == "Internal Server Error"
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.exceptions import FastAPIError
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial008c_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_no_item(client: TestClient):
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get(client: TestClient):
|
||||
response = client.get("/items/plumbus")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == "plumbus"
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_fastapi_error(client: TestClient):
|
||||
with pytest.raises(FastAPIError) as exc_info:
|
||||
client.get("/items/portal-gun")
|
||||
assert "No response object was returned" in exc_info.value.args[0]
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_internal_server_error():
|
||||
from docs_src.dependencies.tutorial008c_an_py39 import app
|
||||
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
response = client.get("/items/portal-gun")
|
||||
assert response.status_code == 500, response.text
|
||||
assert response.text == "Internal Server Error"
|
||||
|
|
@ -1,41 +1,51 @@
|
|||
import importlib
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial008d import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
def test_get_no_item(client: TestClient):
|
||||
@pytest.fixture(
|
||||
name="mod",
|
||||
params=[
|
||||
"tutorial008d",
|
||||
"tutorial008d_an",
|
||||
pytest.param("tutorial008d_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_mod(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
|
||||
|
||||
return mod
|
||||
|
||||
|
||||
def test_get_no_item(mod: ModuleType):
|
||||
client = TestClient(mod.app)
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
|
||||
|
||||
|
||||
def test_get(client: TestClient):
|
||||
def test_get(mod: ModuleType):
|
||||
client = TestClient(mod.app)
|
||||
response = client.get("/items/plumbus")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == "plumbus"
|
||||
|
||||
|
||||
def test_internal_error(client: TestClient):
|
||||
from docs_src.dependencies.tutorial008d import InternalError
|
||||
|
||||
with pytest.raises(InternalError) as exc_info:
|
||||
def test_internal_error(mod: ModuleType):
|
||||
client = TestClient(mod.app)
|
||||
with pytest.raises(mod.InternalError) as exc_info:
|
||||
client.get("/items/portal-gun")
|
||||
assert (
|
||||
exc_info.value.args[0] == "The portal gun is too dangerous to be owned by Rick"
|
||||
)
|
||||
|
||||
|
||||
def test_internal_server_error():
|
||||
from docs_src.dependencies.tutorial008d import app
|
||||
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
def test_internal_server_error(mod: ModuleType):
|
||||
client = TestClient(mod.app, raise_server_exceptions=False)
|
||||
response = client.get("/items/portal-gun")
|
||||
assert response.status_code == 500, response.text
|
||||
assert response.text == "Internal Server Error"
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial008d_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
def test_get_no_item(client: TestClient):
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
|
||||
|
||||
|
||||
def test_get(client: TestClient):
|
||||
response = client.get("/items/plumbus")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == "plumbus"
|
||||
|
||||
|
||||
def test_internal_error(client: TestClient):
|
||||
from docs_src.dependencies.tutorial008d_an import InternalError
|
||||
|
||||
with pytest.raises(InternalError) as exc_info:
|
||||
client.get("/items/portal-gun")
|
||||
assert (
|
||||
exc_info.value.args[0] == "The portal gun is too dangerous to be owned by Rick"
|
||||
)
|
||||
|
||||
|
||||
def test_internal_server_error():
|
||||
from docs_src.dependencies.tutorial008d_an import app
|
||||
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
response = client.get("/items/portal-gun")
|
||||
assert response.status_code == 500, response.text
|
||||
assert response.text == "Internal Server Error"
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial008d_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_no_item(client: TestClient):
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get(client: TestClient):
|
||||
response = client.get("/items/plumbus")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == "plumbus"
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_internal_error(client: TestClient):
|
||||
from docs_src.dependencies.tutorial008d_an_py39 import InternalError
|
||||
|
||||
with pytest.raises(InternalError) as exc_info:
|
||||
client.get("/items/portal-gun")
|
||||
assert (
|
||||
exc_info.value.args[0] == "The portal gun is too dangerous to be owned by Rick"
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_internal_server_error():
|
||||
from docs_src.dependencies.tutorial008d_an_py39 import app
|
||||
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
response = client.get("/items/portal-gun")
|
||||
assert response.status_code == 500, response.text
|
||||
assert response.text == "Internal Server Error"
|
||||
|
|
@ -1,12 +1,28 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dependencies.tutorial012 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
def test_get_no_headers_items():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial012",
|
||||
"tutorial012_an",
|
||||
pytest.param("tutorial012_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_get_no_headers_items(client: TestClient):
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
|
|
@ -45,7 +61,7 @@ def test_get_no_headers_items():
|
|||
)
|
||||
|
||||
|
||||
def test_get_no_headers_users():
|
||||
def test_get_no_headers_users(client: TestClient):
|
||||
response = client.get("/users/")
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
|
|
@ -84,19 +100,19 @@ def test_get_no_headers_users():
|
|||
)
|
||||
|
||||
|
||||
def test_get_invalid_one_header_items():
|
||||
def test_get_invalid_one_header_items(client: TestClient):
|
||||
response = client.get("/items/", headers={"X-Token": "invalid"})
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Token header invalid"}
|
||||
|
||||
|
||||
def test_get_invalid_one_users():
|
||||
def test_get_invalid_one_users(client: TestClient):
|
||||
response = client.get("/users/", headers={"X-Token": "invalid"})
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Token header invalid"}
|
||||
|
||||
|
||||
def test_get_invalid_second_header_items():
|
||||
def test_get_invalid_second_header_items(client: TestClient):
|
||||
response = client.get(
|
||||
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
|
||||
)
|
||||
|
|
@ -104,7 +120,7 @@ def test_get_invalid_second_header_items():
|
|||
assert response.json() == {"detail": "X-Key header invalid"}
|
||||
|
||||
|
||||
def test_get_invalid_second_header_users():
|
||||
def test_get_invalid_second_header_users(client: TestClient):
|
||||
response = client.get(
|
||||
"/users/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
|
||||
)
|
||||
|
|
@ -112,7 +128,7 @@ def test_get_invalid_second_header_users():
|
|||
assert response.json() == {"detail": "X-Key header invalid"}
|
||||
|
||||
|
||||
def test_get_valid_headers_items():
|
||||
def test_get_valid_headers_items(client: TestClient):
|
||||
response = client.get(
|
||||
"/items/",
|
||||
headers={
|
||||
|
|
@ -124,7 +140,7 @@ def test_get_valid_headers_items():
|
|||
assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}]
|
||||
|
||||
|
||||
def test_get_valid_headers_users():
|
||||
def test_get_valid_headers_users(client: TestClient):
|
||||
response = client.get(
|
||||
"/users/",
|
||||
headers={
|
||||
|
|
@ -136,7 +152,7 @@ def test_get_valid_headers_users():
|
|||
assert response.json() == [{"username": "Rick"}, {"username": "Morty"}]
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,250 +0,0 @@
|
|||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dependencies.tutorial012_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_get_no_headers_items():
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_get_no_headers_users():
|
||||
response = client.get("/users/")
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_get_invalid_one_header_items():
|
||||
response = client.get("/items/", headers={"X-Token": "invalid"})
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Token header invalid"}
|
||||
|
||||
|
||||
def test_get_invalid_one_users():
|
||||
response = client.get("/users/", headers={"X-Token": "invalid"})
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Token header invalid"}
|
||||
|
||||
|
||||
def test_get_invalid_second_header_items():
|
||||
response = client.get(
|
||||
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
|
||||
)
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Key header invalid"}
|
||||
|
||||
|
||||
def test_get_invalid_second_header_users():
|
||||
response = client.get(
|
||||
"/users/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
|
||||
)
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Key header invalid"}
|
||||
|
||||
|
||||
def test_get_valid_headers_items():
|
||||
response = client.get(
|
||||
"/items/",
|
||||
headers={
|
||||
"X-Token": "fake-super-secret-token",
|
||||
"X-Key": "fake-super-secret-key",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}]
|
||||
|
||||
|
||||
def test_get_valid_headers_users():
|
||||
response = client.get(
|
||||
"/users/",
|
||||
headers={
|
||||
"X-Token": "fake-super-secret-token",
|
||||
"X-Key": "fake-super-secret-key",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [{"username": "Rick"}, {"username": "Morty"}]
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Token", "type": "string"},
|
||||
"name": "x-token",
|
||||
"in": "header",
|
||||
},
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Key", "type": "string"},
|
||||
"name": "x-key",
|
||||
"in": "header",
|
||||
},
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"/users/": {
|
||||
"get": {
|
||||
"summary": "Read Users",
|
||||
"operationId": "read_users_users__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Token", "type": "string"},
|
||||
"name": "x-token",
|
||||
"in": "header",
|
||||
},
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Key", "type": "string"},
|
||||
"name": "x-key",
|
||||
"in": "header",
|
||||
},
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,266 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.dependencies.tutorial012_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_no_headers_items(client: TestClient):
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_no_headers_users(client: TestClient):
|
||||
response = client.get("/users/")
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["header", "x-token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["header", "x-key"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_invalid_one_header_items(client: TestClient):
|
||||
response = client.get("/items/", headers={"X-Token": "invalid"})
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Token header invalid"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_invalid_one_users(client: TestClient):
|
||||
response = client.get("/users/", headers={"X-Token": "invalid"})
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Token header invalid"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_invalid_second_header_items(client: TestClient):
|
||||
response = client.get(
|
||||
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
|
||||
)
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Key header invalid"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_invalid_second_header_users(client: TestClient):
|
||||
response = client.get(
|
||||
"/users/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
|
||||
)
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "X-Key header invalid"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_valid_headers_items(client: TestClient):
|
||||
response = client.get(
|
||||
"/items/",
|
||||
headers={
|
||||
"X-Token": "fake-super-secret-token",
|
||||
"X-Key": "fake-super-secret-key",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}]
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_valid_headers_users(client: TestClient):
|
||||
response = client.get(
|
||||
"/users/",
|
||||
headers={
|
||||
"X-Token": "fake-super-secret-token",
|
||||
"X-Key": "fake-super-secret-key",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [{"username": "Rick"}, {"username": "Morty"}]
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Token", "type": "string"},
|
||||
"name": "x-token",
|
||||
"in": "header",
|
||||
},
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Key", "type": "string"},
|
||||
"name": "x-key",
|
||||
"in": "header",
|
||||
},
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"/users/": {
|
||||
"get": {
|
||||
"summary": "Read Users",
|
||||
"operationId": "read_users_users__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Token", "type": "string"},
|
||||
"name": "x-token",
|
||||
"in": "header",
|
||||
},
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "X-Key", "type": "string"},
|
||||
"name": "x-key",
|
||||
"in": "header",
|
||||
},
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,12 +1,30 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.extra_data_types.tutorial001 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py39, needs_py310
|
||||
|
||||
|
||||
def test_extra_types():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial001",
|
||||
pytest.param("tutorial001_py310", marks=needs_py310),
|
||||
"tutorial001_an",
|
||||
pytest.param("tutorial001_an_py39", marks=needs_py39),
|
||||
pytest.param("tutorial001_an_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.extra_data_types.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_extra_types(client: TestClient):
|
||||
item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
|
||||
data = {
|
||||
"start_datetime": "2018-12-22T14:00:00+00:00",
|
||||
|
|
@ -27,7 +45,7 @@ def test_extra_types():
|
|||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.extra_data_types.tutorial001_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_extra_types():
|
||||
item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
|
||||
data = {
|
||||
"start_datetime": "2018-12-22T14:00:00+00:00",
|
||||
"end_datetime": "2018-12-24T15:00:00+00:00",
|
||||
"repeat_at": "15:30:00",
|
||||
"process_after": 300,
|
||||
}
|
||||
expected_response = data.copy()
|
||||
expected_response.update(
|
||||
{
|
||||
"start_process": "2018-12-22T14:05:00+00:00",
|
||||
"duration": 176_100,
|
||||
"item_id": item_id,
|
||||
}
|
||||
)
|
||||
response = client.put(f"/items/{item_id}", json=data)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item Id",
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": IsDict(
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
|
||||
}
|
||||
],
|
||||
"title": "Body",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Body_read_items_items__item_id__put": {
|
||||
"title": "Body_read_items_items__item_id__put",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start_datetime": {
|
||||
"title": "Start Datetime",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
},
|
||||
"end_datetime": {
|
||||
"title": "End Datetime",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
},
|
||||
"repeat_at": IsDict(
|
||||
{
|
||||
"title": "Repeat At",
|
||||
"anyOf": [
|
||||
{"type": "string", "format": "time"},
|
||||
{"type": "null"},
|
||||
],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "Repeat At",
|
||||
"type": "string",
|
||||
"format": "time",
|
||||
}
|
||||
),
|
||||
"process_after": IsDict(
|
||||
{
|
||||
"title": "Process After",
|
||||
"type": "string",
|
||||
"format": "duration",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "Process After",
|
||||
"type": "number",
|
||||
"format": "time-delta",
|
||||
}
|
||||
),
|
||||
},
|
||||
"required": ["start_datetime", "end_datetime", "process_after"],
|
||||
},
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.extra_data_types.tutorial001_an_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_extra_types(client: TestClient):
|
||||
item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
|
||||
data = {
|
||||
"start_datetime": "2018-12-22T14:00:00+00:00",
|
||||
"end_datetime": "2018-12-24T15:00:00+00:00",
|
||||
"repeat_at": "15:30:00",
|
||||
"process_after": 300,
|
||||
}
|
||||
expected_response = data.copy()
|
||||
expected_response.update(
|
||||
{
|
||||
"start_process": "2018-12-22T14:05:00+00:00",
|
||||
"duration": 176_100,
|
||||
"item_id": item_id,
|
||||
}
|
||||
)
|
||||
response = client.put(f"/items/{item_id}", json=data)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item Id",
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": IsDict(
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
|
||||
}
|
||||
],
|
||||
"title": "Body",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Body_read_items_items__item_id__put": {
|
||||
"title": "Body_read_items_items__item_id__put",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start_datetime": {
|
||||
"title": "Start Datetime",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
},
|
||||
"end_datetime": {
|
||||
"title": "End Datetime",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
},
|
||||
"repeat_at": IsDict(
|
||||
{
|
||||
"title": "Repeat At",
|
||||
"anyOf": [
|
||||
{"type": "string", "format": "time"},
|
||||
{"type": "null"},
|
||||
],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "Repeat At",
|
||||
"type": "string",
|
||||
"format": "time",
|
||||
}
|
||||
),
|
||||
"process_after": IsDict(
|
||||
{
|
||||
"title": "Process After",
|
||||
"type": "string",
|
||||
"format": "duration",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "Process After",
|
||||
"type": "number",
|
||||
"format": "time-delta",
|
||||
}
|
||||
),
|
||||
},
|
||||
"required": ["start_datetime", "end_datetime", "process_after"],
|
||||
},
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.extra_data_types.tutorial001_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_extra_types(client: TestClient):
|
||||
item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
|
||||
data = {
|
||||
"start_datetime": "2018-12-22T14:00:00+00:00",
|
||||
"end_datetime": "2018-12-24T15:00:00+00:00",
|
||||
"repeat_at": "15:30:00",
|
||||
"process_after": 300,
|
||||
}
|
||||
expected_response = data.copy()
|
||||
expected_response.update(
|
||||
{
|
||||
"start_process": "2018-12-22T14:05:00+00:00",
|
||||
"duration": 176_100,
|
||||
"item_id": item_id,
|
||||
}
|
||||
)
|
||||
response = client.put(f"/items/{item_id}", json=data)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item Id",
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": IsDict(
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
|
||||
}
|
||||
],
|
||||
"title": "Body",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Body_read_items_items__item_id__put": {
|
||||
"title": "Body_read_items_items__item_id__put",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start_datetime": {
|
||||
"title": "Start Datetime",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
},
|
||||
"end_datetime": {
|
||||
"title": "End Datetime",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
},
|
||||
"repeat_at": IsDict(
|
||||
{
|
||||
"title": "Repeat At",
|
||||
"anyOf": [
|
||||
{"type": "string", "format": "time"},
|
||||
{"type": "null"},
|
||||
],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "Repeat At",
|
||||
"type": "string",
|
||||
"format": "time",
|
||||
}
|
||||
),
|
||||
"process_after": IsDict(
|
||||
{
|
||||
"title": "Process After",
|
||||
"type": "string",
|
||||
"format": "duration",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "Process After",
|
||||
"type": "number",
|
||||
"format": "time-delta",
|
||||
}
|
||||
),
|
||||
},
|
||||
"required": ["start_datetime", "end_datetime", "process_after"],
|
||||
},
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.extra_data_types.tutorial001_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_extra_types(client: TestClient):
|
||||
item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
|
||||
data = {
|
||||
"start_datetime": "2018-12-22T14:00:00+00:00",
|
||||
"end_datetime": "2018-12-24T15:00:00+00:00",
|
||||
"repeat_at": "15:30:00",
|
||||
"process_after": 300,
|
||||
}
|
||||
expected_response = data.copy()
|
||||
expected_response.update(
|
||||
{
|
||||
"start_process": "2018-12-22T14:05:00+00:00",
|
||||
"duration": 176_100,
|
||||
"item_id": item_id,
|
||||
}
|
||||
)
|
||||
response = client.put(f"/items/{item_id}", json=data)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item Id",
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": IsDict(
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
|
||||
}
|
||||
],
|
||||
"title": "Body",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Body_read_items_items__item_id__put": {
|
||||
"title": "Body_read_items_items__item_id__put",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start_datetime": {
|
||||
"title": "Start Datetime",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
},
|
||||
"end_datetime": {
|
||||
"title": "End Datetime",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
},
|
||||
"repeat_at": IsDict(
|
||||
{
|
||||
"title": "Repeat At",
|
||||
"anyOf": [
|
||||
{"type": "string", "format": "time"},
|
||||
{"type": "null"},
|
||||
],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "Repeat At",
|
||||
"type": "string",
|
||||
"format": "time",
|
||||
}
|
||||
),
|
||||
"process_after": IsDict(
|
||||
{
|
||||
"title": "Process After",
|
||||
"type": "string",
|
||||
"format": "duration",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "Process After",
|
||||
"type": "number",
|
||||
"format": "time-delta",
|
||||
}
|
||||
),
|
||||
},
|
||||
"required": ["start_datetime", "end_datetime", "process_after"],
|
||||
},
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,12 +1,27 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsOneOf
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.extra_models.tutorial003 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
def test_get_car():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial003",
|
||||
pytest.param("tutorial003_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.extra_models.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_get_car(client: TestClient):
|
||||
response = client.get("/items/item1")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
@ -15,7 +30,7 @@ def test_get_car():
|
|||
}
|
||||
|
||||
|
||||
def test_get_plane():
|
||||
def test_get_plane(client: TestClient):
|
||||
response = client.get("/items/item2")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
@ -25,7 +40,7 @@ def test_get_plane():
|
|||
}
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,144 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsOneOf
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.extra_models.tutorial003_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_get_car(client: TestClient):
|
||||
response = client.get("/items/item1")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"description": "All my friends drive a low rider",
|
||||
"type": "car",
|
||||
}
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_get_plane(client: TestClient):
|
||||
response = client.get("/items/item2")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"description": "Music is my aeroplane, it's my aeroplane",
|
||||
"type": "plane",
|
||||
"size": 5,
|
||||
}
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response Read Item Items Item Id Get",
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/PlaneItem"},
|
||||
{"$ref": "#/components/schemas/CarItem"},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"PlaneItem": {
|
||||
"title": "PlaneItem",
|
||||
"required": IsOneOf(
|
||||
["description", "type", "size"],
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
["description", "size"],
|
||||
),
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {"title": "Description", "type": "string"},
|
||||
"type": {"title": "Type", "type": "string", "default": "plane"},
|
||||
"size": {"title": "Size", "type": "integer"},
|
||||
},
|
||||
},
|
||||
"CarItem": {
|
||||
"title": "CarItem",
|
||||
"required": IsOneOf(
|
||||
["description", "type"],
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
["description"],
|
||||
),
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {"title": "Description", "type": "string"},
|
||||
"type": {"title": "Type", "type": "string", "default": "car"},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,11 +1,26 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.extra_models.tutorial004 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
def test_get_items():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial004",
|
||||
pytest.param("tutorial004_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.extra_models.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_get_items(client: TestClient):
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [
|
||||
|
|
@ -14,7 +29,7 @@ def test_get_items():
|
|||
]
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.extra_models.tutorial004_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_items(client: TestClient):
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [
|
||||
{"name": "Foo", "description": "There comes my hero"},
|
||||
{"name": "Red", "description": "It's my aeroplane"},
|
||||
]
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response Read Items Items Get",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/Item"},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": ["name", "description"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"description": {"title": "Description", "type": "string"},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,17 +1,32 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.extra_models.tutorial005 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
def test_get_items():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial005",
|
||||
pytest.param("tutorial005_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.extra_models.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_get_items(client: TestClient):
|
||||
response = client.get("/keyword-weights/")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"foo": 2.3, "bar": 3.4}
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.extra_models.tutorial005_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get_items(client: TestClient):
|
||||
response = client.get("/keyword-weights/")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"foo": 2.3, "bar": 3.4}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/keyword-weights/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response Read Keyword Weights Keyword Weights Get",
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "number"},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"summary": "Read Keyword Weights",
|
||||
"operationId": "read_keyword_weights_keyword_weights__get",
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,10 +1,26 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.header_params.tutorial001 import app
|
||||
from ...utils import needs_py310
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial001",
|
||||
pytest.param("tutorial001_py310", marks=needs_py310),
|
||||
"tutorial001_an",
|
||||
pytest.param("tutorial001_an_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.header_params.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -15,13 +31,13 @@ client = TestClient(app)
|
|||
("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response):
|
||||
def test(path, headers, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.header_params.tutorial001_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path,headers,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"User-Agent": "testclient"}),
|
||||
("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}),
|
||||
("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "User-Agent",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "User-Agent", "type": "string"}
|
||||
),
|
||||
"name": "user-agent",
|
||||
"in": "header",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.header_params.tutorial001_an_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,headers,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"User-Agent": "testclient"}),
|
||||
("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}),
|
||||
("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "User-Agent",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "User-Agent", "type": "string"}
|
||||
),
|
||||
"name": "user-agent",
|
||||
"in": "header",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.header_params.tutorial001_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,headers,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"User-Agent": "testclient"}),
|
||||
("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}),
|
||||
("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "User-Agent",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "User-Agent", "type": "string"}
|
||||
),
|
||||
"name": "user-agent",
|
||||
"in": "header",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,10 +1,27 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.header_params.tutorial002 import app
|
||||
from ...utils import needs_py39, needs_py310
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial002",
|
||||
pytest.param("tutorial002_py310", marks=needs_py310),
|
||||
"tutorial002_an",
|
||||
pytest.param("tutorial002_an_py39", marks=needs_py39),
|
||||
pytest.param("tutorial002_an_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.header_params.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -26,13 +43,13 @@ client = TestClient(app)
|
|||
),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response):
|
||||
def test(path, headers, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.header_params.tutorial002_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path,headers,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"strange_header": None}),
|
||||
("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}),
|
||||
(
|
||||
"/items",
|
||||
{"strange_header": "FastAPI test"},
|
||||
200,
|
||||
{"strange_header": "FastAPI test"},
|
||||
),
|
||||
(
|
||||
"/items",
|
||||
{"strange-header": "Not really underscore"},
|
||||
200,
|
||||
{"strange_header": None},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Strange Header",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Strange Header", "type": "string"}
|
||||
),
|
||||
"name": "strange_header",
|
||||
"in": "header",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.header_params.tutorial002_an_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,headers,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"strange_header": None}),
|
||||
("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}),
|
||||
(
|
||||
"/items",
|
||||
{"strange_header": "FastAPI test"},
|
||||
200,
|
||||
{"strange_header": "FastAPI test"},
|
||||
),
|
||||
(
|
||||
"/items",
|
||||
{"strange-header": "Not really underscore"},
|
||||
200,
|
||||
{"strange_header": None},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Strange Header",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Strange Header", "type": "string"}
|
||||
),
|
||||
"name": "strange_header",
|
||||
"in": "header",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.header_params.tutorial002_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
@pytest.mark.parametrize(
|
||||
"path,headers,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"strange_header": None}),
|
||||
("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}),
|
||||
(
|
||||
"/items",
|
||||
{"strange_header": "FastAPI test"},
|
||||
200,
|
||||
{"strange_header": "FastAPI test"},
|
||||
),
|
||||
(
|
||||
"/items",
|
||||
{"strange-header": "Not really underscore"},
|
||||
200,
|
||||
{"strange_header": None},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema():
|
||||
from docs_src.header_params.tutorial002_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Strange Header",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Strange Header", "type": "string"}
|
||||
),
|
||||
"name": "strange_header",
|
||||
"in": "header",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.header_params.tutorial002_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,headers,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"strange_header": None}),
|
||||
("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}),
|
||||
(
|
||||
"/items",
|
||||
{"strange_header": "FastAPI test"},
|
||||
200,
|
||||
{"strange_header": "FastAPI test"},
|
||||
),
|
||||
(
|
||||
"/items",
|
||||
{"strange-header": "Not really underscore"},
|
||||
200,
|
||||
{"strange_header": None},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema():
|
||||
from docs_src.header_params.tutorial002_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"title": "Strange Header",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Strange Header", "type": "string"}
|
||||
),
|
||||
"name": "strange_header",
|
||||
"in": "header",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,10 +1,27 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.header_params.tutorial003 import app
|
||||
from ...utils import needs_py39, needs_py310
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial003",
|
||||
pytest.param("tutorial003_py310", marks=needs_py310),
|
||||
"tutorial003_an",
|
||||
pytest.param("tutorial003_an_py39", marks=needs_py39),
|
||||
pytest.param("tutorial003_an_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.header_params.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -12,21 +29,17 @@ client = TestClient(app)
|
|||
[
|
||||
("/items", None, 200, {"X-Token values": None}),
|
||||
("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}),
|
||||
(
|
||||
"/items",
|
||||
[("x-token", "foo"), ("x-token", "bar")],
|
||||
200,
|
||||
{"X-Token values": ["foo", "bar"]},
|
||||
),
|
||||
# TODO: fix this, is it a bug?
|
||||
# ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response):
|
||||
def test(path, headers, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,110 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.header_params.tutorial003_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path,headers,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"X-Token values": None}),
|
||||
("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}),
|
||||
# TODO: fix this, is it a bug?
|
||||
# ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"title": "X-Token",
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "string"}},
|
||||
{"type": "null"},
|
||||
],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "X-Token",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
}
|
||||
),
|
||||
"name": "x-token",
|
||||
"in": "header",
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.header_params.tutorial003_an_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,headers,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"X-Token values": None}),
|
||||
("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}),
|
||||
# TODO: fix this, is it a bug?
|
||||
# ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"title": "X-Token",
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "string"}},
|
||||
{"type": "null"},
|
||||
],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "X-Token",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
}
|
||||
),
|
||||
"name": "x-token",
|
||||
"in": "header",
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.header_params.tutorial003_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
@pytest.mark.parametrize(
|
||||
"path,headers,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"X-Token values": None}),
|
||||
("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}),
|
||||
# TODO: fix this, is it a bug?
|
||||
# ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"title": "X-Token",
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "string"}},
|
||||
{"type": "null"},
|
||||
],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "X-Token",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
}
|
||||
),
|
||||
"name": "x-token",
|
||||
"in": "header",
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.header_params.tutorial003_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
@pytest.mark.parametrize(
|
||||
"path,headers,expected_status,expected_response",
|
||||
[
|
||||
("/items", None, 200, {"X-Token values": None}),
|
||||
("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}),
|
||||
# TODO: fix this, is it a bug?
|
||||
# ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}),
|
||||
],
|
||||
)
|
||||
def test(path, headers, expected_status, expected_response, client: TestClient):
|
||||
response = client.get(path, headers=headers)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"title": "X-Token",
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "string"}},
|
||||
{"type": "null"},
|
||||
],
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"title": "X-Token",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
}
|
||||
),
|
||||
"name": "x-token",
|
||||
"in": "header",
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,13 +1,29 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.path_operation_configuration.tutorial005 import app
|
||||
|
||||
from ...utils import needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
|
||||
def test_query_params_str_validations():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial005",
|
||||
pytest.param("tutorial005_py39", marks=needs_py39),
|
||||
pytest.param("tutorial005_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(
|
||||
f"docs_src.path_operation_configuration.{request.param}"
|
||||
)
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_query_params_str_validations(client: TestClient):
|
||||
response = client.post("/items/", json={"name": "Foo", "price": 42})
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
@ -20,7 +36,7 @@ def test_query_params_str_validations():
|
|||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
@ -123,7 +139,7 @@ def test_openapi_schema():
|
|||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_openapi_schema_pv1():
|
||||
def test_openapi_schema_pv1(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,226 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.path_operation_configuration.tutorial005_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_query_params_str_validations(client: TestClient):
|
||||
response = client.post("/items/", json={"name": "Foo", "price": 42})
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"name": "Foo",
|
||||
"price": 42,
|
||||
"description": None,
|
||||
"tax": None,
|
||||
"tags": [],
|
||||
}
|
||||
|
||||
|
||||
@needs_py310
|
||||
@needs_pydanticv2
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The created item",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create an item",
|
||||
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
|
||||
"operationId": "create_item_items__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": ["name", "price"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"description": {
|
||||
"title": "Description",
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
},
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"tax": {
|
||||
"title": "Tax",
|
||||
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||
},
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"uniqueItems": True,
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"default": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_py310
|
||||
@needs_pydanticv1
|
||||
def test_openapi_schema_pv1(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The created item",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create an item",
|
||||
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
|
||||
"operationId": "create_item_items__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": ["name", "price"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"description": {"title": "Description", "type": "string"},
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"tax": {"title": "Tax", "type": "number"},
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"uniqueItems": True,
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"default": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,226 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.path_operation_configuration.tutorial005_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_query_params_str_validations(client: TestClient):
|
||||
response = client.post("/items/", json={"name": "Foo", "price": 42})
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"name": "Foo",
|
||||
"price": 42,
|
||||
"description": None,
|
||||
"tax": None,
|
||||
"tags": [],
|
||||
}
|
||||
|
||||
|
||||
@needs_py39
|
||||
@needs_pydanticv2
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The created item",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create an item",
|
||||
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
|
||||
"operationId": "create_item_items__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": ["name", "price"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"description": {
|
||||
"title": "Description",
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
},
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"tax": {
|
||||
"title": "Tax",
|
||||
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||
},
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"uniqueItems": True,
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"default": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_py39
|
||||
@needs_pydanticv1
|
||||
def test_openapi_schema_pv1(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The created item",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create an item",
|
||||
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
|
||||
"operationId": "create_item_items__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": ["name", "price"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"description": {"title": "Description", "type": "string"},
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"tax": {"title": "Tax", "type": "number"},
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"uniqueItems": True,
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"default": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,13 +1,23 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.query_params.tutorial006 import app
|
||||
|
||||
c = TestClient(app)
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial006",
|
||||
pytest.param("tutorial006_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.query_params.{request.param}")
|
||||
|
||||
c = TestClient(mod.app)
|
||||
return c
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,180 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.query_params.tutorial006_py310 import app
|
||||
|
||||
c = TestClient(app)
|
||||
return c
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_foo_needy_very(client: TestClient):
|
||||
response = client.get("/items/foo?needy=very")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"item_id": "foo",
|
||||
"needy": "very",
|
||||
"skip": 0,
|
||||
"limit": None,
|
||||
}
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_foo_no_needy(client: TestClient):
|
||||
response = client.get("/items/foo?skip=a&limit=b")
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["query", "needy"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "int_parsing",
|
||||
"loc": ["query", "skip"],
|
||||
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||
"input": "a",
|
||||
},
|
||||
{
|
||||
"type": "int_parsing",
|
||||
"loc": ["query", "limit"],
|
||||
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||
"input": "b",
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "needy"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["query", "skip"],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
},
|
||||
{
|
||||
"loc": ["query", "limit"],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read User Item",
|
||||
"operationId": "read_user_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Needy", "type": "string"},
|
||||
"name": "needy",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Skip",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
},
|
||||
"name": "skip",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": IsDict(
|
||||
{
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"title": "Limit",
|
||||
}
|
||||
)
|
||||
| IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{"title": "Limit", "type": "integer"}
|
||||
),
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,13 +1,24 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_form_models.tutorial001 import app
|
||||
|
||||
client = TestClient(app)
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial001",
|
||||
"tutorial001_an",
|
||||
pytest.param("tutorial001_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.request_form_models.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,232 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_form_models.tutorial001_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
def test_post_body_form(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"username": "Foo", "password": "secret"}
|
||||
|
||||
|
||||
def test_post_body_form_no_password(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {"username": "Foo"},
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_post_body_form_no_username(client: TestClient):
|
||||
response = client.post("/login/", data={"password": "secret"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {"password": "secret"},
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_post_body_form_no_data(client: TestClient):
|
||||
response = client.post("/login/")
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_post_body_json(client: TestClient):
|
||||
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/login/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Login",
|
||||
"operationId": "login_login__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {"$ref": "#/components/schemas/FormData"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"FormData": {
|
||||
"properties": {
|
||||
"username": {"type": "string", "title": "Username"},
|
||||
"password": {"type": "string", "title": "Password"},
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["username", "password"],
|
||||
"title": "FormData",
|
||||
},
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,240 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_form_models.tutorial001_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_form(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"username": "Foo", "password": "secret"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_form_no_password(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {"username": "Foo"},
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_form_no_username(client: TestClient):
|
||||
response = client.post("/login/", data={"password": "secret"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {"password": "secret"},
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_form_no_data(client: TestClient):
|
||||
response = client.post("/login/")
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_json(client: TestClient):
|
||||
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/login/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Login",
|
||||
"operationId": "login_login__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {"$ref": "#/components/schemas/FormData"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"FormData": {
|
||||
"properties": {
|
||||
"username": {"type": "string", "title": "Username"},
|
||||
"password": {"type": "string", "title": "Password"},
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["username", "password"],
|
||||
"title": "FormData",
|
||||
},
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,14 +1,23 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils import needs_pydanticv2
|
||||
from ...utils import needs_py39, needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_form_models.tutorial002 import app
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial002",
|
||||
"tutorial002_an",
|
||||
pytest.param("tutorial002_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.request_form_models.{request.param}")
|
||||
|
||||
client = TestClient(app)
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,196 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils import needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_form_models.tutorial002_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_post_body_form(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"username": "Foo", "password": "secret"}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_post_body_extra_form(client: TestClient):
|
||||
response = client.post(
|
||||
"/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
|
||||
)
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["body", "extra"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "extra",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_post_body_form_no_password(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {"username": "Foo"},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_post_body_form_no_username(client: TestClient):
|
||||
response = client.post("/login/", data={"password": "secret"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {"password": "secret"},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_post_body_form_no_data(client: TestClient):
|
||||
response = client.post("/login/")
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_post_body_json(client: TestClient):
|
||||
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/login/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Login",
|
||||
"operationId": "login_login__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {"$ref": "#/components/schemas/FormData"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"FormData": {
|
||||
"properties": {
|
||||
"username": {"type": "string", "title": "Username"},
|
||||
"password": {"type": "string", "title": "Password"},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"type": "object",
|
||||
"required": ["username", "password"],
|
||||
"title": "FormData",
|
||||
},
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils import needs_py39, needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_form_models.tutorial002_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
@needs_py39
|
||||
def test_post_body_form(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"username": "Foo", "password": "secret"}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
@needs_py39
|
||||
def test_post_body_extra_form(client: TestClient):
|
||||
response = client.post(
|
||||
"/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
|
||||
)
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["body", "extra"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "extra",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
@needs_py39
|
||||
def test_post_body_form_no_password(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {"username": "Foo"},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
@needs_py39
|
||||
def test_post_body_form_no_username(client: TestClient):
|
||||
response = client.post("/login/", data={"password": "secret"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {"password": "secret"},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
@needs_py39
|
||||
def test_post_body_form_no_data(client: TestClient):
|
||||
response = client.post("/login/")
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
@needs_py39
|
||||
def test_post_body_json(client: TestClient):
|
||||
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": {},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/login/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Login",
|
||||
"operationId": "login_login__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {"$ref": "#/components/schemas/FormData"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"FormData": {
|
||||
"properties": {
|
||||
"username": {"type": "string", "title": "Username"},
|
||||
"password": {"type": "string", "title": "Password"},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"type": "object",
|
||||
"required": ["username", "password"],
|
||||
"title": "FormData",
|
||||
},
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,17 +1,27 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils import needs_pydanticv1
|
||||
from ...utils import needs_py39, needs_pydanticv1
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_form_models.tutorial002_pv1 import app
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial002_pv1",
|
||||
"tutorial002_pv1_an",
|
||||
pytest.param("tutorial002_pv1_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.request_form_models.{request.param}")
|
||||
|
||||
client = TestClient(app)
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_form(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
|
||||
|
|
@ -19,6 +29,7 @@ def test_post_body_form(client: TestClient):
|
|||
assert response.json() == {"username": "Foo", "password": "secret"}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_extra_form(client: TestClient):
|
||||
response = client.post(
|
||||
|
|
@ -36,6 +47,7 @@ def test_post_body_extra_form(client: TestClient):
|
|||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_form_no_password(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo"})
|
||||
|
|
@ -51,6 +63,7 @@ def test_post_body_form_no_password(client: TestClient):
|
|||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_form_no_username(client: TestClient):
|
||||
response = client.post("/login/", data={"password": "secret"})
|
||||
|
|
@ -66,6 +79,7 @@ def test_post_body_form_no_username(client: TestClient):
|
|||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_form_no_data(client: TestClient):
|
||||
response = client.post("/login/")
|
||||
|
|
@ -86,6 +100,7 @@ def test_post_body_form_no_data(client: TestClient):
|
|||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_json(client: TestClient):
|
||||
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
|
||||
|
|
@ -106,6 +121,7 @@ def test_post_body_json(client: TestClient):
|
|||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
|
|
|
|||
|
|
@ -1,196 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils import needs_pydanticv1
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_form_models.tutorial002_pv1_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_form(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"username": "Foo", "password": "secret"}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_extra_form(client: TestClient):
|
||||
response = client.post(
|
||||
"/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
|
||||
)
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "value_error.extra",
|
||||
"loc": ["body", "extra"],
|
||||
"msg": "extra fields not permitted",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_form_no_password(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_form_no_username(client: TestClient):
|
||||
response = client.post("/login/", data={"password": "secret"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_form_no_data(client: TestClient):
|
||||
response = client.post("/login/")
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
},
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_post_body_json(client: TestClient):
|
||||
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
},
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/login/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Login",
|
||||
"operationId": "login_login__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {"$ref": "#/components/schemas/FormData"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"FormData": {
|
||||
"properties": {
|
||||
"username": {"type": "string", "title": "Username"},
|
||||
"password": {"type": "string", "title": "Password"},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"type": "object",
|
||||
"required": ["username", "password"],
|
||||
"title": "FormData",
|
||||
},
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils import needs_py39, needs_pydanticv1
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_form_models.tutorial002_pv1_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
@needs_py39
|
||||
def test_post_body_form(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"username": "Foo", "password": "secret"}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
@needs_py39
|
||||
def test_post_body_extra_form(client: TestClient):
|
||||
response = client.post(
|
||||
"/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
|
||||
)
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "value_error.extra",
|
||||
"loc": ["body", "extra"],
|
||||
"msg": "extra fields not permitted",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
@needs_py39
|
||||
def test_post_body_form_no_password(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
@needs_py39
|
||||
def test_post_body_form_no_username(client: TestClient):
|
||||
response = client.post("/login/", data={"password": "secret"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
@needs_py39
|
||||
def test_post_body_form_no_data(client: TestClient):
|
||||
response = client.post("/login/")
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
},
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
@needs_py39
|
||||
def test_post_body_json(client: TestClient):
|
||||
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
},
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/login/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Login",
|
||||
"operationId": "login_login__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {"$ref": "#/components/schemas/FormData"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"FormData": {
|
||||
"properties": {
|
||||
"username": {"type": "string", "title": "Username"},
|
||||
"password": {"type": "string", "title": "Password"},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"type": "object",
|
||||
"required": ["username", "password"],
|
||||
"title": "FormData",
|
||||
},
|
||||
"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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,13 +1,24 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_forms.tutorial001 import app
|
||||
|
||||
client = TestClient(app)
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial001",
|
||||
"tutorial001_an",
|
||||
pytest.param("tutorial001_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.request_forms.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,234 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_forms.tutorial001_an import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
def test_post_body_form(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"username": "Foo"}
|
||||
|
||||
|
||||
def test_post_body_form_no_password(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_post_body_form_no_username(client: TestClient):
|
||||
response = client.post("/login/", data={"password": "secret"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_post_body_form_no_data(client: TestClient):
|
||||
response = client.post("/login/")
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_post_body_json(client: TestClient):
|
||||
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/login/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Login",
|
||||
"operationId": "login_login__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Body_login_login__post"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Body_login_login__post": {
|
||||
"title": "Body_login_login__post",
|
||||
"required": ["username", "password"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {"title": "Username", "type": "string"},
|
||||
"password": {"title": "Password", "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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,242 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.request_forms.tutorial001_an_py39 import app
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_form(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"username": "Foo"}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_form_no_password(client: TestClient):
|
||||
response = client.post("/login/", data={"username": "Foo"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_form_no_username(client: TestClient):
|
||||
response = client.post("/login/", data={"password": "secret"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
}
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_form_no_data(client: TestClient):
|
||||
response = client.post("/login/")
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_json(client: TestClient):
|
||||
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "username"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "password"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "username"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "password"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/login/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Login",
|
||||
"operationId": "login_login__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Body_login_login__post"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Body_login_login__post": {
|
||||
"title": "Body_login_login__post",
|
||||
"required": ["username", "password"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {"title": "Username", "type": "string"},
|
||||
"password": {"title": "Password", "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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,14 +1,25 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
@pytest.fixture(name="app")
|
||||
def get_app():
|
||||
from docs_src.request_forms_and_files.tutorial001 import app
|
||||
|
||||
return app
|
||||
@pytest.fixture(
|
||||
name="app",
|
||||
params=[
|
||||
"tutorial001",
|
||||
"tutorial001_an",
|
||||
pytest.param("tutorial001_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_app(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.request_forms_and_files.{request.param}")
|
||||
|
||||
return mod.app
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
|
|
|
|||
|
|
@ -1,309 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture(name="app")
|
||||
def get_app():
|
||||
from docs_src.request_forms_and_files.tutorial001_an import app
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client(app: FastAPI):
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
def test_post_form_no_body(client: TestClient):
|
||||
response = client.post("/files/")
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "file"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "file"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_post_form_no_file(client: TestClient):
|
||||
response = client.post("/files/", data={"token": "foo"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "file"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "file"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_post_body_json(client: TestClient):
|
||||
response = client.post("/files/", json={"file": "Foo", "token": "Bar"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "file"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "file"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_post_file_no_token(tmp_path, app: FastAPI):
|
||||
path = tmp_path / "test.txt"
|
||||
path.write_bytes(b"<file content>")
|
||||
|
||||
client = TestClient(app)
|
||||
with path.open("rb") as file:
|
||||
response = client.post("/files/", files={"file": file})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_post_files_and_token(tmp_path, app: FastAPI):
|
||||
patha = tmp_path / "test.txt"
|
||||
pathb = tmp_path / "testb.txt"
|
||||
patha.write_text("<file content>")
|
||||
pathb.write_text("<file b content>")
|
||||
|
||||
client = TestClient(app)
|
||||
with patha.open("rb") as filea, pathb.open("rb") as fileb:
|
||||
response = client.post(
|
||||
"/files/",
|
||||
data={"token": "foo"},
|
||||
files={"file": filea, "fileb": ("testb.txt", fileb, "text/plain")},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"file_size": 14,
|
||||
"token": "foo",
|
||||
"fileb_content_type": "text/plain",
|
||||
}
|
||||
|
||||
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/files/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create File",
|
||||
"operationId": "create_file_files__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Body_create_file_files__post"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Body_create_file_files__post": {
|
||||
"title": "Body_create_file_files__post",
|
||||
"required": ["file", "fileb", "token"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {"title": "File", "type": "string", "format": "binary"},
|
||||
"fileb": {
|
||||
"title": "Fileb",
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
},
|
||||
"token": {"title": "Token", "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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,317 +0,0 @@
|
|||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="app")
|
||||
def get_app():
|
||||
from docs_src.request_forms_and_files.tutorial001_an_py39 import app
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client(app: FastAPI):
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_form_no_body(client: TestClient):
|
||||
response = client.post("/files/")
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "file"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "file"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_form_no_file(client: TestClient):
|
||||
response = client.post("/files/", data={"token": "foo"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "file"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "file"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_body_json(client: TestClient):
|
||||
response = client.post("/files/", json={"file": "Foo", "token": "Bar"})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "file"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "file"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_file_no_token(tmp_path, app: FastAPI):
|
||||
path = tmp_path / "test.txt"
|
||||
path.write_bytes(b"<file content>")
|
||||
|
||||
client = TestClient(app)
|
||||
with path.open("rb") as file:
|
||||
response = client.post("/files/", files={"file": file})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": ["body", "token"],
|
||||
"msg": "Field required",
|
||||
"input": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
) | IsDict(
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "fileb"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "token"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_post_files_and_token(tmp_path, app: FastAPI):
|
||||
patha = tmp_path / "test.txt"
|
||||
pathb = tmp_path / "testb.txt"
|
||||
patha.write_text("<file content>")
|
||||
pathb.write_text("<file b content>")
|
||||
|
||||
client = TestClient(app)
|
||||
with patha.open("rb") as filea, pathb.open("rb") as fileb:
|
||||
response = client.post(
|
||||
"/files/",
|
||||
data={"token": "foo"},
|
||||
files={"file": filea, "fileb": ("testb.txt", fileb, "text/plain")},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"file_size": 14,
|
||||
"token": "foo",
|
||||
"fileb_content_type": "text/plain",
|
||||
}
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/files/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create File",
|
||||
"operationId": "create_file_files__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Body_create_file_files__post"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Body_create_file_files__post": {
|
||||
"title": "Body_create_file_files__post",
|
||||
"required": ["file", "fileb", "token"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {"title": "File", "type": "string", "format": "binary"},
|
||||
"fileb": {
|
||||
"title": "Fileb",
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
},
|
||||
"token": {"title": "Token", "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"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1,12 +1,27 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict, IsOneOf
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.response_model.tutorial003_01 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py310
|
||||
|
||||
|
||||
def test_post_user():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
"tutorial003_01",
|
||||
pytest.param("tutorial003_01_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.response_model.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_post_user(client: TestClient):
|
||||
response = client.post(
|
||||
"/user/",
|
||||
json={
|
||||
|
|
@ -24,7 +39,7 @@ def test_post_user():
|
|||
}
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue