From 55035f440bf852f739e3ccd71b67034016ae9bba Mon Sep 17 00:00:00 2001 From: =?utf8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 17 Sep 2024 20:54:10 +0200 Subject: [PATCH] =?utf8?q?=E2=9C=A8=20Add=20support=20for=20Pydantic=20mod?= =?utf8?q?els=20for=20parameters=20using=20`Query`,=20`Cookie`,=20`Header`?= =?utf8?q?=20(#12199)?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../tutorial/cookie-param-models/image01.png | Bin 0 -> 45217 bytes .../tutorial/header-param-models/image01.png | Bin 0 -> 62257 bytes .../tutorial/query-param-models/image01.png | Bin 0 -> 45571 bytes docs/en/docs/tutorial/cookie-param-models.md | 154 ++++++++++ docs/en/docs/tutorial/header-param-models.md | 184 ++++++++++++ docs/en/docs/tutorial/query-param-models.md | 196 ++++++++++++ docs/en/mkdocs.yml | 3 + docs_src/cookie_param_models/tutorial001.py | 17 ++ .../cookie_param_models/tutorial001_an.py | 18 ++ .../tutorial001_an_py310.py | 17 ++ .../tutorial001_an_py39.py | 17 ++ .../cookie_param_models/tutorial001_py310.py | 15 + docs_src/cookie_param_models/tutorial002.py | 19 ++ .../cookie_param_models/tutorial002_an.py | 20 ++ .../tutorial002_an_py310.py | 19 ++ .../tutorial002_an_py39.py | 19 ++ .../cookie_param_models/tutorial002_pv1.py | 20 ++ .../cookie_param_models/tutorial002_pv1_an.py | 21 ++ .../tutorial002_pv1_an_py310.py | 20 ++ .../tutorial002_pv1_an_py39.py | 20 ++ .../tutorial002_pv1_py310.py | 18 ++ .../cookie_param_models/tutorial002_py310.py | 17 ++ docs_src/header_param_models/tutorial001.py | 19 ++ .../header_param_models/tutorial001_an.py | 20 ++ .../tutorial001_an_py310.py | 19 ++ .../tutorial001_an_py39.py | 19 ++ .../header_param_models/tutorial001_py310.py | 17 ++ .../header_param_models/tutorial001_py39.py | 19 ++ docs_src/header_param_models/tutorial002.py | 21 ++ .../header_param_models/tutorial002_an.py | 22 ++ .../tutorial002_an_py310.py | 21 ++ .../tutorial002_an_py39.py | 21 ++ .../header_param_models/tutorial002_pv1.py | 22 ++ .../header_param_models/tutorial002_pv1_an.py | 23 ++ .../tutorial002_pv1_an_py310.py | 22 ++ .../tutorial002_pv1_an_py39.py | 22 ++ .../tutorial002_pv1_py310.py | 20 ++ .../tutorial002_pv1_py39.py | 22 ++ .../header_param_models/tutorial002_py310.py | 19 ++ .../header_param_models/tutorial002_py39.py | 21 ++ docs_src/query_param_models/tutorial001.py | 19 ++ docs_src/query_param_models/tutorial001_an.py | 19 ++ .../tutorial001_an_py310.py | 18 ++ .../query_param_models/tutorial001_an_py39.py | 17 ++ .../query_param_models/tutorial001_py310.py | 18 ++ .../query_param_models/tutorial001_py39.py | 17 ++ docs_src/query_param_models/tutorial002.py | 21 ++ docs_src/query_param_models/tutorial002_an.py | 21 ++ .../tutorial002_an_py310.py | 20 ++ .../query_param_models/tutorial002_an_py39.py | 19 ++ .../query_param_models/tutorial002_pv1.py | 22 ++ .../query_param_models/tutorial002_pv1_an.py | 22 ++ .../tutorial002_pv1_an_py310.py | 21 ++ .../tutorial002_pv1_an_py39.py | 20 ++ .../tutorial002_pv1_py310.py | 21 ++ .../tutorial002_pv1_py39.py | 20 ++ .../query_param_models/tutorial002_py310.py | 20 ++ .../query_param_models/tutorial002_py39.py | 19 ++ fastapi/dependencies/utils.py | 90 +++++- fastapi/openapi/utils.py | 86 +++--- .../playwright/cookie_param_models/image01.py | 39 +++ .../playwright/header_param_models/image01.py | 38 +++ .../playwright/query_param_models/image01.py | 41 +++ .../test_cookie_param_models/__init__.py | 0 .../test_tutorial001.py | 205 +++++++++++++ .../test_tutorial002.py | 233 +++++++++++++++ .../test_header_param_models/__init__.py | 0 .../test_tutorial001.py | 238 +++++++++++++++ .../test_tutorial002.py | 249 ++++++++++++++++ .../test_query_param_models/__init__.py | 0 .../test_tutorial001.py | 260 ++++++++++++++++ .../test_tutorial002.py | 282 ++++++++++++++++++ 72 files changed, 3253 insertions(+), 45 deletions(-) create mode 100644 docs/en/docs/img/tutorial/cookie-param-models/image01.png create mode 100644 docs/en/docs/img/tutorial/header-param-models/image01.png create mode 100644 docs/en/docs/img/tutorial/query-param-models/image01.png create mode 100644 docs/en/docs/tutorial/cookie-param-models.md create mode 100644 docs/en/docs/tutorial/header-param-models.md create mode 100644 docs/en/docs/tutorial/query-param-models.md create mode 100644 docs_src/cookie_param_models/tutorial001.py create mode 100644 docs_src/cookie_param_models/tutorial001_an.py create mode 100644 docs_src/cookie_param_models/tutorial001_an_py310.py create mode 100644 docs_src/cookie_param_models/tutorial001_an_py39.py create mode 100644 docs_src/cookie_param_models/tutorial001_py310.py create mode 100644 docs_src/cookie_param_models/tutorial002.py create mode 100644 docs_src/cookie_param_models/tutorial002_an.py create mode 100644 docs_src/cookie_param_models/tutorial002_an_py310.py create mode 100644 docs_src/cookie_param_models/tutorial002_an_py39.py create mode 100644 docs_src/cookie_param_models/tutorial002_pv1.py create mode 100644 docs_src/cookie_param_models/tutorial002_pv1_an.py create mode 100644 docs_src/cookie_param_models/tutorial002_pv1_an_py310.py create mode 100644 docs_src/cookie_param_models/tutorial002_pv1_an_py39.py create mode 100644 docs_src/cookie_param_models/tutorial002_pv1_py310.py create mode 100644 docs_src/cookie_param_models/tutorial002_py310.py create mode 100644 docs_src/header_param_models/tutorial001.py create mode 100644 docs_src/header_param_models/tutorial001_an.py create mode 100644 docs_src/header_param_models/tutorial001_an_py310.py create mode 100644 docs_src/header_param_models/tutorial001_an_py39.py create mode 100644 docs_src/header_param_models/tutorial001_py310.py create mode 100644 docs_src/header_param_models/tutorial001_py39.py create mode 100644 docs_src/header_param_models/tutorial002.py create mode 100644 docs_src/header_param_models/tutorial002_an.py create mode 100644 docs_src/header_param_models/tutorial002_an_py310.py create mode 100644 docs_src/header_param_models/tutorial002_an_py39.py create mode 100644 docs_src/header_param_models/tutorial002_pv1.py create mode 100644 docs_src/header_param_models/tutorial002_pv1_an.py create mode 100644 docs_src/header_param_models/tutorial002_pv1_an_py310.py create mode 100644 docs_src/header_param_models/tutorial002_pv1_an_py39.py create mode 100644 docs_src/header_param_models/tutorial002_pv1_py310.py create mode 100644 docs_src/header_param_models/tutorial002_pv1_py39.py create mode 100644 docs_src/header_param_models/tutorial002_py310.py create mode 100644 docs_src/header_param_models/tutorial002_py39.py create mode 100644 docs_src/query_param_models/tutorial001.py create mode 100644 docs_src/query_param_models/tutorial001_an.py create mode 100644 docs_src/query_param_models/tutorial001_an_py310.py create mode 100644 docs_src/query_param_models/tutorial001_an_py39.py create mode 100644 docs_src/query_param_models/tutorial001_py310.py create mode 100644 docs_src/query_param_models/tutorial001_py39.py create mode 100644 docs_src/query_param_models/tutorial002.py create mode 100644 docs_src/query_param_models/tutorial002_an.py create mode 100644 docs_src/query_param_models/tutorial002_an_py310.py create mode 100644 docs_src/query_param_models/tutorial002_an_py39.py create mode 100644 docs_src/query_param_models/tutorial002_pv1.py create mode 100644 docs_src/query_param_models/tutorial002_pv1_an.py create mode 100644 docs_src/query_param_models/tutorial002_pv1_an_py310.py create mode 100644 docs_src/query_param_models/tutorial002_pv1_an_py39.py create mode 100644 docs_src/query_param_models/tutorial002_pv1_py310.py create mode 100644 docs_src/query_param_models/tutorial002_pv1_py39.py create mode 100644 docs_src/query_param_models/tutorial002_py310.py create mode 100644 docs_src/query_param_models/tutorial002_py39.py create mode 100644 scripts/playwright/cookie_param_models/image01.py create mode 100644 scripts/playwright/header_param_models/image01.py create mode 100644 scripts/playwright/query_param_models/image01.py create mode 100644 tests/test_tutorial/test_cookie_param_models/__init__.py create mode 100644 tests/test_tutorial/test_cookie_param_models/test_tutorial001.py create mode 100644 tests/test_tutorial/test_cookie_param_models/test_tutorial002.py create mode 100644 tests/test_tutorial/test_header_param_models/__init__.py create mode 100644 tests/test_tutorial/test_header_param_models/test_tutorial001.py create mode 100644 tests/test_tutorial/test_header_param_models/test_tutorial002.py create mode 100644 tests/test_tutorial/test_query_param_models/__init__.py create mode 100644 tests/test_tutorial/test_query_param_models/test_tutorial001.py create mode 100644 tests/test_tutorial/test_query_param_models/test_tutorial002.py diff --git a/docs/en/docs/img/tutorial/cookie-param-models/image01.png b/docs/en/docs/img/tutorial/cookie-param-models/image01.png new file mode 100644 index 0000000000000000000000000000000000000000..85c370f80a42e52768a412c066e686e4f2773ccd GIT binary patch literal 45217 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 Z$9uHPzzXyG+-czTceLOz?2Y@+{|zze?=t`Z literal 0 Hc-jL100001 diff --git a/docs/en/docs/img/tutorial/query-param-models/image01.png b/docs/en/docs/img/tutorial/query-param-models/image01.png new file mode 100644 index 0000000000000000000000000000000000000000..e7a61b61fb4f66857abe4b63aea3e901efd7482c GIT binary patch literal 45571 zc-ri|cT|(j_b-YS8}cGeML>gsbdlawf=HJxgpTyyO9)tAAav=SNH3vEuaPdjcS7jB zLkJ`!xq#o_TKArF*IDQObMLxmovf_&Jeg;*XJ*fyy+5BllLUTLk|DeE_znpP37PB% zpehN;jq}^91Glc8U*0gvT{*v8aaNUiM^e=HWSxZMDTyrbt-43@7SdB!9Zl%i?|;=( z((}hm=u7V0RSRQ(x_Br!E!(ovvzudhXxepitQRC$jAyH>v{>#1RmR7dCT3dNRsUpG z|902ZkoDyz^p|kU>z1A~0tp4xc=Xp5wD*jRd!KvbYI1+VR(KU^_tb}4AW4zr;$`74 zCof-IUp5?CaQ))*m+WsO_bzT6Ze2ILERcFia_!>g$<-?lFRpX{-1>d_Kr+0%ygY!> zM~IViySezk3IopzPY+jv|D2s3a`*iHE95uqtu40D&FH4)gpB~0>gHt`qlS)-4o;Yh zYp40XNqa~hUV-HD0iTxZwLMdw8;}3bt&s%0`TJDp`qfvLUw`(`mkULb+;5tky>VH^ zVf+3QA%_>pTKC<)TmMa^um6*y`X99O|2GkHHoI2`o^MGWSz{wYlIx?g%{8X4^$r8Q$K#-ZC%x;1@^Kc9AxB-tvL=N$WI*TfY(|k9^>Kffs>NOxhR_cd>C~)4mO< zj3a+`Y&hA4R*)25=tl@rh$(iwCe8?zh7a(u-Gh&uEh|dmHF>rBY?NSDyKEOD(xsqj za{4$;>0?oXtTFwwU8+)5e(X>YyPQQl>dlt&%)W~~@t=_}eghlhij3In3X-Cx)tBBL zUkzJ}|0r3gW~!outqcXc+$#?R450h}X!C+NyCS65`N|TO%&#JL<7WKlB zwZlNjCKn7EKQ=;y9v)JHrj&6l?$KkcqYwr4y)7<%q_{?rF((%fGef7B8K$#5|1kB# z;qNbD#Czyl!$KMlLq+eKDxn~3hu=EFLe`Fy%8TlPPlN3ruskUp<6DJIR&nFDQ@;+= zorz!A3Lm}TD-ssO{#poGn-(~>7FD3g{T^rMp8r`|*KcxoMi z@-!qQYQI!A>4D#q(LH{&;CZ@v5)xmvlXr8cfjCf1R_wLBM8|9PfiZ>3)O4j5TSXpT zgHk`~%igMW927k}Cf&WYU#V}*Z)S>nTNQo_lPi=0X`LHv)T^p%SEuogjy4xZ7|5?g_qCiA?&7+Q7<2eCCO~{{{do*EsZ%I5mmTt&xA_eZnFsE z(lKhEpj=Mwma2+PN)dy4h>c}^BMzj;ntExOygDgtLOY#cW=I>`>ZsZ5by4jAz@^WMYI0-%+=FRIPE|hG}g@x;<6?1mR9o>cDJK_ zo@X?EIKYX*VUiUJ_h5i znIr8Pb6A^gcd5|kJ0>0W;*!Yo6*yiq62BHk?jb_Gn)>hc4kx@~^=^<*OJ)MQ zVsn%nE93g(?_047*DS@$ih;uH^>6`GwjLnCh@kLs#%gypY&1H+3*s!c<1tTQGcWe4 za@pL`NpKPK#hSCWXlCC->H*U0HF6vaF$y~>iNE|%nPSch-yU)Ua=M2+DKA`z1@_i& zT9cU?@AV;NE#HInjjZV*uE)n4ue0os!@pnJ&8F&gjzbQ0q9pbgYbaOSR`rHsoDY-) zCU&+~hu>wceb$fA$V`7)3~U{pM%CHTb_Z@-$Jz?34$OV9;I}t0(27X^R{xrUXJwgA zTFe`-TT~M1Ep<@iRo`B&798zcD(Wm_>Kes=xHqU3rroMdNtd2ycW}p&dJ}Nkq1@=1 znfXcAL=>FbTeMPt2M!fE9{Vy8+Xql}6kD8)$)j|GbrrkIw=|-8ItSgOI`z2R|^HJYiMc&LMwhXKVMi{*wDA%tIXecoiII#Y#Wtr zRo6D6rU;(YeF3oBDI#-gEQ#c$7OsHl>3-)2XqW~HyLRaH;?T>0pr^`$S`-u-HqffL zcmW31K1nYtnXK!s17rmWllE8-%s&15+jrKh?S^d42~*Y>y)@_A^1aBL!r~5El7}Iw zyH!-BV{vlWhEMM2VLo^Wu8439G1}Ps$HH zme+~l1q5&W+NumzS0hg=FHIH^3ZLK8?af-gww>ITi}Gk@)9`cbfVcm95@HFT@#p~n zsB2jK8{C}?|0rIY_Ike|mj3RHNy;U>63_3Y_Lgu%tLqQz%-X_Ojcf44Y*QK9-#knY z$#@yI6!jKw7*=S~j_8bARN+j5Fgpr$wI^z+^~*9Q+Y!FIgXHYa541MnK;!tEEL@Z7MxEs#sR^I$GnC^axjx5nXsFf@1vKDkP8Xmi_l6L-ruYjo0R z3u~Day>A{2)p~=o-?gWke6jZxdE;(B_n*Y0;dvFwKrLL!h@($gI$l!5O>Dahrjv1u zIOw}j!&bVd&G*k;%E(`i?h9YbnOKf;$jqX0M|5E49-0Dz4SzDY=k=%J4*BlvFI$6y z`H&;;hO`x3#W$M*DYZurU)eY)$S5NFBoT*el#aCZ9ZB^rBR{hMfb<|wLRlPX9#?(v ze1~4(iwYTi6-Y}R8ixEvo6RGlAO zer!K#NK*8N`{l2jUbs`Rs3~nV4hIg3rv6MFXaSkV)qc>P=YdSN6|GMVasO_qsei>? zocXj^;j96IN4_ZTDGPB;Fc#ZYmjI$vnVVwnl4%lPP@Cz$0y`WV0h z71hu@Y&%9(qjtq10oom}lv-rmE9T?n+e=4rezRN z)K(a`c>ykd_a%ChR8q0{)H@Km4mHIDlH`sO@~}N4nfwMf!yy|9lRRXhoN<{xzvht2 z576iir{3(yg`V?wOCCtemx0e>;V!R%y-s_pZQiOGGwwL#Hf;n01R_wxU9kPb zCMc+$p{&l80(ouO!EL)V?Tx`pCyl6|Qc~Z@a~N&sg6mrX4rB|Qsy=M@n)^awStj1x&r(J!3<7VBI zjigFi>ebb*_Uj95E19h*+SJOxoZa{_xjZp^mmtDhH%dh>XLcO7?WRxe;t(dPTb?2G zvQiJe9B#=VtRKtWWtd`~8LYmP8pNLG+|jiVDm+H|PRzi&#C(fYz62)oW8!rX~&xXtw=)j zzpAacZ#A9%n%#3X?)E8~)>jqlwl@p9?&Y@z+b-I6dFX+gntDiHT z*h@$Y%%TRoeb%Vbr|PZC=x+CKC19s&;f@N~J1~HB@a@Z8n;ElqQt@KC3aP$cD%O_<&sM;so zmpqCqiy#;t7XrGPt$Qp;dah4HcSW!36F*Km&&XUK6zGhb0JjE`KsC|kEcUu}guTfg z%fVt#^U)G=7Pms)--)NozK9;7xO)}0OgDjkH!6m5+q_pkyLF}hyOf-0uNa_JU;FwX zBjBp^^Pjuo%9XFX*O#c_YMTWX)TeuYN;c}(P^D?7{eiawS82$OH|XfIX(&|C4+n3U#CMIg1aVxKFLaQyc;$b;4?F}B{<49+!Mzp%D~wbjT&HN@*2 zo(?3slk`noF24Jayz>fO76uKHCPW@wvUOg zGT(+vbr9|MtAC2ror;M)JC4eM>3LN%Ut`op?62-e)v>kZ$ZX-ofv*)n3mf-DaegR> z!B*SqL)zV$STC+D+)>E7zM6k6__G1*WJ-IWQ&jEV_jKZ#jkUa+)2~K5sqRYKqWQ%0 z^^%g53z&RAkYiqTH^3L}hf^GY$trbvRt*N%T3ML}PfNv3)Lo<@NVGCeuaooL`^S*A>8T9;6vJd<~+5ekUR|hq@%sWfC zpzw*o?RS4Of0y9R88yXRv73hvYm$C&ZQU|u>}~YuPdWqTy{L0rLyxQjBAjneN^?57 z={OlJOz}T5GWovQtFP(KD2VZT9@qSDrpO{fA42-?c)|}|Um6$BjacB~fGC&b-6?hb zMRAD;jf!<~GCYU$_cxF7buqcoL%K1r>BqS)*$j&S?7?FF{L(E42QQ zQ{}?xTQc@IXFVcQUCT6Op) z0;}Vm#$k#pqSx4r>c8JZlU*;o+{9gb1l?euVW64f7aJjlD*=nLP}=Stb0UW@>!1R` zyHm;aoTYnV&*Jn-Crcpn;?u=*D{{z*4w-+N5?Fwn_o!s!HX2!oHpZrI8p8>$@s{DDuEt)=scGx)A_&W;;t6iz+# z*5h(TYIGQawJu!y&q?d4E7ea;_v`%(7#a{$H+i#c6<;z4w{J$K6n#-X!Sm*bPZp;k z!!Kh8nPu~pYB9)u88NReCIXsUq0b2^=NgUjA;b23lehT_zf)sB8?+a?%Z;jjAw2C6 zkx=G;gtYIIa9TP&?LV&b|CrcdYTi!m0`E?)J8{>yAl^11y=Fs&4V+0MFftG1siv+c0xd$6;!f&g%Ga9;K{H*#OKD{B>KUDw*@ z!|&!M>*nMn*F`9Yk>_Nkz7$BI^;g8A?Y*B+OR%q4jhpv-w|;llYgx}N-_ zWWS!Zt9=CDQMBA;r~g(w{ks;{1>fi2zTk@~cOR_%jjxc^~(=(Cy1G4k+k=Q9- zi_roSk+f0(G42YQDnC2yLwaID4-v5&my@Jc&B-KjETYA5xUp9coz)a)S?(&iITVL0KXZ5t2 zcwNc5$9Ik@k{FhEbOm-pP35&Z>=hO8p12?khRVPrpt-em#98ZOh4uKxjJs7HttoHh zrl}qU#i{2sd4emz>!W)3PkC`=Lk0D@8YcPo?~sEgmvJI={IkrLYjtk>ks;1a6%M%O z{ZsLqE+eU(vhrd-Y)hag?^{o@nMdqel4^HCV`iDA^FjXa{|yP*rl{hp@fw&&H;~-5 zm+_L3Mn0Js8rI`9Qwx+hj2q#UG#g>I+7Ed`qp^C!P+3pjH07Cv8g}84^N`6qkWQCE z%KKghikGoRDd3}3Ec-PRto~>0k@i~%(|65+qTU`Ex1Fvhk`fm5=n z8V@iQ(ckeDIffpAQnW=Qy#bci?I)da;&(4|m??attOI}tVs<-fg9Nfh%}pAi<5OQr zx@BsY4n5lGx<9_HlC!D2&zGD;>GJRZW`qcFGX79=;^6V=#M+PG#zAYP+1Plk)d@*9 zwxwStHC+Q&8a4~gY&w-D+$zAgF7u*Adc*WrXefwjWfD6N(dhip+oWNpG!GT!V!{n@ zd$V}PYL-fPF%)_N?DrfUSZHt+CEp~Yi5U}oB__s}Z|2CVF)(1sh^`a$#8v(aS(hZH z#(*sNqLZY7+u4@ubPuT>Pi5Hm{PAg;^8W>|?LMiZtOYFP$$a%QcY-*xU z$F8+T*YcO{$hV!-Eve!q$J+NYCmps0ihT*-2R=R=)$Cx{+`eBCP*lg{KI?#Zi{ zf}ZvXZZxlSL9+6V8iVLu@!cQq*S8eI%7$NnF6G=`^1m^c9F)PSXRi{(gK!yiYZ;XD z6B0&mEsAq0aGpws+n3IawPQDB;f?PG1#6d0wo|0@eWuh--g?DM%FfgOOcBnMrjtGe zF~(YX<^KvxxGP_{p`_1!>^uxbucb9hcQR*a02g1+--lh%4?2S#DX))Sv&}Z>{}c=q zH>&O@;ob~WJSV6(dNx&)JUIC|QxrA@X^RIzZETNyw|l0=MRIMlC26_jia34+Rp)iw zKYEns+@(B5Ec@*mU$J1Ar(uv)YY*(6ZrG+?`Rl!!$R1Nv(2E<5RXD1E*cvBnKB6tq zgG>Y>-MW{H)*x1u!tX<1UansEDdx0+vT!EpBNna^VksT`IwdER{AA4tCE9CF^E?R% zZ1pag!PnHe?#^Ur5WUTmYoJXhP5Rxx>&_$-mjqj?oYu*gnJF9}nAmA_G}Q{W}Ms3Q>=-**|fONZcpQM~q!y*k} zt2DAIMHR@mJtGpGj zoyj(uG*APSQb3|~WHcsdGFQ8X8EN34@CVKuwlXe4esR~Rtlp=_#oW9Od|+=BbR&FQG`oWs_SmZR!A;mj~>_cIEL(kTTyxeSd~*)&a9ipSPx zN{Z6NMr96d=VoK42YKbt01!Hkjh00b%~BCHX2Y*r&*%CB1FwqlF5Nw%O|rEWEsqz- z^{ef1+i}T?3NI@xQ3V3CTvSm%3@#WHi6N=rbCRTAY)2xBq%0J)EHQaqAIqr9vr68T z7byV%%+`t-ZDzTu<&KU+wL-3Ii@5f}d}poFSOX8!#rXy4$$YKd!|k6BhUcR&1f5~K zqVh!G2td_{KR3BNF265NF&@Kf1c;%wZWL{>Ky(N1+&Pqms{ok*G}cBj>EEZ(IL^Px zvX;>JtG1#Z>G@Kkv=!&Hc{@vB`=mh*sBoiwSAIksBpL`+*W3};d;yS_>Ar70Qe{Dn zoQ59`hsyK^(J?PEu7n z+rrLkqg)xfi1bZj-K93SWR1!uALCN7=^cXIyyRZ))k-b>r=8dwB~Nc3Towx#NT9eE zj^KH4QTeAQJ13{O!`$zfqpjJ7y2Z@5!KJz*a>oakdy%BePp?eW6E?psFBe^#`s;;% zznwI>!*|)z_%*dlcTXb3_`mG-P#Qbr#_ND(+iZ!`)o_{%>!&pC%$@w09ZA0PHD+~QdS>Q~%0Z5R2BY*R9 zXP>WRZwIaJI!>ae3 zzkie%&eT8uZodU$XT}(Q`ss={b|YEX8TedK5Ho2Gt8f<% zZ3??G;qiGmKg-(ff)BlH_*Q%LM09>WBE9y5xwDbe*{_N^)%)hFu@BpN*9D*lpmg7%r3z>tLLai1C<;Zw}k@XfMZe$HwC_hfbE z3%MU*Q-NKQjiMWqC+{d!WP0@QX9H7NWpWGVAz;Bt06^aab!Op)@y{>ItQT3yLRWl8 z+@LeRhkTj-7`xC_gD)B`zFg_!{l0`ln6UH8Jw^##;7U)z;?~v~20=v5N+TVP@BD6G z!svT=nfm9glnC((6M9F=dsGTX0NMT`l2xx4XSR%`_9K=~m^yFY#)uP-7w6+ZD>Xt! zgk(<;dX)=g*CVg29hLga8C8zaf3ncuU3+N}o$1oSsgUDI3a0n1ZEb;n;(1I`eXy41 z=Bo+3=2La$blpl9N(rO71eQ zkvdUv6l!|=bcKnXw$brW)EQ^_ZkISt^j~*xtefk0p`d`pcRF`ex>G3rNP1X_kVJ^! z*<7WF0^47AX0uy1XLYH3Tc7&{!Z?(mUYHr<#v}L9ajc^zh-bbD{9ucpQ0Q)Mjq*D2 z;HX`URq0a&d$B=*@8(=lVFPb}*p0fA%3TTu9sKEsw0|*S|4>+pFWF}_Lx7pvOQgrl zJ<}#MJj}a_F>fP$PXhS}dhb9h;PHC~@P=u>05?;^M;ANAlx3egBR7uftVY?geleE| ziHiCj_6lNV!Mo*_!@wows=w`Gs?^ zQAO6YDV;8ES@40qB0IXnuD0mZWE&{G=f35@v&B?T+Zj(=HeZ9#52etDa`$dVbStJ` zc#kh;_MMS0hO-sxhyW!29WwKsK|uH)AC zE#PYM?;G5|IRA2ncLYfz%#I<}T=nrUSh!`LAE{!xagIn@>jw{p%arMQn7cg<61hE= zFBdq$MgqH*VR_zG{41l$U#55b7MkzNWjVD4Q3GDvPVv#w#>p^3Mi@+IKe;`+B9PeU zuT)VxI|rXq1QP(Cvb(ys3aojOxoSo*3Gh{k{hi5ePh|ZWq?}7GkE5s0q(lFrk+AvA zVniyR<&a6Z_%Q*uD;VzsXQc|FX4kB)t5Z(o>ztkj_r&vrf>y}P!$}H8a{JqLqdK21 zXy8IS4TXHg#(JgMRbK+Ses}I7@Oz_N?4}b9E}n{5G-B}-lNyvei&3s)vwvU&5hp zfzL4~vQgK^`o8s@apKNMwrC&zjD8mk&3eg;;%K6set81$QHBR1x_%pFF!k+Vt$Vr! zQ&DPq;AMbRXU+IGMHZ`7!wVn&ri||#DSW*~s&8cO_m1b>?CEqPAVQP#sa?N|()dNm zANhnR=cfF&`Jf%;&?NA|XI@-K%l9?2y{v?wtuPCrmzJgnY_h(f9QB%;;-fWf?}Njd zuQQa>jS_|ui0|PT#nJtV$dq_pQ6b&r5`oe^E5?!;!>7ELO;ei0Q5b53PY?(o!Ii)d zvO{$JjbLo*AGgSqYOB%McQMc!eFM`yBu2)&;c44b)%ZQsx;>S+*wc@o$Wp>#f0bFh zbSp88fs(s){^Rs-k_xIsAP1JSdt6$aW)Q%#9);1Q-yb=pTDvR#oXJz(3a9`|@!j0J zWgr~M4Om$sC+sTLu(T`tOjjCf=Xd2`@5E({r9!?={y6FsrOV3EM2Z0N7a3Yz8)7_C_K3F^>&1LPC}AZob_?dVrPxXw^0C~orx}1 z0&_I;h?^6xgqYgfcg~U4wW4G~G?90V)SKwWSKG8_u7@|40Mf*~kGrEtPc%^yuKOYD z%Eg^DrfVm+Dfrz5KNo118@89-rLh;3Q3QW}vrWb&UPSkJHX}Rd^pAg5e=q1n_Vr!c zi;(?B%?L)qksjz}MF54%xZ4)0uuB)B?*u+ZZ-Ka#b_x%369R0CIEuHosL@qglD@7B z6S;=I$}u{u3oFa07Y77HFKB1{W32`q8mIkG<1#hzOHQl4zh0A`ub&+FYl%Q zzW_Oer?oZV71$P6#AP`Csr~UK)g8a)&Q4j!So+|azAM!t`#K!4`T1EG#@}BLFs?+kB#2sA;XCF*>%GC@Mu=RngEm{`V`qTzyU; z5QU13_>Mql%i7k~*3#!HB(!KX)Ox&8PPB0Ckpn&Vr9v&*y>5P(vh+i0$u;(M)`^fL zarAg!3K?;5q=}Nwzg9xuT}_bc=Qz2nU;P6)Y`jP|;scqe9tn_J)zzqYi) zb&Ntix}2oc_g<$bn=O^Q%4eO!_Gqjb3ihLzocHF127a!4*>`qG;>%I98WV4sZp=Yy zLY-mEO=%1?mmbc9?;*U$46J2D8cOwrcw~Zb6&`81Jn`ymRE3ZGd)idtzc-}n+tXEh zjq@flvuM?KH4W?+X7gnwoMeVClQYR{bt13(bx{d;EOiv-Q)|4Ox<#j}*>Ysi|A_Z& zunHr*knea=|5{gXXX6Wwlh~5EnJ2iMRja{^ie+Qi;u+1;GrUn?K8j;*F4f27npF!k$GcZIO?BQ;r3 zl1bDmIh>+)?P@sx2F$@NZVG&(qA>j~bg%YU^kQyu9aUkztJ~KO51q7%r=J5B-y?av zKM+Hw-KvI8C+o_s2RW`eOy75^tNLI$DY|d|AhhpJ=_kFEoO{Rj@_tn~Yo!XQ4HwRN zw9y4{tSYJHCL=X)lwCG}g#rRjJbx!BKLy;qz%JdYy}pxQ-tX;j*f@%!*YH=Or`8uu z_i~v$dSsB2qVqG%0~@?*G6Mv*MiS;1ofn+?Qxi%IKYxQl*;W#&eSw-)>y%oMljWW7 z9q9L2T1iUc0ileN*-3GqUQ0mE@?yIFP}GKSBGlb#oV`(Mo3Xot@a7#)pPlMgf)o)8 z5O`eCMCES$?90d8yPDf}3v**X;g}>JU$eaDjg$-9)t!-r_-xIs&CDc^ZVI3`CEoxwi>F=s z^;%3wWdw)s&D^_I@~*Z*HMg~r7?%$T(k$XUADiaspuZZ8AG`~>s2iqXH9B=DRTwoU z4Q#E!;x+t+kRV%BLOnLr5ggQj6&HRwr<*h;D8!+_)V@{DVbahi7ixeTcty^$l5HLn z1M z+?^anwYF;2CsMvKy)0dmq1 zHz$*nd}#12e2zk@IgYzi!j6JFYk#d46W5wW6BumqO-2d78K1-tP))AhsxYk!iwlNg zI2Tish1ov6Fg`OyTi-?C^vZo~0bolYmz%rE&~(3#&t5e4gb#ns8OE5VfZf}s7X8)3 zB^P4=17|CTjj@`dnq?$4P@`q9P7P-}0UE8DtVX0-9gL&q)Rz7VM9c|@&seM+TS&AO z)!~Yvu^cx)J7P(9{cL#=n^M(s?iiEGY@$!C5y89klU22)2E;4p_l)4x?b7cVg1a-| zVZ}zbq60Sm7Lu*c*6aEIz?c73$xw$vU3Pv#kDaI;sUsqzDr2A%})`nVj{9CrP( zEjyN_1XXt?(^T#Md~uTINZ~4@?#g7=Ao1=qsu`z0p(lBTH0)&ci~;~AI-}Tcn~fHd zBlLOq*a3j-5q?-URdDvIfdLV7D+RhgM@_t#VrjaTPx=i9faO#$2RwL$of zRs$2RXu2FSLjV2T#S-<-idJI2CK-{?1Vf{s@0QhhYo`yG1I2Ln-QH%|Dr)uuv{oCg z>AH2vW(F2ct4HrGYc^zK3L@T)FIsh|ky8gk*W4R%GqW3;bih{cqd$)Ft|Xe4dK!ZWqT_f}Rb;RA@AKO+*A8kw?o=2NQ<03~?zI}LJb9Ts`IzyEr z`B%i_Q%ZMVE`TvllC*pGPwNeiBVd&VD{QB z85IT0PTXz~3RR;0xnGkf6Q2ZKn_By9uMzyY_%+`el|36anFO;MIw6C`taz9O)d zk^KF8bp1rt=(?aJw|mRjzk7B_?yY|YGsbHbr*|JqWKNpSnLYecSa~%ATBr7->O&rM z;z8xIEzhKSijEJ}Hk+pP_Vvj_)D_K&k98A;8#n3o>g-^$f-9+O?2xqMp?huS9021U z>gq!0 zHh8sxV7y+X0}R)vt7HzTg$%4sG;LOG9UULnM4G@Ac~m7%Mz&o0(4od+9rWB7cmZ7_fDY zE1=t(dYO`2FUD7!Mm*UI9H`X`0n^m~($8)4X~Pbh>WTGey+51odU`9QmjM06E{BNT z$G!6f*C*)S;L?9?I|yXG$x_J``8W=r6&N?4=2zbjlzjoT%bCPN8@w#sf-3yp$NCwA z{TY(BW>G3$(U)yDvoL!&q`OLkE+-)(sFp}~O6rEH**QuY+u=k%7Bc(8U;hza#8Ab- z$yPKyRRl68r;x|SjpXzT`Lm0d!hm~TIT?7+zUm{f&Ma0=zlsSTH zwp2vFIPi(+Lb#1+!JRtdd}<9m)`pWNf@@Hm9pe-Cla_*Kb%(GQLrofL%*B&f{`KoD zz*r1x>z!CJWbxqkE%HN&O2N11%ys63Ge9A(Hc++o^Q%ul7mBJ1(MnU>S`}bx{PAdW zm7rczDsq{XvCoHfCte$n3ZBo*5R6xSZ$OGJ$nka3GwBjr~){ngsuM_Kqw5C`aI$*5Ii#ww+`Ta#SB z+FsSMRAlj0vPFFZ|E#ML{KldlyRHV>w5kB`=dTiu7?l!cpajR;$vfbK)1Jljr2>hR#(&1Vk#$ zB8HusES35=&#VN*^MBP7ER|wLeb=1}eH z-DXJ*gJzV|WHmy6^9w2;q9)i^v80Acy=GE# zQ*hze-^@Wi>r(3`;}^Khmtvgh<)C}xdd=|8VSn2h82O*iw z31Qk1zFL1sGg!ryG-;!t$SE&xjjwvNvz_REllfBImkApI_^3{~X<4t$@fM}$@LFAR zg$|f@^tt+@dpTo1Y6nuPgR#9uQ4G#bPF9VN;{U4j4Y1X!A~q{0@YZ@2mwDlF_Mm&< z2W_v!QL74fVc%PS$C4>RqNVJ^j8DzMl9nd){vGiNui@lsx7Cg$#otPakaBCjUTgs1 z|32@um!npSS-nlZ<8=^a9NfTeDVMk;@%zPo*$zZ7>lhWKnuWGzhkDj?>p%Y_XN}qhlHkK{y!|-2H~KaXU&-E}1ma*%5mt|p3;#-jzYJLJW5mR) zm^5a<64`JTxXx(yZ+|k1$#g$I)7x^KIKP@xK8c=6k0HG~+Qd^v5HCa&L!t9?8-BaK zanUL^PIp5dO6)UIGI=)|ygF)o9tHK@S6A@fYVK#2`SfcMWq@4|EAMft@a=%&2-v0g z0e`mJof9RWC$&&HrjLP}(-KZSt=evkT^Hu$kfHJ9kM4d{=fe8onzNq&O`$+&*4z@f zICC4%BWhXR|IvU|BDuy!n$V;W)rPE)+WjrVc;6x;l2?>m5&yFqDXu3&4Lk8Rw=C^y z7#B~0Jt0;K%iooFbW?Fg&gN?Z8C^mkeJH)h=ymcAUb zfhD~uN5y8Z99vROykk|hd-W+uu)cDxMUUmXzcOyT)j{|PVlGAlp$#12N~h`!CmKev z8h;=b%|@Q0(}_5?tnwD|{vHNog_!Fye7Ai$7}_PxX)IjnG@14@4PRIL$Tyhyc}Fr1 zKjydx?b~QRI`M;LCjysRYt2&)C=4FBp|-Snan?SB1<8Kj7|v&w5WhCS+m>(e;bQ1Z2uK{e`v)_-D=?y!`JCt*taRou z;#ae2G5ut6-j=9@&A~0({aKU&$Sdf#BZZ>J^=eH|x3gNt1CDeuE0hvzb`!ZI?;Bd4 z=Q9Q(Pf*e2XFzPkgY_k5D+(TqfX3Ips=3e)vw2OtFa`!H3P@RTxmY=mrSnw3OpnQ# z?uZ-(kLzPz>!CNj_U5V#I5`-2c#o(QG?q>06W_>nXo0o7Eoc);%+dugE7l+o@9hq; zoR)SoV<)Jc6ru7YO!|pUc$f+kmNeK1<4^Ck9}2M<`cWho_$qTpC%ODt=R?nF&zSf{ z=R4BtltVehfe+khTME2$&(zpsFfsd5pB%&MQ5pcHD_87XL3-k&6^!r+H57`pq%Z129Yx8CffSR+2sANA2d?_A zKl2NIcAbEBX0KWb^PLc`=*#WD#4UH2N-pO_BS*ULhtC}G&y(0p;KQfgVWLO(d~Lil zb$T9~wii*e&qBH_e#I-&P-Jzg*XhOzY^{XxGlHyo5C>*=Gydx=fHxLR8ca*aVHPC| zR9~%Sb$x5g2N?R56$h1gD)f;ZAR)pSp%9g)+S8%DCjH!WKSPQZCN7-2Q*O)>jv9KZ`OZvx{7O-`4d|bQrvq12R9FB30F3qVoF;J)hD$IA2 z9S~WQ-^}6Uwb>tTQ`q35-6wnMd%Ppg)g?kC5`%?XLWHD#lkjxCeEH!+s;b54bCbhQ zQ%#!VPQ25veJgBhA);Gmw$nS6vCBCH{k7$>Fq>_Q*Z_@|k$2r{azW778~>2LHM{OV z{Nx0Gg@KKq-R0(rBLdywptrjTX(*DfMtb$(APwAC_a?79kZ30243O;aSkp)J$HZ+< zxC64^L!aRHCVdNLt=A+E*IBIHH5j>$gQhp}9C^@L3Tdr!i&Fef*EMuEarjS#hYiX8|)nKXVBb z0|kbhNGZ*%xNcV3<3)&@g>Z~dqvh(gLxMzKG2HVa&R73Y=ZUlaY;YLSLN@yrtLB(- z?4VKF?jfC=@2$}U-_*sjgL`U(n?GqEoCPn(m_I2{CsyZZm=vv}r{|4W`qTOJxm2h0 zX@?Td2YP>-f4#I)xJF<*3SPLoQ4Uwm3oACcO;sFXv$bv%F+?6He?O z+$k6-MfMzc$`*D+PqFB?x_X$Gvd z8`DWlg((?#;s6ws^vlySi0RW`Neu|L|!-TuAT&OvLr&b zLjZ5>nYI!a&Ydu9K$$-deL^@(5#*xtL@MGt?(y1TcgOsEL0;Y^W~+6(Hu+mThocCk zAgq%4DElfA;pev!5!GSd`e$FpuYd1s1YM|R+u}W&Map0-moQVXK?;;DUDS`X@?Ri<6uV0z@H=bW zSBoe)OFb_6c3Es|rm9#aJ!Bl|Xu2u?C93^-*iMGyF1x3*2Ba&b5z(MjEIF@R)1~dZ zGyB>+yT7{qDOf&^tJq1OK^#5E?(^uy0~#KDss1=xg{qLv2jUsJ(`D!EGroNw@0CFR z;e))qJQ%%vcsgU}ZfmWj$RJ?yEy?uA!#pJRd4L_4f}&NF;CnMWFvs{pSx) ze*TBGT)**m=kw{yzhM5&yLaz^&w+(B6v}$Y8&4>JI`$tndo3GO=&AmLfBC2U&^w#} z4Mr}OYp{hpI2`Lx1N|4hgG@V*^toq$asKt|e(%x{WWMKN(S4ianNo!1Um7G3?r25dKU8GJdjjpU8fJi(M>ISa;_| z&mMib8`j;l=p*L)TQ+ck{F3Ex#fCNfOM5v=TIgTmbL$Qv`D%(&LZ%q0)FVaIk+_C=BG>PSw{uM%Wb2-Ih7dD({(koU3Dv{f1i-Uq zBlcFO>d%Wr&01?u`s9eDN~o#8Ao`z(?MDL4ZvSw)Xgq6{ks#x^V;)8Bk2g08b!d{$;f!UL zb#K%1ox@GGkz*xhPN?S2fZ!|%Y&l|mbD}O7Lu#oX5y2F4^v3-Gt6%Z=3}XI6s88;` z@0Inn>AIcSHj1M|LtMkB*6#pj@0t60=C*rSz{I*tvMVHL4*w2=8gHmj!3INA#f@Ut zU34m5^(-9)bOPvHXbwI}v&N?EuK7q4gPLa7uP}xplHcFN)#o~M%1EyDTOrIUu!xit z5O2w!tw0O!K0J9Wx^Sw_s&|y}RJ#FKDYssETVbEiGmlX|jSyf_SUdbF)bP=R zt8r&i`TlKHnKLPCNBCt;|C6btWO~9;icZB@yydz?B`0~AQA0qM!o25UNq0U}?i8ia zr>+#4Nf+o~GKe35;e!h`E&eRQmU5leKmQd)Xvpu#74d1<>Iy_NXEE`EWas4@t*4}C zipFGBh62uZ&(~weiCl!v^%W`y3X1aA^NEGOAkLfGNA!$5hD~R^X_S3qoa?hwxMTY$ zOJjqF*@n&?O*Z)RM8{DGg--c;2It!PQEGs%PrBz(h4_IP8U!)jd zwEP0kZi@qY3mTOT>=x4mF0zD6cv=S7sWqRUhFb<%SG~ez`)hzkeqdr~flQkreMrce z_n_MV);4M)IWn>Ayt@)jvjM};KB%m~oM>d8tamX`R4X@fILj9fHrnh*91EWUojXkT zPQdM@fHTxw@oU0<%G{(4%hAF>kNU8E|CghM>im|kf0B7|2lFY^8<3NQgt!5^$wu3~ z2LfQ9YRt=uSKghifxFNzD|3Y5rMO7EM?P^~RJW3~TK9etk(;aA8;C>Y!&4zA#{~R3 zq8qHK(QK!ht-GVrB3Wni>%AbpQUxO(HRHCrAxN)KDZG?>V&tEzzcX3UVhq6??`^8# zT^W*g_@^tHDmkp0hQHHZYz3aExpef%_n3xU$WVxQ^k#kyq;B;S04e95#LUg_oysX! zy)(es-SN>Amp4mx`Ub8^FXy&_B9|BS#TKyi)->xrmXYv2`~7;Sq>`MuJ_WccV6`P~h@RMmrz3_ap#`D-)g-#Q(d^3#GKG;+VzQBb~ zGs+mlK%SjkFj1GG!DE&GEGi;zoh%_77KCleHFcG*>oI6N?MwlJc)aVI*eoP7n%cC? z6(8xtbRy4%aw6#3#@Kf4vc@FM*~+Q`fa$;4mS|vm4oRx{0Tu`YomI-*&M|~Ja6Ne~ zLqIe2@NI`Lb`GT#^ZO=6jjSiF);D1QIoIA~79dQ#F6)}wr{A#S&!Nry> z4jnrE+x`7RGd?y-u+Wp$+Tz`v5Bk#1O&-RI&g7>z6OU!Vu2{u;?;J9DR=J>3MeG+*nD974!bJwtgPT=u(s6Yim@@Sg>=!{jXl z7@QN%mv0-VhX0aL0z6Ra)u{$Bnp8C>7NH(WJDIX+{`QeTDO0pakx`rpY zW%Z)_^+n7B*@CIluD1L{T1@h)-|ec12%|1d`d38~`a3ifd-c#+HVb%8mD}=&mWs%T znNCo_8fm~&@?WaswLXyg(Orp};Hf)9yvHk6`E(&AGcD)uj^@mhg(2AVPF&NPpq`4c zVpe8qo-2PpZfvbU>d#&uG>}ch!!^j`!e>BAURT6pr!_^18NV+yP5 z5!!;TOqZMOQ}%!N>laqAt1%>2Ob097HfQEU3ICZ(^#4XY@bzLVwl9Cxt_=A{$#q;t z!5)Tdc%Q{_|C*ah3(+Wsl5|ix{O^Y*_di{kgc5oaKaw{Zu(1tN$3-{oP066+Bu+V* zh)TkZ!>d9hS!fp4#KUI(QY?iM0^zfmtG)u!l zk}TW);bhESLZ;*S1iGD|8S;%j+|wg|=%Eb7=#7gP>dQ*qyrXJdq>qiIVRaGlSj@u$08pbeHa z3!3)m)Kcac;(Cs$8T=fg^!;0&6gPLTgUIKQH`;)2FX>{s3JMCs(wr`C^Cl_ z>M8clb?Of(XIy+Y( zSxB&Ipm!8n!sqZA%%bVc{I5`rt=2c!hG&o4FFaP}N%TZjKjYf(ZH!ZOJ@dS?p56g_ z9?oZ%f;cltrtgPJ3jq9gv&v!&!Yy#}HLhqk@JUufnbt=#ly#&Qqi~?w^W{QW@4qx4 z@_8+tRMQ+afN^sHS6_?MqRYv?!1j>DS9i4zLO+YnBWHSy@I9KO(W1vYSoF_I)@>U$ z$rvA<&)V1pk1=>zZ*xP+4$*_DA_hqH7(s5V&_U<1MPRrIG%` zYjl<3{nu9_QtirSv!&X$E_a>f5#>E8w3V3QiM@pub+~2y>gML?CB>j*FOpu~;~*5-Zp=gGeNXU< zm%V1BIQ@&1+wq(0KPKbhr)xiER^6n`!H|^U!j0(186r~KgOe*>5}mJi*uE(z zj7c&K(V@L17(4Dez5Yh@2s2)gkS>Ik3Y@PE&iBu#Uj+lyxVS2Y47~#0RTg~JOd8?T z(fwSatLxUK@rDtkB&McWPD#9+*X=7zp#o-mBZ1vo>E}Z)AQQtBk6znyNC8U+Wm7%- zfWe8J3Y8Vn)=Yv@qX}@2>Pis6d>6-<&NY=&y1q&D52a#BSZ41qpKk1bJLk`S=fq2J znvhd@=jW5;-|=cDsp02iC%vpUcxx}b9sZTqV88Jw-+`U&h6~U6{_m|xoS{}6Mf`g% za;@OqJbBHND49g+?f&2pEL3Q3fS&8TnT8mMVtqp&YVJ(%T7QcIQ|OR~{6TSs6)7pE zJF_-;{yd*+hYU9c+kBMT43qTa%-?p=u#>Cm=VxQ;=P#{ zOg#|L7wRs&3mpRpuu*66optjtlB>n-{7kEM-p3&irY*Y|&6SF2n-8@u%JqmY%m9-s zA}YcekDQ?a1DHkCDi4-rsRtI>S#d}GZ`2|4U+x%S*U*)At`d^H(u6$c^{5S@wbgam z#3zD!o`#}wa<~&N%$ZcnH=n*dKCwE(n;QFQpe>4H9v+j<$pm zr`t5Z>V{SD>3jMka=9P-2#iWgW&XEXHm_+^JFulbVXp+ZNofaqLoXmN(n)4;qzc9ZJSX3Y5(Yj4w_oqY1A)XL35r zO7E=mPd{;dkT?6J0SNiCl~uh)sDu1yMtKE}yRwF~ql7iR!)LcNf**w3+g7L}BhT=J zmCr|~$ffR$7-KhdAImp*>{rrOixK0Najkb-t#`$Rq>USbhmWrQcnm@^=Y*x`2cX{ef=8=%oyLj8TPKlYqo`%BHtkJ}UO&8@> ztAt%g%5$yjk8at;kCT+d3JL#3G|W=ru{5Cq5z`JM5hF5o*l10?na2w0#w|b?jjy-T zhlslDC8KRB2ZdS-zO78SoD62=D{S0qIBTm=kM}cidE6W$*{J{xN$bxBeQo2}?LDjB zP8$o$1loTYoRULQJ*)`qE+QYF#N@3|yj#ZIWwx_`pB|8ltK5y65YcBvg0{u*J(-30 zQ3W^{viqv&kBn=LROA+NAVl)y#69sHiCIoW$QMp#n7P)Ar|$P1~WF6lHG_(z9uEn?1|| zcp^dyN&9-cbt^(uvTC=zQF&Me%i=j7Y_oi3PC<;W>)t9NDPi?H@|5(&HAH4r6&ZMr)=>Q1zW&}6A5($3-405MGON<#G!izY zYSx6JuhaILESa;_exNt(+_sZn>0(qt!!-ht$a0`GoRJnRoqTF6Yt!#$xy((DHIw?=oAuy&vj<5e8=+)Z!;MbK& z8-i1c!YlezCzqMu`k&zC1)CMoYH<5*CQ}Sr99~DsOOrIlR6TL*)zr`(0c$L&($S>R zpe>xLwpNJebl(ZK(Y-tO*#Op5U0OfFr53Mi%LBS^yYiKh)Ba-UWLRM81`l3QS(*oz z`Z0Ql(~6~)rH#lJq0;9}>=O#^h7C%eZ9kc|G+R<1+E+Qnj4>gxnCF2fdnWKgLcuV; zZKh^o3SKsVn>Jh)Ho7tEX5R8;e2lfji+hz*G1_)5GA1j|jo`$g`|`#l<=q~*roTo7_exeZEFX8I<4lnH`sPTM^^ z`rg$WiVP@~UM1UdFWmSC9xdgj7qKAISH5(*u02v2KC(6MI=y>q`#Z`~_Im{s4Hm;P z%Kq>puoY#6j=c&Ez4}}VDtR9?GJ+>kNKpo&S}X<*uiIK*6$(m8>U=u-P`X*s+uX-%phT_nOIr@hB-KmIJ+{E#XB=1C~r)rZ;Rk(u!`22N@RudK!CmD|z6* zoIl&t1bDW*&pT_g=^ERvpfu5QQ84hgE%o{sBbed4Vn!kDw`G&6GH-EM&R5@%i_!?+ zZzO9ds&Y)okJjckKS1J6sxcV0-t0!Q6HJP&X69#RovOnmY9&l{X6QNUatQDHU6pv@ zCu@=Q#5FmsyQX|&ddncL1}AK3v~ z17vjFcYIvtN2u%#l4r2V`X-0+nLgzI(E?}(R&(y9RsGo#IH;lC%(lUN(+*rre%Gz< z?rWD?|D{o7ey$S6Klr(dfH%PME4Z?Nsp8XwX38VgRU8fUyvCCv54@l&Q`L?`ubdBG zQmFilMa`HMsV=BnFjN?$UlG?L-Gb>-oDmQ>lD z?Ip>e@BPIQQDb5 zIY$u3@zd2~LG{vyntXFzqs4$ujQTC0KeumbZ)KF>!(O8(B@RjkdJ1ljpoz6ou@ry_ zD+!IkNT}tb5293{5CQusQ;i8wbs>KgRs1+SX6fzWltk1f=J7A>-l@+U1LHjHjFc9rra7WMPF-qGfy}+q#@`i2-j? zmMW5@6roNr4tVZ0MgLTds-}by(!58aGZW41uN>8MbS=C}bn1;Laab+QLcSnK%9qcS zSHZecmzMb%Z7X}LwJK9OLR@m7*{o?l<#lGR-^=rma} zSl=A?9h;O8`y=7EBnEGplC6rrG6x-L_tq$@4`J>vFiy1QrK+fJpdpw$>)$4Hq+~E z^~O8oyBQw@=e^qp5~%Rrv><~(4F;uRCn*!RmyEWX0pnmYglUoD_jAADIRz+oT-_*5 z^u>;4iddR1ukOBO__$C>aj#ZIN|()^6sWP21xu3HQ{TSUI@4=bUprn9V1k;R@tVPs-O0^i1 z#P-96X$F*HKm0jn1uns+Wdj;lL0S)tf>We(JvXzd9i8)kC^pcG5u09%*8Jfl8oo7 z(vP3~_nzB{aj@nUuIlrAUGFgfKp(dPX+x2nB|?%r1FW^5;QTMUx31es@;FphI9afS?&UWOq; zTJmh?o`ze%fW-(D-D@W&C;movt5}kTnwGPC`{o~-0Sm5ik3S$#miXSNp+V5u_S?V! zdwERSCSqQ`5;ED?GE14`h3<32xeRZU^VLC~TO1xv4cT@TZtqy~n*MMr8~N-BaOE<` z_vb{#nhAmi@4Vw{c9k1QCB(_st)CA?A2thH1k`TA57fKl2xA?7!KCUf7*WJSIRJSv zV*Ue3Emd}Qwz_(AA*}G;9fI0Wz~w{y9?B%|<#OVeBYcvaswZ5srs_FlsoCJG*@#at zm|;+4b{cb5Ok`+MbG<8%H-@>VBgO(nxg5u~6*zI1+@`inlL_9U z_Acq;8+nr_OI$A@c^5sEYWH0#YbYP&aC&zhMEbE{fL?q)%e_)Wnmo8Jq(?qQPI&&f z1wb|y9(kC7T?)pF+KV9Gr>V6N7n=T6kK0}^V2ZX%KhGoe7-M%U>oDV)?(33W-VS|h zcmp=JY(eZ<=fG&U36y-Mx2Y>r&91P(t-I5dx0I0D@Pv#aeub!!xygTpK2(d|rm`P5 zZ(QpV_;FOn-C;Q!XlQWMc;4kT;2-P0T``Xt*t@oFXn_l$&00hoU#k-* z$1W+!lX*;_scUqyIl9skS4M=Lg-(!-vKN}+-L_+fW&HoVAgGQU_ z9k9>Q(0}^3{+omA^@YLSx>d+-y%>jBRTP&PsDFp!ub6ZGo5Y|-6bVvt>c5N)tQ`94 z5bYh{P2EkJ5SF3jl&qncCUNogpLa7N?)JpLP2@W8WMMq|gqY7I#_n65UPaO~_YX)j zK}ES0Ca{%)&iZ*SeFLTYp2T3wg33i-e%bNdFUCXyY?n{n!&N>Z5|#p2-8jcK zyhI`g9VBcLhF?>KSB}Ri5!=n8_^h>B1dgHBMKseSRX(5&H+Q{h(KW&JJRYSOrvnsFmr;E-Cxf+HI)+`B-m&P`^ zA`a;w6U2;-5UMX%CabXkU;fEm8Vbw;>3oQFfr(;A9kyJWb_XqcMmsPgo?MUs{vzz_ zQ9oGodQG8khmEb`EWW%?>2v<7jQkugIB<6fm;#;#4wu01(uprOtKkpXboZv(AdMA! zpALVqlo&TWZW)q=;cXj-4XOebHP9>6(w(md8vV!n_Y9jNYR89~K#SNhodgkS`8mB| zCb|7`$PiP(;ZaPT)qhEdzwo`%P1G0WQ-9I>irIW%hlI@k%A6-pC+npC#_q}r_Swb% zK>;$dqX*N)4$dtx;*lG;*&xC@VRY7*{ELpJqk*~uAnTYtQVbO)H^;1znc6b`aLFj^ zVCfV;u$QWkbL2Yc8dx=1?Y)RpVl3vh$=70pzHHe;$gc-2N(I-;Zo{|3=j3dYwdqk-E=}td|mEaL$YDURyvjct@z4QBfOoPqXpU;tT zomSPd} zJ@~KlbR6eux|7K)?+8OELdPx>dO2@G2fyX~wQwg}EKMX^Bra8!eRQm*=f3?o8d2P7 zZm#ptx~xEATc0Tvmrr0j8SdhdVjG8fAk*-St`tK5x63wwR>pWPagM=}ZWT8zF7c7vDB z9C8NcVFBc`_4o{0i^6IqlTqy8BYf-PyEfl7(AiS~muKR(v%_sc-PA3CRo%G_hQzg( zOY!MStw;Cgzck4Gl@!1G;V-VN(Kf(EpEEQi=P~)75uOa2N~^T0d<4*7WpdpW2kpMw z-x|>(^tjiFc4Zc}mx+B;jBPw>zBY8`xjdaz*8ir79K=gSaf|h|6)B}K7zs$>_xU)7 zl;sOdTpTgy`j<_1y!gPHgXFSiP(F2LmEx(HEpp$R>YM1ywlmK$XJ>~~uz`UQ%V!6z z9y=RvESdsNF1O0DH`hqB8}`DlMFZG=r`NW@KH?6fv*zr zNoa5sT>TNUFgvu0M4`DuYA#S-*3d+(=M9qU5?MpEl$5`Rk}fU(&G!YdDvU)0ol)c! zFE3G1(dm~gQF`Md%_T1#M}mAOC zKaz?Kx+{}2FM;z5dqaiLGasVr{+aX^$(2dd3ZE%L^SROoh^_->e1aAqSekK@h^v{+ zT1l;dKkKPyWE{osYWx|x`jkRYM;$o4XcHnSNhHc=uOG7+#gNJy8$7us!L-)@72=ac=OudgzOcOoQ{@^i>u8-XG5e8RDn2L6%f?Hn3jJ7%Ga~ zxvxi(JUomXu^dLkywGcRb|@D18ynlYzPxFt6ag_h`3RVwTI|m4Pe1rjEN52fKQxiV zQu?c0)^Zo=Gs&gCH`y{>We=atrPz}^Y9{QDh&IIf8(66P92AAQKG~0m_=n|QFE(&a zUY};FMsV1Qvc=e*1o{LU2fa{aAO{=8!)MB3V_l?k0Wxf004em^tbVQ26jTGAWbsVT z@?`MHBe}E()^eSgx*1BizJK#a_c?+;yiG_5Fd7Vd-z|nDd1_n9rX*Kqpp z-e1qfwFhH|BmHD7l8PR;J!!5#HQ5te0rR_vn2Y8@^k*cSss|tI(BEZtKAx}<_$43^ zr?nYy1;ng`i_$RLG=7!xwG8ddJyrSesJK1J0<=Rtp~XZsU%o4R+3(m#q1S%E&i)Kb zeeAE=m=W`fIv^J-ur3>n9**#nVTr#PWxx{?j#|yGFyROG+=4b4w&|8qTvq9Ev50@i zDu1-pU8+)?-N10+^cM^)j&j`Vampa)<4)3jgnX^5UROMh?Hn{0&3m3u7Aq;bTz?1^ zg0VdodmQRIHl;{j=e_hyuj>42T3GJgCJqZ)+otOV!72GcGZ2fRVlk6?#yD2)UfgRS zT3VK@fp%bx0Y4u1Wuqp6}` zZVlh4in@X-lShwj`Vr#^z=AEO;g2HIafwmA)|=SUJHWK)fDcSFHEp|3zt4U4mO=%% zkVb7Mx~G1m6-o?HGoS`fZr(=gf&{kH((+`giZzpjyk48sN|1vGE5#!s=mPFmR*Cx1 zfctq6CcI98i?8x)RK_Dh1l1{AH*2&kfsqkUY+xHc&0vF0Xp6)~&D~ygk&8x;EA+EI z`zPhv0m1PTkGfqjYazF^PCj%;7f#r6R4MO<747$$Y*I4XNXtydfOh*Ak zls~4p6a^mqY$o^?g{rjqqAGr&;Dw&H5yb=vXmF&$6B>-@> zOmirfsYaQc+MVj6;en(yk$!tZKwokbgL7iHVe>?Yj0MntEU&48j|3P}=Q1^3RIo^{ z$>Om~vcp3-Ev9r$3Bf0LEftrzQVshGehMMhTN1g#BvBRiwAzmm0F$Gh;NdV^Ekssx z5AmCPcw)mxlcehKTCw2Qo;eggZ6@a!Ok$+3b}b~HYW!u@NmIJ5E1x+Wkyp>Ft7yVX zau05#H7}bYD5L*K#CEcpYg&&H3i8jQRUO)S_3_G)l@ngaw3=WeJzd}rz}OXOraB?&F6+l2k%nJQIUmlwH#a+&!e4v zzfu2s%v4|V#pFd$a%-ue5OM}yzELAOx@#jG_p(KF74e4f7X1KIl>Q_RRf(Vyt>gdCtgLrPSDo-+xIR7hz`ZX3bhm`*l5m=kG+!7DoWI zV-oYsld7|;GF_(9{DP&Nefv)8OKYZ}k7VX3`7_&9b}W>lIV^ydF2#mMBg?(LhJkEh zMrk|#TIMD0pesfxQhVRVSnI>`bOWma)b^;X5L;GsInpxK?O|541onecjMryA7{T+8`V#v42m{(IY)DHzV~|v zyaWcVViS050ySK^Whf_R1NJTaFUJEAL!FglI-wI71E8JZQP$IFzTyE1 znJgJ#KXZektNDQ96Oyq&L~Y1Irw&Ft9O$)Z&YnZ*g}vG!F2W(owV*sr6dXOiB6F2D zTk98Hyju-(Jl8v+oX^(Ni&R?E1s(bc52Xry(R8R(=@+GVl~bEnD|Z&3aCvU>&cSVhpB69GJo+j&~NocsRR7dc5vLml(}v!yu434ay$HXun$UkY&2FW0u$JT@}SRU zv~k}m9$O;jJ}VCwrNz=PjVT+~XEx1j@fon`LK+>=-?RdqId}&c9%5k{j9OeYTqiIE zXZmqObAvi{u#)Lj7;^t~q=qzfNc=C8Lc~Y?q}`bsd%~bVZ>NwDkB(>N8qtgN{L70( zj#f|I&`#Q$dXAmFQbii$T{y;CZBWd6t=|^wDx;%NBC4{#g&C(y@I-&k^nxP42E z>iQ-j>XDnrJxS%cyQjQqcb@TPsPp*2=cngyRGYNPcN?de>{! zlP+V|N?t|Wf3mo~=jP@%o$j9mf#EZ&9s_f)1I|xBVYg^PCcN4<$wb4daW_k0JW+mqU_h||IN4ER%(!qG5sfhY~|)tenHSgl(0Y2V#y6jxW`o zc<=KGWA?BMWW5k>8$*Y^_z8wLVB#-jj;$?H<|Mm2G8O<&Php#ujr$i}gF>OQ7I8$i z3CVjBpYVG(RI4f!rCt*Nn=`m z79W_r0XafhINB4~6vAP@)KP070lU>gE92>Fg6Xwi=jV1YS5+zXN`dUZ zdfv+n!FNX&A6i3jQ?%ofBWV0g@tv%C@un4&?*X6`zI?WmtFjin)V#IpPr&uQ4OiKM z3k1FXX*e+@b#f&f*ph6Ye*DLh_RMx1F;@yZ^B>;5v6(6>ROh~)Z&|OM0m7|!rd?qt z{Y>1M|Ni^0*CamwA1tn7j*j{VaOJ zW}KVfQO}UEpZ=6-SD8D1OqxgL?t0<|>O7LtIe#fz*4Hf~At@|5_gU(&Y?D1EmXGl$ zvV*Ko8#IUQNv^qWR?PuZg?M8_Sv5z0<~pI$-4j#>gK!c5Jm=8=#_$g0i+OE>48*7J zB~{}cb`cFAOV5u@FL*zWClmrRM*2pu&?aqIX938uUESCT{2r$>M2H4C>&3<>bf} z{iXTK&gDw{8hm|4xHZO`U&ndSXx*rEbgOd~`5T*~oG&>$!ef$w)z{D}u;42T!P4RX zqXif@AFz}1r+pc2Bt%YtYMxPcdTa*%TVME+q$O62O zE2Li_kAtva>UjCm&Hd}|{n5B^fy{l^QFUXlu9eaw#Kif*-pfLnDu`A5?nfk2BhZ|g z>uJO|&{DiyO5$4|DjW{8NOgbucTMp;+V$jdIg6#pDhg1OfsIDVZ=FkXAC+e8d+Y=3 zC~_-xze@zK3fUCzx<8!r2{kJb2mG z#Rt;LpByA|fA9X3DMy-;&r)@+T&@D_4_PjjwJpWT3-j*{My3no4_P`=-O}_E+7+YN zi`Y2o{}qT=ZGn^~@c+?-ai z)1F(Ze3nzazaCt?R>B7khO|6Q`bF)iPBB(piQcD#P=cFq8+Cc;nti1(OA*M9*&qy?OK zqZw+296u;VXSn6?L=vt_G8(R!K6?H8U^&1)DmMe0LruC`pfmlkKv9!&A2O=!C>0 z33E+>M+pEqV;vnqj}=vlRB_k2QGonx3xtrL6;fYbe2GWS|AyXe&5orLUG>MDviN2v%N-8g1_8L1#5iIF! z=257WiwIYgmGyS0IxV6K4n}0VNFRTcu9QCq?0=)wAyteG3~~6gy4qg9p*sJay&gO~ zyU}7{3{cO~Q2{-aA>L=WGsydvK zx_!$ujSqi9X(|zHh7?g#9-S$6vcWs=BQVFD09kBL?mJFxDr#s{37b^b45B)Cb}?VI zyy?jWJzkeOVDfnbHh4dcN z@n(LOl+^S})uqsO;l`vL360KXc6bY&>{-!rS6*Yba*evrvUqatpcE0V7K!hrKt|=iQf%^92oQtRY^_I=k z^*AADw93A&eM{3Hhd(VWH{pd7to|FpvWn`lOnj6SX@GrFQ95`F?0+_^;ITQiAlH(2prTHV?n;Yyq?7rTWgp zM(5{)nckK1`#YZD{@wdZsma<8=2EBGALS`jf~2v$e%r!&Mu=E6b_`=nUFf^>a<3R((szesfyOvcI|>?lkY* zTc9FUYY}W-scmB!WRcY9u$@LO)R}`Yk82UyxzZG|&YpuE)Sha31)gHYD{BUvt(<-A zk>p86MF+x7=UEuV=DVSfrr~zrHacvV`C!vrU(RrCaUwbkQQ=jZ#VcJ8G@I;u&9We zM;g~*(>@+jSyk?Q1*{6gkp`b_UKJPjwR?Fnl_%>UFjy*!UbbOzlDh3RaH)x*q0chy z0Vmh_?KR}sU-MbD0Zc|y^)m%5jjNZ@*;dOpB+)Hzxg}odyV=kZ+4|=4JH#xbR{noI zEuTeK=Fn0aJ+;Cf_}Z?`aZ zcG;0)h<^9(T~3b8ObtjJG&eF?P0|7yUE>4I)OdS&dAV7c`d!ulY<^4=ssYs6opt_I z#T;X+k2acaBj9`q7&ObJBhsbiuZf2e)OJ2`?lE3#hr#~9h(wfF$k_z zR@*Q%M1-wuY!v8ozni`h#~>Uq>I;>#_+%|+iFbPVb+1srSNiQ+4RhENS*+7@xh?A(LLI@=~H^4O(XRaC0CfQ%djW}x^8g}CC#R{sY5i0p`#%SbXp@8m58032si}eCVMJ$juZe-V`Rt_9 z6^z@(v(uH}N>b;{{VaOvqyL}~qYv%mHH?oOS-S0w<0tP5c%r0e7R(-STvK?=&sAzR z@lfvDC=C7b!##+j=G*Svq3MOmV=K&?4?xm|(BtR$C8cK5Oj8FL_T;vbjK{ZFPItbC zdv&dSXivC(9=VQtz;C-Sts@!+c?ETP;EL+EAGs`^>hfOehLzGrXuK@>z)WqB9o9o< zD^Nz%_66-M##LwA3oJff>nt1-vAXnDanv|LDKS${iemWWn7ecQ+MisBZ!e!P{PW^# z2Xz-Odj|8Jh|Q>Ee*7QAqK;F0UT5J+%3y(F|A^aY2!f$nm$y?nCxR{}-zDguqAUyK zl|2+z&Oi6zhE${DVU*wjo9Y@)7*DPHMxCn2EE8n z>ZJ%sy+#p10g)y}Pzi$cj?{?sfOH5Q8xp!oClu)fLT@1u0#cOTK?uD=2%XSjAm0D1 znNR;Ovt}mW&dPfCd)`x?v(G+fKabi|fWBh5AFrLI1xY;{fNFEt6;t}d2m0V&;g$c1 zJpaacN~r$j5;e7DBl~Tn6;Pj%?}9FJs3`iQkDYh@uh;hGtNl^36Xp)dOh(CDD<>M- zG$7JLwzCeh065XX*w8OS9MNUPS7Q5rv`0`kc7bRNwAg`RAH$^fk^> zy#7uBU)xq^NADh`K6J97|C*m+ia1s=KB5RU?>U==ymfZ?a6B`3%F~5wb>GzwN$!}c zxjogtox1$D5WQBO*y?KkK&}u>WA2VKDV+7Oga9y!Nlk?|N{hE@=s~|bY|OUccM8Ta zPr`*ew>7rWV{|bdgQOIAL2Qk(tmPDhi2W<(?5x@LoQ?%qSo9 zK(CV|*847}EAFauta;BmgQCA5tZMz~!r5-cNxO08wI@l_{tyChm}k<-t`9GtwDr9L z&+Cu{fguH64U@m=FlWqsI1I!sKo34?!H9z(gX~!zj2il(b+2INRU1PnwY)smBpx^7 z472jvSLbyX5wWY=J4?+*rFQm{ey~LO?kG7s3&n#je8b_r_tuQ-ZjO}{a&CJ^Uvi$Erjh`Zh$COAQ zg#mWwY;~+Q#Rz2&nm;N1@C_0#{?(;&C%vWZ<*nJpne;EfftnAThP53sQncuib+Zj*&{>jp(8o{Y~|4($mkkBY~&T+P);t_aNuQopTj^{Y!xXiU7?_oZI_ zj90AJioeOii3WMgkou9nPmts}s1yd(oyDB7r1iF$ZU9{yohy`*caLy^d()J3g=1Rk z1QV-!eV=4lp^^ms8yqqwMVldxb7uy!f6lpQMmGg}hxzR*@0kel>V;)1XPwMz)b2d0 z|D9ke*zJRc%lMC41~SGb3c} z9vkwcp2fe2#iJykERyGL2sh2=EZB}G`iq24>0G-3#78)3RwjwFbCG!ti|Vu^sN$h% z+clYm#@*cmwIewc2h!4^8QEqVcV<@?R`_!RCoy78Q-wwl7C@NA^hC;48oNVdYm z(3mt5nhYBh1b9*r_fcg+h42C;4_w0f+Y}*AQI@Rp60i{+%&zImp9#C|@E9OnHCXN5 zeqU1O?)%ECC86)kqM||s3;Od^v{^WcOaTuvDC?Z&Mfz^2L!c}z3-FXAMQSb=1MksM zs@V(=9k5>~vrCkEXf3*6$6K0;-{gdW;e%ilwZVuc6M_>)dw+*sb=O)MuTz5s=P10^ z{8GImq=7mL{&_6%l!QJ2-0K0d10$KSRs{)79Y->)J~Em?QXX`}=gSWNgN+>jEjIoe zLSOqgg#Mc4Ury@Ue?#cM$^Q?Ucr4~gc5oX4fv9sya7b`e8Wl#EQi0Mn$8(&9w#6|Y zKgwHNrbPm#lm647rlF?RmE$yWf`S@YuzUd5h^(!n0`Gjg46*~@3INA97j`_Y9SYR2 zoIqfTsmaA+p|lW0RVyMAFkXvihVDmxO=-KV$-B2efCP`2nAnAPd|bW{Q%ZY(zU}0u zXkqY4>PY{rpx&rV&bMb`A0#a$^~XHBWhVS}Y?05x*A>GE(;$KTq9Q>7K4$vJ`P(zHUd3#Z?c93>d=?D8`U%NhYlHLsPF z?WMFsBZ-;J+#f@ZZ${%!TGs?7Kk}+>_`zFUPmZ$;H!wxSQ)g%UAPyrLkFjenS@*}j zzmA=$olk7bRW1|&@c>&F25hqUkJOWfO^d6TtOViG=Y@10+^V6FF?!Tz=OLzx?MBJ9 z%kD0DCeHd!0pXaV4a*xelBk3!ZB9dF12ho>`3fNSk4K*ZPJM{7nH9q~vT|?aOUp@1 z2`cerus=$FeBPGW$_xY!&wXZO-XIgEC%f>pcx1&EBAcv>b5clN0U+@6+aj_vvDgLM zG2UVs;jHu1t3LZ|y~jg^cZCzXfQ!$YZuV7Q*B>^`#qa{ZuPZ8Of+z^-**dwM#<5kk zy_R~bS3d4?MI2C9bs2rxo_{xqxfZT2n78k@l4NjUNZe+>IEw4g_2l&Gi`>D$j^gBB zuj=pO-1oL_gCntl4F^0yffZ|Ol2NqFr$5lz^z=IGM?wQMfh9bbl_P$?@F391N|DyKc2gPCgA8MJ~{D0_j0L5ELp(*&sikbYHWf%hEE$kZ;)NLNb?VH zM(Kl;s{&=Aqx*{I4hWKBWvhb=}yV*rPJUgfXJ!;@ZIkmuXxPo-CAKhnRxbP1AGps+USkrycV^o^>Kc=;O2@VMRgIU zVl{J4cf!|-LQOv)MTG>oZrIhf#dv_liUo0n!3W=EP;J$2EMhUaE}^Xgjl$g-lJc=z@Z zV_S{>SBY!X+z0CY&QTZ@>n_v8x2W%qoO{zKmnX&rW!ADMM~g&x#RBoWZ$%&LaE+8Z#f8Anbaj*^@gYSR7aZ8cT)W=+V z<@}KwK0x$_b&k~t%^~{m#V$Ke4n)g&lBx6dd=g2%f{Pe~{qiK--tf4uI;AaEOwO9Eh)qx&nIvIT>ki*s+%>pH!HQKe zOd4|R{SCsvG)}VYZUYCEvy~tC4qLAtMXWO(g^7(`mTUD-d+Rm^ZUegP3(w^gT>!+bC&3eRdE5NEev4xO*8`2fQy;9U;=D zSWTgaBwPC#y%8c)%KMZfDsAb0nQd(DPBBI9xQrkil}ZnjwP0c_WEW}QkPMf?@9~L9 ztUr14K#ZAdtm?tuyNcQY>{sp%aZYs=iF{n;TXTNgEZ+i4b>|7CAP@XF^990Z9;4V!TWdbA9_cv4fO6jOWYKf|LK}J*Bg$!E9 zf309TeXFhEy6O9JtdnT@sb*T`rgOn={ICL1gIZ(xW)Wl9Vv$*hmMKGqUyEP!X_RlO}R)?aFh>CAXb<5$Eaj zxK-J<*OtE-s!Xy~8zJmR{5KDt;eK{K+Og+4jT5 z@CXx!bS7I_2nJAcih6W^2Pdl~qyH%-@)kIa^qmWB)u7Wsa|ZVx2-c@U=;=L_(p(0P z!jHr7j;=`ZDJKd3kH@NPgJwLyIR7*aaBO|{Yo&L^FAAC5?7S0U!Axm_2+Rp{rBPN1 zsu`FO^X*$&p=-wt8ZJqsXTeU9%{BTS8b!zMq8PRxeiG%%QCt-7~wdr zKG?3IBB6{=NbO7caa$@#(S3bXK$99*&0WU-vFStX@c2R-yGXTvVp-cO-x^}KCgw-X z;a)}sr*Sm16Be?CX1Lsb*yG;ErYkWQ zc3$H91GVH&nYw|@P2CZ`Gz`p~`Ql62fnZJ7#fH^Uom_#QQ~!xeH|PKk86!jOP1~ex zq<1IpZ!iB;)JU#TK&vJ^0>*IqJB_Dvd$P7jAWyT~X3#`}{{ra6kyhV?#BRvFp4p1S z4gUas;4NJ~<}kfG-!~2#(~~mslW>fFhK14n`4PA8L-ZdNiA26v2dg4;yj@BLpvcr- z10ghQea1+IxFix!I8DK+t*o;jf?{m8s^;nBBq|6Tei#+pi@Sg2(ZCml4vI8!bBLF8 zJekG&D^ z@jn-!zj;1>Kf<&d+^DS$(YGJs7Y^9!+>F^dok|grDJiu_f8L++(F47R2vkRCOG=3o z&W$cxhHV@>GNKyAr&TbdoLxqC{#x+<{o1Xtx9&6#{{iMyR0m@!JEXS|eWrKzYOid; zqNWwPN!WJp%0q{E^vZxL?30M4e z3{`pVhA9{7NsQMfa&UYYn>fVo3S9V^{kMSqqb=EuMy=d>-ZMYeJE`ffL!h}2PcE-) z|K`6^0)tt0v+i!|N5dYy>9i?sk@#(IasSa|pkHe@En~e`b_cgo!)Kw?n<*y$0FMGO zA~vu=TW})@+=M@+(ntX7S8g|WtGIk0Dd7-K5OtM>6L)nr(9+- zJ}2>u$5X0oH{Hvr`gvugc`fnB=Y|ewujaZ8pB+_}um zF8uDm3|UcX3JQv#%#6|k(sd~I3lM=ninhseXqVQ1!%*q;Dk@!rj&39rW;YiT7?5zY z-|Y*({hn;*sK8l}Ml0wZWqcHOye0L<)X=wjGM4A1008~_%ew%#aQEsz=5%u~e5pPu z^e%9tjJT`iOYYA3$kRQ!*t|_7Q%Aj}zT&lZjJC44y7x&F=9YzQB3p4`^(RK-rB0I* z|Drx*ug|sSaNoDK3Hkq6b&9Ca0D)z1TsLk28|9-X37L#-DH1-j5)3$@!GyRt;~Yx% zqs74hE`$&rJkgGx-YQo`=evpym{nXv;$Bs`q)4IW&EL@em_~ER*U-8JC+z;=x@!-u zFj=dIoy}aXAP1vogv`S9w5D`xp0n%4#)A1uiGZ`Sa~+ICwA^4lY7Gz+o=@vn*jKYH z6g&$U?Ju?#pgy)WX>QT0I2N~VCHlL6Qaq&ZSO}`LGDJ|!Qc#EKrikY=UBJoNb2ZRx z?{Jq|B3RH&M7qYbIjl4#(@4lH`B1)!hkv|I+M?L-<}gpj+3x+g-576sSK~X83BBri zt`1J6ZNS+vpwBYvL}Pf6O4$QWF4oD)AQU-xa%Tjhs$$=V-RO0E^T1OlG_UC>igjeA zA*qHv%Cyh3#XJ+=t7tLOHCnPAw&5GZ1ECX0O{r+g3tJwYOs-sn!G1dVNz<~-#laq4 zai9_mG9!gzUw2%Lfn#ETi*=v@eR<2g)+S*+BAAv%8Wy#9X|>xQ&AKujxmPQ&IXG?W~iQlBkV6*3MSUpmF6s?UiInGjSd%n3avn{)b-l4Wai^HFA;0&S z8-LT-)&ygx=b{y&zR&Cw zCf*zK_kg7k4n;flN6eM#OT@4!-bb>jH3!EWnFzb>|4bLL2qbNxS*S}tYK#p72|3e&DJ6%V;Y~Hz6gbmdmxmyvT z*!aya^H&R^+f1wUR+|WvCDh)lT7ATKrf)K9WPk}uaqJ5leURbq>0Tr~vpB<71|1n& zz|%mYrX5rK@Z3bP8V?=Xd{ec~~?$p zpU2*0ppQX&XG<`I_cizAW1H3&TfX1l>K6MH({OF?{@vu8<^Eg5a~GhETE>pyVR^dSly4zT$b?aKx}^_Z==V$x^e?Md;w^YaV+ zOQ%tvtMp-8VKy&#SYCm4&wXsV|2}W!Cvj+<)%~wWb_A8TY#>9!(Fm|Z*zE=hm6y+s zBGWJiS}r5lset3P9=qW#vl4pHgLB3Re7pfuxN2(Q+omj%S*#Z@gl6Gu^$ZsY3R~K z{ z!|57wPTBPdNJ;|x+*1F&Q(qqmgM{shV!QT*BGBX$n>biiYez?VB!!kidA;LtfQtrxOCSSHk-fYh|{b1y+;?dW} z$kHrdj8$m&EFMb+59RFtZmwdppBW=p@nlXDjD0F3spFjSoXL#ym_~imlNpg;CM6l` z4ar8IoJf}HJB%ek8|PAko8TjRc25k9_|(~Gnn?NCy%nR*fFeODSENZ@#@;IW>TuI2 z#r@>EjR>NCh_0Sqh}Y_A%EAwN`LB-;@lI~v%E>>w^*?By8rV`DuD??7t8r+f4vQu_ z<}$Cd(Mtv*zh{kQt67>5{A^uZwP7f4uX|a)+>2f-4zE9oiu|~~S1&qu)Vvj~uecF6 zDSkpq^aVJ+qgg`df17oiU7K6*+-g{1VQW;$zYi8apr}bc#GuEs4yoJ}@b)*(%@{mJ-ffsW|1V2IjkT39z(UCT|sVsVn zuGLh%R`0@_jKA-ffkU0gYW$%-^AfM4s;F!?5}=IlcscY2!N4bXIIvz9E+lRpA69O* zA@D}ZEnmA?+ZE`9|4bm)S9HP@hdq{(ybn^fX>T4<;EBy9C$}+ulB|%g!KSVsl6#)5 zwIH?LHKEXbZnDY|)7!G)8Wrm;z0++s=Ds>j%JzPj)Ad>LU5Z#&jO*%;g^66hbz6K~ z`OfZoQTRUrB>Hb!|cm;>O4uW?8+UOHbvE&W4M(6+r1kT0AE& zu#)QC#D~)-MQbYfMSkrG@f=^{B^8P;@)fzY_~EN>#!3KxCaerQZ9}BMoe$Ts?ZG@bXjh zLdJ-!taD3aPc=wTWvg4o@vdc5C?5{?=dBy_40q&Oo&cEYN=Y~|&ZgVZvog&SPC6Ki z&StjGXnIM*sX_d=JD{F9s>-F0W-!M1sjmCetCgg>m6%_2xac2j4E}!_s{g-X;=_QN zU1d&A&IEwzZsKE;<#?M%x_ z5PX*{^B=9t(dX(82LKc4E928$B%a5lqNJo%?9#v5HC3Pgzi5cY%SGq&z^F=ey$L+l zcNW=#)UMv8BZwW8D&F>pFeQD=cSKnmz(L^E!3q8)dH*BPN2Tx=p{^2^mUl4vwuzE% zUhvi;(xFkW=%^(?HYLTS6Ii`>xUrl5o|3-wPJo!bc~N<*C4*L2jq_aHAs^f=N;l7_ z>N>Y55pniRS+aJkCRn!9cBerlWyd;~6)nSD>T%Y(y(z3R6}itu?>3B!s;keDIT`IS zQymZ*$-Trdmks}_6>V*YhS6xHi-g6by|^#b5mokey3%X#+-`zmJQ4u0Txx5 zu4LHrEZe!Cc#oMaVZ!Y(1OG~wt}`TZ`@kzgX|f3UM$02Sd{5M>&UlKzmR+vg40)C6 zo^R#lkcGX=>%EP$?~q>)JQ#g#)d%fh?)ElK>hb4$G4X4P)Me9baYEpLy~eZCWAM?a z_*Lim&6%0Qow8w7c-^9@mCE__my%lFJH+rhQ@#a5dvW$`?;hUIKc^61Hn-OV${!6| zCIB~X#KL0ied@LrH~*n8wUvU5_`xy$#XM(bR%b+HbVm3ZTXxMbVXK=z2$mpoTqkMq zUA?)?#kBOE%*Ha)z-{dUw1mteO2LhNg+$umV1TQ7JLR|21?EbW@!l4a8?yv3XM?dU za#4K0e*KQE6x#pET`mz2i`jxloPzy#?C6?=%YUtSFs_54E2|cY8f}=1J;HTbgz+6D z^#;T0Az>KFS;K%BEgQI3H1h;Fr0pJoYmou2(w;4lSKWN40BRnPD~k`b6rwMk7xMO2)t*eU z!lkkD0fE&mSkY2n6@`f)ehu)~=k)Zk+8$eudaoQ}j)%nzYh5Gls<;{Te*RX1ejOM= zPuu|l+g0{`J$=R7Z1A6H5^u^`pC^OOJ+sePj zp=0Hpu-g>J;K{0W%#3N17<+Y*;r&^*o9qZ?`o2#ifyc4O(k#V^TWNGV?pq&^LEarb z$J^bnT7FI$czE#sLAxK!S4+J1Pd2F4l()xd$J2q7T}#tuy~`K6e@umI%G!^v3N1N3 zsLqq|Z=z6VebRiMBS+Hkoz8K|Lm=$(s`3j81eiN&+?G;Dk!BBjE7Oxm%a5o>Q+>Wy zE_@}<*inl=HZqc?ime)nTx%7oY7zT`ZlaI>3)M~nSyRFtwXZ$<=d%1#K^;`|{Pl ../../../docs_src/cookie_param_models/tutorial001_an_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9-12 16" +{!> ../../../docs_src/cookie_param_models/tutorial001_an_py39.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python hl_lines="10-13 17" +{!> ../../../docs_src/cookie_param_models/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.10+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="7-10 14" +{!> ../../../docs_src/cookie_param_models/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.8+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="9-12 16" +{!> ../../../docs_src/cookie_param_models/tutorial001.py!} +``` + +//// + +**FastAPI** will **extract** the data for **each field** from the **cookies** received in the request and give you the Pydantic model you defined. + +## Check the Docs + +You can see the defined cookies in the docs UI at `/docs`: + +
+ +
+ +/// info + +Have in mind that, as **browsers handle cookies** in special ways and behind the scenes, they **don't** easily allow **JavaScript** to touch them. + +If you go to the **API docs UI** at `/docs` you will be able to see the **documentation** for cookies for your *path operations*. + +But even if you **fill the data** and click "Execute", because the docs UI works with **JavaScript**, the cookies won't be sent, and you will see an **error** message as if you didn't write any values. + +/// + +## Forbid Extra Cookies + +In some special use cases (probably not very common), you might want to **restrict** the cookies that you want to receive. + +Your API now has the power to control its own cookie consent. 🤪🍪 + +You can use Pydantic's model configuration to `forbid` any `extra` fields: + +//// tab | Python 3.9+ + +```Python hl_lines="10" +{!> ../../../docs_src/cookie_param_models/tutorial002_an_py39.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python hl_lines="11" +{!> ../../../docs_src/cookie_param_models/tutorial002_an.py!} +``` + +//// + +//// tab | Python 3.8+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="10" +{!> ../../../docs_src/cookie_param_models/tutorial002.py!} +``` + +//// + +If a client tries to send some **extra cookies**, they will receive an **error** response. + +Poor cookie banners with all their effort to get your consent for the API to reject it. 🍪 + +For example, if the client tries to send a `santa_tracker` cookie with a value of `good-list-please`, the client will receive an **error** response telling them that the `santa_tracker` cookie is not allowed: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "santa_tracker"], + "msg": "Extra inputs are not permitted", + "input": "good-list-please", + } + ] +} +``` + +## Summary + +You can use **Pydantic models** to declare **cookies** in **FastAPI**. 😎 diff --git a/docs/en/docs/tutorial/header-param-models.md b/docs/en/docs/tutorial/header-param-models.md new file mode 100644 index 0000000000..8deb0a455d --- /dev/null +++ b/docs/en/docs/tutorial/header-param-models.md @@ -0,0 +1,184 @@ +# Header Parameter Models + +If you have a group of related **header parameters**, you can create a **Pydantic model** to declare them. + +This would allow you to **re-use the model** in **multiple places** and also to declare validations and metadata for all the parameters at once. 😎 + +/// note + +This is supported since FastAPI version `0.115.0`. 🤓 + +/// + +## Header Parameters with a Pydantic Model + +Declare the **header parameters** that you need in a **Pydantic model**, and then declare the parameter as `Header`: + +//// tab | Python 3.10+ + +```Python hl_lines="9-14 18" +{!> ../../../docs_src/header_param_models/tutorial001_an_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9-14 18" +{!> ../../../docs_src/header_param_models/tutorial001_an_py39.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python hl_lines="10-15 19" +{!> ../../../docs_src/header_param_models/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.10+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="7-12 16" +{!> ../../../docs_src/header_param_models/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="9-14 18" +{!> ../../../docs_src/header_param_models/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.8+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="7-12 16" +{!> ../../../docs_src/header_param_models/tutorial001_py310.py!} +``` + +//// + +**FastAPI** will **extract** the data for **each field** from the **headers** in the request and give you the Pydantic model you defined. + +## Check the Docs + +You can see the required headers in the docs UI at `/docs`: + +
+ +
+ +## Forbid Extra Headers + +In some special use cases (probably not very common), you might want to **restrict** the headers that you want to receive. + +You can use Pydantic's model configuration to `forbid` any `extra` fields: + +//// tab | Python 3.10+ + +```Python hl_lines="10" +{!> ../../../docs_src/header_param_models/tutorial002_an_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="10" +{!> ../../../docs_src/header_param_models/tutorial002_an_py39.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python hl_lines="11" +{!> ../../../docs_src/header_param_models/tutorial002_an.py!} +``` + +//// + +//// tab | Python 3.10+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="8" +{!> ../../../docs_src/header_param_models/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="10" +{!> ../../../docs_src/header_param_models/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.8+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="10" +{!> ../../../docs_src/header_param_models/tutorial002.py!} +``` + +//// + +If a client tries to send some **extra headers**, they will receive an **error** response. + +For example, if the client tries to send a `tool` header with a value of `plumbus`, they will receive an **error** response telling them that the header parameter `tool` is not allowed: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["header", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } + ] +} +``` + +## Summary + +You can use **Pydantic models** to declare **headers** in **FastAPI**. 😎 diff --git a/docs/en/docs/tutorial/query-param-models.md b/docs/en/docs/tutorial/query-param-models.md new file mode 100644 index 0000000000..02e36dc0f8 --- /dev/null +++ b/docs/en/docs/tutorial/query-param-models.md @@ -0,0 +1,196 @@ +# Query Parameter Models + +If you have a group of **query parameters** that are related, you can create a **Pydantic model** to declare them. + +This would allow you to **re-use the model** in **multiple places** and also to declare validations and metadata for all the parameters at once. 😎 + +/// note + +This is supported since FastAPI version `0.115.0`. 🤓 + +/// + +## Query Parameters with a Pydantic Model + +Declare the **query parameters** that you need in a **Pydantic model**, and then declare the parameter as `Query`: + +//// tab | Python 3.10+ + +```Python hl_lines="9-13 17" +{!> ../../../docs_src/query_param_models/tutorial001_an_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8-12 16" +{!> ../../../docs_src/query_param_models/tutorial001_an_py39.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python hl_lines="10-14 18" +{!> ../../../docs_src/query_param_models/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.10+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="9-13 17" +{!> ../../../docs_src/query_param_models/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="8-12 16" +{!> ../../../docs_src/query_param_models/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.8+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="9-13 17" +{!> ../../../docs_src/query_param_models/tutorial001_py310.py!} +``` + +//// + +**FastAPI** will **extract** the data for **each field** from the **query parameters** in the request and give you the Pydantic model you defined. + +## Check the Docs + +You can see the query parameters in the docs UI at `/docs`: + +
+ +
+ +## Forbid Extra Query Parameters + +In some special use cases (probably not very common), you might want to **restrict** the query parameters that you want to receive. + +You can use Pydantic's model configuration to `forbid` any `extra` fields: + +//// tab | Python 3.10+ + +```Python hl_lines="10" +{!> ../../../docs_src/query_param_models/tutorial002_an_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9" +{!> ../../../docs_src/query_param_models/tutorial002_an_py39.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python hl_lines="11" +{!> ../../../docs_src/query_param_models/tutorial002_an.py!} +``` + +//// + +//// tab | Python 3.10+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="10" +{!> ../../../docs_src/query_param_models/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="9" +{!> ../../../docs_src/query_param_models/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.8+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="11" +{!> ../../../docs_src/query_param_models/tutorial002.py!} +``` + +//// + +If a client tries to send some **extra** data in the **query parameters**, they will receive an **error** response. + +For example, if the client tries to send a `tool` query parameter with a value of `plumbus`, like: + +```http +https://example.com/items/?limit=10&tool=plumbus +``` + +They will receive an **error** response telling them that the query parameter `tool` is not allowed: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus" + } + ] +} +``` + +## Summary + +You can use **Pydantic models** to declare **query parameters** in **FastAPI**. 😎 + +/// tip + +Spoiler alert: you can also use Pydantic models to declare cookies and headers, but you will read about that later in the tutorial. 🤫 + +/// diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 7c810c2d7c..5161b891be 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -118,6 +118,7 @@ nav: - tutorial/body.md - tutorial/query-params-str-validations.md - tutorial/path-params-numeric-validations.md + - tutorial/query-param-models.md - tutorial/body-multiple-params.md - tutorial/body-fields.md - tutorial/body-nested-models.md @@ -125,6 +126,8 @@ nav: - tutorial/extra-data-types.md - tutorial/cookie-params.md - tutorial/header-params.md + - tutorial/cookie-param-models.md + - tutorial/header-param-models.md - tutorial/response-model.md - tutorial/extra-models.md - tutorial/response-status-code.md diff --git a/docs_src/cookie_param_models/tutorial001.py b/docs_src/cookie_param_models/tutorial001.py new file mode 100644 index 0000000000..cc65c43e1a --- /dev/null +++ b/docs_src/cookie_param_models/tutorial001.py @@ -0,0 +1,17 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/cookie_param_models/tutorial001_an.py b/docs_src/cookie_param_models/tutorial001_an.py new file mode 100644 index 0000000000..e5839ffd54 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial001_an.py @@ -0,0 +1,18 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class Cookies(BaseModel): + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial001_an_py310.py b/docs_src/cookie_param_models/tutorial001_an_py310.py new file mode 100644 index 0000000000..24cc889a92 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial001_an_py310.py @@ -0,0 +1,17 @@ +from typing import Annotated + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial001_an_py39.py b/docs_src/cookie_param_models/tutorial001_an_py39.py new file mode 100644 index 0000000000..3d90c2007b --- /dev/null +++ b/docs_src/cookie_param_models/tutorial001_an_py39.py @@ -0,0 +1,17 @@ +from typing import Annotated, Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial001_py310.py b/docs_src/cookie_param_models/tutorial001_py310.py new file mode 100644 index 0000000000..7cdee5a923 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial001_py310.py @@ -0,0 +1,15 @@ +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002.py b/docs_src/cookie_param_models/tutorial002.py new file mode 100644 index 0000000000..9679e890f6 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002.py @@ -0,0 +1,19 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + model_config = {"extra": "forbid"} + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_an.py b/docs_src/cookie_param_models/tutorial002_an.py new file mode 100644 index 0000000000..ce5644b7bd --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_an.py @@ -0,0 +1,20 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class Cookies(BaseModel): + model_config = {"extra": "forbid"} + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_an_py310.py b/docs_src/cookie_param_models/tutorial002_an_py310.py new file mode 100644 index 0000000000..7fa70fe927 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_an_py310.py @@ -0,0 +1,19 @@ +from typing import Annotated + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + model_config = {"extra": "forbid"} + + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_an_py39.py b/docs_src/cookie_param_models/tutorial002_an_py39.py new file mode 100644 index 0000000000..a906ce6a1c --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_an_py39.py @@ -0,0 +1,19 @@ +from typing import Annotated, Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + model_config = {"extra": "forbid"} + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1.py b/docs_src/cookie_param_models/tutorial002_pv1.py new file mode 100644 index 0000000000..13f78b850e --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_pv1.py @@ -0,0 +1,20 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + class Config: + extra = "forbid" + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an.py b/docs_src/cookie_param_models/tutorial002_pv1_an.py new file mode 100644 index 0000000000..ddfda9b6f5 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_pv1_an.py @@ -0,0 +1,21 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class Cookies(BaseModel): + class Config: + extra = "forbid" + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py b/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py new file mode 100644 index 0000000000..ac00360b60 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py @@ -0,0 +1,20 @@ +from typing import Annotated + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + class Config: + extra = "forbid" + + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py b/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py new file mode 100644 index 0000000000..573caea4b1 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py @@ -0,0 +1,20 @@ +from typing import Annotated, Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + class Config: + extra = "forbid" + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_py310.py b/docs_src/cookie_param_models/tutorial002_pv1_py310.py new file mode 100644 index 0000000000..2c59aad123 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_pv1_py310.py @@ -0,0 +1,18 @@ +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + class Config: + extra = "forbid" + + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_py310.py b/docs_src/cookie_param_models/tutorial002_py310.py new file mode 100644 index 0000000000..f011aa1af4 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_py310.py @@ -0,0 +1,17 @@ +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + model_config = {"extra": "forbid"} + + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/header_param_models/tutorial001.py b/docs_src/header_param_models/tutorial001.py new file mode 100644 index 0000000000..4caaba87b9 --- /dev/null +++ b/docs_src/header_param_models/tutorial001.py @@ -0,0 +1,19 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial001_an.py b/docs_src/header_param_models/tutorial001_an.py new file mode 100644 index 0000000000..b55c6b56b1 --- /dev/null +++ b/docs_src/header_param_models/tutorial001_an.py @@ -0,0 +1,20 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial001_an_py310.py b/docs_src/header_param_models/tutorial001_an_py310.py new file mode 100644 index 0000000000..acfb6b9bf2 --- /dev/null +++ b/docs_src/header_param_models/tutorial001_an_py310.py @@ -0,0 +1,19 @@ +from typing import Annotated + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial001_an_py39.py b/docs_src/header_param_models/tutorial001_an_py39.py new file mode 100644 index 0000000000..51a5f94fc8 --- /dev/null +++ b/docs_src/header_param_models/tutorial001_an_py39.py @@ -0,0 +1,19 @@ +from typing import Annotated, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial001_py310.py b/docs_src/header_param_models/tutorial001_py310.py new file mode 100644 index 0000000000..7239c64cea --- /dev/null +++ b/docs_src/header_param_models/tutorial001_py310.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial001_py39.py b/docs_src/header_param_models/tutorial001_py39.py new file mode 100644 index 0000000000..4c1137813a --- /dev/null +++ b/docs_src/header_param_models/tutorial001_py39.py @@ -0,0 +1,19 @@ +from typing import Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002.py b/docs_src/header_param_models/tutorial002.py new file mode 100644 index 0000000000..3f9aac58d2 --- /dev/null +++ b/docs_src/header_param_models/tutorial002.py @@ -0,0 +1,21 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002_an.py b/docs_src/header_param_models/tutorial002_an.py new file mode 100644 index 0000000000..771135d770 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_an.py @@ -0,0 +1,22 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_an_py310.py b/docs_src/header_param_models/tutorial002_an_py310.py new file mode 100644 index 0000000000..e9535f045f --- /dev/null +++ b/docs_src/header_param_models/tutorial002_an_py310.py @@ -0,0 +1,21 @@ +from typing import Annotated + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_an_py39.py b/docs_src/header_param_models/tutorial002_an_py39.py new file mode 100644 index 0000000000..ca5208c9d0 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_an_py39.py @@ -0,0 +1,21 @@ +from typing import Annotated, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1.py b/docs_src/header_param_models/tutorial002_pv1.py new file mode 100644 index 0000000000..7e56cd993b --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1.py @@ -0,0 +1,22 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_an.py b/docs_src/header_param_models/tutorial002_pv1_an.py new file mode 100644 index 0000000000..236778231a --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1_an.py @@ -0,0 +1,23 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_an_py310.py b/docs_src/header_param_models/tutorial002_pv1_an_py310.py new file mode 100644 index 0000000000..e99e24ea55 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1_an_py310.py @@ -0,0 +1,22 @@ +from typing import Annotated + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_an_py39.py b/docs_src/header_param_models/tutorial002_pv1_an_py39.py new file mode 100644 index 0000000000..18398b726c --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1_an_py39.py @@ -0,0 +1,22 @@ +from typing import Annotated, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_py310.py b/docs_src/header_param_models/tutorial002_pv1_py310.py new file mode 100644 index 0000000000..3dbff9d7bf --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1_py310.py @@ -0,0 +1,20 @@ +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_py39.py b/docs_src/header_param_models/tutorial002_pv1_py39.py new file mode 100644 index 0000000000..86e19be0d1 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1_py39.py @@ -0,0 +1,22 @@ +from typing import Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002_py310.py b/docs_src/header_param_models/tutorial002_py310.py new file mode 100644 index 0000000000..3d22963454 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_py310.py @@ -0,0 +1,19 @@ +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002_py39.py b/docs_src/header_param_models/tutorial002_py39.py new file mode 100644 index 0000000000..f8ce559a74 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_py39.py @@ -0,0 +1,21 @@ +from typing import Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/query_param_models/tutorial001.py b/docs_src/query_param_models/tutorial001.py new file mode 100644 index 0000000000..0c0ab315e8 --- /dev/null +++ b/docs_src/query_param_models/tutorial001.py @@ -0,0 +1,19 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial001_an.py b/docs_src/query_param_models/tutorial001_an.py new file mode 100644 index 0000000000..28375057c1 --- /dev/null +++ b/docs_src/query_param_models/tutorial001_an.py @@ -0,0 +1,19 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial001_an_py310.py b/docs_src/query_param_models/tutorial001_an_py310.py new file mode 100644 index 0000000000..71427acae1 --- /dev/null +++ b/docs_src/query_param_models/tutorial001_an_py310.py @@ -0,0 +1,18 @@ +from typing import Annotated, Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial001_an_py39.py b/docs_src/query_param_models/tutorial001_an_py39.py new file mode 100644 index 0000000000..ba690d3e3f --- /dev/null +++ b/docs_src/query_param_models/tutorial001_an_py39.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial001_py310.py b/docs_src/query_param_models/tutorial001_py310.py new file mode 100644 index 0000000000..3ebf9f4d70 --- /dev/null +++ b/docs_src/query_param_models/tutorial001_py310.py @@ -0,0 +1,18 @@ +from typing import Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial001_py39.py b/docs_src/query_param_models/tutorial001_py39.py new file mode 100644 index 0000000000..54b52a054c --- /dev/null +++ b/docs_src/query_param_models/tutorial001_py39.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002.py b/docs_src/query_param_models/tutorial002.py new file mode 100644 index 0000000000..1633bc4644 --- /dev/null +++ b/docs_src/query_param_models/tutorial002.py @@ -0,0 +1,21 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_an.py b/docs_src/query_param_models/tutorial002_an.py new file mode 100644 index 0000000000..69705d4b4b --- /dev/null +++ b/docs_src/query_param_models/tutorial002_an.py @@ -0,0 +1,21 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_an_py310.py b/docs_src/query_param_models/tutorial002_an_py310.py new file mode 100644 index 0000000000..9759565023 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_an_py310.py @@ -0,0 +1,20 @@ +from typing import Annotated, Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_an_py39.py b/docs_src/query_param_models/tutorial002_an_py39.py new file mode 100644 index 0000000000..2d4c1a62b5 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_an_py39.py @@ -0,0 +1,19 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1.py b/docs_src/query_param_models/tutorial002_pv1.py new file mode 100644 index 0000000000..71ccd961d3 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1.py @@ -0,0 +1,22 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_an.py b/docs_src/query_param_models/tutorial002_pv1_an.py new file mode 100644 index 0000000000..1dd29157a4 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1_an.py @@ -0,0 +1,22 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_an_py310.py b/docs_src/query_param_models/tutorial002_pv1_an_py310.py new file mode 100644 index 0000000000..d635aae88f --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1_an_py310.py @@ -0,0 +1,21 @@ +from typing import Annotated, Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_an_py39.py b/docs_src/query_param_models/tutorial002_pv1_an_py39.py new file mode 100644 index 0000000000..494fef11fc --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1_an_py39.py @@ -0,0 +1,20 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_py310.py b/docs_src/query_param_models/tutorial002_pv1_py310.py new file mode 100644 index 0000000000..9ffdeefc06 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1_py310.py @@ -0,0 +1,21 @@ +from typing import Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_py39.py b/docs_src/query_param_models/tutorial002_pv1_py39.py new file mode 100644 index 0000000000..7fa456a791 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1_py39.py @@ -0,0 +1,20 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_py310.py b/docs_src/query_param_models/tutorial002_py310.py new file mode 100644 index 0000000000..6ec4184991 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_py310.py @@ -0,0 +1,20 @@ +from typing import Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_py39.py b/docs_src/query_param_models/tutorial002_py39.py new file mode 100644 index 0000000000..f9bba028c2 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_py39.py @@ -0,0 +1,19 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 7548cf0c79..5cebbf00fb 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -201,14 +201,23 @@ def get_flat_dependant( return flat_dependant +def _get_flat_fields_from_params(fields: List[ModelField]) -> List[ModelField]: + if not fields: + return fields + first_field = fields[0] + if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel): + fields_to_extract = get_cached_model_fields(first_field.type_) + return fields_to_extract + return fields + + def get_flat_params(dependant: Dependant) -> List[ModelField]: flat_dependant = get_flat_dependant(dependant, skip_repeats=True) - return ( - flat_dependant.path_params - + flat_dependant.query_params - + flat_dependant.header_params - + flat_dependant.cookie_params - ) + path_params = _get_flat_fields_from_params(flat_dependant.path_params) + query_params = _get_flat_fields_from_params(flat_dependant.query_params) + header_params = _get_flat_fields_from_params(flat_dependant.header_params) + cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params) + return path_params + query_params + header_params + cookie_params def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: @@ -479,7 +488,15 @@ def analyze_param( field=field ), "Path params must be of one of the supported types" elif isinstance(field_info, params.Query): - assert is_scalar_field(field) or is_scalar_sequence_field(field) + assert ( + is_scalar_field(field) + or is_scalar_sequence_field(field) + or ( + lenient_issubclass(field.type_, BaseModel) + # For Pydantic v1 + and getattr(field, "shape", 1) == 1 + ) + ) return ParamDetails(type_annotation=type_annotation, depends=depends, field=field) @@ -686,11 +703,14 @@ def _validate_value_with_model_field( return v_, [] -def _get_multidict_value(field: ModelField, values: Mapping[str, Any]) -> Any: +def _get_multidict_value( + field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None +) -> Any: + alias = alias or field.alias if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)): - value = values.getlist(field.alias) + value = values.getlist(alias) else: - value = values.get(field.alias, None) + value = values.get(alias, None) if ( value is None or ( @@ -712,7 +732,55 @@ def request_params_to_args( received_params: Union[Mapping[str, Any], QueryParams, Headers], ) -> Tuple[Dict[str, Any], List[Any]]: values: Dict[str, Any] = {} - errors = [] + errors: List[Dict[str, Any]] = [] + + if not fields: + return values, errors + + first_field = fields[0] + fields_to_extract = fields + single_not_embedded_field = False + if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel): + fields_to_extract = get_cached_model_fields(first_field.type_) + single_not_embedded_field = True + + params_to_process: Dict[str, Any] = {} + + processed_keys = set() + + for field in fields_to_extract: + alias = None + if isinstance(received_params, Headers): + # Handle fields extracted from a Pydantic Model for a header, each field + # doesn't have a FieldInfo of type Header with the default convert_underscores=True + convert_underscores = getattr(field.field_info, "convert_underscores", True) + if convert_underscores: + alias = ( + field.alias + if field.alias != field.name + else field.name.replace("_", "-") + ) + value = _get_multidict_value(field, received_params, alias=alias) + if value is not None: + params_to_process[field.name] = value + processed_keys.add(alias or field.alias) + processed_keys.add(field.name) + + for key, value in received_params.items(): + if key not in processed_keys: + params_to_process[key] = value + + if single_not_embedded_field: + field_info = first_field.field_info + assert isinstance( + field_info, params.Param + ), "Params must be subclasses of Param" + loc: Tuple[str, ...] = (field_info.in_.value,) + v_, errors_ = _validate_value_with_model_field( + field=first_field, value=params_to_process, values=values, loc=loc + ) + return {first_field.name: v_}, errors_ + for field in fields: value = _get_multidict_value(field, received_params) field_info = field.field_info diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 79ad9f83f2..947eca948e 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -16,11 +16,15 @@ from fastapi._compat import ( ) from fastapi.datastructures import DefaultPlaceholder from fastapi.dependencies.models import Dependant -from fastapi.dependencies.utils import get_flat_dependant, get_flat_params +from fastapi.dependencies.utils import ( + _get_flat_fields_from_params, + get_flat_dependant, + get_flat_params, +) from fastapi.encoders import jsonable_encoder from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX, REF_TEMPLATE from fastapi.openapi.models import OpenAPI -from fastapi.params import Body, Param +from fastapi.params import Body, ParamTypes from fastapi.responses import Response from fastapi.types import ModelNameMap from fastapi.utils import ( @@ -87,9 +91,9 @@ def get_openapi_security_definitions( return security_definitions, operation_security -def get_openapi_operation_parameters( +def _get_openapi_operation_parameters( *, - all_route_params: Sequence[ModelField], + dependant: Dependant, schema_generator: GenerateJsonSchema, model_name_map: ModelNameMap, field_mapping: Dict[ @@ -98,33 +102,47 @@ def get_openapi_operation_parameters( separate_input_output_schemas: bool = True, ) -> List[Dict[str, Any]]: parameters = [] - for param in all_route_params: - field_info = param.field_info - field_info = cast(Param, field_info) - if not field_info.include_in_schema: - continue - param_schema = get_schema_from_model_field( - field=param, - schema_generator=schema_generator, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - parameter = { - "name": param.alias, - "in": field_info.in_.value, - "required": param.required, - "schema": param_schema, - } - if field_info.description: - parameter["description"] = field_info.description - if field_info.openapi_examples: - parameter["examples"] = jsonable_encoder(field_info.openapi_examples) - elif field_info.example != Undefined: - parameter["example"] = jsonable_encoder(field_info.example) - if field_info.deprecated: - parameter["deprecated"] = True - parameters.append(parameter) + flat_dependant = get_flat_dependant(dependant, skip_repeats=True) + path_params = _get_flat_fields_from_params(flat_dependant.path_params) + query_params = _get_flat_fields_from_params(flat_dependant.query_params) + header_params = _get_flat_fields_from_params(flat_dependant.header_params) + cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params) + parameter_groups = [ + (ParamTypes.path, path_params), + (ParamTypes.query, query_params), + (ParamTypes.header, header_params), + (ParamTypes.cookie, cookie_params), + ] + for param_type, param_group in parameter_groups: + for param in param_group: + field_info = param.field_info + # field_info = cast(Param, field_info) + if not getattr(field_info, "include_in_schema", True): + continue + param_schema = get_schema_from_model_field( + field=param, + schema_generator=schema_generator, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + parameter = { + "name": param.alias, + "in": param_type.value, + "required": param.required, + "schema": param_schema, + } + if field_info.description: + parameter["description"] = field_info.description + openapi_examples = getattr(field_info, "openapi_examples", None) + example = getattr(field_info, "example", None) + if openapi_examples: + parameter["examples"] = jsonable_encoder(openapi_examples) + elif example != Undefined: + parameter["example"] = jsonable_encoder(example) + if getattr(field_info, "deprecated", None): + parameter["deprecated"] = True + parameters.append(parameter) return parameters @@ -247,9 +265,8 @@ def get_openapi_path( operation.setdefault("security", []).extend(operation_security) if security_definitions: security_schemes.update(security_definitions) - all_route_params = get_flat_params(route.dependant) - operation_parameters = get_openapi_operation_parameters( - all_route_params=all_route_params, + operation_parameters = _get_openapi_operation_parameters( + dependant=route.dependant, schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, @@ -379,6 +396,7 @@ def get_openapi_path( deep_dict_update(openapi_response, process_response) openapi_response["description"] = description http422 = str(HTTP_422_UNPROCESSABLE_ENTITY) + all_route_params = get_flat_params(route.dependant) if (all_route_params or route.body_field) and not any( status in operation["responses"] for status in [http422, "4XX", "default"] diff --git a/scripts/playwright/cookie_param_models/image01.py b/scripts/playwright/cookie_param_models/image01.py new file mode 100644 index 0000000000..77c91bfe22 --- /dev/null +++ b/scripts/playwright/cookie_param_models/image01.py @@ -0,0 +1,39 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + browser = playwright.chromium.launch(headless=False) + context = browser.new_context() + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_role("link", name="/items/").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/cookie-param-models/image01.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/cookie_param_models/tutorial001.py"] +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/header_param_models/image01.py b/scripts/playwright/header_param_models/image01.py new file mode 100644 index 0000000000..53914251ed --- /dev/null +++ b/scripts/playwright/header_param_models/image01.py @@ -0,0 +1,38 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_role("button", name="GET /items/ Read Items").click() + page.get_by_role("button", name="Try it out").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/header-param-models/image01.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/header_param_models/tutorial001.py"] +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/query_param_models/image01.py b/scripts/playwright/query_param_models/image01.py new file mode 100644 index 0000000000..0ea1d0df4e --- /dev/null +++ b/scripts/playwright/query_param_models/image01.py @@ -0,0 +1,41 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + browser = playwright.chromium.launch(headless=False) + context = browser.new_context() + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_role("button", name="GET /items/ Read Items").click() + page.get_by_role("button", name="Try it out").click() + page.get_by_role("heading", name="Servers").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/query-param-models/image01.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/query_param_models/tutorial001.py"] +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/tests/test_tutorial/test_cookie_param_models/__init__.py b/tests/test_tutorial/test_cookie_param_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py new file mode 100644 index 0000000000..60643185a4 --- /dev/null +++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py @@ -0,0 +1,205 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310 + + +@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.cookie_param_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_cookie_param_model(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + c.cookies.set("fatebook_tracker", "456") + c.cookies.set("googall_tracker", "789") + response = c.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "session_id": "123", + "fatebook_tracker": "456", + "googall_tracker": "789", + } + + +def test_cookie_param_model_defaults(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + response = c.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "session_id": "123", + "fatebook_tracker": None, + "googall_tracker": None, + } + + +def test_cookie_param_model_invalid(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422 + assert response.json() == snapshot( + IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "session_id"], + "msg": "Field required", + "input": {}, + } + ] + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "type": "value_error.missing", + "loc": ["cookie", "session_id"], + "msg": "field required", + } + ] + } + ) + ) + + +def test_cookie_param_model_extra(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + c.cookies.set("extra", "track-me-here-too") + response = c.get("/items/") + assert response.status_code == 200 + assert response.json() == snapshot( + {"session_id": "123", "fatebook_tracker": None, "googall_tracker": None} + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "session_id", + "in": "cookie", + "required": True, + "schema": {"type": "string", "title": "Session Id"}, + }, + { + "name": "fatebook_tracker", + "in": "cookie", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Fatebook Tracker", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Fatebook Tracker", + } + ), + }, + { + "name": "googall_tracker", + "in": "cookie", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Googall Tracker", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Googall Tracker", + } + ), + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py new file mode 100644 index 0000000000..30adadc8a3 --- /dev/null +++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py @@ -0,0 +1,233 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002", marks=needs_pydanticv2), + pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_an", marks=needs_pydanticv2), + pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), + pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.cookie_param_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_cookie_param_model(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + c.cookies.set("fatebook_tracker", "456") + c.cookies.set("googall_tracker", "789") + response = c.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "session_id": "123", + "fatebook_tracker": "456", + "googall_tracker": "789", + } + + +def test_cookie_param_model_defaults(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + response = c.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "session_id": "123", + "fatebook_tracker": None, + "googall_tracker": None, + } + + +def test_cookie_param_model_invalid(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422 + assert response.json() == snapshot( + IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "session_id"], + "msg": "Field required", + "input": {}, + } + ] + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "type": "value_error.missing", + "loc": ["cookie", "session_id"], + "msg": "field required", + } + ] + } + ) + ) + + +def test_cookie_param_model_extra(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + c.cookies.set("extra", "track-me-here-too") + response = c.get("/items/") + assert response.status_code == 422 + assert response.json() == snapshot( + IsDict( + { + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "extra"], + "msg": "Extra inputs are not permitted", + "input": "track-me-here-too", + } + ] + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "type": "value_error.extra", + "loc": ["cookie", "extra"], + "msg": "extra fields not permitted", + } + ] + } + ) + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "session_id", + "in": "cookie", + "required": True, + "schema": {"type": "string", "title": "Session Id"}, + }, + { + "name": "fatebook_tracker", + "in": "cookie", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Fatebook Tracker", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Fatebook Tracker", + } + ), + }, + { + "name": "googall_tracker", + "in": "cookie", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Googall Tracker", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Googall Tracker", + } + ), + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_header_param_models/__init__.py b/tests/test_tutorial/test_header_param_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial001.py b/tests/test_tutorial/test_header_param_models/test_tutorial001.py new file mode 100644 index 0000000000..06b2404cf0 --- /dev/null +++ b/tests/test_tutorial/test_header_param_models/test_tutorial001.py @@ -0,0 +1,238 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py39", marks=needs_py39), + 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.header_param_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_header_param_model(client: TestClient): + response = client.get( + "/items/", + headers=[ + ("save-data", "true"), + ("if-modified-since", "yesterday"), + ("traceparent", "123"), + ("x-tag", "one"), + ("x-tag", "two"), + ], + ) + assert response.status_code == 200 + assert response.json() == { + "host": "testserver", + "save_data": True, + "if_modified_since": "yesterday", + "traceparent": "123", + "x_tag": ["one", "two"], + } + + +def test_header_param_model_defaults(client: TestClient): + response = client.get("/items/", headers=[("save-data", "true")]) + assert response.status_code == 200 + assert response.json() == { + "host": "testserver", + "save_data": True, + "if_modified_since": None, + "traceparent": None, + "x_tag": [], + } + + +def test_header_param_model_invalid(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + IsDict( + { + "type": "missing", + "loc": ["header", "save_data"], + "msg": "Field required", + "input": { + "x_tag": [], + "host": "testserver", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "user-agent": "testclient", + }, + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "value_error.missing", + "loc": ["header", "save_data"], + "msg": "field required", + } + ) + ] + } + ) + + +def test_header_param_model_extra(client: TestClient): + response = client.get( + "/items/", headers=[("save-data", "true"), ("tool", "plumbus")] + ) + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "host": "testserver", + "save_data": True, + "if_modified_since": None, + "traceparent": None, + "x_tag": [], + } + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "host", + "in": "header", + "required": True, + "schema": {"type": "string", "title": "Host"}, + }, + { + "name": "save_data", + "in": "header", + "required": True, + "schema": {"type": "boolean", "title": "Save Data"}, + }, + { + "name": "if_modified_since", + "in": "header", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "If Modified Since", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "If Modified Since", + } + ), + }, + { + "name": "traceparent", + "in": "header", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Traceparent", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Traceparent", + } + ), + }, + { + "name": "x_tag", + "in": "header", + "required": False, + "schema": { + "type": "array", + "items": {"type": "string"}, + "default": [], + "title": "X Tag", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial002.py b/tests/test_tutorial/test_header_param_models/test_tutorial002.py new file mode 100644 index 0000000000..e07655a0c0 --- /dev/null +++ b/tests/test_tutorial/test_header_param_models/test_tutorial002.py @@ -0,0 +1,249 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002", marks=needs_pydanticv2), + pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_an", marks=needs_pydanticv2), + pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), + pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.header_param_models.{request.param}") + + client = TestClient(mod.app) + client.headers.clear() + return client + + +def test_header_param_model(client: TestClient): + response = client.get( + "/items/", + headers=[ + ("save-data", "true"), + ("if-modified-since", "yesterday"), + ("traceparent", "123"), + ("x-tag", "one"), + ("x-tag", "two"), + ], + ) + assert response.status_code == 200, response.text + assert response.json() == { + "host": "testserver", + "save_data": True, + "if_modified_since": "yesterday", + "traceparent": "123", + "x_tag": ["one", "two"], + } + + +def test_header_param_model_defaults(client: TestClient): + response = client.get("/items/", headers=[("save-data", "true")]) + assert response.status_code == 200 + assert response.json() == { + "host": "testserver", + "save_data": True, + "if_modified_since": None, + "traceparent": None, + "x_tag": [], + } + + +def test_header_param_model_invalid(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + IsDict( + { + "type": "missing", + "loc": ["header", "save_data"], + "msg": "Field required", + "input": {"x_tag": [], "host": "testserver"}, + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "value_error.missing", + "loc": ["header", "save_data"], + "msg": "field required", + } + ) + ] + } + ) + + +def test_header_param_model_extra(client: TestClient): + response = client.get( + "/items/", headers=[("save-data", "true"), ("tool", "plumbus")] + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + IsDict( + { + "type": "extra_forbidden", + "loc": ["header", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "value_error.extra", + "loc": ["header", "tool"], + "msg": "extra fields not permitted", + } + ) + ] + } + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "host", + "in": "header", + "required": True, + "schema": {"type": "string", "title": "Host"}, + }, + { + "name": "save_data", + "in": "header", + "required": True, + "schema": {"type": "boolean", "title": "Save Data"}, + }, + { + "name": "if_modified_since", + "in": "header", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "If Modified Since", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "If Modified Since", + } + ), + }, + { + "name": "traceparent", + "in": "header", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Traceparent", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Traceparent", + } + ), + }, + { + "name": "x_tag", + "in": "header", + "required": False, + "schema": { + "type": "array", + "items": {"type": "string"}, + "default": [], + "title": "X Tag", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_param_models/__init__.py b/tests/test_tutorial/test_query_param_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_query_param_models/test_tutorial001.py b/tests/test_tutorial/test_query_param_models/test_tutorial001.py new file mode 100644 index 0000000000..5b7bc7b424 --- /dev/null +++ b/tests/test_tutorial/test_query_param_models/test_tutorial001.py @@ -0,0 +1,260 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py39", marks=needs_py39), + 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.query_param_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_query_param_model(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + }, + ) + assert response.status_code == 200 + assert response.json() == { + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + } + + +def test_query_param_model_defaults(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "limit": 100, + "offset": 0, + "order_by": "created_at", + "tags": [], + } + + +def test_query_param_model_invalid(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 150, + "offset": -1, + "order_by": "invalid", + }, + ) + assert response.status_code == 422 + assert response.json() == snapshot( + IsDict( + { + "detail": [ + { + "type": "less_than_equal", + "loc": ["query", "limit"], + "msg": "Input should be less than or equal to 100", + "input": "150", + "ctx": {"le": 100}, + }, + { + "type": "greater_than_equal", + "loc": ["query", "offset"], + "msg": "Input should be greater than or equal to 0", + "input": "-1", + "ctx": {"ge": 0}, + }, + { + "type": "literal_error", + "loc": ["query", "order_by"], + "msg": "Input should be 'created_at' or 'updated_at'", + "input": "invalid", + "ctx": {"expected": "'created_at' or 'updated_at'"}, + }, + ] + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "type": "value_error.number.not_le", + "loc": ["query", "limit"], + "msg": "ensure this value is less than or equal to 100", + "ctx": {"limit_value": 100}, + }, + { + "type": "value_error.number.not_ge", + "loc": ["query", "offset"], + "msg": "ensure this value is greater than or equal to 0", + "ctx": {"limit_value": 0}, + }, + { + "type": "value_error.const", + "loc": ["query", "order_by"], + "msg": "unexpected value; permitted: 'created_at', 'updated_at'", + "ctx": { + "given": "invalid", + "permitted": ["created_at", "updated_at"], + }, + }, + ] + } + ) + ) + + +def test_query_param_model_extra(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + "tool": "plumbus", + }, + ) + assert response.status_code == 200 + assert response.json() == { + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + } + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "limit", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "maximum": 100, + "exclusiveMinimum": 0, + "default": 100, + "title": "Limit", + }, + }, + { + "name": "offset", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "minimum": 0, + "default": 0, + "title": "Offset", + }, + }, + { + "name": "order_by", + "in": "query", + "required": False, + "schema": { + "enum": ["created_at", "updated_at"], + "type": "string", + "default": "created_at", + "title": "Order By", + }, + }, + { + "name": "tags", + "in": "query", + "required": False, + "schema": { + "type": "array", + "items": {"type": "string"}, + "default": [], + "title": "Tags", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_param_models/test_tutorial002.py b/tests/test_tutorial/test_query_param_models/test_tutorial002.py new file mode 100644 index 0000000000..4432c9d8a3 --- /dev/null +++ b/tests/test_tutorial/test_query_param_models/test_tutorial002.py @@ -0,0 +1,282 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002", marks=needs_pydanticv2), + pytest.param("tutorial002_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_an", marks=needs_pydanticv2), + pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), + pytest.param("tutorial002_pv1_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.query_param_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_query_param_model(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + }, + ) + assert response.status_code == 200 + assert response.json() == { + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + } + + +def test_query_param_model_defaults(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "limit": 100, + "offset": 0, + "order_by": "created_at", + "tags": [], + } + + +def test_query_param_model_invalid(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 150, + "offset": -1, + "order_by": "invalid", + }, + ) + assert response.status_code == 422 + assert response.json() == snapshot( + IsDict( + { + "detail": [ + { + "type": "less_than_equal", + "loc": ["query", "limit"], + "msg": "Input should be less than or equal to 100", + "input": "150", + "ctx": {"le": 100}, + }, + { + "type": "greater_than_equal", + "loc": ["query", "offset"], + "msg": "Input should be greater than or equal to 0", + "input": "-1", + "ctx": {"ge": 0}, + }, + { + "type": "literal_error", + "loc": ["query", "order_by"], + "msg": "Input should be 'created_at' or 'updated_at'", + "input": "invalid", + "ctx": {"expected": "'created_at' or 'updated_at'"}, + }, + ] + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "type": "value_error.number.not_le", + "loc": ["query", "limit"], + "msg": "ensure this value is less than or equal to 100", + "ctx": {"limit_value": 100}, + }, + { + "type": "value_error.number.not_ge", + "loc": ["query", "offset"], + "msg": "ensure this value is greater than or equal to 0", + "ctx": {"limit_value": 0}, + }, + { + "type": "value_error.const", + "loc": ["query", "order_by"], + "msg": "unexpected value; permitted: 'created_at', 'updated_at'", + "ctx": { + "given": "invalid", + "permitted": ["created_at", "updated_at"], + }, + }, + ] + } + ) + ) + + +def test_query_param_model_extra(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + "tool": "plumbus", + }, + ) + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + IsDict( + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "value_error.extra", + "loc": ["query", "tool"], + "msg": "extra fields not permitted", + } + ) + ] + } + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "limit", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "maximum": 100, + "exclusiveMinimum": 0, + "default": 100, + "title": "Limit", + }, + }, + { + "name": "offset", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "minimum": 0, + "default": 0, + "title": "Offset", + }, + }, + { + "name": "order_by", + "in": "query", + "required": False, + "schema": { + "enum": ["created_at", "updated_at"], + "type": "string", + "default": "created_at", + "title": "Order By", + }, + }, + { + "name": "tags", + "in": "query", + "required": False, + "schema": { + "type": "array", + "items": {"type": "string"}, + "default": [], + "title": "Tags", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) -- 2.47.3