From c8bbc4cdba9f3197a73925ec894c2c48bf0e8bcf Mon Sep 17 00:00:00 2001 From: Yohann Gabory Date: Tue, 15 Oct 2013 14:55:37 +0200 Subject: [PATCH] updated the docs, bugfixes --- docs/build/.doctrees/environment.pickle | Bin 63133 -> 65766 bytes docs/build/.doctrees/index.doctree | Bin 5874 -> 10943 bytes docs/build/.doctrees/introduction.doctree | Bin 47009 -> 46323 bytes docs/build/.doctrees/references.doctree | Bin 113318 -> 113323 bytes .../build/.doctrees/representing_data.doctree | Bin 9570 -> 10134 bytes docs/build/.doctrees/tutorial.doctree | Bin 3117 -> 3148 bytes .../.doctrees/using_user_endpoint.doctree | Bin 17768 -> 22984 bytes .../.doctrees/work_with_pagination.doctree | Bin 0 -> 21214 bytes docs/build/_sources/index.txt | 82 ++++- docs/build/_sources/introduction.txt | 4 +- docs/build/_sources/representing_data.txt | 11 +- docs/build/_sources/tutorial.txt | 1 + docs/build/_sources/using_user_endpoint.txt | 84 ++++- docs/build/_sources/work_with_pagination.txt | 215 +++++++++++++ docs/build/datastore.html | 10 +- docs/build/index.html | 107 ++++--- docs/build/introduction.html | 14 +- docs/build/representing_data.html | 10 +- docs/build/searchindex.js | 2 +- docs/build/tutorial.html | 12 +- docs/build/using_user_endpoint.html | 80 ++++- docs/build/work_with_pagination.html | 299 ++++++++++++++++++ docs/source/#related_ressources.rst# | 7 + docs/source/.#related_ressources.rst | 1 + docs/source/index.rst | 82 ++++- docs/source/introduction.rst | 4 +- docs/source/representing_data.rst | 11 +- docs/source/tutorial.rst | 1 + docs/source/using_user_endpoint.rst | 84 ++++- docs/source/work_with_pagination.rst | 215 +++++++++++++ rest_api_framework/controllers.py | 19 +- rest_api_framework/datastore/simple.py | 3 + rest_api_framework/datastore/sql.py | 43 ++- rest_api_framework/pagination.py | 26 ++ rest_api_framework/views.py | 23 +- setup.cfg | 3 +- tests/controllers_test.py | 21 +- tests/datastore_test.py | 2 - 38 files changed, 1335 insertions(+), 141 deletions(-) create mode 100644 docs/build/.doctrees/work_with_pagination.doctree create mode 100644 docs/build/_sources/work_with_pagination.txt create mode 100644 docs/build/work_with_pagination.html create mode 100644 docs/source/#related_ressources.rst# create mode 120000 docs/source/.#related_ressources.rst create mode 100644 docs/source/work_with_pagination.rst diff --git a/docs/build/.doctrees/environment.pickle b/docs/build/.doctrees/environment.pickle index 2459568bc96792086bfdb2c4b98e2f25eeddb790..e2f15c678861015937f82688673f95e8d7caa537 100644 GIT binary patch literal 65766 zcmdVDcbpr=-9K)c%NcAi9SrF10N)v(Y)mf(+;Ps>U|ZP1h(mWLdnN0n(@3&?z!FG; z4GE+JAw40z!;|s^(n;^6_uhN&<@RZYQ%8n|hQX^@styfxHHvsVEq}*``t;%4A%*er`iynC zBUDip3!_7Y%C5phqu%97hMBpk%~F$M4)^Ov^ldq8%9JUk>S%Fyeb&fmZG<8l*=lXH z*yxgcUG>=zccfoGsxLP~6?QdhEj3cC&&f^6brqUTRT^$J!R%TDput>AGR=CocVuh0kek*lY^SpIp4?%AO1-x=yrm23j2E_-s#@OZ9+5i3 zub&BtxhWnXndd^%=RndQSU=z3ah40xf_%#Z5Ch)aEyvJiEu@$!WK^+XJdlNX3;p^c znwOgCY_Hop0~UBzaj1PXIJO$4>h_^lqo{_8)zR@<3AQd4{m=31=LY(p=jwmHqyGhg z{udhkFLL$2SnGcY^q)ulD}@Qj%j_yOy-d?9W~#+qm5IyzC1_ z{h_-YR-WTpS-%2y9uCQ{OVvhOBT^uiih;}g`trcQ6|R9R9RpVd2Cg;+Ug;XRMjQA9 z7}yOlo4rCaQ)*;v;as_KQ)cPaYr?`=AB1UZ{rXijtuu;sFlw1=lqqo|HQH3E6jgn_ z7<9E?za}tfL!Yi*8=-Ab88OX2PK5fsjW1iBSDIzrK~Kx2IhX zC9h6Ua$89?nnR6dalD=vC2#QSLxGYvx~LZ%<%V6AMgmPojSGre$!$=w7fP<8kjzF1 z$w;%xB38(-P=`foJCyhQdWp(+N>heWH?$cw3-Ssrl-+8xG@|SK4l%6a*T(|Gs;*%* z$FOnNu(~lv1qL>>VNDp8fuN-kL775zG{Z+VRk}%wZo#UZetj3M>WE-BOsce*bP`P( z9c4qXvrs9G7Me9RglnOJn__)J%)802-yE2Ci))_mn0Kpd-jf2eZZn47uFZS0@!L`n znhAt9GR<0M0{u#61*C5j=}&=$Pxb3}Sif~bvj;}r)Mn};!rWvNi9?-i5o^y_y8 zCOys6m#4#mpuXJgn08NK%`;phpQ%lH7EC%FA~qQj8TQG96i&+&(4nCN9In-Nur;VZ z8#X=1uRoVIbxia;7DONsSn_IP$!lCoUh7!$y1ujxRHY0*;P|KFaVx zr5LstZ-PN@_Umt&6?VO{%`T1&Fy6rktU7`r&_iO-!+!mL0)yVx=bfMTF35Wq z=DmyZp6N2*ZuHy`>q4YMaag_sdcM=I|F6|^a*B6B&xb`%kNx8es2EoFM?}L%{rbBD z4d0Wf;d`MWNm*BQOYuHv@_xVm0csLW`5+`d8cL+IRdF1ph|MV49g43%CXzqo*FPLc z{)ln*M_p%s%yIU|180B2b@nHB@a)8A_<`9Q4L(ZV@aZ&#Be*Fu9@?UgY$uBudf7w;_E2eGyYGBFNwDMnv^2bBm zW{6{1l|hpk0diRT4Os9^zy2*+u&;dIh8dran$gO2jn>8r8157I@A&oa0_R3^)USW9 z52N{Nty(Dz7b^|F{(aamRzpL^7lR+CRM05(4@h{t5t3HL|6SMZCS=9(Bw4bRp6(& z!>_`y4%K#zX!x@|x7UB}{nY!}9ra)A8Q$xOR^Cs%pXLtJ8GeZjze0vz-ckQGGyKT= z5k~0DFmt15o&mze`frf!x5)OJJLpZ5pfL&jF_;PDB+{wK!kf9~6ImK1x8hYI7Rp>6hkB}*qr1r2oh00;2J^Z&eHWwtb39V_=E7^_LtSvQ*fl1lcIMR#tzr!+Dr4CGss$}^5qfMtp z$vQ;`Y}x3rs{T(f;oGl)`P^h4`?d=uvi`6C)~L~9r3i5T-DcFn$rOqfn=EztPn#}% z$vnaaEE~2gYRW-VrbIE>Z~y35I(3ldGAS*MQ}W^^M9tKEoW>@FjcZe^HmJk3Wp1rnFgZ36KI(`VIw{>kXi*zF zx(QaZ;@EW1S~!l%?rLl=p-UWBv*UJ6US(1%xH>Xs;`x)Kv^<#b$c`0u6o*XLu8xZ9 zw!eVs=3UK+19m{QFKEAxjx(IR{Yr(+bkXXVxWSVJG*lfxX&LAoq*mYXmu9W(|&tWQubuv+}GI55dCTr$crn-eHHx;jM zcwgGHWnfa0EQ`_#Q-n~3d)fiPYmw&G@KrAo%aqaQJ%lBpDR(s+<`t-n)YB!wz7-vU z=~`c%5q&l?KJKeC-Dj=b5!)-Z;h|=2q^XL|S-etxxXVo~jp1Fbq5An4Yhh}B zkbV~KhWU~!l%pl37U0R2HL!VMj_>~qCu*4WsV-bm+_`Y`$_<0pt<0@nI4(;{11ur~ zh4Ior@QSt&j2a7NN^fD-Fm0#-u*@A}UJN;l0k}s?d=pezB;A#pF;db?Pz|*Z5B%UQ zQY}Jsa6aCV)d-b48__LS{8!59#UQm9oN`m4$hOjUbq*r--B~~CS}rMF>RcoNvGWl4 z>U>=LadE+ceZ@ zZQEAbtuEuUL$fG5xufv*U#f1akxmx+8tQVS=G7Ir_E{!NNpM`UZfY44_1aR}I$~yp zyNalmli&#W=!sHtGOG*2H8r7D;BlGEa+&qjVy05U1#6}wsw8s)ncB7tW(YW4kj))& zRjDyjtW*lsVy)FsD?!y%t8gu#sMN+MRB5}{RIBko*5(#3I{TajnW(rcDXFVC-a@tD z>aAMC+$TaUZD+!IsCoivjx6pTskBCmn6tv%iK;f#ARgy)Gldsh9=x-x#iO!V!~&W- zWKs5PbrtVsj1{V!?Luu^M>4e@*S@m2={Q|&SZh#ef;v+sV0m>luEMe~cu-SagIFOq zt69Wb8Rh{R)W%aABo<3MN#>GLF__i|~CSuDNNb0!&cXN#0q4QRtzqcv==u%+ku?yq@85Ex_yjTkG7NSEERW+V3}VPky6Y}!+JsywOegNv@Fio z=A+2rEgUobYCBU{N^*x`zd>tUdAMKK8pcdvu~}f}tV(#|t1_;A-Ynlc-S-yz-csLN z?|XUQ+u?f?zPH!+?)AL~eeWUPd&Ku1^S#G0fi}yurz2>dS=@GzQ|_oCtLBjDaa2V_ zj8R1UHUUm8W0XRxx>OZ8Wd1&(=Z|){s)nq0+^)uja~;>=Emd^?Jv}{LS7PlAvy5D` zjuNqUV`9J2#a!D|Z7j2^sCL{`Y;Dh?#ufF%U6O(Dq>$W3jQJh&2naFs8lUA zhlctZ#mcq?GGQ+Bk_$5P=bQH+qYhPKrf}{uvn0$Pa!x2>i^Zj_kR}P=1n$R|q zDgR{Qc1at54uAR_dQn>E&tN&K*-}*@44!NB1CXO448giuwHTrrfEBE-3RXeP=uAgb z@Foun{cVMTyIZh}fttv|ANHl}C{A$I!O6&^QDMP{WM9;a67tNlPjHCC1MS8>yb{d_ z*F+x#7?rkVj9&%bo<>6r;XK<*B_xvBYA;D~{|K zqHI%`%sY*vp@ohl%b7g`1Igx|^ATz}zuHRoD_GiNa807C8ykH;fZF3rAKWgxpy)v>(VDBhBmt5zjXc1%DJ=nKb#-#)y(AHr$ zZE)t^maiIuY8)+0ksh5aOZK&e3(cwt4jrIVX1>5{HRwU8No?oI^0 z+J)=zmY0D>dQw>rvwi0ZkjS!_?q&5Xj7`)dz{vc*$yTq}JZ(!+U0p-i5m^};LY>|k zFkfJ{Zbeuv$h6VsyL{gm*gJ`VS*@trt?ir;J8u#@KZ`vnliC>v&)S255GvfHV~*G; zBLS4z51Z(lFe2c%b>Qs0OA$b+vunFu2}?AKvodFgI0W3XTr{nY@?|m< zZHfm0>?w`*;%5fuy`t2a(J4MO&S!Aa{{KlgY(lB(5+yQcmCid>3Mbe3-Vqv zxHer2JCF%-&m268L-|`K!x&@w$t*(_?$S*%UaM%{3lcJT582T@->l*TRJ-)Wg%BUA zVt9Wm0$)7|*WoSq9UPldyE^!ICc_F<$Y7bmeA|$fi8VVz7G{jjdf6sq6Y3DxCOAgc zbF#9Lki*rGRKMO^l7unqSYbD}Q)884K~RxwqiXuaA-q{H>Cv5IPeLx~D0L>eQy>I+g8Q(Z2OT=)EkGk7Yrsw$ZJrG2VF;Mmsfg6M z4Xp`s;(HnluZ5u@&yLr|rA^6(>MqDE>JQswQE?L5zzjW@#~zH`4hQtXn#pCl@keTO zbY?|Zgaf>6J19t+(M$k|v7pOWw@J<7`><17ERI1noHKZ$X<>qL8@zM`nYsq&?3Fj@ zkW-}fIgvH_c9F+D;$bdZ@j~8Kfw1CrCs(|n`;a$h^<;2I<$DSOUp*Dq;Vq{_)?~BP z%L=0#v}=R8(G}>IkZ%w06uUHuEPZ0&I#y8|ifTW1VC>6^j2LW!e>SQBS>q20}L9nidY z%lrCR3xmgiB2ZXXTj>~#rwf^pN)f9s8GI$0)wA2xBtfG!xQMfqG9ebQV#8f6Zby4d z-MAYYnN+6MY7TReLq7^JAoQZw56FwX>E z1ehyOR&&M3hcB;oaYX=XW1sjQYZ|U9mJ9m=AhM-KrG&Q-W5WXBrmtt6?X;Iv$h^&N zniwypyv`aCEGOwA0&~c7(mzosi+3fJ)P?Sg+)~!Y6MEH72#e$Ur)tlW8PIMG26c4&o;hLu_8c*5EBgESt8y&u2>d%VN7zQekNf3egveXj3)9b#3 z)0&Q8qN``R!fw%7?4dL}Po;Z}aPP-Hr!d+x4MUqR0ZVZSTS;34>}xo+*yqH;wfb|_B1Q{i z!FxUiAAAuG`uf1*ZW;Q&_db2HPmk}=PB9Z#&Tv4eFu0FRY!b5syeyOqr5d+6d#4#Y zJ0e)Rdg#BN#HBo0M~swjU)_Q6VxR0RS^>*s@W(YzejaQ$=?7!om4jFBm9OZ+tg&}j z84ug90)?G396mJ8z_y%$kU4|rw+pfQ1bNOMT*+be9??A_45R8f7&M0pT3RG<`&2x^ zl)@OHRV*BhXC@nsW1Fhl3!N|+zY~G4?!t9=%lq*iqzuOGD=YVi1d4RNvSY?ckb)7y zIl-X4ZP6;S$Rm|vm;O=$9n8&W<}c3l;9A7yq4*2!HhfLS448+j#8at0b|YaNi7fmu zFP?>43vjpuW=gnGswb3%{WI9csU~EfL^M-SaZQ2u58y388nPw_iLi2%eNPXkTQLH+ zwFt{nRd(GPyey*_OX~G+lQ-;ZN05^_vLh{p30^2< z3jihcMpQz-!JWL9lhe-$B^7x>ih*11FOwgq#BT8ZzRT)2Z3 zTSISFN`Qujaqrju40N>$oLB8LV_q{Fjt+Em4Hc_;#Hyam<||ijSc%2S%wXNn%#u@w>zM9W}*yj2{{ef+JNQfN&LZs%gU!0f6u*`JIB9{Gc($)h_ZRqU) zzil-)G&F2$kQIA50hiO&xo`F?&6B*_?YmivVof!*8n$u6=(%r)gOoKyD`s<#xAg~M9+m_d+t^`ikQ9`1Y;C|aE2nU3Qq)<>4K95*FZTPB%X#Q2Tc)FB zUUw$i*#2lz&jK?9-P&_`k8}cZa5R0SQ*F{acgOVManh-EJ#6e>Fh6G82X72m* z{~t=`ju38(QM6F*3^N>9QM7C|ylimoyNSGxESZOrI4>xD=!JLcw+G&_z&K-qpj2C) z2NIg% z*t2)baS7Sm_PjiK@b2V%y7UD6DuYi>&eygtrf2ZXy<1L5kZXsh0mm~3FWtLkc08vu zVKFSmC!#kb3Fg#*~EnT+!Dtx2Z#)Suv*c{>a?pKvY@yQ={FHbIXUHg}O&LIrR zJ0=Pv{5BA4?q(AW_oB!*Y<$uf$5)|xV|TD*HN-En3bODmpDb8Qg64c^2EtZzeduS9 z!GORN5i2C@5v^6oqkD+9^DgWbC6_`_wuF{tOpRa?J$eR0UsCZfm|BchjtygTwmr)- zI8Ty86?aDqyDW|>ZChykhHbd+W_i5!%Aw4BIceK1Ej!!6loZkTi-2fr&q8+|i4@qb zX*Y&&BD|lQN%`D)Jp@BMv}M{!7~|%3Y7z(t3W%QIj)E1N-v(oDnI@AZI>393`Svvg zHo3yA#Wtnl$*JN-I=&${u!vrM?~A$HV_kE7)f&Gx7EL_U2L_L=#d1WTOt zkR{3ngn`Kx6SznJ|qSI*v|HuV*<7=%FFzV6v8F;;n{eoKUZu7&lWoaX3a-$ zm@zag{?suhEqn_nl{3dP+FqG8M5b9tX~>pg|47)X%7?c`(?GBb3cgk!$95m;e9vZ)00_Y|X&CiJen0GO(2qQ>0F9qPfPY=)rk}e$aI5I^S>Q zvIv^DlE4bHttZmm!Bb(`ao9(`ab6y5t?hGqsRfy^0=Bhh&ZqBK=qrt6wzaDAnJivr zn-i>z1+>tO=C)RaN7IUiomD(6_j%IN@GETUo6sJn*Fl4wZh7!UWoTHe;b~_QO2e_d znAdmbc=7J;Ho%+)!Xs(DkrKsM-t)r-6<=lXQ>u)#x(o6CvM~Ar6Xs;o8=VcI`IG-8 zOe`1}sCH#= zGgsrxSIqd_diBap;dVd6#jM@QAjj+#jnG`Ze&eQqv0G7*OBz`gnWztf$Ib`3w&FO` zWME)`$Ql*#$y*O}?1{3orOHmH$?U&{Pyhvtw%$ zcJ~$JfXmFPVzGjk=puT;8ppDF_q%pyZC7*;lr3laaDFUkrSJUKD|R*`)+H8&jh}wU z;dsCzYi5Sx-^81&vPZnf;TGKx=VFgiB?HzW8S+NEEOEJ8bAuvdEt4FawUv))6#5!WyI99;m`-+sY+H(2X z7nEfD3XCnr7$vf3230rE(ls?U>N2a1oS2YvuN&6Qaz0JiV(CFsTZ13p{AODbi$)w* zVmrlCnHPvs+Vnl$qq6v8(gO+_?^sl zsw&8I4o|A#*K475&KkkPgCEwZAqywzs zNjUDypd)hK;KO_Exk91FS>2ciZ0;X?Q0EHO4u4D`$aTTsGL27EEvXhKK&v@}C+w9U7jtqsOlDGPGKr$DdzXO8gM)A4+RwA3 z`f#bGEq8E$F;+|xs|=p=q|XQ$p2Y?0qJt!!s}waI4=1Z@tU1p4O3#MAQk%kpSC(g# z8$JsQqfVs{BbsKV5JG;mX5vA-y4fOz#JF|3Tp@|S_U1SwF|+o8Npdm=B@an)`Sz8H z%Tuk~Y#jAFRBer6;tku$ar)zP0Ew@jOGkRM1`qL0A3W4sJUGK!I(Uq?9`XF(RBs3V zPYfRH?HxSGyBGf-#Q%o|r+beK9_BrUJdY0^*2*0PuEW@}#c4frn(*_$Ri7q&FYa<( z+xVFx&lzo~`*f1JUker>4N@4bspsRdd`%Gl?Je~H)98|fUshBvU}F2v5vmvRDSb5( zOwFkm*=PD)l|P`TUd(6l-=9-2;d6e>pZ+6m>ZQ!%{sx+QnU&y_z?btGYlL0B2!4r6 zy@E;nq9PcYs|RiF*iSjBS2BqOD(Sb9)T@wMykmoUHNsq1rB>Z8XW2H?Yw*BVuf-Ma z_Kx&U_s;MZd*^sdy=C5d?`kja-Qex;D&B;5legEq)4SKZ&wJ2&rT38cu=j}fsP~xn zA@6bg*ir8A(IQVX!EbJ{6unN^zFycq0+*{d@HRI;n_Y;17L86nhz%(sZlGgDcF<%{%h-A{QBG5>;8=Al96^m>SRm?Zmy z!T;==fKzkMNrI5@HWJKBRVpm*4VwIRfXIGY&4s2T5L+`W!Ms#N!Bj^?6d6 z-_ccHkVsqvwV{YFB5c<^Xy3Zjmyis`Uq;}ouizRm{wgE<>9Y}>p~%^JoMAr|l$g>- zO9XU)krsO*e!Pv}Ng-F|&X(6*rka%fHHQ27+r;YYAi_?IZy@m1H*tkVUItvLqyEvh z8G!-nTfxJlVz8-iOJdo${5y#G>btlGHhxbc>EL}I=`r`1YS^WIfK*`jLj=D1k+3p; z|1l%E>9&W(52Tr2P4;Hx^lwLFUZyf}SME?A=YLl_n$t{YpI1L2qjhCg;XUf7xUMU+ z^6pVT!?l>3>S0Glv3eKInJ(-`1K;!p56&HK+6TbL8w34nkVY9bwW9EgeQ^ z;4u6Y@-${D+3%RVQzlFEa!CU9O_8HM=+eLhu4`^ zV3;us?fQ_Kg&c6uYy`eK5?4A%hU3Zkk0QtJGOb8<5`Ia%tZM@Af{J3Ep~{$N-s!eQ znj^B0*0QxbkC8}U-s{hM^Yh+Wc~Ae~xjGgJ){n;t-SIBn35j&wWr`pkCo~;@($_gu1FCne}pXlr(zFxrIqxfDCZag$R6g5w5IK zsoCK=Dz7V3;IFcF4yusb!(1#GE#jAmfJ?OiU2m31Bo3KH{4%6l#4i`}D_ruWDddzy z)0ZK%h%d*zuT}_!L3|}65yX$_#n6Ai&Ls@!1@=w>r-GEais4)ti*xSVax*&$)M_LH z?kf@aY7MT0n*;ZR%mABur-EIMBK!p5IjDJRgx5+W9pS5xZV_H5V{`xyPqTWnjh>qMKaS{v=P z>m?HZHEoqgf~`+C2;GoNcVi-5cbOuHRe|Z&-NT6ZYD7qkzeXj}F1Nc(QKT;-wC>)9 zdtYrA3PbKO5^?tw{v^2k7QIoU4D(s8X2VjzHNF0MBxH6^s6Q zsuD+2nH{C73=%kX2LfMJaD`L7%i%F<80%ujf`C{_Pjq+|~I9KMFm)k{f@=;Gh0(^2C0vryAD=QkOq9fVhBWkH@Rp6Mi z_Ci}<@?^lhrgfYl&VqQiy7*2x&oWTo3ibQ<@6;;0GuyH;H#J6YRhRP8}Ou-o#Od& z;r0s6P2>5XMAGqmCDJXPuM+ZCyX3D)A*UqT_gaJ&&)4DJSFaZegXbFF8MoB$SH~Ty%V9u^M7>9J>7!SfMDB6xQ7>iu`T(t$J9Bl$Ur#F4Z}JkE66b$wpwzu?k; zF@>I@XxNt!T1R{t_n7k)3WMZVB@!1;NhJRoLhFdHrD5sA94UIC_qDUEdRC z-`C8vGkzeEI2snrA0pj?`6D6!u}l7w6mm+Ubw5RD!TcHSef4vpFkt?Ikv1?(RqQ7& zjqVqi)QsYO$xws2MzfKF)zq)>9AN$$0cLG+wP3or047zO49wVD%Ws8a-nBoY zFZ45SsjV8n6K#I4wb9=AgG5&5y;XT{b>6!&@2%0xGk-*atrC9{x<9*ge@Ue4E>i?? z`76_{8~=ugul_D1#t;9HNW0wbGDVU8p9rlR|Al*B{aYvu`F|LRxN(XHW}<%ZCbLu< z)%GSsWhi0FA>z4i6!6Vn0S8hv%uh09HmuyZiC>sxf?t?b2O%@uc`yP@_2LS5dh6gn z>L&inIm;)9B7=UI@D6jBBwCkD6^_$1N9~g75=n<<2Ggx;x`ckFOMiF@Jw?&*BbaVo zGYb)4%@z`a?U53R%cm&PABE7mW)ALsb+k|z@?#i@xaNr7g2GPB`fVp%7#Yvil_QA;#NylI=)2$Cq z5&BbI`qNVADT+pA5Ly>>&T^`fvohI89C`KW$81^n|7`06ZN34hy0pzbo2O9sE~^>Kk@w1^G}*R1BMJ+e?D zaY!tpi;!*+JzK~ZyX5DjkW&(kJQtxw^gP_-$Etq3Tl zM7aX}x13R`F2Y0Lc`*WCU4kp|jGipoU8ZKKaF(MOUMifHXigf#%OsMH;pIrT7+xXd zOI`A1DddzyyOtxg7_Pv*uT~0$!EhBL5e%pH%HG#b@uNzVvzp;t*{ydKf8$MEi3h-L z4FX?10as#&AJm;9$!G(pO{cgG3YWE-i^lCLiKOGU4(S%R^+JBNOMXoXIVI7a4G1l6 z8*%TeO+sOC%Q4c1n;dl1DQ;AWa;{~laa+Z6D%EB@0B+pR=&L87R+vg8nSr zV|rC64Eb{yiGV(<*Q@OsXx2D$K2U0QiaAxIyyr61b7)uAc4aoz`t+#LX`4qhT#ZF zKCV@-!9(EwS_Hm&9j>;uNgpZY#7p-h<6G9M>Kzp?xPZkqidUpcQf6>`yQcxuS@^F z6ncuH!S6?C;r#&aef2@1Fz`OcNCe)Qz4#rBksWe$QK#rqFADn*L;YHG4Vi4@C*SJB zcnWYog1}cF#g*XlwJ6L3Y^hu_wzg&Xm@xRbW}wmgghb-#S?fQ^blWm~O6WiB(tjp} zo}y^bXPIu%{~RK|dR#~he4m#{Ts}pS{tE~#`d`Gouf8M{hWyKnM9{~)z<}N+K2R7h zb&5YVqqwgyH1h(Pjk1$~lQ!zBcn%MI4S}z|jw?ODY0RZgF8F{d?%N075EkFmEVK{4 zC6ROtzRh&&gYO9acU}7LrO;Cpjru-9>w+KP-d8^q3WMQ~BoY@-NhJROGZB4 zM*mtk{YG=rKKQLf;viTIe}{C7;qQg~4=(v1Q^+ZacKr#V#qiI#$F5hQFc|)okqCzD z;TCs_9u=aLzcGwWs{9>K0pC9mV0$aBw)F^`X5dGyI>ql_!s_3emB#Ns5=qBz3dZ3U zzk_h^tAlZcCYt;ZiNwh%iIyEoa!=4K<^Vs@mEALWwINg<~sT5~i)i`g-_ z_tmjNVIV(_kqBnfd(HR1o#IBNDCu~HbGJ+bGV8~ABtS!*fJcDtLDddzyJ9`jXM0;_M&8i#!i&le$QX(1Zy1rmvaZ3kWhNVi~Tg?yn)z9@y9l4$wa2rbx)aqp{hgu;M*E+Y}J zkL#7wclmO*y*9uTIR|*oeW!?1Man&oq24;XSt+t`vbwA9hV*md+Y5G?jn(Tv6ia+aEU}V=DkgMFPHbO&3l{mYTKnqu)~HW zLU)-{VZyrF zRg!4IUx%2l*5e9}8vLszk`BT(Ot((hAoLqu`b{bH6h#wr2(1sU#l5dK3xyHS-l&h5 zMSPEat9VZGCnB^yxDNNe+A0)={CY+rJ~#&3&#N*KIWd4U9a{Xst461Op@Ni}XQ($r zUuC&vmRfG=1|-8jLkN6zBd)AK+zg$N8D68_y7~mJDr>)>g19@+3zE@-JuK`;G<)r= zQHjLSvS1gHZo%Fr&>CouG$m5Vd&7BeB=3#py`pZrT1c>-+$nUsT)N$fblqi&ASM$` zx30Vi5ntUbB*quFNTgkEcbTF{?<2IXycPHO`FEi(UxIXt?n{OIWiI*4Q^+Za#=ZifMfXA6V=h)G47#skB!cdoUVL?o$#6Z! zl_}Fs0j7GC`D%u9ZZOxw!x)RqPbZcL}pcG&2q6qY_C6^W8|d zV7^Dl-|LdUFNK_vXx;k}S};F=d(6uUg#q(1Mj~LIXg_pFI|Y_nQuK!y z&XwOmFrPN?Gp=ZZ{bA$*$R9y~Ia^!_GLQ2~qX6JEqf@{?CX7F>8Ee2lA(3>zKZ$e; z_@{*Y(=PdEQphQZR(}?u1^jck$4stJ7{EWzNCfcdy^Yowe;&0{e5n*AeSx8#PuQqA zs4wCX;QSH-Uws)@!pZprlL6>ar^(QZja$AVL>#y1#)0D&{mk28yU4GK(64Es8tSi0 zq?Y%_^Ikpgsl3n>9SE&mSFt&hHoh_AjUBnIT~OQcC3|f9EiPj^(Ld;jc#uXg3M}8xb zbYygDdM&;_^Nf)m18RPC4p{DcBL^ ztAlV2JaMo@(vdg>>DCd43i)9!`P3A0N}^rU5L!P>$Gxv+2!#Q%OCoXUltl8G2(2Fu z$GxwP5DG&+i;;*QruVj=YcPoes1zm5W~f`SmHMD-btE1E`J)i{Y7VYMo~@Y4fT}^A zk|A#!C?0JyMn671Mjo?4(-|z%$KtuKj>9!@)bSFDLuHHn2~4+8pD6Svx%4Ne&{Grv zn2XRteG2YAdUHjz9qL8`VDCZTt6p3QyPT2d zHdg?jS|)>UTb|Q}#~GT3Zh6j>NE}8B!#t#0FZ2m{ze_$pg`ART)maFw6Bgj!R|7&} zP|QjsE}fD{z7U~x!Xn)J>TIDfym z^+|@C-EdIA%V}l$0^xn3=B=G^kwoHf*fM=F(k+^o2>GQh`H~cJN}`>YA+%^-j(cBS zAruD9rHn+-Jg3*$FI1=w6e2sVBpQ_Wf#Jf)4va}V1)us;sbvi3Zdr_Wgu8_>UyY$C zy5mf=gI*Y^6d5B-EeAFHv;qP4*5XP(9T+dvAc8WPOW8QJyOa*s&%6m+*H(#2tF=nn zy;n-a&wID#y(i_p+w$J+dfc@J3AU0wLFfiuy0wXP-DQd(gjX@$mWy?WV47D*jAO5s zNW0wbGDVU88icl7Y`{H^L=Xx?zKM}Yxd_Ky6EK0cXy&)IkGrT0CFB_D`J$zxGDQ?@ zJ8jVA^3xaLTl2NZ4zF%TfI|;(g;%{ja1gZ<2f0J9Ck77_hF;f6qIJqv#C&x-6$HK-!_I$2IVzlE~BZ-rafcp1k*r zy!T99y&6ccE^G>2%ca|yNY`Da2tu-p>DGt45%JZ8kQgW2B$0Nx-DQd*{mlri4{yP} zuY92}s@!=F&0DjL5my-5si`tfws0<}MiQ(KWr^6YWTG)nNDpWGV!DK9c#bA_s zT8hK9+78(ebQ|)+mA51C)st~${dyieNA=q4myXA)R%_V$;wi%CshW?jRd+}v9g{ss zFPn4i_6qr(F8N)IxaZozo59g`djXF<&`zV|9(4zx+E1R#h3DKojf~fodGzES^>jqm zmESA(cgsCbl-#54k$WB?xko)i?s;J39`#JQ=K+y>)U$B!oQv0X7T&Xw+9y(keVKgA zWLPGrvYavLo~6gL@rWXviB%zcHl9X4ayrX%kf}9{s^Kgh>8hqvynC%vo{OBAT&5zn zsk>Jq59GZUW>y%*)Z7wZag9};W@xnJm>@6tVxNQY@*JZJHH0YY0pUWj{Ny+|lb z0e`VX+QoL4DT(ASL1^p8OL33=w?bjaU(QHd{eS`B_u{aA+^Y?0s~=Q`5?;Y@t{f-2 zwp8VDZ}{CDv&jqHk8UZ|gUE$i@k#`~dKIp$6|aD2s8r{*BHG5qeb^fI9<)w+wa9vn zmZfXXYbElUy!YC?_qx3I`n>lB?WETs!8+;nLiYxj?v06b-DQd(25&-Wo%Ckh}xYAc|g|DdF|EaGYvc7tk$a+M}(!P3B zBJCe;!`9~RPa~7CI4?TJ)@z5hGLkS;asGSx6HdwwC4vtQ3sE;Evob?F=zWOAt zbk@7zEb69PuQB`4usKS{g8nI?`Lw3dpnpap>BfJS=@$IY3H{?P{pVBYDT=0j0inhI zi@3+8U7;}GeOV%L@svdJuOPJ8e--z>`kGJ}@~<=EV!!1~6nK91ho62NK&#YtAa*wV zO0^L`)*#tQ)S9Y%gJFD!$v5#B?)Vl0Uws=_xFcb=JC#p%4qtbohZ=lGczsv%(r)^m zMB+GEn7@y73-b?z{D&_2k5b4fiMIV1p@sP;xW~R;p)fH2j1d>+T>Fe7Q|rZN5TbApI*N5lCnDh9{?Y3MciVu)i_XyM3=NY%f*yocrJL6j1&H0e1W1 zN+|iQK$r*329@g+#(xQ`e`{76#{Wnp9mXkBA=lz~5F*&_%SUz?b%;db^b|$Y4n?}f z@h~Bu>XJ`mB!Z*+;WXHG(hgHC%9_qF{47W>I?upEpxA`~JAZK{itQgxQ?q0cZQFae z&>x}cHFUEil5YBJrrWmnNTEND-#ACJ%? ze**4(b)rxh@{<^eAV0g8dr@V7tNvW7L-?s11)j`MHww$Z$XWQGixl|a6a<(##?>|o z(R?V)R5aNU@pkq!VVBYDv`e}r634_s+JkfpX|Irgq6F7+j9a~s@o~B zSz)+PGt|H?l1MtRXCvJLyI9E2ammk3A*Uo-c^*Ow?D@F&)dfOffW45B2(U-?;s-8p zWXOKwO64f=B8GFf%qCjbtOchK){Bt@ST8}~t4nbutO*%`Ds`Oo!+g+w8(qDR&9C-T`sN(+<>JJqAiA%=QOm;G*mbw$H} zBN73B0fDcEab;cMmabSl2;L0htzP7ZMi~W$m@(m9CE5Y(_~<@*cefz;o<;i`}wM4 zzOsX;=#VGbIaHPM6~nn(P6gs}n90SN6^3!!FKDX7$h1iotFN>5Er2e;9#WVeNV za@csWX^XxO?G%x_v`FpL-4cnzYU{}a(ydo+67ri}@>^2KDT%1~2(4Fc#Xa`?3Wf2? zZHz>`a!hYwXQ5On4CA$PA6-haFQ_1;-p)|(m0xPPIfI}lBN+~O3Igo=#gz`=UipN~ zK%II|hPo~HcL>itnx{s1uSC)jz7y#d;k$(VX)gKGQ^+Zaw%(1?_+uI<9Y8BdGC{Y;_`kZShqc2=pJzCUXV!FU8V>^ z@It0rzr6?%U%gmJ4Dc_JNW0wbGDVU8r3kIxUWR*Ly<8{^`70QS_-zU;0KZR$GnbEP zi`r%`sSG7N$WS|JBNoi%8#l9%ts3f;NQIMLg}_&@#uZNTJ_mnL8S#g^nE9IEVSL3! zy;c&f4_=2Dj?Kjt9JLSLAdz$|-pF+8i#G}Vn_c?1q|j3oEqyCO>xzeP@2iJ}!ifJL ziNwWI63O3&(7NL7xcAjNgu;-&laYukP7XJ}Z>y=X0=_W8ueMZ2_?c#>UZS29{=W?M zB0#Xg-7?Ksrs`eD1#dlqz*mpr$|}X*Axo!$D`||bT7mz{+F9r&oTVD|Zpmmp@E(!! zUM)p?@O=`ALuftlexzFud_c%Q=#oE{LQY8p;X??m2R@8@%nS>K@xVtJiFkm&=&_?X z(DX0?-x%0ltQJ*iq|qt%)R1C7#!!Fda~0TOR-f!LmYw=I(t-LX5MYKFSEA0Ze3Ehl zZ>pY(cUgmsrYyRw{3#(~3#T)%1J}>I$8DYZv}o`dt%3H)XC?Bby!Yk2_m#Z&)x7sL z-L8EO3D%R33*F~kx-TTsb(blEn0%4x)|FpE1Y3iJ#Q5SX5^0x-J^ zkE0!g!jON1k%%k9T^o5N|OF%gK#oVeG6IOxNjry z)pu}Z{F{wk3;P8R0BA>G3KcOn0WOa9Lka!R7f|3YYC{x|Ni30No$ z%u}!-H43vkOC9WnnFL?zMPUbl8}N0fw zRLx4mI87qyFiuChg>iC*4pmb-^3}74%CLrILx-=0!)pm#*qy5&Xr{W`^+ZIK4Ti1;%LUOS4SZ~pr3;P zhwS1?=(+Eu6KXhxrgrL-V?@ZYT8MVaaS};)%JE3IPB}ryPjtyoN+G8tqH!`p>y){; z_thyvVVrU*BN3+@@2tSc?{?@j{X2F8b)?|a80ufK3mbR&CPJUy-%uIk0Px)ia568h z1fIWQmz*C^(}2mKj`h*KLc~5=R}l8m`Wb#%SbkjF{yz2TqRAPe$*jD0dfr={_m<|p z^?5I!_jcsHi9CLJ*1I?FJ(%|%!bCQ6_T{GPodjwgqVk(P`(AR*^y!t^{w-&~){a(b zb5okCkHX3#;{wn{ej_~7H&Q7y8ktg+n^5+#+}mG10-J@%u0LdV8Q4j;QS9r_^zEBL zHY!*DHduf8Aj5Tjl&k)^k^|-H{hNxO6|%epieh8Uf$}RKZnRtwvb;jG9OAFk95CO5 z4c~!~?-KBpvUQ+Ly}!};Y{-$Cj_YO31NYNhqv^ts?=tY!`#Xp5^SB49Zuw-xc~Quj z%6c~QMetuP9+D~%2`bmW?1@BNkPofEQL z3YL7g88YVv9T3;@QAXEuL&g_^@#c-I*Q_Wt#tY36Y{K0S+j|VZ^Fn^-8Gf7b6V2oM z#cYOQc7Dig6_|}0#fDU|-a-@9f#tNv%D3&vNrw9cA$PiIS7Uo=2$~)s*V%^ag&|iq zCDE?|_6=zCGlhQdpY;4iA-@&iho)(W-@K-18D)`elWq~n7+o(8d7lH`PM5gPw%)W5 zn9JxdPcf`630YkVR+BU$A&0CZ)T(!y;dyDulMTq#6Zp)r%3t3KW)k;}XD~gS)l0km zqQFFIfm&~QKlVMi4>+M8m8+) zrWbcu8TgE!!daL5sAp?xftO8}6IHz34rb_a4u;a?=Zy%Ft+S zq@i+n?5k@j+MBg{=@;+6>f_IN;B@bF+}`)r%|HFobZ;@AuI;=0y~Ey8x&71)fBciT zo*CZ!yGxeM^zwW<hGKycjiuMs?yldwpy)Otu>1n=x)Q|ht-kd z5QgL<#k+D{UZK()suXt?D}#sS<}^5W)f#S=nw8?vZmtU5l{>OHHe4JXYBVP*#fDcb zHt)(EK3*IjqC5E-k~__7HpeesxNz65UDfR8*s+nuI2J>8XIsruC0oUhw=}vo>kJzuy-QsOHvPA&tz!1|V)h}hNab;h zR;>#?(%5*ZQtVPU;7RVR>-+jvtX!5`eao74tJdGLdFh6AYu2s4W#xtq>o?r8apkou zH>~VmxOLIMx%un+)etBKr|j9FZY1e+s>Y=-RX}uIYdCjEvAWYcICr?(HWC_svN60_ zt5q7=Qn9&B4ELI2m4#}UjE+MaRvao-F~H_h-B7X0eFmd<=4PV@Z5DU47afBqns?@A zm8#>dCUT6__+e;nMyt7P;6j{&yt<8 literal 63133 zcmdUYcYGYh8MeXTB4Zm&2Zx*_f+b^}ZF&F$#tlbcgYCl#<|0ng$$RJ1oo?>Vwt!7A z2{t4lqyp(AB#;Jaq&Gr(?;#ps$52{oT_N&u-GBZa?qlHqwRz1WLHtK~^ z$6ADyb7T2TxlmnQ7_N6z5AEGkoiRK(I0RnxLV0koqn1bT%| zb5&mDb0dSf(yrV@t=i#9f;pKP^+KIu4)d#Xdj<}jHf>s=Jd)pCoi{vE8K%fux?CB_ z*E%F!M|D2L9qw0;=*i4hxm~qNLk;Jv3o_F(9l3g46^0shFgwz(9@UeXHPRTTuC?mX z+04x0Qm$6R^D)`Xp*r-~Y-W04q*gsHo0&C~8{VPvqqXYs*+yp8um@aJzFa*aTRpK^ zkuCY%$lt<&GokC&d}+8cmQU3ysmmwoUZtGcl&{rOE5{3|wF;tlRn(3}wG^{Lz;pFN zrQBJ4!cBXsC;8PU_IL{(2bWZ4#&`}&)~lV~;f)ozdYo$Obi1lgHs9)LQfN29O^C;Lq^knbtyBNm}r^T>75MyJxA>$!Ld;*l(!EyYI!x7FOQ5@3drel(f>5R zdU~M$8Lpwvbo4$e(EDs-=yP2CpRDyi7y2)v{-xXmRzhuI+NQ=bOzTWgPjnw)yvgWg<4Qf*KFRBT6y{Uu$)$(f~@uX)k~SR_9!kx zrdGO{G9_-f!aS8qc~#vY8QSPqFAp+wg`1&GPKGuIdDvp|ka4qkrOwb+WM~dDv?>r% zs}4Z*r~1{asCskUHYm9!0)I7+`HBffT^}?{O z??uVj4!>FoGB)OBtn6g0;%00-$WYZ}QR$4;kg*g5t&9jt<;o)|231tyS}nScTs8da zPUfmDf?decj;1WJ`9iaduA@ARwtKf^XTq;u8)WCY9`DSocUIOrJL{d3^-Qh4-W2*x zEu~z_yu;Fmnoswu&!Czt^ixyZ06ix}Pmfi27KrMNIx?OsRriR3H~Q6Q1`0mQw68Y- z-k^Qm>)^gGfcj?F0=Hoalp!EshacQBvgSrMh&{J;D zqll{Ni=pdF{Oax0b&8~SK*Q&Uh#vvQV+{;8#!LCIYq(Q%d#PW2S)kj?-J<>q2l7`2 zkiW{5?pM3e-=(2{jVVMKc0{SMib6l{<$!vM0i}FcgI|lh-0fFi$Gor*O-=E7Wa*_% zpc94M2yHvKGgm5%olas+WyV-e*$>V)NuJ6|w zd@C}@24|%RO$9=09M4Q(Xq8$G>6=CR+mOY#`_*?ai|x|96PdiXDU-7yFU->elBak1 z)prMZdXK3u?{(|T`<%SJKgh-h++2Q8=jlVp6J=0yjoXKz`A7WfgVa14^HJ#ct|t9h zxKw@|1(wYyOhIL<4~d>1^Q#{Z^!$X2{U;s$KILllX@mV|0;NBz_52+4WG&n5Rd%H) zgP5~ls8v4?g}>lezet4>l6(o8f6Udq`sKz@BhxWb8Ove(OqIXlSHB7A&tnK--j{BzfO$N-U<#TG-t3GU> z5ZK7fubIMcj7^|nsLR;;k<4M2 zVMK%)wq7*+&fY!M?|P4T-?^#!y}d*GJkiSgw)aTpP@UlWNbmzB`2J1RN15PZ?_msX znPARl(L9CWX}NGP{qUyhkCCjAnYj~qbJ+Eh{%M(+m7!vOxL&LN)CVG}vb{1+ zb!&bjbI`C~{aKIqQ{O|vM&{sgbU{C-zxs=wfm5Z#U?`CrFAR>_QGc37kOG(<%GL7e z)$rQPDZz^t0YMG%!esHvqFJVdLu_k_t$fVb7GZ4ouV_)- zUy!Ue)qgjski?pMBsBJ6Mw;xS z8&);#plQ>ZAZeo+a4eJFRoh-bM>nnxBCBW*Gm3bpZEeb~c% zyV^j1st#*aF2SfS%Sf}4ZeW?dk;j0spysx++wZR1_7`eiE3*XM!X_zh;ApDZ{8sdO z_ow=+!?iw6^RYah-sAw|A!lfm4)S$GYrYcd`=oey)5@c6i|j&s8rnYr6bm7&3UWw@^L`IU6b$;j4t!p;*U$rK)Zt1vexAbx3 z*_RtH^aT^IrC?NBDyw8m(}rnHJrOK3N15p!r}cn)q`>)|N+If&%Cm`0;x5ya~9W zsR%Zy092-fB!gI-L)s-wJ98vIn!`ey>Z7k?4D(rR#vrLmGvH8}L8_&Eo~!dTSZj=q z7Iv#;3_B!^vXeOibNWJgv_d-B1goj#h|Q|g@Y`dVoKAvc;&oGJAX0C(B<-=KR=YcF z>P!;Mg^ivnB`38mH&js*>MR5o$t;uFSk9+P1^i%Zaaa{(rk|>erZDg4B0xGb_p(B5 zIA1E|%K1v8rp^XcU7ds99EwV1d_onrdv*0>1PmMrL8mQSe)^JBRNT3g)R7-=pjvQ~ zQ=P}u$0N1QXUtxqI-fL$=XVd68YB5ZbO=DaTvIC$oXyM;UTk?PN?m}UqU4ALG&6l! zdbzrgPqW8zWl*xVT|_dq62Cn~G1D=++OXE3(gby;NWik{V*CorT>n9JwFrf| zcVB66O2L&U53!2SYn=54zqEoIcfu9{LJhM z8UT*(wJvy)|l`nIlsHUz!XjW~)Z%<}{-BRCOPs1t|){M=Fi9{d} z;MLU@MjYu-3{x`9q%zGU+GY* zVTO%WcJ)-ou#{vD#kPE7TwR6d4UHkJ|K;mBcFt-WLVR^KetW!mzSr%0%YE-6-`n7O zSNmSk_jdc<9^bpo_wMw)dwlN!-+KsaV)IO6I*hiNWi3lKnIi_Rj)SJVQBM>8gXG`5 zmCl7$3{jY+4s{Ju$m)5_YC^QPRXHTRX^$Eb&cpZ}8YrXh@9OI6xCA>Xz4QeJP9=E2^H#lyxmDT$>+RlG;$IudOu7BOM(b zvYHg#>FUwzhP~%@r0_S6QwEWA;cnby3C9cl`H7X0J++>w7yLy4+T_&WV4+;74-WR! z@};fVTx)o)DZmBXj5CUhRjlz*~~ zxuS_b$38s{y(q2Ir?3xHZ>X{m2H_h00OY6$L$IMz&WET5Uq!%I@8b;yv4(| zcT-{Dc@OOAQxoi6rqCFscH}3x2Ja+f)QGTPAlc{j61{|3_Av%=c%adk#46E*FikWd zz^E{qGIkYsy8?Yi2KD-Hmt!a9S!~{t3Ie8L6Ntn{K|X4WM4s9@6iY0Iz5MWwLCQ9T z$xLV*4J~veS;gcjWYBDj8(Rt5BQyvWghnfB4DmSbf`-0P%BfUNj!UEiZC#FR)sRyo zHjULF%%{?Ij6iT;si!xffy{&~aX0(wARV>jpJk{KPn9b;Pf<=_5k*er3Mgl8qY^NO z5?v^LD12SyM%|;du9jh263j4qNU?2^X0a&ifUK3abuJLd;8xo*G)BvSDkjcXBWQVh z8{qMTm5~0E6^B{bx_Y-ZrN}@+10>+9$)h81|0R3(0k1f)*`IJNtX~$)J=V1ll_6X7^RbPVFuU}8 z6k)X_)kK@^@O_g(Zdy;4!L(LX6?Hy$NIpxF&oAMO$CUYOTSaTWOaUkHwDjt>?WDJUxw^T)8aD(xS&4){ko(-*Wek|xo2x=&ZB0^{h86yg)p{Ie6xuR?6isv~+9oKtaEN%D`5lt;o!LMxNzKR+Bj+_rRa7e)G8kt{ z{uyY*M_zPluBZ9bBq9^6*+c`!=uPV zIiDZN>#-ztamihnjY$H5L3WJ{v4v$UUH=l)aM4*iR&i0kH_WGALpQmkM3^REE?n)V znQ#pUNyrWA_;r4e*flsAC7T=B#=y>ukfeG_N3ORBo681KfvjybbZTd)8^sAl+ngY_ zfCE}s6~LLn9-%dDSuK6}j4ImSe-?LfLjzZa*hKdZF9g4{ID1n=<6o!<~U!*?VM`sgsEJK zXZ$BnG12wtE{Ay(-Pxp4KWWB7&4mvp0OTSV$l)Z==?ezuy_Vpa(Eh(Od1r9a{{K_{fmhw|mAFvggEs+=JS4~E8NUaM&41u+S{-F9@(nN{lm)$V(77o-AJ zF}xp#*>CUk$CY5xQI*~kbAtm4? zbq7LF$3jujSk0LXSlOj3rYD*x5P~$pb6C%V4xRFrpplRbV5f~XA%l7t0w#7TB5`d) zYl4(`y9C2)VQ9$H^}7^G4u7W;G ziIsBWg(aLjt9OWOL)in}>qm(DwwZp3u7Zu+2eLusXi zpcqMnS?ffcd5T86TYo{4ZCy^VJCL%rb;i(B&I~r>3LG`GG;xL(+TAoa0nNNy=Idh( z3?2iDKw;TxrC~6h&ZUM+dF;NV@FrziuWpy)1dUW+BCb-(f>^+c4R<-e9qlc3;{j_V zQmIO#KEzE99TcvtUb23}7HRCq_2w(ihGW|b=OUeU((IaW;#aR!u-0K4VZB|lottF-4Mw5@2vQS@92nV8uio4Nz55(a}5@s=93YTXK^=RwEG!vW%Fjb(e z=8BOIC$DyMMF47IA3KjV4Oba^g*^cf=|Zhkz%0b%VTo|lzo#yD+Dj^AX0ux+#`6i& zStEkwBw0j|9rB#=PZUaHuB76+(8 zYMrW!wozkSM1^pnBVVF;&?O4jJYCTUX%=iXWa7Svo+fdirYU7?PPYlwxk%y6DD51~ zX$YPA;i4aG?T{BT3(fixw&e_jEa*S0S%}pqNOM;I8V;*>i|!F&7*#L8pgB~~(jtOqr&=RSDQqFM ziiM-`)KsH!oKaO1&Tzk5r0X`n?ErFxR1(KR4Bd-#ktZwN7Y9@q!o=U>&a18cX$Y6ba)NwkkvUzL@r0z_jGZ&6(ew4i?A(K zX4kF3OVf+7q~8BFX~VvD7%7<|J={=O&~-Y3s0{HrLiCQ*s$Z&vtuf$0Gm_b#&f3v`@U^8F#l zjyRl^$2~9@LRU*pu$Z7R(uH#m+goafe)N3$0ly z02*eDXTSDmpsQ8jx@wOZ^P16cbfBYasN||ka@CdEddZqiYp^+)x?shH`rwrtBo zTUbC^M#t#rUD+5cqU_5!ynV^%r!K^9E6zuzh9;=V>NT5JO|6O<09$RLh@dy6*h|-6 zwtfrk7*s-`GewW=*b0dSqGhnRwN)Ap%6SA?TRVh_#4M7Eg3t1KF+peXBsp3k9RA|cKpQ$U14QmPkmU%IeimfB=pwh4a49->&G`00@sx8|9 ztgv}*7q-w+rY3aRN`#hS^O{T7tdb%Xw2RFRqMgT?y27>GXgv+u6i&BR*g7TYoH`Yx zVxh97^gwo>MV}S&3rm+rR^WfM@imQC?EcmN6(9vb|;$A~~W7a6UggGueevL%+e zM$jhN$`s_CrAccL?L5QUW_os~Z#$4GEebA4Ij3TmIMo3K8OocQiFF5Otb&??*l0r( zK-O-Fi)7c7{FXWjF^)>D=L4J0kPtckg-9)8zqlxiVVUWaMJ)T@xT6P}+R)npe$#Gl zn9;DUK~kLM1YAy6=g#a|nx~lCP2Q|Uv8I|@4coY3^xU(#H}wZ$8kPhZ+t^`i5EYbi9BsfdE0=I8Qq)<>4K6*`o#yw(m-En4woFIMyzWeN zW_zPiT}#XmbX(VjUD7!e*g4=vkL~#$TnA_x`rclrnh=+;sSA5IQx&u{t+j)u&blo@ z7F!)^hIKa36y)JFPg|(Ut^!WyGma%r?~9st2PL~@X+pr@5C@UX2NEO>nz{4m{~t=` zju38(QM6F*3=PgN^4p9qPAVlZYCxlV-C;QcAaaRo9c&Ql))3CNvJHcDt;P~nW{NhEpqj9CEev7SG z!=8%N*GXP=3s;kylA5)m8h4|)-f52-L>VgV8_T}5PIOsrd`W5(URrNr*7}OwC0n*! zzQ(?Ir&H&KhOMxb7q7YuFDN!~;q4dzhiJO&!X8m_DFkIpXj!(X5iFudFF@#tD;@?@i_w~~Aso)OH%SJ!IdZ7t zo@im0#Zjeg3ys}y4A(s@k7=)5QOwCn(`jir*$$?ph|VtpqHSGE-E|~V;JBte7{Z0{ zULGc8xHCNjLj>9~?L>_6@H#aK1Ox>{FK|b}YFXa~V;-3%lNH**JjT5H8Uh<%VbfO6EeJc!88B-;`oN5#VezMq zEz-hSIH{a9o{{FttRXVZLPA4!G0u;Kt*X3uYcvf6yP)7z^>G~cu_iS3Ne_Vw?i|4_ zu+m7`X2f1{AKJF%$d0WUn48!&1tSAT8L>p_)Fx)v7!^G@ubB^;Ze8blty~sCGb;&l zVUG1gx;t1ZEISVS$UDxff}^!PPA|126;{Be_RM+p9UFax;nZlOEU(F8GFzWuWz3<4 zuGL2yCEgY+8n##Qu-)fLOT)LYsc%esm|O=9cDm)k>x*H=S{j~aCZROk0F8Bhca0Zw zceerNG7y5K^+rq-Uzz8J4JzKs;;U3CX?2%k{<1VWfeBNx>5Z<2F#A*g#Vjls7^rrb zEjdMHiqqs3h9{;~1*iE?_GwGGpEppnmQLX&*#=IunX7AIYdKF?84XHen3%h9<}GHt zZoO{JmhiZr;bQjgWRPRdibiNI-?({8z}T&*NF|Ldi%ir8L9p|JuB|xEG8q^cAks!f zyz1xm0i&@P_~@utLm|#m3;DB@7URd*q2xqHhy}J!|{Mc)~pP* zz7ubf${F!4hg%_Q~OtfU!3M^$zU&w*H*_nL$+0i!cIW7!!2 z4z8uxN!e+TTkxIjp%w1pSHQ$Aw3F332#9uBv310>ajbyB`xOaewN>&F6qICq6U7!| zj1pNigQ^>7>1MTM)Ma)XxiBGDS=X$Y<$jv5#nOYOrUt+D@SAN#EE=u2np{$D$h}=4 zJ-p0_&F2-@b@3W=UKWQlKHLGa@_K!lkLm_!xzhldG8_RnQryCp^f#S$PBl6NE~7yGf8ZP233#w>{+xF z=hqj>xDpS76MNJ__8+yT&J&5x=Yhfyw+lcW;g?zzc7n}O!rVm1u@7gAQFFCaQ)zF* z1&<~TtodSG;k%zVKP+A>ROV2WGxF3El+fxevl9Z2p-9tW1Kvj4Ba7EOof;`9$Wbt9 z3D(80>c3{+0KYO3O^P8)kmT0>JN6Bnh$P`uFWo%J4iaD7zdr|oI;r9`ORA={JtnrDeB^4w{bWMLHE{`oZZtcG+E}b1h z2K*}h*TrR^Y2)ZVn@wkaAKb7V@H&oLF5GBvkZ4iteKa9o=8mrHwZ84x)qkqKiYc1V z0skbXILffvs!tw+k6yG6v|7-A+&=l#mZQ7FWDb=klPK!CcL|u>-G3kVer}ZN!>yL4 z)WPk=*fEK%G6?52pAj;IwF=fn2T{CNDVlWzPF2^M?pJyy^p)Ba7QCW_QEnI(7DgRQ z8%8uuOCf~(Xw5`GYjv|l43Vw5)8z_L{Pvk+kl4i91}4f$9F#mnwaT|sDsE3TGV^iI z?_jwxhJ`mAC&%rN&jKXAx`~GL=Jg-!b@w0QE$^S@UDSV+x1oQ!cXj^^uZaJ<5w@rQ zAn!K(zZ3uOLD~oU5A`1EpWetE0ggjBuEixibB*v`aMafb@5583W0bEb@`BNtx>-l5 zTPReDp1`S|4Zk32Aj7atJ%?d@tpGP>;RPK@ow~P^PcbB>Alpu$Gg{iz_7N^ro@egC@rf-HU4HR9<7ZuXDWcQVz$n>^BfYaej|0<+s_FfGaszT;n zBJ-3)7GWVF{cHA9-fNkDNh0$wL*^oSh`5_1`-8#jCMV!RTX^XnGx2&7EJ{==EVDJH zz6YrZ*}ZTZnZr%Z!v&*q1#wNi0pY%SBYsgZvg%EIXsr=%MsT#WzJ(#t;&&f|*w%$A zsQYbLXbAOIhQ(Byw=q1vu)p21aq7l95SI7$WYs(I&?ADKy7>SplB)f?7&%1+eK*rk zo0iXosrN7{p}Bc4;_{8mY~GzL9~V>aLx`U_1jyIa`}wRsfM1~9d|mEP1E18?=bSs# z2ayK1f`3Q|J}d+am8Hzj?H?F>esZ0*|#&d`IDB{8L zAvnJJm~b&HKQ5kTmYqdPC+ANv-dvbT zeD!HkneWi5&xog01ht`v&%$liJur5M`W&Lc`15dl^#%L}jK4?^zkfE2TNk;W50oHA zV9Cq?3+x7<$rG?Lv-oBeQk8CQc%4P6N!eeb`#8S%tG)~dIH&OyIKKKSexaS0f&}WT zzvDJL$c*}05O_ojgzD=OSv2PV27JEyCVqqbJ}jPOAist97E_sO-=V&ZSm5{w9AAA$ zSeY{LU3xOJY#)s;rp+~tgogtd`SLCHd&B?}A6!0!-vhYCz^uRCj8&h~#4QHtnP%va zRo^4y4MjE~d)4>xyP?R&W3T#wJhRoC%8B}w;SDD z-RnP?MT7=G5y8icXBrH(Ip;W)Rs9@^8bf&`njKU(^$Q6sno9Re_bFAvJD2?T3FMT-Lj4E0MN@75h-Y8@NhplD|4dJ$+8o)90TymEsP*CA zk3M-1rPgjuqJ9+m7rOe(hwC*nxkN-WQ-4J)s>$Er`0DTY1s2kdwx$L2R9e6;1{J2L zYX`n1UDWUb^P)!I>$Y{~AHx2hn!Ro^9}~}_tk;wEdb8f*tfxP3t^S1wYodP(-G5xV z|HjgF7Ab<+{U774ji%u~C0`wc-@wENi>Fy`XOW^vKOOPbMu!Obp)UChdLlNO#vFj( zso9`u>()7HYTBp_CCsFo{0VP03-K_?Y&gE^z%O+7@X78X)oE$D<^+K;E!SZZX{|69 zK3~nlZ-B;p@g$>hIODA~ju8306eh&d9At?OKQ$QtU#y3kPtlZ`x(K-Ahs%*2d+iD7tg+0EEERuQ|XByepELG!+mzH+^08?+Xb8o zQtA@A3uSD`wOz~2?3k-QL<8sM zbi`YP&k*u6UGlRM$SH|=JsYk?_#8a@>d8W35I&cl2*RD+aubxr7?bP1U=v$byKC*j zO>L>dd35zh#W%uw$^y<^^Cs5ne53{TE8zI*0{jyD6tJI)5{6**rZNP(=t$3fp%Aeb z(UxM*qQku9wk5krv{|XO(N?=yJZH9im|U%5gso7kg>a2axHgusvq%{PYaQdQy)S{s zSL=nu80;zHX%^gBq$tw&!?pIl6wki8OehTb26`g)p2p9s%ZJs24rRIi+$N!Cy=0C|s>?wFt6l-eSDWw)t9lo}WYn;wkJ%gq z#`G~;B+^(+4g<@)$Jj&`j@)hKU-?m}5aU1fK9$w%c8 z0h^4%@zr+xvZisZHIfWAqL#W^1%@eVD>QW`o+Px;7KCw8GuEcrA)Z!jEVL!WTWH6G zyzG)!638iuSsjOKp{?Q>mk0`lfwo3Z1lqaX9IQ*njn&U~k)>u7SEsvBR!S}f8|kmt z;ZR5e;Q)3g9NgW4UjoY&MmH63rHYBT7NcP97G@KgnFjM(@g#$J9pWvR*9*Dtl0Q9x zoRXNkXTY^!-hgN9feD2H^G13iV9x8-i`4sBPN^E@J(I33r>k)ax-RExSWcgX2*7z0 z9AE9luPvvMWWbYJPK9Sn^S@7s*!)KUzgfiHqQz;zpDmtNz-`O@9L8J3pDXmYy7bRW zprOtjFBkGxxa6-)Ag3he>Q#)lR(Lf$ zzPd|D42-W4Ppf!}BK>ROS}WX*XJ5TeC=B`Q>4{ijem5Yis6KopYXl#h+um+7Lft6v z9=Z!=&5SjN(2`SA)4;vyscR)MpS5Fh2{&SD(YL1=CFhFsb5HV6x3_ZoLV0 zb6A+pyT~?6pBDwbpcT*t_@a1LXT3F9Z*A6Fm-Q~uYwBM@gf-lkh3+dZ-B)Aju6<<yT{$aS*a^J$Uuf8o5hWrtFB9_BvLd}x*MaTm4 zqM5JDHjU@03?+Pr?!tjiG%Q=a9PS;cnJ@38Hm%vbg)b7Mg0G0G?;EH$fM9*@lLl6r14~65a85D?KI+_OHQ@MEXn$XI~v56viM6=!qcO z(H(4pw+krMqO2q7E|fjw)#$S2DoS+}0)gkzaIk!fU*Z|PK(ez)%@W}(Mln2AI31@s zX$+4SPcnulAl_nlqL43i$)AuwPD#wyNpLNOPsFpYQbJ)c?4&1x;f!uM;@B>JREcuB z=q{9#F_-aEUaA`b!0u!?zIqaVi5qWf9ZLyG_ z>XI)>Ag3herw^{hEsbZaz6yoGZ5cgHxXFDs?czq2C}%lcjoVsYN2pFi0B}1Uj<3$Z zufHo%UPO>#_eqJB;$4t;w^4Z7V>jl^79hNDT(l>+F^K4jp`|dL;sZ-^FlzwF4|_gzdL%Gsa^P}8wEa%uAVHfj2w$ngNOl; z*TC^r4!;DFljUeSKuSfYg0y9wXh?`Smxy*Q!y<1)%hRCc#nXzp9d?c~-h#ee=slOd zkU&pS#GnY*g1!UKzA6cY0ey^~2=y~?h>dWGx9eT90vm{T>%E7R5MM3+={rM6V$ zo5}^;$%P0&J`M+86v8hd=L&Kp84#zI@rc{ezY-ob%|pXj7f&mU7KR4n?Ko+t(C>2T zcPG$O6tgt}*IMCPJmV^8p)fFBFP>KMltgkLuC>C`@$9Q-2!$cPfu4vJ4(Tq8EN>Tk zDnu!J=!PRCd52Qnh(O@}OgO%J7JiAl9U(~)U{0OlG3O*XT8M8FPJ1;cjs8CIv}WI; zcQfKGdbbGqvt9D%B#=`Q^YvV~7QI{X?5pPqg+cE&dLrl@=8W5G?V?DBi>{hIhyfHSMBHy(sL}boI38 z3NqQuH+$7x2nD#Wf#a*!;+NoZS`?-Mwp1=2TiY_+Eeu|#8EEugFP>KPZ0_%2ylolo z75X>0^lwa{rzmFVO^mnbzZo81y+ue2eD{f`RX#iR2%J zYfbPFo^je$C=B_>>4})2qZ`N7^*spf;!m|G>l1W!8@`~O=URor zVE8q9A{erVJFQ*xs1T)moo>sb$~O=S_`V6pR}bUYwjN>A4E(57yZC)eSbbZw()c|h zo@D&KgLsSIcZK|WF8TKp$SH|A`vF{w-=lc;)enWj;P)eXTJSr)UHqsJrTmz#Gokni z{6OxfaD4SM{1Q25LP1U1#q8(8GDE1hZM)<~7fDaida{^e4It2WFx@+&IoRziaBx2m*9}f#a*c;+N1l z2~gsw(^Tk9HWv7s@cz5ztr7i)cv=y)z0p4rZxMY=$p7V%|2u)4l9 zpHLV?r=e&>5j~IftN z>PY+&cAg-OO%Cj-{efW5j%!Hl=54S;xT8es(ORmu!!hF7l=U`ey)9WUll89D zyKToJ!VVjb6T0JFx)WmQI*SxRAWmewHQ+*ceDwq&F%~#UJk4@DixfrrC&IM`OySvA zokC&AyXc7+aGD2ZqJChr4LVazyKPj461wTO&P7f}Fbwo0IKDaszc7%u4Thi=VhDF% zZBY;yv#-`8krw=3_;9Kgzu>6BKUF-*AS_|LHA0`zr(OD`3G@`jEG~mV(<+{lNPY%fYlAcK?5nec!jPX$Ps9dC;S_cmja5ZW^x@V02ES`mYu7GRkW$Z~ ztIq^qX1S$ST5jseh=zU6h2yL9@XHFsGr=*5VKwTlt50C6qP9!ZcF*~e(1N`}*k7R8 zYhzs~o>sIh*cTz*g1u76FLudSC6H4Rv%MOw1$zyieYI974A|@FiGY2y>2PzU8jc7n zylsWosoF-H8dB^fboEOKy7vV;91FJW)Oy4N?x(=D4uJwUM}kmWxe67H=^6F%MoEMd4MEfyfcs zh+np81z3yPwX|v11c5PaT23OZQHJ32)i8d8Hf=;a$>8J}Z;dl5^xIu}FM*z-nDqi& zYnvjTeYHa<47ernw2G%Bl8?c)wkhM;R~4Z!2Y%?FHDfuO548x*ZgHxD_x=~=2 zu3ji#Z9fSujFdtQtWtyHt2%yJwYX3oO$RekQC+D5+Z44Sni{c&B(&)66wbReXI*7> zi>DPHi|z#CExOkV`E@S&^$FyZ#LW6|ExJ#~GuC2-!k~KtJrQ&lbmLV~EQaebt}K}b z-S-p#Q$5PuLwBLP9=wXYQ2N6?>>CjYjGqYyE426}#(a~wB`IL0#)&W&quAag9QSID z8ryy1NyheO#9M4{5%Oodf1hn#+byrulA>QtccEN& zZw~nLZKi00eHYRIz;??Jo;{9Yk{ zgG>I#1aeAZuHOXL0{&(^Vh1U?oLo;Z34k7TnhL#^amzb|h~pOBIB?ve!@Q#HBHt-OAJ9TI)bA2cIqOxj z-gwrlW<90HE$>EzZM@$jbnkWP-WN;PS)>T&{QZo#Hu?ZOI43J42ILQkr&(@ik)lZd zVYt>tAHlP)9ux{g{!w}&HVVfrMPz|_(afvO;}$AI2@lcLFQ7$tgXNtK231tyT6wum zeGF+~w2#B_)hF-^qj|evDyk-?awlS+3<6^YEuWG|YmraG=c~`)7aX-kJ}aJNWIo4u zYnjgr{TE#NFDB4a6!ZNh##_sL86IDKMM#YNuZpKtK1GrKYjCY)zK&-cT@?yL{!Mx! zmPy>+pTbbojRGI08@s*#EyTbq--hF>NASzK6uZ4oMRk=5j8lwS;yc3RyPAo%#P`IL zjKud5Zw>JSA%D~*|6u|-B{5$=f@|&YV?6unCqiL>{Hb_arBf2ge+Jju;pcev)h~p? zkpGgNh#h8io7;$kR%{9bP$^3K6C>(v;F zqB~ASJLsj6N|7?c)JdR*ot_8>XKV3GI~^D=)F6T~l}Xt+HJg+M*J0jnTh}^8r7o?K zHgC6hnqTb!1}7uJR&{p(F+fjd4NiVTUnP+jOM)wrYHB$W!=|Uf@zv@0 zg-yL1U>@qG&9fN?@%7Udd9t9NAv9-d8V&kc;z`c@*^IZ~pCj~7cInSepr^ z`}6Vas}(|Fz`H;^t>P((f2-T9`ND*;khfg@O4BdR&+@%>&qW!K7{! zxQTA;0CqECfaexCzRKX2c*YK3sc5^9UMcLhYIYh@p7-ye-e+aKvrSRpJysUdry|}$ zdXBZ zZ3v-&au|-UM(|50d9*A{gZxstc45p5t5MBL!?;~M$uN41w>TDrzUb2LNT8=EW~~I* z;y8w9UzLTz;8>w2f}``EO}j8sEy^0F8=fN7@7Yul2ox0@U)AtS6zwTeNdge5Sv-ig z?X3%aL(^;Mc8aGJI-B)fjJIv?ZlRxW>90+orzmFUI=B}3>+y_lOA3X7?&;!b6;DYd ze+FEO{0(@<%?3hY$Zw=4g8ck$9$k>*5c+g2c(>ESQa1{GCSBbqTnt9eI`Oj*0~_1~ z2X`Cb*ER~#bT9-JjWzJY%w7pi(=Ed?8(Z$i$p^442DA_}nH4F|U(;Fqw*eO0xyNL{Bwl|$;} zm$ezIhYu{^I-R%2wmPpD9q!ROXiMBHo}04X-mJGT>)o97Zqdt|Z$N}?ZQdw!Z*uA0 z981?(qzD4?7RFl>-UpAb?iUhcgSU#OS#D>MqDcQXxYmSk$Fr~AAryxEo%BRZIE^^~ zzgxl^{`Tk`H61CTGL-ND-Gu`uqkwN|;1on5S4s^9o3i+5%?Qu7=7%bk9da=ET}Tf* zz8elMRlqOo=sgd0f$E9XoFz`}Z(ZGN3|U*eSNOb7^U=0=zj%@{`2gaJ<|5J$3i*dz z@()# z;qPAckUaB--@WQ%^2{53_o|QMIr);&Paw8OI19@nc@@c|Na}p^t)c`CwwwG`(N7{# z^R1$~R_Pe;Hfw`VAtlZkQwO`a{2B2yw@fH5pGAbN51$jd&%1PAh^50-xd>-z`yyOh z9lnHTUwv69OnLr_c$&p_7AcA3UxjO{!`JZatFH@%A^!$FP1OO+qTTInv9nGpLkZuc zyHGB7zO+!@!IKFr|CNRs<|o;B(-UrNc^Iis3BCmfS1RC_mEaCk0xC6C)8#ZtcUluX zBAmXXIq7QfUGcmk>%B7Ty(;UyI_uq~o22g{!kXawLiYog?$KDf<xHhd+dCP4FW; z`|8I+VKDrOc$&p_7AcA3KZR>e@H0I7>gPgX$bUgklL^2~v=5ef?$mi|YLTc6CH#`E zp7`q%pko!q55r=8Czs-p&{94;Ch{vJh9!Ot$5+3>FD>zUSc1B>-yFdv&drvZ&b{fc zG34E2ZS`A`^*b#~+v@k?c~jPVbJlxH*1Iq3-LGx+2Siv~{ZZ)tPA zh4I!{e}%_ae-jde_TRqtiVS1Fc-*+W`2M3 z?iiJ!gn!f3r+QY7$U&aq<=`3@=Hxq+;o#~&NDg!T7Y@!6M-vCYsq7g##hJT zH?ZXK;(2e@dtcUjf7bgz*88BYUMC>Jn(#!STjzcuZB(MMq(>d;b&76*YbmFQH7w9qes&sTl;4WLhpCmDjJjJGCOCiKf) z`qL8VDT*09o$=NLXTamDGlj%pc$Rot>t&xUJFa1NevL#DOTH$7oRXNkwQ#LL*5TP#mk5PHbiH_5rBf2gp90q!q#w_| zx>P6(`DOG(404jbzNBv?UmI41p?n{^3@%gaL07vqhN@Ej2D%FeP6Fa`Z^^l-)rN7? zH<8swq=cm|hvTa&@JmZgO$W0vU-4#ZY6dsigwbYjvjnpl)CsJ$wjdnqyZ8<2-<9HN zh1=Gjt%$eQ8W8fQy5v_SkW&)T+6LEJ>uNmvDk~JmT2G@VVy&aPb31c|Qf>%a3E?t9 zn-z!(QtBXGeO!K}<>oAGU4v*CCI<%>+~Svp;c@wx#IOeSPP9f7!o$LIMDx_uDKDO6 zghvr?5#BE3o=aXxAg3hewFuWDyaUg;a6u>x!ejJA5I)kL+H=mAwp$6PABC3b>hCB7 z7xIRKunJ;HP>y?|fh%kR;&rUkrIdMbEXl$#4Rr#U*zd&qV$N<`MQNDY5oJfF;Z zpUQfl&U&B8dY{#Odjk=+V(b*UT`t}3Sh~(4MG%4s##_5x3lFYn5E29Y_2Oxk+gYS2 z())0&-JXtT919i-Lw*B25xa#4%N}AbkQdGTv&}cuS8lK?1Rr&H{&;GWNr~pG8WHfytT!1 zg#NiM{jCY~6vdoA53V)EZFt5CuTU5)Um%`V@svdJ7s9oscoCj`^CxG!AnDJHOX~of+;nj$@X1GhpU*nR$ zHi4Xyh{WA+tr=d2XJ5TuD2y5Ip(kPne&Aw9zOU|K&R*-=o-gNBVK}^hV+sRMLyEnZ zu72C+GO)u!0NJH1JM{*{1Nt|@@ztB~OX&HwPi*QplUrwz`X_>3)JUUIi+0;@5hAvA zIssca9foh);o~>?-S&Hj_aP-#dhyGzqIum}Z+X_cDC=#=dROB^d0B6FR=t(6J((GL zMMJ#}Ubz;3@(ub_kKV`c9e5J5*w&tYW?Egnox+MD<807HzB8HX87}2&wG_TlJh|*% z?|11;ykQB zlka;&z88aU_}ShA<~-MMeqYF$#Yx{vexO{v-%{E4hb+$k%kbKV_KSM)aHHM_LS9s_ zRLPAD@=MQcXz0kbrTte_?}H)Bm0-z6a?o5va6nv((~Yhl3K^dR#?cSi?nmB_GWcifk`HXuT*>*lfYd*EpTRhC@`C!PMHriF& zUKqp|)(?>DL5Ax`L#}K=qGt;y2Ot+Y^w{hDju<=?@>>mlXpsi_22FyNQ5NYY>83mt z7Z_bX7Vg zqRh`(1T&J!l?1xD-48 ztFex?8N0ywwoOtk^?Ltdc0MoJ5q>oDq{9LyJNPnhvuN8`{+(>U5M?{*(4Z~0#rX!F zFGe}zdykkWOkIGI6)w&+oWB%uzEGE($HUh9J#qMQ$djdotq0C+mT?<+<#8$P{f*qe z5^}d~6Tfcrc+@T)YS8#EN?SJc{z;|37BZGGiF}9T@u=+mhHSqcvONn(?{}5p zd9PmYmt^&gkQK*wZPp1gNQYu=yENP2`OPTPHfuxz(?bl?hoel}tPBZE=NP8n3YnhU zW<7}K=>3ssemi7ms#SXpay-xCETiQkQMPTD&v>@pKd9<=LY_2yn{9hM$Ko`j=yyY= z%)i-a%}-h45=mX_H@A<2@o1v*kPdje5w}+3N3ugTG+um(D^o-^2_U^>v zOG>{lJom@mJrerw2cPo_?*Tp@`{+%JAM+mKW9OL{+E;Fbfio!pl!kMFo zG5X&zm>Z&j{I9jRH%awW_W7<*Nnp7Un4`PrzoWCX0;pIyB zE_Z%szSMtcWYcC9f~FVsup=Wciyg?DkCG{jA{Cq z{jhVRUMQvYSGc$81e+zkL;VD7@PX%_O16I{*`AIZsh{H!%~uCHow0Fz8M#CK0wI}G zukPttz2@S~y6e|(SiAB1tt&TeSifQ2^=me5+PLZZ&1G|?b@8HZ~YIHcv_=zUt^-85wOBeF>QOUShA1f_Y zzagVz_@!oiksf1dF7OTJ%iNt9xj8c*on$?~n;oTmY5eBQyh3@rQAdj53ePHJW;g1i zedpl%%lNf#PQj_)A>3EL$8V3E&GKgAV;b;zdvZ5zQh&sgdX{7YDK!Sq3R2I;QzJ7E-@6_}QP&>?uc;1%`kDEI}#j@42lNC#XcB>#3q2I85c}wa)x7CvF?PA-2Y1TfrRg?K%rr8%&Aq zvFIr*9N4mDi)~aR&oh<-x#;>egRR*0hQ~zj?7}F%HdaF(_=aREH0*|JR3$*r+@s3K zP}rdx_@ZxiVHgWs-*GEU8Mf~jpLN^47fp0mis>DDna^_IOmWo+* zrj%X7+#Myc(;TWqQi66%1(B>UF*s}H999Wrf@x^h9JZq{s0Kmk2O+amXvyOZ%>D{@JqP59{Uvdr(5;6`P*2owJJfJTEbfFFq`jPSc5vatkl9=!$?T9hMtL=-8r=0)#zo{-7PTx4sBAuB#-&PV7!Mi8k58R^ zVCw!8#)-+}_ZM?d2a!>+eZzL_MrefGRT`uLQla^fd8M@#KgVkHZrfHC9}V5R~981ase++)F7xzjFte`vz5a0eI>Ie89i2t%LN&)@aiYKvnp^k?zauK!QMp?nii|1nbK-t% zqm!|CAe+9GAL2(il$p*DFX2$&g9ScX;`fz!p~R1t#6x_q&5xDDOL?Ci@v)gco(g_y z-F}6S@?e241Dc0paSCX(AKY|{uJeO02M;GxqIxtm@d%OhiVl)MO$Q;T`NZdl=arD# zqp>&x3OV6?ctg2i>)ADFH+W7=gQc^vI7ck)x~=7PrZ3MMr{~VMT)P2>U>k~sMo={< zrnsRCk6yMF+zmX-GLwtfSyY3U$iWS`@E7F>KF}Zs25+mOExREHl#5S3YBaF9nDmRr zMe-F@xUC4=$#DIO7a>$l<>u^~>ucXWW}J@TO#INTw7mz?y`H|Fbv?jRW5yg@zH57m zkTwEev9a8=lz}whEFA1KI4#ExZDYyyTs#D{Mh{d2S%>44s#q-MC`#1>$?z;*)uykg^bqr!ikUpKRmy zXHr2Z=@8RFIEt~A1pFJo6Vm=q;DaWct{p3P``O(luSMcZ+bj4WN46BdwOjfI^d zUC3NmIzyBRXQg2F`%%5jBy)r_v&9@tHwDT%7U-K{}F+SJ3NNy1W`-uXW^E_wZiPeP7NZIffH#LD;8f6-t0m@hLNpd>)D>D zzaV^O_NJJFHFIZ53#B4TG)h7g_#&?}v%hYyIIIzJA!oLdhZ{zGOVmyYkcvf^shxJC z0s_{i^gMAM75F{;-Y&Wc*6o>amr}(yL5O82ekB&GnM{#xB8qUrc|8`-63!erqJ%{T zp2uW2(${W=^o|T^45Xbf#Or|d^|5#^!+O1i^+qh-kYQ~!{aZKIH*SXY&J61pq*z-p z#Ph)VLM*;8!+N8I^=2&A2X*0|RGt6%W=JjyI7XkYh$Kp#e>^EE3 z-x7;2CG2~Ey&id?i*(XrzS9U?KkO#p%QhomBqQL%u|PQ1!EJ+aX8T%`NrdqLkjV)1<$pD7{c zj<#9i`)LpVK*8)y2E`99AlcY*-v;~nu8j2`T9<|qKFbYRJtuw`NZ%QYA0eUa0;DOZ zWa?^-`8vJ7Dt;8Szaw>_1I=~f$B2v{-#|D`7yT1D61^)HKS?4WFz-wSt7FekZLoIc zE=t#bdYx}?z{-(xSSz3{Cw>MLzdIH`n}K~-O5M)(Y2xRIz@IOeeON<>@`Y`Q$?Ab+ zTl(TXH*r?PM_bldD1JfvI^w;FjLjWb#8w)fTXDm+mgn!qFisxg z7kQPusb=Muy7kTK4UU{n<-{+8i}%IiR}wBj>#wE)5x-V2w-cx0{rtTPeV`i1af+^r zUuSKxaD=~`g!LP+Knf-q;o>)8cfS>j-v(SAbKeKMLjvEQVHUrm4H52}JUD6p1F`sB z%x=?uJ%G4zxVtKTucdviRsQ=MsI95;KhP@wU@ZQSxT3WmXjl0kZLoGGPh9*lC#8NU z7Jrg0{9w9JtMS9@)yUs-lP4<6h}}qKANhR z{rn>xy7Jv`PX2iVXft;cH~*q_@`+gdD+z=a ze!N{L|F)SZ!ENBl4J@#$D@fyV?$t&&fzS4kRjhLEecO&)<-P_$QV_8s5QcDLNugyN7P?g`+Bywr7>Spt6{gbSMKiyETkG35&$&+C8Buqp~)1`n30tf-IYOqYjK+E`L*B$|sI3#%^k z9A%s`uB6Z}T0L*RTtGgi=Oji&?hROI4zx;mk}Es)3At;C(OY+maJ(`=N40P8mln zi?UP8Iy#kzPP@~rcvz%*I(BpBT~PeD6g(q}uSCgauD2!rraye@^11V8T$Jj=3OO`d zjmivI@BC1>LY9;ndnw%#O{|5Z$4@+1oWy^}kB%7+OioUY5tuOpwvGF$a#X7^sXa?F zB$*f3PC7K&5dzJ@jxZ#L5qKyl?xg@^_R%$fA1JvGuWM$%XZy7XcVBWp-gPdJTyAVq z138uj>jMOJ;#M@f70o&#V#|4bmI3irG)sfrt!UPab@^M-?DILA$=eZIaQ33=$-nma;i`-;*9CgnlQm^BBRy%{JADLJA69m22Kj{(XDl{~Bm zUqpku)oQ&QcviMb$s-spNrOJ_>FD*)@b1!I^YnFJ*HR=YKzO&F@M4-UfFhW_JXG=? zJ$f&GDTtaovo;fo$nq$L(OA;trP-4r3wqfoeu;WIAcD{P^z?q$NxDzuF}$MzWw4!C zCCBvAaauZ<(UDMC#8@mR=*w=DvUInEl~nqaoYc#X)5m_?!-mL{lstjgwmK;?B)T?p za!=Y`goYn^KfVX@Bz|N00L?XrGkIFns{k$+m3#@lfX=<>&IqJ~%!X=`sFCAFNIr;f zv*u1&tzZGto=AG~A&i+LJPaFC6BA(&puxyxVYL{@+5`{l-h_N9%@}~(DonRTOyT_| z8hcoSI86)i@?{$KkirqJVWo{1NDLO$%1?BF3>9QS^2FYLHHzO(qXfiJ5o-TxQqhB|UdPsf}*+Nck4(?_dSyK8KI! zf?xAjaoyoB$}4*E5N(GxLw>a$ z-PNTrS;DZ{6NT0BhvaMMIhTT>O=KmIT)$n(@+wUm&`ZV@#AVa_F3WKh_07T7wGhjC z?EoANnvkmQimcEy+HrQ-T6Ud~O9vw}{pL`okxE7rd(8Q>AgM~veQ86TtkK(r|G)k! z$qC>tl$1g=F46Wz;}mp=QCwl5@{gh%ss0)UX3ZgOrY)r`>d~F5j+PEfMCj;K(!&_8 znaLWPuBeVN^GNE7mY0`{$w5iGA_v`Jn5-tGPqPqE(3!Skaks`j{6)cxhPQixUy}`b zJk+U-t{p8f<(l4UL*&p=v&p0{Y4KLqk%}IMu(jH)D8%5JIg)r0<}qshQ_xb#lhZdi zi!cRyGF7kMYf3I*uw)+48_z{E3wm47Cdqs<1V=98>rLG1*vrkK6%0|h(V}dIh*g3e zmCgO#K~Z-F%Imafi|$~P&*C|juf=bH4=458eYjZM%TGq;a7VLd60KuB|8@A~bZv*@ z?CbSf)OdA+LDt!aW-PQ_QNPLPVK7B0Y literal 5874 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 diff --git a/docs/build/.doctrees/introduction.doctree b/docs/build/.doctrees/introduction.doctree index 0b8f096857320f138554fbd27c39480bec18c0aa..d0edc5ea37f05de54b5b3b73c12d0d3d46e725a2 100644 GIT binary patch delta 3172 zcmaJ@X>=P`6_y>#wrtCBoLGr>J8_m;wc}7X327P{LhHIFP6{64QryW%8hK_S&8T-q zjtx~zfItF;(mr-5bz0Ia&f++AIq(NKhaa57DHM9x*Rq$Tr72LLrSRQ1vZ4T|Km6vs zd%y2{-@ET>Jjb6aec_qXxg#^;$Y5P}+)1Z(+pD5)lm!CU9HwvLk)tOuZZO(LZwLee z-5uK!PJG*b@r8#w#H!=VyNsyyCFG6kjdjLmr6R9D-{M$%j=s%Yad=;g`1(xs@~1dD z@c)EXrH>q@?;xF{?=r<`Qv#4?1KF6Cjahj|-;WPMiUtnQGjXu9{#vv}&FU8z+d*81(GX(svCH^-U zv5(~wEEdUg_AuwSW#agz$lg{y#qT&bFj|GG?A)#=2**DE25Ge56@CbRyLEXHKxi=Oz3yMi%w)7Hbq+d_h{g zqm3>8g>wU=wXnET8M*c5;gP{zzRkZP1Dk)tBS$Y{EK=lcuCn=eiTa0+5>K3LYxpPT z#a_8KFUfko%o#TOFG+gECs{thVx?lU|CVOU+u3YDeC_0#{*s_HR*FZCmSALKc{WR4 zWvoo1f};IYb3-}i|81#4Xs07w!;O`kWm_Rhslv#@KFMO8Vq4YVtt{_gS2f~`riEajX7iEXG^aOZ0LdJ-P&!(A(QfD>xlBdW!dJ0J&u}!kK{r za3z-5a}^#rYQ)HfeYVBYg-XC>6V`IiW{Ga`(fCp0URojEP#HB^_&9Ci^65-dJMxh3 zkl@Y&IBGSEd(L#!uSNtN+Ke--k&rHN{!ApqVHR8~o;b6zVjWi2_EJ%Dwn+qMBdzN> z%g5dzDc4|xVc$YGTd0m&ti~>)Vsv&()3sQ^MUTYy7VuGPgZT2<73((2cwbRmpoV_g ztVJ)I*h|T4#7ncATdxCDI_Q_o>kCYH-kf*9qU>Ocu+MI7+KLq%yjJ4374T83Uplx! z#2LIv`(~J%7qJ3 zq$gb(mdrgEl}ZFy(51aHc)NJ+d}QsLFdw5gGho?OP9%LqW);zWp{eyPa=wp|p}6VE zGq^M=qjw1HLS*`_m=7!U1yd?EbG32oc&9|Yjk&Oqama(=F$u3U6Zuo$h4oOG5-zsk zk}9eDnHpZ)R+Z*7#BvnlOr2>b%r&IPJrxBU*FBfwl9%9YrK@|MX^*?4i^jS3^+^OB z8poI;1LFpzzEHK87C(?O?E@~ELY-@Fu#g{CYA6|3W16e0SxV6Xao=3$vJ^5(2#1a9 zOro^-%3Q;=E%CLkM`lLN|EqV&krg+6iVol-W8?2dFPSvP;WBdi}(%HximG{@<^QdV9DI%C3g zP0t~EpLlw{rR1o1WxlKC7!b#lCAvL9?-#w7B8?xAlsbJfTqq}y>6^gc0jDKh0=DzcGP0(K{MeQ z^zmR|V_>LKsYN3>bY+82Ffyo&+yYOZPJ zAbL~@jqA3Kw-<`|AOgcmt-Q!9Rr;)q*0^cB#nijAc%!-W5Msxa8r{wmhj8@JmxvNAiT$)Ts|k~7cm-}@pX&;9P?t|=es*D XAu>jfVN}Zb!a!S)(8n=mW4-?YcP_%a delta 3797 zcmZu!3wRV)6;1*n3FJjcLX!}Z@C&HX5^)EF)k&fw6IovA|en^cr;Kxo%FOsUkUazlKrgD?zlE9D@)uoxV9WoIBq)19USF(F_Nlld4at_5Wgph z-}i~ubL6LDr= z<=huBvu=YKu(rW=lt5&QmhcSS_RO&6nT{>i-&>zOAWH9>srlJ{%AD;m$o4ZllJpX6 zCL3b^T+SOhjmA#;1&^&Tti{Q6V{WH0r_)%}nZTY{EN|eK{A}&07iX5v5IY7-Q}io% z_Uxu#YP5GbTcsvCK035hn7_;FAAV;$Qah9amsCDY4GVuIhFq z?wGc>gMJ6lWm@rj=8Ds)nwD1>!0q@021)uOFQBAp9%mZgj#s%I+hsfcgr%`|{8@5e zo5 z!DGK2oNQt{SS+{WpZsj?xNLU%7Z%Z(e@|Y=x&DLMSZg@@Wm}^sLjGU6VDjetmv2t( z>R|2I#rdBM#a+zG<dzgQ@JfTo2y^WYpor{(6~Wqzw!FE z_DvgoofTMt&PqIzG!u4QL{2k7=M@rF<)cL1(Cmt8j4u{jBgM|*6&717Nwa;DTV&^~xq7AgNihTPCtOWJXdGj%GTPJX=dR{n9r9BGX?b z16=I~=$M42G&eWXQeF-iP2)9O!y2{+8(70KUT@97mtzVsEAU9twXnPuKZeB#GGaho ziMhDy$zUW{SWEp*kK(>Vowv z9k6zY`9~TycEi)AOpWMahvYYy4CyJum)YrZ9M5(fMaWcXiJ*?QWO$h@NA03*Q5$L+_Q~0oX>REK}c*7Vd%e9+&otlgDZ* z?!kJSQb=84HKe(^nxH7%D_%O*klKe8r2u;i>rA5iFdtBg<9eLM=%_*%8@*rhi(T|A zu7;gh48?Kj0qHIB9F97c@Ielki*@R!hj?5R)$HyB>IGs%r~T~9(QOhus4s|Uo{r-X zi_`nyV3D$rYE<8)N9p}y@Ob&656G3Nh89Ijr2m8P1ap<5w4$iyx{&!H@#66Xt3S+v zOIeW%zkzsB?2bOd?%aqTN_5jlWx!HB7Sg#*{ZZXDblsze#rhNTx<1CpDI7ZN(Z_jG z-@UzXdyPPz5&<7XyYGk8*NJqLBqesP4 zCuUg(IC?&+-86s6(g=N$r_=R5)8oVACEWCOhd4&AG9~QTU1m2uhH0B3+Y$EEU8ZY# z4$(pBtq4aQtw+^kni*BKNQ7`$QJP_~b!1-QAwc#jlXQC*9TsUI5AR{Iws-MTqi+coi9TV9hZb+K1t)w#|h~zaS~oU;o*lWI*qN;2;53; z!s}{UK__`UH64o2-zh(mX-}YxGOR#Sno=(IdYT<2YQ%K45Pk%wEAS~vF2E&=Lx$N$ zpJo`JWG$rjni0>S&%hJRSBh~Z9lD_Dvkc5t+HZiS&+#}<^+nO=d3JM);_BqK^gD(o z&Yo;dJVBpmFiP2nSjsPsD{Xeo1-@-56eX>dpjS zeJ(u#-(IDVZ&>CoJ_(;vXLw$GWlKwMZ*OzF-!mM$*&(s-RN>q+yj<$`+n&~^8fLc< z#Xpa}%L6yd0&OYk_Wb!-!|%n@_dbqX_b@Dci!NR3up^=;-PLP~|(9}78AHS@~xU+Le$ zaxhDo#IN&n7~_AS;_T@88RzBrX;^$&*YNlI8FA?J;`(Rd>7?gil_}gv(4Lpj7hn^i G=Kllk9dM=q diff --git a/docs/build/.doctrees/references.doctree b/docs/build/.doctrees/references.doctree index eb204d1f0b68ba2ed980df84c7726f7529e58d85..0d236e7544b4eac7b9f3b8af5724a63f8e504b12 100644 GIT binary patch delta 11070 zcmZu%2Ygh;*Ubb-gKSAiXrTr|C!_GQ!Iw7U+cE!`<* zd7bj}?35J$H2)I+i3#~dw%XAo`id{r60ex+_zJES%jDOn67n1MjaL+;f6I;Ulyk1w z>NuohV-)*+wsJ2QL$gQun};luKQQb^C48b=>QC${P5+sVht+9h{3Tm`P%4HO|4JNd zgH*-+Gb{b4`&0dA)X3k^F7~&A>UWO%Lp|UVLvx72KiR(yJyw+U&un$jFUp;N!H%x} z&DejCFChA$r zjz~jU>@V@8_)CMt5wG>dYyG>yn9lwT?XOBpNlB?ULPpF0(8j-y_%oTmIty5TwGApe z!-7;@m?~RSaVjF$sUAiu2m75izb(U~l)=RrTy8-8PaqVby8}N`+FqR2UBHmV$lIGk`ltu$ zMJ%H)8kmh9>PSdj-&N0!>_F-3WMiec*EMv>odB>P5bAKg#?82{u-jGDzV+CgF| z%^1U|qa136PvncS>fl0AQhYRwPy|(WMT}!EQp9+UIz}nKD0UNtT@lBsm;7R+V*N6gX@RTI{F67p=6)-;@< zg>nu%X4@RL8^c1W=1#e57(y#>4u^R6teqzsM8;gSFj?o2Qf|%mQOlG_Ed^?c)a^Og zBn&aS9&Yr;V1@>iw1!OvqHU*_xOjngK%>=QD+DPZ81@iLx)IegDx&+|%Uq<9^Em2!HDkUQ>?R7kMiN>hOJGJ9TNt`jJw0DcbuU9hYvckpU#K-gbass_ z=a~{KSR*qof)_*=bHqxN*5V+JHjS)e|0UXA)d9<}(TL$@*vMHRtC{@lO98|(G;#7W zG?CElO*MSLyYC9XmGI9JWd4^?yC5b#G({t*#q5 zE5sBxCP&^(W>W{Z5CQ8T?N%~ZtLZU3FK)v%dbSBwLf(#&)~ZQIDcWh_4o2OnwXhit z&WUPrP^U_2;V$m($Ss7S-frc{yHOIq8A^1?P_2b++)ML$v0ZCn2Vm&pJxpDu=wLL#jGMqmhP&s1ch`83`Now0NB&@;a9WiJS!ThPwM)k$=jY zXrfDRas1mlp5;l5=n_`(3ph4Xj&~UIuEtd1e3DWZiHF-#y!Y=E@2j%&MBeld5P=jQ za`;C&oKp~C`xGBD<`a!+_avh+e98%0+Q#r1fd78teGVJ4eZfp$Y9`J`+{tW4ycYlb zBQUJ582PmgXG-215DbAD>1GC^irN)0+O&p1-YqX#Q zJm$^N2czO4G8HzADz^jbgzSjYTJe9WDN9As_)Z#-#{P8euj+z)S-x<6KWW6`GThG` zojDa5jLlRxEfvMxve5YN;5(}imWr9FU7Tuui3bXFti^%^@c{DOLaA?qS@CN2>W6N#@`jz72}u5Jk$x9kJ1`H^@CSqFc0&L zD_)S+`$0FxcXz5O3&hC2Jy;0Ht%~0Y? zhUyK!kF$0D>-p&!)cL-cs76uVrypRrUHzG2KzoV;r+TDOi={`xy2gtNpx@ zSyP1@X-mORj0v2=I}vD8Ko(d|f(_{=Gu;$T$GaIN(CO$k`#APbwfSv1_=#~m=O~)S z5ZcX8;E)q_NL5EjuHF~((QP@&**dd4e>!o};y9V=>Lfx=M;l$LK$VcEptNRy7xMW7=Aab_RT)rC%TPqe{+%1;nRw&>1K#VxnnJJc}_Njj8Gc4t^9- z@6Y5kqpdV&0fRJ2h^&GESQ6BFb+|~#dX##Mh%vaKx1a{Xed%kr z%~BfTXrx-d<;{*p;Ju#1=y=AYgqWI;c@MOF4{p?b%FKu6*nyZ|LS5;UVD zYX54H+jk-R7HOZgEhK+oSj>Az#<>hSZ4IG^{4;slJ?C#bUT6HAzi_K6mbTB_Dv zDu(0`g?(#SW^N4^z>Y3n$k^q|b(uIJX9XI1FSv-!7n^$l5!&~Hm3l8&rT2nam%t6G z)f{svN^8LqOWR&>8T&8SeqQkLW@wFmZnGueonQ?osJQ}IQyUs8*TRBSS29(rrs7pg z4)%vv#0J#B8 zbm)3i3AquaHFn}_FV-6vbEC#2_Yq2Q6Q^jhQrrv(QdoPDyajFKxRv8?)A2lLVoc_+ z9pyGL@^%~2whDNe-@(Io-^mbqTyEx&yVT!n#I32Doa>sz1~qA|NOx?3!#cHX)vG-7 zZVs!i>ZaTcBCnv%7Yg;0+h9a5w=>ZWMwFO{o{YeKrYz?1*slSPqDsie)Y4WlCdW3| z$Jzdbda6~7=>8-c{~hd8=3vKMCCXBt)`K;k0n{4ov&``vN*XMaV6e|~TMzaHy4Hhz z8kg+``yveO2TOgT!5&aqSBrw4FQJJc9z>Op|3hhw@L3vRp7LBR3LP(N&?^jjRb6zo z7~Vq<6RXcUw7tgO*UdxQ8)#v)v?Q@->eZAyw7tp8aMWAmq*eGfhrWZ7#%P8TUouo5 z+TPWNwsh+Md*-3-eZVk)518V^_7rcMhqjN{`7s^Zwq<{UhILl^RG-y8WB=zm*xHnX zCXml+U+6vfONw0^q{_Yrf5nls2Y-z#`sz}(UKHgJt$h#v*4%@?gB@Ky#MtlYcs8`- z4`}F<*^ju=yYWv9_}ScziQ2v!ALdC+{lz?){R%fof8&_nQCc^WSlV{uKiL1L_VdYX zfiDy=_hVZM-jDy{6yCpqhA!wASPLxwfeq=7FdgKPOh*aYc4Pq$KGWFzwj8`8cW}`G z-qjHhe$DR05ozk+9U?b1-9=}yQN%*`JcDZ64w;EII+4X8ol#n2?&MPY2aDXoF6_y+ zd35^ZX~yB=(~J`cKFzo|z?}?$)N)*Wni)rTh)y%aNu9~1nocuaIVR5@lg|w;&u*&! z29bLdF;iG~s{eI>=>Z$Y(GwLu&7ic#(N*ogRrDEHz>eNFhiz%`#@>f#(cPCJB&mKJ z(qFy3L98hlfChR_G*FbeuK55{=;8xR+CXwus}DDbRbvO?8oen(m5_r`k|<3IqSbeo zk`yy!h=!;G8%1HkP&EGkRKr~K#-$$FC^E%x_3TDbJhlX2WGZEv5t=4wG7vXsG9PgC zeX@*!z|jmCgVLNq`hF8Mku2n+T>JsEY%E2u4N_%S^3fbh zN*;%+gdET9lB&LH?t1Ko+iw&uHzl#l`WSWaMtr+D7G?})0>@2M?wdq;$4O{tRZqs1 zR`nDH9Ou&STa?DG>ZyDqQ;)}e!TVOtG`K-}0>_+)(pnkB(x&Q@*nhJ2S7D(hh3ahx z7@_(oZiy`ui+eg}5*2F3&7x<;DTqa)Q<8vAG1{5D$F^GvPh(*e<6 zaX4a@*0V?JnOH~*XHsp`^I2%46IC2?HcD%>XPA0Ehdr}x9-Tg^=W4Cz8X#EDb2z}8 z41k{JntG;tLwY7o>Wq(S(sL~?60#1ZPLG&+ZfHFlTF=BxVGUIOOV5q4VH|!`2^m0X zjiXlUImnKAHivCZv7SSE7MlR!CuEo-BC7m0aaKVySxbY&(zd!;&hxdLg;bB<^iENjdJR*%uVoKy$Ja4(gSy~O@tpNn9=t5BH3!C=ePkl5Z}n~o76X( zh0&8@%_I8FY`+C15>^$^kF9-r&6T&pfK^98+@q=3dij^u+g$3yrDBZwe!a+Otvfw8 zb(4#Jb{wZhY!PWKw*zgRWbeRrLf(mzmLbzYTAR78rF9ow>yzvzT(&#OZh@iwNtXIa z^R|_0{oA8;Ji*!hZdh03FO%C)C**dN*7V(h2T@5^_1+dy;n+baeB)uzy=v@MF)LqB z1qp&Cbtk)ax%jup9=p-PUj>x4X@w_v4?o*FXIg&;vlnxKUlc3$Y5Mz^<9?Jha?KGW zVqQsh>3JN{#nG72@${wOv|ICUEE8)Y;P5pfY zR|&bF+w|8jh<%iH-Mn=FFq*B$RM&0d#4(QpfPp{3=qHot(jdj7TlDUSv;8H|1W2D^ z*wgBYZTPE{XVm6xqDVZe4sH{9J)eUMM9(wg1(en_KecYV7&!GsSSmeP^+sJp`eK1d zSRPPbS7&XHb~@8SRxG zN3+pPJv5{duET9P%mfa6APh8n>mp4}Mi~A3g{|4K2+>+8ePZw`JK=jRmmgnCL z%<}s}_-e_pe+bselWle|m%yRdqq)did(sNio8L>O+BkOYU7PE71xy>m(f&f z(7jt6GNOJ1pT#?_m<1DDF%+-sD|GYg(qMwL3+({B)+tu8-6O z!VMAAm!TfoDe_A)*_TH%iLXsO>0WFb%^?g}W)Wuwd?ka*M%T;EO6(Hdin_pxVWibX z!VQ5&navD5>sS>F0?|M$BBeu3+$9QxQ_bEb2DZ3>S>@?qgcnFR!=3f!)BMUs5ydL9|$9R*cXb*T(;-XxT5jeSRjTz>cMqo&d&O`TYMJ|&y#r!cF+p4 zEsK2jD4IrRBpz#y$Gli8k$Nmr*$rq)Yp2$?&E zBAB9~>hV2dz_?+s1bKluXwUGr4RknRQv>y~gf1LdV)k29s`~B~1M)|}SLw+%XN%Wa zRF*Yw8XnW$pC3lojYU5tfCwDYCQ#>7LTOWh#ggiy71O!9@8%hyQqa+-c zVX#+FL8>5j#E!l9hF$-Ppki14-+Qxrwe>=Z*ZY(qH>+F;iU!|&;BWBya!S(WG@>_H<`9F2oEeg}WT-SYfjb^ELKgCJ(Xc;cPX3p~z4DF|N+$P7DH`+LXM>WV%xvf*lKaI^B)XKd|LoO25O5gCflJc@s+w#d}RT`XsY%!Rr~G(VjAnywZ0-PB_*ZS8}vrKFtp%5K=>Jq zUzrJ8U!}!VWzabMn>KBRL(}xiRw=DQuaC#8QvRO)sCoibCClfQ}OQ1csDg2`5X^`cYiFq_Gf zob=(W7@-FHL?3f);R-+2driW`Ph!f(fUv z{#32!@~-m-qh5)-V!>x7oW}T-r^D7@f;N>?AVH`z80t(7#Y`aZ1QVvRewsyZiNH*l z&Jjvxz=WA_7JJOp9u*m&>3{+A7J6iTgz94!S?7%|3lK0-!$o~%lASyoGURp+x|pm$ zYc9iCnkVP7;yjDOa(kF3vpFeeC7VzkR*Z5f+PZRL{BMx{k)8%q6)& z8Bd%tY1JgI+;hV`F;@()!2rqCqKio{S}JS8rgY%vNIw62WKSPMl@%l7S1%WVu9w& zLgq{}MBof{R?aNKSaarl_PId0=8Jw#0=uw@17S89Dl8x&-{H;VgU0cWbLV(@cU z!w>~&*vZQ=M35^O~JR#TwPu8&hDvRC%&pc^ic*oT+VV<5KBo3Ii&LHZFwk6hoW)OQE)blR3s3S&iKmzggP(>LK+mwpvuMpiA&dkU_Ot#u zn{tmAc}@!?GvIlKobv*Vj4c_(;12pDQTZZdz<-JTUq)-<6Jmk}udx1Ai{280Y494y z81y=u(8=iyc6n2~RI~>@!*z8LZ*<($aL7xKjJL=mZ)RD5z=<1gQ(yUSQ~3_WNby~C zG5H=^GewGk6j9JG;wVWm-)EZ-)Su_$wDuw62NL)nK?{7GXi{pzO(C6${1{Tef5NT@ z(3Nk{)d|pU*kos6@OkU}&tj`KXj%LWXExG1Au>iMO3k)qx8{r=o9I z@vTK+sW2w~|9GLD-?0f%>3eoLsHR*f9?T$r--L?m)vHl)ZLYq@Kak8|{h>JP#bNbc zOf0bd2rV-DiBW!5a~6x{9D>A>egO0%EdND)y;$_^{3`~aIH`RTrULT^n)q)vwWgEk zr_Nj=`lbGEYjyYss?u2^cgY{H3uFmCYW~SMf1$Tt?aN62xt9ERZrp{j~x^}fpJe}nk+PR`o9a=2%dpCOO{UJ}(J6C2x zaqLoL+4WMqvRT+v!7@>h+TNa6j}GuL*CU(ZZ5lqnXjl(BkM(*u=vuEwdt4^1hZBP2 z^`LZVJ#whmMQ$dG70HEkc^;MoeN5(|HCH5CWnCn?W#qG>lU+52MS1tm7-4d{m;$WC+6LT`2i|pf}qVsXx|NXf8X zP1Rds*Dufg385uGops+Iz^?T2JP=ngIY>Qxi5QVXxYm8Y)Xp!@_A`Fg7ULsY&__kaquvxWje_5kRjv=3^_qV z^8Q6ZbVz#9K9Th&S@f1T{JfYLkJD!oo6-I~ncYs-ZWV2@0n}o)beB#c^StV^03lQL zoJxIV+(w>;F%ml+T})0vYpw-hCU4_su+5p;hF`PvlZoO?Xleu>>BF2QB<~tm#zR)FfxST( zZ7#2Z1mRj3?rIIkg-ii-INCE>S$~a1Z;8Tt#@cw4;&p6Bdxm1S_1cYJ@50pqD*tQA zI?r7eAYdxU>!{anfH0BQV~DhFKo^rYqBU265R-X)6WiRZZQ^?l1-XTTgc3n+g%N_7 zdzZWoV??=~5$@0kJbA*5M=`#24aRνoUJrKM1q4|m3wq03!tMo-|o*=>_{t5~O& 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 diff --git a/docs/build/.doctrees/representing_data.doctree b/docs/build/.doctrees/representing_data.doctree index 1ae60d1bddf12ceee439ceb57242746e6e96aaee..838d2ce7e5d03ad5eac3395199e49bc2e9147531 100644 GIT binary patch delta 2471 zcmcgu$#WD{7*7Zx3z;Dy1R-k=BoL4UBFH9!D+0mbLPG?Hrf2%iye2)peD5_RhpEVc zDyZ^c*;U>=aJI^_%74JKH@#_<{YbK}dvpD0;&*SmM}OQsC_k;MtJ@z?#nyt9VUsYV zz@Y>DdZ95B#6}@@J;x||!uAc@cZ>=M*Dsf1&mf^x5K~d8RuUm@!q>KT17D3A2eqMO z5JOeEj!5n{^d`5PvxngXHc050V9xW5pk!z%jFKxnM;X2lj&Rt21zy6~Ltw12V4q_m z!?CqJnyl&>o08in|L-Jb|H~wzuP&UAzOF{!49jl$rRg?8EX(~_T#zJv1HvR4ksoZKtdR-^Be%ga`b9hW0= zk?hJxKMbQ*jq|3WAK~|>YIL0)+(TjFS`zl;#(swT8`bC+=Jr6R=J~;nZ>Cxw^mHA* z#cpp8n@uE2dL|T;4NyhQ(VY}JXOkcOdeLlQwoPTBqu(Z1K;%JolY!o?v%gPvfPHVc zFn1QWKKdh;o5qiGR(}>@Ki00NM*YCSBzi7p+4fqazmUS+YV+)33w7Ga1k9Fv@`|V7;INFl|tZI z^ID2Bfb*1PW``Z?V4hR6w=CthCvWt1Z|g|u03R!G8K7<6%kYz5XwrcaGfxlUB^;rpr+NhkJp=-`(selovQY*YG&SNEwWx< zRqA6-yX!kHiK4b@qoAliRU2StvuY7qquq*(WVU}GlLH#i8k|*Ho4nJ%x^ECPTzA4m zK#t{zQ237Ui!N$6lw9ratY4Sh>d(!tXL~K0?4l^x#VH!b{i2x-MaUsx+xggEv;pXZ znN^zH&?*L%3aX)KV~S>UP_$SV)tOTCFdJ+HpJr$i(;3h9%duS+P;>;1Ft$keG|EI< z3kFu8T9uGCCzl60_ih0^j+!H6g%s1*OJe{h%vNc8Xo5H^JbHx18qLgHNS>_};@q0t z7+7~;8(VJYkQ8u<_IOB-9%Z=E5rw!++f#$>qEZmttQk)zDTJmSz%QA7L9D}AziF&0 z?PR4T?4_t_7hf_8UGd|JHC@7|fp!B!p_e&vCA3gSUooucG2j?wJU`bj1#`lCYQkB5 z>ik+^SJ)MLJh@`@?%2!Ln^F0Zp)K1S+Q-)pG3{!eCLOD}%%5NzjpmYK;FsJoJ(-N< zy8532IAL}by}+KbM8$SJ%XS?80--8TCr)lqJ_C_UW`pq0(zB^_E#y#%*gum)@$5KRKWyGVP#P_I`~v`tm81FR^q&B`KJm`miu<71Uvj6OL@d9ZJz0PCW;l4X&fe1zn{x$>)RXI;T?F!--Gr2!C2C$*Q4^h7Tl90ZwxX PHw|iGDjMO83#0!4fxsS- delta 1980 zcmZ`)TW=dh6poWPiIdoIF0N^kCcY$&8=78dDQ#&BujY910loU znu0DE-~~UdYcWzAP-Pc1M1awW;*L&!N3-@uZz91;5DGr#3f# zMW)vq!EeaazDg(lUbJURcA*s9C~Dn`$RGAKX3v%EV#%H_g~;~`x&MjjXdidiX~AEZ zo4*^uO^m5^p#Al*Y~b~JT}Lo5+~6PFhh;`Td1N}>?1*3iw0On$h35-~nXhn&AlQIT zT(v#~Jfp@qbSbc#z$Cv|EBmg#;l&|}a0z-yIc$W&-_W$;^(t*2X~*t#ZMIL0DT{ux zh?$NsgyVZ01{6AotG!@%YJ+A7z&xS5j>jO5@RBwn!0>p*hnkKgxlu)?3FS6~E77m} zfLDj4GVgONony%a4C8(=B9}AC=7_Q%CHwfs;Vt50h&3QZnxupM0n8M7tKu9sDYrB! z`#2K$ydO5B-iL8q8!$n(+KBujGa8wcH!>5c zEr={>gHSQ`vcZ`if(uh}Yc@MKjT$Y6mo-`0Fjpai`*kf*XLVxAFfI64AS;7xs+m(! z;=-pW<|<}ul|!BaL@|4C5oSn^yM|W{F=4vtTMT9i>tr4jNQhb3SeU3<9p-RPoKzxp zmz`%W6cJm*g6I|+Ii4yPY*mK+_JHAHiIm;EOy*LNdu1V4nAt{glSGKS2DdSui$!Afhg{FibqxGKB4U^V1{4YHSx=tJGY?9M$FJRlF}(}jH$ zD5~|CzE^RouwMm8v`Fda9qtGp;DCHNKN)#YzLlTJA4H4GS{L)q!y#onWQDc5Ugs>d zkX_A%!}8O7y7`b&j?j&GSQd^zI<0FxtRf7w#vgV1DjZS93F?a5YW+wnz8{KuD0Hm9 zPxA{NMOcf5qO!0L9wWKG9g2GC@m3_K>D}RTl4XMy8ApP>DaZaLg2BeC}FpH$8< zd_i@j;jF<^WJX_?(fxGtl(*RbLMPi ziNGP8qXNGLcwVh;VAW3`ibJ-_@U1PmF(h@4A4!$e!441>R3LWG%eAapp;uPa> z60yrP)a$n%(sS~?+1dG1sG$Mo31dySovQ8PUw}q%x9ejDmr0y-Jo!d$qUU+sytGU1 zER0RB5Z>unaGHb!w%qM?dqJ9o+@>?A!gg$@Otl$@v-0i2mNO+(t!o2dT)@OoUM+YL zA?(w*y3l_kM5h>GSxenHDp`AQog=3Xt;?}E7$`TOEJx=Gqo$HATmbd>4O44sg diff --git a/docs/build/.doctrees/tutorial.doctree b/docs/build/.doctrees/tutorial.doctree index 3914f7b06142d7dfd09886e34f345bbcb1046055..6883200a02df237380fc6e88e86e659c016eaa52 100644 GIT binary patch delta 1521 zcmZ8hNpBoQ6n320p0P8IV0_Cv;!EgLlA$T}T=_W^0ueSsm|$?L2pS?OpF{UdIMh&nxd8 zg>@8A4wYLaX;Id(*^RD@24QQM8zWQ4VwFm41u*FhPi<41gb@bITLdJkqw=^iZJacC zR{$j?n04GZoULtw%DG@H#&t}fvevDKAnl-r36%)0j(w0Rt z9SR-hPQ`X#)9BR6RE!?taW3sBT8*xQ`7}(f6L0WvmXOa{oGVs_rzYnE*lL;~Yj7)B zZenZ~XNNE8ESf-Agc@$;{)DMBN*EPXOcA8I`e6Cke@6@m_MrX@ApNhYk$U ziCMt=pc3=6m6JA9FC=zhkn(Uplm#V*WJxkS=7FpV#$tKhoQ&~-8mY|$ov&r>!2m`h z9+URc7d%uOf=VAn%&jF{4;L#Vmv34u&Wv2J`w6?;j`s#^KcJh6~{`|>z^is!=X-_s)a>gGBR^cS>?3BX9Cy}#+uJEnQ#>| zE`%Q*;_+Z)TfNdgAGL~7H3j&hF_<|?Z&NSJk$aRzQ*y4*6N6*6M z4X0cz`;cE+hOypA`qa+i%LK)nevY9hsD;j5mODlcYeD3&`w9%@t~qlpz6zuL?|npG zqt8|PztY#CyjnS9iRl}#jR|FWg8=l&zZpQ8CLp7Zs*P{K0OgA}XqgC_^tWpz7?;Lq zv(ft<7=rBwZ=l{MVTARXgKabkPPUy7U&hJ2_m`mDlBqP@%lZM~Ta{}= zm=7skHlgujnA7&K_z~?QpXv47pDIp{Ubg3BzdlmO+bmM8D5jsoPhd-|NsiO7OQpr% zBNxq_iH^g#A7Qq^=~Ea~0zae9Iy_hV&x@|1;l%R!#BcLeZArTThfW!+g2xXBKg;W9r)3m4Co@Qsdr@DJ~ zy%1y0u;zy0zVACB-1qr{koW=o2A=o{RIk08B@ZKwx~r?c@2jusuepSph~PF8N#u<0+X4{0xU_)LnoITFzLKi8H-Gs zk{S;0q~>@NGObm(3rcijm+?5%6RtZsNkj2MVh=Wv0q%yXAd8SHX@>m(WK}Pg$g3A+ zg7?gm*i6v9(yLv-dmB)Tc|y|55OCjo3F;#h33rZg-CvyAa_JQhz;r<2=rE?72@$~u zp(JE0@8F3BOv|)I&UAj6Y!^%E@u6b8vTNrGlta46N-zZH*^*sF0QR?+w%x< zB)OGto6-5B)Xd2xZZ&m-5?aYqe5^QLIq%rxP$ALw?Y{g?$7&;Bz;E|0KaL zM@F|*2cH`0NsV4qPI+VT=>}|%62ljmOu0%J7b1)jkt~XTE4%v73}Ca=i}-8ZUxrfd+xxDk6Uy-x0T_{g zqXAW#j*Kd*F1`t4WIozcpWhNolRIXKKFikX;{~nQ`jn`zuiGNG&Z7vVK7LR^>V)%!gz>m(pl5%xSYY{D?MFF#P&Zd|dod zyJXiVVYO(_yDV0nIHAwuNf=KI$#DvHDqYknJNlo-1DFis&CYN-4V#p}DAHfsO* z0Co%wEEBmpM#S33EE?JQ3-a4YB{tlVUpDb8I5+%LXPvZRJCQI!PJv&qz}D+6Us^x- zCajuOw(ASy+eY4k?Pu@2;b!`7ehfA%;qdz&{!sk0_5AveJ^U%YfcD-+J^Zlt+=dJz!0S75kcmMzZ diff --git a/docs/build/.doctrees/using_user_endpoint.doctree b/docs/build/.doctrees/using_user_endpoint.doctree index 1d9e928fa835e76b40ae7971f3509a8701aa285f..1db6be078e50b041c4640db57b25cdc235ef6364 100644 GIT binary patch literal 22984 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 V;IBj>K{Q!9h#x);;%{j<{(mp9V|M@m literal 17768 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 diff --git a/docs/build/.doctrees/work_with_pagination.doctree b/docs/build/.doctrees/work_with_pagination.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c62f83836281060b7afd0a013c49f51e6b9101c6 GIT binary patch literal 21214 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 literal 0 HcmV?d00001 diff --git a/docs/build/_sources/index.txt b/docs/build/_sources/index.txt index d4c2c38..a16039d 100644 --- a/docs/build/_sources/index.txt +++ b/docs/build/_sources/index.txt @@ -3,8 +3,15 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to Python Rest Api Framework's documentation! -===================================================== +Python Rest Api Framework's documentation +========================================= + +Python REST API framework is a set of utilities based on werkzeug to +easily build Restful API with a MVC pattern. Main features includes: +Pagination, Authentication, Authorization, Filters, Partials Response, +Error handling, data validators, data formaters... +and more... + Contents: @@ -13,12 +20,12 @@ Contents: introduction tutorial - datastore - controller - pagination - authentication - multiple_endpoint - references +.. datastore +.. controller +.. pagination +.. authentication +.. multiple_endpoint +.. references Indices and tables ================== @@ -27,3 +34,62 @@ Indices and tables * :ref:`modindex` * :ref:`search` + + + +A Full working example +---------------------- + +.. code-block:: python + + from rest_api_framework import models + from rest_api_framework.datastore import SQLiteDataStore + from rest_api_framework.views import JsonResponse + from rest_api_framework.controllers import Controller + from rest_api_framework.datastore.validators import UniqueTogether + from rest_api_framework.pagination import Pagination + + + class UserModel(models.Model): + """ + Define how to handle and validate your data. + """ + fields = [models.StringField(name="first_name", required=True), + models.StringField(name="last_name", required=True), + models.PkField(name="id", required=True) + ] + + + def remove_id(response, obj): + """ + Do not show the id in the response. + """ + obj.pop(response.model.pk_field.name) + return obj + + + class UserEndPoint(Controller): + ressource = { + "ressource_name": "users", + "ressource": {"name": "adress_book.db", "table": "users"}, + "model": UserModel, + "datastore": SQLiteDataStore, + "options": {"validators": [UniqueTogether("first_name", "last_name")]} + } + + controller = { + "list_verbs": ["GET", "POST"], + "unique_verbs": ["GET", "PUT", "DElETE"], + "options": {"pagination": Pagination(20)} + } + + view = {"response_class": JsonResponse, + "options": {"formaters": ["add_ressource_uri", remove_id]}} + + + if __name__ == '__main__': + + from werkzeug.serving import run_simple + from rest_api_framework.controllers import WSGIDispatcher + app = WSGIDispatcher([UserEndPoint]) + run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True) diff --git a/docs/build/_sources/introduction.txt b/docs/build/_sources/introduction.txt index c3f3823..90ce8cb 100644 --- a/docs/build/_sources/introduction.txt +++ b/docs/build/_sources/introduction.txt @@ -346,5 +346,5 @@ is the same as with the PythonListDataStore. Where to go from here --------------------- -* :doc:`Authentication and Authorization ` -* :doc:`multiple_endpoint` +.. * :doc:`Authentication and Authorization ` +.. * :doc:`multiple_endpoint` diff --git a/docs/build/_sources/representing_data.txt b/docs/build/_sources/representing_data.txt index bb030d6..da59b3d 100644 --- a/docs/build/_sources/representing_data.txt +++ b/docs/build/_sources/representing_data.txt @@ -48,7 +48,7 @@ You can check that it work as expected: Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 23:41:55 GMT - {"first_name": "Cap tain", "last_name": "America", + {"first_name": "Captain", "last_name": "America", "ressource_uri": "/users/1/"} Make things generics @@ -77,6 +77,11 @@ Your code then become: obj.pop(response.model.pk_field.name) return obj -And reuse this formatter as long as you need +And reuse this formatter as long as you need. -Next :doc:`related_ressources` +Formaters are here to help you build clean and meaningful ressources +representations. It should hide internal representation of your +ressources and return all of the fields needed to manipulate and +represent your data. + +Next :doc:`work_with_pagination` diff --git a/docs/build/_sources/tutorial.txt b/docs/build/_sources/tutorial.txt index 6abcfa8..ccaefd5 100644 --- a/docs/build/_sources/tutorial.txt +++ b/docs/build/_sources/tutorial.txt @@ -8,4 +8,5 @@ Tutorial building an adressebook API using_user_endpoint adding_validator_datastore representing_data + work_with_pagination related_ressources diff --git a/docs/build/_sources/using_user_endpoint.txt b/docs/build/_sources/using_user_endpoint.txt index 28119fe..5994155 100644 --- a/docs/build/_sources/using_user_endpoint.txt +++ b/docs/build/_sources/using_user_endpoint.txt @@ -9,9 +9,11 @@ First you can check that your endpoint is up HTTP/1.0 200 OK Content-Type: application/json - Content-Length: 2 + Content-Length: 44 Server: Werkzeug/0.8.3 Python/2.7.2 - Date: Mon, 14 Oct 2013 12:52:22 GMT + Date: Tue, 15 Oct 2013 11:13:44 GMT + + {"meta": {"filters": {}}, "object_list": []} Your endpoint is responding but does not have any data. Let's add some: @@ -59,14 +61,14 @@ The list of users is also updated: .. code-block:: bash - curl -i "http://localhost:5000/users/1/" + curl -i "http://localhost:5000/users/" HTTP/1.0 200 OK Content-Type: application/json Content-Length: 83 Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 17:03:00 GMT - [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}] + {"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]} Delete a user ------------- @@ -94,8 +96,35 @@ and now delete it: Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 20:41:46 GMT +You can check that the user no longer exists: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/2/" + HTTP/1.0 404 NOT FOUND + Content-Type: application/json + Connection: close + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:16:33 GMT + + {"error": "

The requested URL was not found on the server.

If you entered the URL manually please check your spelling and try again.

"} + +And the list is also updated: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 125 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:17:46 GMT + + {"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]} + + Update a User -============= +------------- Let's go another time to the creation process: @@ -114,24 +143,63 @@ America. Let's update this user: .. code-block:: bash - curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain", "last_name": "America"}' http://localhost:5000/users/3/ + curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Capitain", "last_name": "America"}' http://localhost:5000/users/3/ HTTP/1.0 200 OK Content-Type: application/json Content-Length: 58 Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 20:57:47 GMT -Partial update is also possible: + {"first_name": "Capitain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"} + +Argh! Thats a typo. the fist name is "Captain", not "Capitain". Let's +correct this: .. code-block:: bash - curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Cap tain"}' http://localhost:5000/users/3/ + curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain"}' http://localhost:5000/users/3/ HTTP/1.0 200 OK Content-Type: application/json Content-Length: 59 Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 21:08:04 GMT + {"first_name": "Captain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"} + + +Filtering +--------- + +Ressources can be filtered easily using parameters: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/?last_name=America" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 236 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 12:07:21 GMT + + {"meta": {"filters": {"last_name": "America"}}, "object_list": + [{"first_name": "Joe", "last_name": "America", "id": 1, + "ressource_uri": "/users/1/"}, {"first_name": "Bob", "last_name": + "America", "id": 3, "ressource_uri": "/users/3/"}] + +Multiple filters are allowed: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/?last_name=America&first_name=Joe" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 171 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 12:09:32 GMT + + {"meta": {"filters": {"first_name": "Joe", "last_name": "America"}}, + "object_list": [{"first_name": "Joe", "last_name": "America", "id": 1, + "ressource_uri": "/users/1/"}]} Error handling -------------- diff --git a/docs/build/_sources/work_with_pagination.txt b/docs/build/_sources/work_with_pagination.txt new file mode 100644 index 0000000..eebe89a --- /dev/null +++ b/docs/build/_sources/work_with_pagination.txt @@ -0,0 +1,215 @@ +Working with Pagination +======================= + +Creating fixtures +----------------- + +When your address book will be full of entry, you will need to add a +pagination on your API. As it is a common need, REST API Framework +implement a very easy way of doing so. + +Before you can play with the pagination process, you will need to +create more data. You can create those records the way you want: + +* direct insert into the database + +.. code-block:: bash + + sqlite3 adress_book.db + INSERT INTO users VALUES ("Nick", "Furry", 6); + +* using the datastore directly + +.. code-block:: python + + store = SQLiteDataStore({"name": "adress_book.db", "table": "users"}, UserModel) + store.create({"first_name": "Nick", "last_name": "Furry"}) + +* using your API + +.. code-block:: python + + curl -i -H "Content-type: application/json" -X POST -d '{"first_name": "Nick", "last_name": "Furry"}' http://localhost:5000/users/ + +each on of those methods have advantages and disavantages but they all +make the work done. For this example, I propose to use the well know +requests package with a script to create a bunch of random records: + +For this to work you need to install resquests : http://docs.python-requests.org/en/latest/user/install/#install + +.. code-block:: python + + import json + import requests + import random + import string + + def get_random(): + return ''.join( + random.choice( + string.ascii_letters) for x in range( + int(random.random() * 20) + ) + ) + + for i in range(200): + requests.post("http://localhost:5000/users/", data=json.dumps({"first_name": get_random(), "last_name": get_random()})) + +Pagination +---------- + +Now your datastore is filled with more than 200 records, it's time to +paginate. To do so import Pagination and change the controller part of +your app. + +.. code-block:: python + + from rest_api_framework.pagination import Pagination + + class UserEndPoint(Controller): + ressource = { + "ressource_name": "users", + "ressource": {"name": "adress_book.db", "table": "users"}, + "model": UserModel, + "datastore": SQLiteDataStore, + "options": {"validators": [UniqueTogether("first_name", "last_name")]} + } + + controller = { + "list_verbs": ["GET", "POST"], + "unique_verbs": ["GET", "PUT", "DElETE"], + "options": {"pagination": Pagination(20)} + } + + view = {"response_class": JsonResponse, + "options": {"formaters": ["add_ressource_uri", remove_id]}} + +and try your new pagination: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 1811 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:32:55 GMT + + {"meta": {"count": 20, "total_count": 802, "next": "?offset=20", + "filters": {}, "offset": 0, "previous": "null"}, "object_list": + [{"first_name": "Captain", "last_name": "America", + "ressource_uri": "/users/1/"}, {"first_name": "Captain", + "last_name": "America", "ressource_uri": "/users/3/"}, + {"first_name": "John", "last_name": "Doe", "ressource_uri": + "/users/4/"}, {"first_name": "arRFOSYZT", "last_name": "", + "ressource_uri": "/users/5/"}, {"first_name": "iUJsYORMuYeMUDy", + "last_name": "TqFpmcBQD", "ressource_uri": "/users/6/"}, + {"first_name": "EU", "last_name": "FMSAbcUJBSBDPaF", + "ressource_uri": "/users/7/"}, {"first_name": "mWAwamrMQARXW", + "last_name": "yMNpEnYOPzY", "ressource_uri": "/users/8/"}, + {"first_name": "y", "last_name": "yNiKP", "ressource_uri": + "/users/9/"}, {"first_name": "s", "last_name": "TRT", + "ressource_uri": "/users/10/"}, {"first_name": "", "last_name": + "zFUaBd", "ressource_uri": "/users/11/"}, {"first_name": "WA", + "last_name": "priJ", "ressource_uri": "/users/12/"}, + {"first_name": "XvpLttDqFmR", "last_name": "liU", "ressource_uri": + "/users/13/"}, {"first_name": "ZhJqTgYoEUzmcN", "last_name": + "KKDqHJwJMxPSaTX", "ressource_uri": "/users/14/"}, {"first_name": + "qvUxiKIATdKdkC", "last_name": "wIVzfDlKCkjkHIaC", + "ressource_uri": "/users/15/"}, {"first_name": "YSSMHxdDQQsW", + "last_name": "UaKCKgKsgEe", "ressource_uri": "/users/16/"}, + {"first_name": "EKLFTPJLKDINZio", "last_name": "nuilPTzHqattX", + "ressource_uri": "/users/17/"}, {"first_name": "SPcDBtmDIi", + "last_name": "MrytYqElXiIxA", "ressource_uri": "/users/18/"}, + {"first_name": "OHxNppXiYp", "last_name": "AUvUXFRPICsJIB", + "ressource_uri": "/users/19/"}, {"first_name": "WBFGxnoe", + "last_name": "KG", "ressource_uri": "/users/20/"}, {"first_name": + "i", "last_name": "ggLOcKPpMfgvVGtv", "ressource_uri": + "/users/21/"}]} + +Browsering Through Paginated objects +------------------------------------ + +Of course you get 20 records but the most usefull part is the meta +key: + +.. code-block:: json + + {"meta": + {"count": 20, + "total_count": 802, + "next": "?offset=20", + "filters": {}, + "offset": 0, + "previous": "null"} + } + +You can use the "next" key to retreive the 20 next rows: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/?offset=20" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 1849 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:38:59 GMT + +.. code-block:: json + + {"meta": {"count": 20, "total_count": 802, "next": "?offset=40", + "filters": {}, "offset": 20, "previous": "?offset=0"}, "object_list": + []} + +.. note:: + + The count and offset keywords can be easily changed to match your + needs. pagination class may take an offset_key and count_key + parameters. So if you prefer to use first_id and limit, you can + change your Paginator class to do so: + + .. code-block:: python + + "options": {"pagination": Pagination(20, + offset_key="first_id", + count_key="limit") + + Wich will results in the following: + + .. code-block:: bash + + curl -i "http://localhost:5000/users/" + {"meta": {"first_id": 0, "total_count": 802, "next": "?first_id=20", + "limit": 20, "filters": {}, "previous": "null"}, "object_list": [] + + +Pagination and Filters +---------------------- + +Pagination and filtering play nice together + +.. code-block:: bash + + curl -i "http://localhost:5000/users/?last_name=America" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 298 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 12:14:59 GMT + + {"meta": {"count": 20, + "total_count": 2, + "next": "null", + "filters": {"last_name": "America"}, + "offset": 0, + "previous": "null"}, + "object_list": [ + {"first_name": "Joe", + "last_name": "America", + "ressource_uri": "/users/1/"}, + {"first_name": "Bob", + "last_name": "America", + "ressource_uri": "/users/3/"} + ] + } diff --git a/docs/build/datastore.html b/docs/build/datastore.html index 8bc2380..40e8ad3 100644 --- a/docs/build/datastore.html +++ b/docs/build/datastore.html @@ -25,7 +25,7 @@ - +
@@ -115,8 +115,12 @@

Make things genericsreturn obj -

And reuse this formatter as long as you need

-

Next Linking ressource together

+

And reuse this formatter as long as you need.

+

Formaters are here to help you build clean and meaningful ressources +representations. It should hide internal representation of your +ressources and return all of the fields needed to manipulate and +represent your data.

+

Next Working with Pagination

diff --git a/docs/build/searchindex.js b/docs/build/searchindex.js index 6cf16d2..be2f2e2 100644 --- a/docs/build/searchindex.js +++ b/docs/build/searchindex.js @@ -1 +1 @@ -Search.setIndex({envversion:42,terms:{represent:[3,12,8,9],all:[3,6,7,8,12],code:[6,9],partial:2,list_verb:[3,6,1,4,7],lack:9,steve:2,carfulli:2,follow:[3,9],sould:[12,8],captain:2,row:[12,8],decid:3,typeerror:[],depend:[3,7,8,12],authoris:3,sensit:[],tweet:[3,12,8],show:3,send:[3,12,8],tain:[2,9],decis:3,get_connector:[12,8],islic:[],ressource_list:[],sourc:[12,8],string:8,fals:3,util:3,verb:[3,7,8],mechan:1,objec:[],veri:[3,11,5],hanld:3,retrrreiv:[],list:[3,11,5],last_nam:[2,4,9,6],"try":[2,5,4,9],adress_book:[6,4],uniquetogeth:[],"20th":1,cap:[2,9],naiv:[12,8],rate:[3,7],design:[],pass:8,chek:[12,8],integr:3,compat:12,index:[0,8],what:[],neg:[],abl:[2,1,3,7,8,12],access:[3,5,9],delet:[3,6,1,7],"new":[3,2,4,8,12],method:3,metadata:2,hash:5,behavior:3,gener:[],secondapp:8,here:[],behaviour:[],modif:[],address:[3,10],eventapp:11,becom:9,modifi:3,valu:[3,1,5],search:0,reprensentaion:9,later:9,permit:8,codebas:3,bob:3,explain:2,environn:11,control:[],useful:[3,2],extra:12,appli:2,app:[3,6,8,11],add_ressource_uri:9,from:[],describ:[],fron:[12,8],regist:[],next:[2,4,9,6],everybodi:2,live:3,call:[3,12,8],type:[2,3,4,8,12,9],tell:[3,7],more:[],clever:[],actuali:[3,12,8],under:11,notic:11,"__iter__":[],particular:[3,5],actual:[12,8],must:[2,3,6,5,8,4,12],none:[5,8,12],endpoint:[],alia:[],prepar:[12,8],work:[1,8,5,12,9],uniqu:[2,3,6,7,8,4],firstapp:8,itself:2,obvious:[12,8],whatev:[3,12],learn:3,those:[3,1,12],purpos:5,root:[7,8],def:[5,9],overrid:12,sqlite:[3,12,8],quickstart:[],give:[2,1,3,8,12,9],process:[2,8,6],accept:[],topic:[3,7],want:[2,1,3,6,8,4,12,9],paginated_bi:12,alwai:[1,5,9],cours:2,multipl:[2,10],first_nam:[2,4,9,6],secur:3,anoth:[3,2,5,12,9],write:[11,7,9],how:[],instead:[3,1,9],csv:[3,12],simpl:[],updat:[3,12],map:8,resourc:3,huge:2,max:7,after:[12,8],befor:4,mai:[3,11,12,9],datastor:[],data:[3,5],bind:[],author:[],correspond:3,inform:[3,1,8,12],environ:8,allow:[3,7],callabl:8,talk:11,oper:[12,8],soon:[3,12,8],userendpoint:[6,4,9],paramet:3,render:[3,6,9],fit:3,carri:9,trootl:3,main:[3,8],them:[3,4,8],good:3,"return":[1,3,5,8,12,9],"__getitem__":[],handl:[3,7],auto:[],auth:[5,8],number:[3,12,8],now:[2,5,8,12,9],nor:[],enabl:[3,4,1,8],choic:1,somewher:5,name:[2,3,4,6,5,8,11,12,9],anyth:[3,5,8,12],authent:[],wole:[12,8],easili:[3,5],token:[3,1],each:[2,1,3,4,5,7,8,11,12,9],fulli:[3,5],unique_uri:8,mean:3,compil:3,domain:1,idea:9,unique_verb:[3,6,1,4,7],connect:[5,8,12],our:[3,6,5],meth:[],todo:[],event:11,special:[3,7],out:9,miss:[],newli:[],content:[0,12,4,9,2],rewrit:[],rel:[],sqlitedatastor:3,wich:[3,7,9],to_dict:5,earlier:[],free:[3,12,8],base:[3,1],mime:3,scallabl:3,put:[2,1,3,6,7,4],care:12,reusabl:3,launch:11,could:[2,5,8,12],keep:3,filter:[3,1,8,12],length:[2,4,9],mvc:3,pagin:[3,7],think:12,first:3,feed:3,rang:3,dont:[3,2,9],directli:1,feel:8,onc:[3,12],sqldatastor:12,placehold:5,hook:3,alreadi:3,done:[3,12,8],messag:3,count_kei:[1,8],primari:[12,8],given:5,"long":9,interact:3,least:6,get_list:[12,8],too:[3,5,8,12],statement:[12,8],john:[2,4],"final":12,store:[3,12],schema:[],option:[],wsgiwrapp:8,part:[3,2,9],than:[],serv:[3,6,11],notfound:[5,8,12],keyword:[1,7],provid:[3,2,5,12],remov:[2,1,8,12,9],meen:4,second:[],bee:12,project:6,reus:[3,12,9],ressourc:[],str:[],make_opt:8,fashion:6,apikeyauthent:5,argument:3,have:[2,1,3,4,5,8,10,11,12,9],"__main__":[3,6],need:[2,1,3,6,5,8,10,12,9],check_auth:[5,8],date:[2,4,9],load_url:8,dictionnari:[12,8],self:[5,8,12],client:3,note:[],also:[3,2,12,6],without:[6,8],take:[2,1,3,8,11,12,9],instanci:[1,8,5,12,9],singl:[],even:3,integerfield:[3,12],object:[2,3,5,8,12,9],dialogu:7,plai:[],america:[2,9],"class":[1,3,4,6,5,8,11,12],ressouc:3,don:2,url:[3,7,8],gather:[],flow:3,uri:[2,8],doe:[3,2,4,8,12],ext:1,unique_id:7,clean:3,pattern:3,constrain:[12,8],jsonrespons:[3,6,4,9],apiapp:[3,11],wsgi:8,ressource_uri:[2,9],text:[12,8],syntax:8,fine:5,xml:3,moer:3,onli:[2,1,3,5,7,8,11,12],locat:2,apimodel:[3,12,8],run_simpl:[3,6,11],configur:[3,1],should:[3,6,1,7,5],dict:[3,1,8,5,12],local:3,oct:[2,4,9],variou:4,get:[3,6,1,7,5],stop:[],becaus:[2,1,7,9],get_us:[5,8],cannot:[3,2,12,4,6],other_paramet:[],requir:[3,6,5,8,12],retreiv:[3,2,12,8,6],userapp:11,emb:[],dispatch_request:8,ressoourc:3,yield:[],"public":5,book:[6,10],bad:[2,4],stuff:7,roger:2,common:3,contain:[3,12,8],orign:1,where:[],view:[],respond:2,set:[3,12,8],see:[2,1,4,7,12],mandatori:[],result:[1,3,6,7,8,12],arg:8,close:[12,8],extend:3,databas:[],someth:[2,5,12],unauthoriez:[],behind:9,response_class:[3,6,4,9],between:[3,7,12],"import":[3,6,4,11],insensitv:[],attribut:[1,12,9],accord:[12,8],kei:[5,8,12,9],unauthor:5,itertool:[],extens:3,lazi:[12,8],come:12,famillii:10,protect:5,last:[3,6,12,8],easi:[3,11,12],howev:[2,11],lowercas:[],etc:3,instanc:[3,6,11],grain:5,basestr:[12,8],let:[2,4],com:3,becam:[],"30th":1,can:[2,1,3,4,6,5,8,10,11,12,9],pop:9,loader:8,header:[3,2],dispatch:8,path:[],usermodel:[6,4],wsgi_app:8,backend:[],evalu:[12,8],been:[3,2,12,8],wsgidispatch:[3,6,8,11],json:[3,2,4,9,6],basic:8,validate_field:[12,8],immedi:12,tigh:[],convert:[12,8],ani:[3,2,5],understand:3,togeth:[],required_tru:6,wrapper:[12,8],"case":3,look:[2,3,6,8,11,12],mount:8,properti:3,act:[3,8],formatt:9,defin:[3,1,7],calcul:1,apikei:5,elt:[],abov:1,error:5,"na\u00efv":[],multiple_endpoint:[],pythonlistdatastor:[3,5,8],azerti:5,loos:[12,8],finnali:5,coher:2,advantag:[12,8,9],applic:[2,1,3,6,8,4,9],implment:3,dictonari:3,ratelimit:8,kwarg:[1,8,12],allawi:[],"__init__":[5,8,12],present:[3,8],thei:[3,7,8,5,12],grant:5,perform:5,make:[3,7,8],same:[3,2,4],preserv:9,tutori:[],sqlite3:[3,6,12,8],apiappauth:5,complet:12,http:[2,1,8,9,4],pouet:[],hanl:3,action:[3,5],rais:[2,5,8,12],user:[3,11,5],mani:[3,2,4],outbound:[],respons:[3,2,8,9],implement:[3,7],whole:1,well:[2,12],inherit:[3,1,8,12],pk_field:9,exampl:[],thi:[3,6,1,7,5],fiter:[12,8],apikeyauthor:5,rout:[3,8],usual:3,authmodel:5,construct:8,identifi:[2,3,5,6,7,8,12],just:[2,3,5,8,12,9],dissuss:[],distant:3,expos:3,hint:[],point:[12,8],except:[5,8,12],param:8,exempl:[3,1,4,7],add:[3,2,12,4,11],other:3,els:5,save:[3,6,12,8],adress:6,modul:0,match:[12,8],build:[3,1],real:[2,12,8],indentifi:5,ressource_config:[12,8],format:[3,12],read:[3,7,5],pkfield:[3,6,12,8],mon:[2,4,9],gmt:[2,4,9],password:5,helpful:6,like:[1,3,6,7,11,12],specif:[],integ:[12,8],server:[2,4,9],either:[3,12,8],authorized_method:[],apicontrol:8,twitter:[3,12],creation:[2,12,8],some:[2,1,3,7,8,4,12],back:[12,8],intern:9,use_reload:[3,6,11],peter:2,kawrg:[],insensit:[],definit:[],totali:3,werkzeug:[2,3,6,4,8,11,9],adressebook:[],duplic:2,localhost:[2,4,9],refer:[],machin:3,who:9,run:[3,6],stringfield:[3,6,5,12],autoincr:[12,8],step:[],offset:7,"__name__":[3,6],post:[2,1,3,6,7,8,4],plug:9,about:[3,11,1,12],obj:[12,8,9],memori:[3,12,8],page:[3,0],constructor:11,disabl:3,block:[],subset:[12,8],paginate_bi:[12,8],own:[3,1,8,7,12],"float":[12,8],empti:1,ensur:[2,4,6],chang:[2,3,4,8,12,9],your:[],manag:[3,6,7,8],accordingli:8,wai:[3,5,8,12,9],support:[12,8],use_debugg:[3,6,11],custom:[5,12],start:[3,1,8,12],suit:[3,12],"function":5,properli:[12,8],max_result:8,form:8,tupl:8,link:12,"true":[3,6,5,12,11],count:7,commun:[3,12,8],made:8,possibl:[2,1,8,12,11],"default":9,start_respons:8,displai:3,below:1,limit:[3,1,8,7,12],otherwis:[3,5,8],problem:[2,1],unrel:3,expect:[2,9],datastoreinst:3,featur:7,creat:[],"int":[12,8],request:[2,1,5,7,8,4],"abstract":8,deep:[],repres:[12,8],exist:[3,4,5,8,12],file:[12,8],curl:[2,1,4,9],offset_kei:[1,8],check:[2,3,5,8,4,12,9],herit:8,automaticali:8,when:[3,1,7,9],detail:7,rest_api_framework:[1,3,6,8,11,4,12],field:[],valid:[],test:[3,12,8],remove_id:9,you:[2,1,3,4,5,8,10,11,12,9],ressource_nam:[3,6,7,4],architectur:[],relat:[7,12],finali:3,multimpl:11,developp:11,queri:[12,8],sql:[3,12,8],endoint:11,receiv:[1,8,12],dog:1,descript:[],place:[2,6],time:[2,5,8,12],far:[6,9,11]},objtypes:{"0":"py:module","1":"py:class","2":"py:method"},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"]},filenames:["index","pagination","using_user_endpoint","introduction","adding_validator_datastore","authentication","first_step","controller","references","representing_data","related_ressources","multiple_endpoint","datastore","tutorial"],titles:["Welcome to Python Rest Api Framework’s documentation!","Paginate a ressource","Playing with the newly created endpoint","What is Python REST API Framework","Adding validators to your DataStore","Authentication and Authorization","First Step Building a user endpoint","Controllers","REST API Framework API references","Show data to users","Linking ressource together","Using more than a single endpoint","Datastore","Tutorial building an adressebook API"],objects:{"rest_api_framework.datastore.sql.SQLiteDataStore":{get:[12,2,1,""],get_connector:[12,2,1,""],create:[12,2,1,""],paginate:[12,2,1,""],update:[12,2,1,""],get_list:[12,2,1,""],filter:[12,2,1,""],"delete":[12,2,1,""]},"rest_api_framework.datastore.base.DataStore":{get:[12,2,1,""],create:[12,2,1,""],paginate:[12,2,1,""],update:[12,2,1,""],get_list:[12,2,1,""],filter:[12,2,1,""],validate_fields:[12,2,1,""],validate:[12,2,1,""],"delete":[12,2,1,""]},"rest_api_framework.controllers.ApiController":{index:[8,2,1,""],get:[8,2,1,""],create:[8,2,1,""],paginate:[8,2,1,""],update:[8,2,1,""],get_list:[8,2,1,""],unique_uri:[8,2,1,""],"delete":[8,2,1,""]},"rest_api_framework.datastore.simple.PythonListDataStore":{get:[8,2,1,""],update:[8,2,1,""],get_list:[8,2,1,""]},"rest_api_framework.datastore.sql":{SQLiteDataStore:[12,1,1,""]},"rest_api_framework.datastore.simple":{PythonListDataStore:[8,1,1,""]},"rest_api_framework.controllers":{Controller:[8,1,1,""],WSGIWrapper:[8,1,1,""],ApiController:[8,1,1,""],WSGIDispatcher:[8,1,1,""]},rest_api_framework:{controllers:[8,0,0,"-"]},"rest_api_framework.datastore.base":{DataStore:[12,1,1,""]},"rest_api_framework.controllers.Controller":{load_urls:[8,2,1,""],make_options:[8,2,1,""]},"rest_api_framework.authentication.Authentication":{get_user:[8,2,1,""]},"rest_api_framework.pagination.Pagination":{paginate:[8,2,1,""]},"rest_api_framework.controllers.WSGIWrapper":{dispatch_request:[8,2,1,""],wsgi_app:[8,2,1,""]},"rest_api_framework.pagination":{Pagination:[8,1,1,""]},"rest_api_framework.authentication":{Authentication:[8,1,1,""]}},titleterms:{control:[3,7,8],quickstart:3,creat:[2,12,6],show:9,argument:7,rest:[3,0,8],api:[3,0,8,13],mandatori:7,tabl:0,thing:9,miss:2,your:[12,4],paramet:7,what:3,from:3,describ:12,field:3,databas:3,newli:2,format:9,invalid:2,should:[],avail:12,how:[3,5],make:9,valid:[3,4],summari:6,build:[6,13],indic:0,adressebook:13,sqlitedatastor:8,ressourc:[3,1,12,10],document:0,simpl:[3,8],method:1,singl:11,refer:8,"function":9,model:[3,6,12],option:[3,7],get:2,python:[3,0],max:1,gener:9,first:6,here:3,backend:5,framework:[3,0,8],step:6,base:8,link:10,user:[2,9,6],offset:1,datastor:[3,6,4,8,12],data:[2,9],than:11,welcom:0,count:1,chose:6,endpoint:[2,11,6],handl:2,architectur:3,togeth:10,tutori:13,uniquetogeth:4,author:5,list:2,plai:2,authent:[3,5,8],defin:[6,9],delet:2,exampl:5,pagin:[1,8],updat:2,thi:[],error:2,implement:1,more:11,where:3,other:1,view:[3,6]}}) \ No newline at end of file +Search.setIndex({envversion:42,terms:{represent:[4,12,2,9],all:[4,6,7,2,14,12,9],code:[6,9],partial:0,list_verb:[0,1,4,6,7,8,14],lack:9,steve:3,carfulli:3,follow:[4,14,9],sould:[12,2],captain:[3,14,9],eklftpjlkdinzio:14,row:[12,2,14],decid:4,typeerror:[],depend:[4,7,2,12],authoris:4,sensit:[],tweet:[4,12,2],show:[],readabl:14,send:[4,12,2],tain:[],decis:4,get_connector:[12,2],islic:[],ressource_list:[],sourc:[12,2],string:[2,14],fals:4,util:[4,0],verb:[4,7,2],mechan:1,objec:[],veri:[4,11,5,14],snip:14,mrytyqelxiixa:14,hanld:4,disavantag:14,retrrreiv:[],list:[11,5],last_nam:[0,3,6,8,14,9],"try":[3,5,8,14,9],adress_book:[0,8,14,6],ynikp:14,uniquetogeth:[],"20th":1,pleas:3,cap:[],kkdqhjwjmxpsatx:14,naiv:[12,2],direct:14,famillii:10,second:[],design:[],pass:2,chek:[12,2],iujsyormuyemudi:14,compat:12,index:[0,2],what:[],hide:9,neg:[],abl:[3,1,4,7,2,12],access:[4,5,9],delet:[6,1,7],"new":[3,4,8,14,2,12],ohxnppxiyp:14,method:[],metadata:3,full:14,hash:5,behavior:4,gener:[],secondapp:2,here:[],behaviour:[],modif:[],address:[4,14,10],eventapp:11,becom:9,modifi:4,valu:[4,1,5,14],search:0,auvuxfrpicsjib:14,reprensentaion:9,permit:2,codebas:4,bob:[4,3,14],explain:3,environn:11,control:[],useful:[4,3,14],xvplttdqfmr:14,extra:12,appli:3,app:[0,4,6,2,14,11],prefer:14,add_ressource_uri:[0,14,9],zfuabd:14,instal:14,from:[],describ:[],fron:[12,2],regist:[],next:[3,8,14,9,6],everybodi:3,live:4,call:[4,12,2],typo:3,capitain:3,type:[3,4,8,14,2,12,9],tell:[4,7],more:[],clever:[],actuali:[4,12,2],under:11,notic:11,"__iter__":[],particular:[4,5],actual:[12,2],must:[3,4,6,5,8,2,12],none:[5,2,12],endpoint:[],join:14,alia:[],prepar:[12,2],work:[1,5],uniqu:[3,4,6,7,8,2],firstapp:2,itself:3,obvious:[12,2],whatev:[4,12],learn:4,purpos:5,root:[7,2],def:[0,5,14,9],overrid:12,sqlite:[4,12,2],quickstart:[],give:[3,1,4,2,12,9],process:[3,2,14,6],accept:[],topic:[4,7],want:[3,1,4,6,8,14,2,12,9],paginated_bi:12,alwai:[1,5,9],cours:[3,14],multipl:[3,10],first_nam:[0,3,6,8,14,9],secur:4,anoth:[4,3,5,12,9],object_list:[3,14],ymnpenyopzi:14,write:[11,7,9],how:[],instead:[4,1,9],csv:[4,12],simpl:[],updat:[],map:2,first_id:14,resourc:4,huge:3,max:7,earlier:[],befor:[8,14],mai:[4,11,12,14,9],datastor:[],data:5,bind:[],author:[],correspond:4,inform:[4,1,2,12],environ:2,allow:[4,3,7],enter:3,callabl:2,unique_id:7,oper:[12,2],least:6,help:9,get_random:14,soon:[4,12,2],userendpoint:[0,8,14,9,6],paramet:[],render:[4,6,9],fit:4,gglockppmfgvvgtv:14,carri:9,trootl:4,main:[4,0,2],them:[4,2,8],good:4,"return":[0,1,4,5,2,14,12,9],"__getitem__":[],handl:7,auto:[],spell:3,auth:[5,2],number:[4,12,2],now:[3,5,2,14,12,9],nor:[],enabl:[4,2,1,8],choic:[1,14],somewher:5,name:[0,3,4,6,5,8,14,2,11,12,9],anyth:[4,5,2,12],total_count:14,authent:[],wole:[12,2],easili:[4,0,5,14,3],token:[4,1],each:[3,1,4,5,7,8,14,2,11,12,9],fulli:[4,5],unique_uri:2,mean:4,compil:4,nick:14,domain:1,idea:9,meta:[3,14],unique_verb:[0,1,4,6,7,8,14],connect:[3,5,2,12],fist:3,our:[4,6,5],meth:[],todo:[],event:11,special:[4,7],out:9,miss:[],newli:[],content:[0,3,8,14,12,9],rewrit:[],uakckgksge:14,rel:[],sqlitedatastor:[],wich:[4,7,14,9],correct:3,to_dict:5,after:[12,2],found:3,manipul:9,free:[4,12,2],argh:3,base:1,mime:4,scallabl:4,latest:14,put:[0,1,3,4,6,7,8,14],org:14,care:12,resquest:14,reusabl:4,launch:11,could:[3,5,2,12],keep:4,filter:[1,2,12],length:[3,8,14,9],mvc:[4,0],pagin:7,think:12,first:[],feed:4,rang:[4,14],dont:[4,3,9],directli:[1,14],feel:2,onc:[4,12],sqldatastor:12,placehold:5,hook:4,alreadi:4,done:[4,12,2,14],messag:4,count_kei:[1,2,14],primari:[12,2],given:5,"long":9,script:14,interact:4,wrapper:[12,2],get_list:[12,2],too:[4,5,2,12],statement:[12,2],john:[3,8,14],"final":12,store:[4,12,14],schema:[],option:[],nuilptzhqattx:14,wsgiwrapp:2,part:[4,3,14,9],than:[],ascii_lett:14,serv:[4,0,11,6],notfound:[5,2,12],keyword:[1,7,14],provid:[4,3,5,12],remov:[3,1,2,12,9],meen:8,rate:[4,7],bee:12,project:6,reus:[4,12,9],ressourc:[],str:[],entri:14,make_opt:2,fashion:6,apikeyauthent:5,argument:[],packag:14,have:[3,1,2,4,5,8,14,10,11,12,9],"__main__":[4,0,6],need:[3,1,4,6,5,2,14,10,12,9],"null":14,check_auth:[5,2],date:[3,8,14,9],load_url:2,dictionnari:[12,2],self:[5,2,12],client:4,note:[],also:[4,3,12,6],without:[6,2],take:[3,1,4,2,14,11,12,9],instanci:[1,2,5,12,9],singl:[],even:4,integerfield:[4,12],previou:14,dialogu:7,most:14,plai:[],america:[3,14,9],"class":[0,1,4,6,5,8,14,2,11,12],ressouc:4,don:3,url:[4,3,7,2],doc:14,later:9,flow:4,uri:[3,2],doe:[3,4,8,14,2,12],ext:1,talk:11,clean:[4,9],pattern:[4,0],constrain:[12,2],propos:14,jsonrespons:[0,4,6,8,14,9],apiapp:[4,11],wsgi:2,prij:14,ressource_uri:[3,14,9],text:[12,2],random:14,syntax:2,fine:5,xml:4,moer:4,onli:[3,1,4,5,7,2,11,12],locat:3,apimodel:[4,12,2],run_simpl:[4,0,11,6],configur:[4,1],should:[6,1,7,5],dict:[4,1,2,5,12],local:4,oct:[3,8,14,9],variou:8,get:[6,1,7,5],stop:[],becaus:[3,1,7,9],get_us:[5,2],cannot:[4,3,12,8,6],other_paramet:[],requir:[0,4,6,5,2,12],liu:14,retreiv:[3,4,6,2,14,12],userapp:11,emb:[],dispatch_request:2,ressoourc:4,yield:[],"public":5,book:[6,14,10],bad:[3,8],stuff:7,roger:3,common:[4,14],contain:[4,12,2],orign:1,where:[],view:[],respond:3,set:[4,0,12,2],dump:14,see:[3,1,8,7,12],mandatori:[],result:[1,4,6,7,2,14,12],arg:2,close:[3,12,2],wbfgxnoe:14,extend:4,databas:[],someth:[3,5,12],unauthoriez:[],behind:9,response_class:[0,4,6,8,14,9],between:[4,7,12],"import":[0,4,6,8,14,11],bunch:14,qvuxikiatdkdkc:14,insensitv:[],attribut:[1,12,9],accord:[12,2],kei:[5,2,12,9,14],unauthor:5,itertool:[],extens:4,lazi:[12,2],joe:[3,14],come:12,tue:[3,14],protect:5,last:[4,6,12,2],easi:[4,11,12,14],howev:[3,11],lowercas:[],etc:4,instanc:[4,6,11],grain:5,basestr:[12,2],let:[3,8],com:4,becam:[],"30th":1,mwawamrmqarxw:14,can:[3,1,2,4,6,5,8,14,10,11,12,9],pop:[0,9],loader:2,header:[4,3],dispatch:2,path:[],usermodel:[0,8,14,6],wsgi_app:2,duplic:3,evalu:[12,2],been:[4,3,12,2],wsgidispatch:[4,0,11,2,6],json:[3,4,6,8,14,9],arrfosyzt:14,basic:2,validate_field:[12,2],immedi:12,tigh:[],convert:[12,2],ani:[4,3,5],understand:4,togeth:[],furri:14,required_tru:6,those:[4,1,12,14],"case":4,look:[3,4,6,2,11,12],mount:2,properti:4,act:[4,2],formatt:9,defin:[1,7],calcul:1,apikei:5,elt:[],abov:1,error:5,"na\u00efv":[],multiple_endpoint:[],pythonlistdatastor:[4,5,2],azerti:5,loos:[12,2],finnali:5,coher:3,advantag:[12,2,14,9],applic:[3,1,4,6,8,14,2,9],implment:4,dictonari:4,ratelimit:2,kwarg:[1,2,12],allawi:[],"__init__":[5,2,12],present:[4,2],thei:[4,5,7,2,14,12],grant:5,perform:5,make:7,same:[4,3,8],preserv:9,tutori:[],sqlite3:[4,6,12,2,14],apiappauth:5,complet:12,http:[3,1,8,14,2,9],pouet:[],hanl:4,wivzfdlkckjkhiac:14,action:[4,5],rais:[3,5,2,12],user:[11,5],mani:[4,3,8],outbound:[],respons:[4,0,2,9,3],implement:7,whole:1,well:[3,12,14],inherit:[4,1,2,12],pk_field:[0,9],exampl:[],thi:[6,1,7,5],fiter:[12,2],yssmhxddqqsw:14,apikeyauthor:5,rout:[4,2],usual:4,authmodel:5,construct:2,identifi:[3,4,5,6,7,2,12],just:[3,4,5,2,12,9],object:[5,9],dissuss:[],distant:4,fmsabcujbsbdpaf:14,gather:[],expos:4,hint:[],point:[12,2],except:[5,2,12],param:2,exempl:[4,1,8,7],add:[3,4,8,14,11,12],other:[],els:5,save:[4,6,12,2],adress:6,modul:0,match:[12,2,14],build:1,real:[3,12,2],indentifi:5,ressource_config:[12,2],format:[],read:[4,7,5],pkfield:[4,0,12,2,6],mon:[3,8,9],gmt:[3,8,14,9],password:5,helpful:6,insert:14,like:[1,4,6,7,11,12],specif:[],manual:3,integ:[12,2],server:[3,8,14,9],either:[4,12,2],authorized_method:[],architectur:[],apicontrol:2,twitter:[4,12],creation:[3,12,2],some:[3,1,4,7,8,2,12],back:[12,2],intern:9,use_reload:[4,0,11,6],peter:3,kawrg:[],insensit:[],definit:[],totali:4,werkzeug:[0,3,4,6,8,14,2,11,9],adressebook:[],backend:[],localhost:[3,8,14,9],refer:[],machin:4,who:9,run:[4,6],spcdbtmdii:14,stringfield:[4,0,5,12,6],autoincr:[12,2],step:[],offset:7,"__name__":[4,0,6],post:[0,1,3,4,6,7,8,14,2],plug:9,about:[4,11,1,12],obj:[0,12,2,9],memori:[4,12,2],page:[4,0],integr:4,constructor:11,disabl:4,block:[],subset:[12,2],paginate_bi:[12,2],own:[4,1,2,7,12],"float":[12,2],empti:1,ensur:[3,8,6],chang:[3,4,8,14,2,12,9],your:[],manag:[4,6,7,2],accordingli:2,wai:[4,5,2,14,12,9],support:[12,2],use_debugg:[4,0,11,6],custom:[5,12],start:[4,1,2,12],includ:0,suit:[4,12],"function":5,properli:[12,2],max_result:2,form:2,tupl:2,link:[],"true":[0,4,6,5,11,12],count:7,commun:[4,12,2],made:2,possibl:[11,1,2,12],"default":9,start_respons:2,displai:4,trt:14,record:14,below:1,limit:[1,4,7,2,14,12],otherwis:[4,5,2],problem:[3,1],unrel:4,expect:[3,9],datastoreinst:4,featur:[0,7],creat:[],"int":[12,2,14],tqfpmcbqd:14,"abstract":2,deep:[],repres:[12,2,9],exist:[3,4,5,8,2,12],file:[12,2],request:[3,1,5,7,8,14,2],curl:[3,1,8,14,9],offset_kei:[1,2,14],check:[3,4,5,8,2,12,9],fill:14,again:3,herit:2,know:14,automaticali:2,when:[4,1,7,9,14],detail:7,rest_api_framework:[0,1,4,6,8,14,2,11,12],field:[],valid:[],test:[4,12,2],remove_id:[0,14,9],you:[3,1,2,4,5,8,14,10,11,12,9],ressource_nam:[0,4,6,7,8,14],nice:14,relat:[7,12],finali:4,zhjqtgyoeuzmcn:14,multimpl:11,developp:11,meaning:9,queri:[12,2],sql:[4,12,2],endoint:11,receiv:[1,2,12],longer:3,dog:1,descript:[],place:[3,6],time:[3,5,2,12,14],far:[6,9,11]},objtypes:{"0":"py:module","1":"py:class","2":"py:method"},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"]},filenames:["index","pagination","references","using_user_endpoint","introduction","authentication","first_step","controller","adding_validator_datastore","representing_data","related_ressources","multiple_endpoint","datastore","tutorial","work_with_pagination"],titles:["Python Rest Api Framework’s documentation","Paginate a ressource","REST API Framework API references","Playing with the newly created endpoint","What is Python REST API Framework","Authentication and Authorization","First Step Building a user endpoint","Controllers","Adding validators to your DataStore","Show data to users","Linking ressource together","Using more than a single endpoint","Datastore","Tutorial building an adressebook API","Working with Pagination"],objects:{"rest_api_framework.datastore.sql.SQLiteDataStore":{get:[12,2,1,""],get_connector:[12,2,1,""],create:[12,2,1,""],paginate:[12,2,1,""],update:[12,2,1,""],get_list:[12,2,1,""],filter:[12,2,1,""],"delete":[12,2,1,""]},"rest_api_framework.datastore.base.DataStore":{get:[12,2,1,""],create:[12,2,1,""],paginate:[12,2,1,""],update:[12,2,1,""],get_list:[12,2,1,""],filter:[12,2,1,""],validate_fields:[12,2,1,""],validate:[12,2,1,""],"delete":[12,2,1,""]},"rest_api_framework.controllers.ApiController":{index:[2,2,1,""],get:[2,2,1,""],create:[2,2,1,""],paginate:[2,2,1,""],update:[2,2,1,""],get_list:[2,2,1,""],unique_uri:[2,2,1,""],"delete":[2,2,1,""]},"rest_api_framework.datastore.simple.PythonListDataStore":{get:[2,2,1,""],update:[2,2,1,""],get_list:[2,2,1,""]},"rest_api_framework.datastore.sql":{SQLiteDataStore:[12,1,1,""]},"rest_api_framework.datastore.simple":{PythonListDataStore:[2,1,1,""]},"rest_api_framework.controllers":{Controller:[2,1,1,""],WSGIWrapper:[2,1,1,""],ApiController:[2,1,1,""],WSGIDispatcher:[2,1,1,""]},rest_api_framework:{controllers:[2,0,0,"-"]},"rest_api_framework.datastore.base":{DataStore:[12,1,1,""]},"rest_api_framework.controllers.Controller":{load_urls:[2,2,1,""],make_options:[2,2,1,""]},"rest_api_framework.authentication.Authentication":{get_user:[2,2,1,""]},"rest_api_framework.pagination.Pagination":{paginate:[2,2,1,""]},"rest_api_framework.controllers.WSGIWrapper":{dispatch_request:[2,2,1,""],wsgi_app:[2,2,1,""]},"rest_api_framework.pagination":{Pagination:[2,1,1,""]},"rest_api_framework.authentication":{Authentication:[2,1,1,""]}},titleterms:{show:9,rest:[4,0,2],through:14,paramet:7,how:[4,5],should:[],other:1,format:9,python:[4,0],framework:[4,0,2],list:3,authent:[4,5,2],updat:3,where:4,summari:6,mandatori:7,what:4,databas:4,newli:3,delet:3,adressebook:13,sqlitedatastor:2,method:1,refer:2,full:0,chose:6,gener:9,here:4,step:6,base:2,offset:1,filter:[3,14],thing:9,tutori:13,pagin:[1,2,14],implement:1,view:[4,6],first:6,api:[4,0,2,13],miss:3,your:[12,8],backend:5,from:4,describ:12,avail:12,handl:3,more:11,"function":9,option:[4,7],link:10,togeth:10,than:11,count:1,endpoint:[3,11,6],uniquetogeth:8,work:[0,14],defin:[6,9],ressourc:[4,1,12,10],error:3,fixtur:14,browser:14,control:[4,7,2],quickstart:4,creat:[3,12,14,6],argument:7,indic:0,tabl:0,welcom:[],author:5,make:9,get:3,invalid:3,field:4,valid:[4,8],build:[6,13],document:0,simpl:[4,2],singl:11,architectur:4,max:1,object:14,plai:3,user:[3,9,6],datastor:[4,6,12,8,2],data:[3,9],exampl:[0,5],thi:[],model:[4,6,12]}}) \ No newline at end of file diff --git a/docs/build/tutorial.html b/docs/build/tutorial.html index 66b577a..6838f03 100644 --- a/docs/build/tutorial.html +++ b/docs/build/tutorial.html @@ -68,9 +68,8 @@

Tutorial building an adressebook APICreate a user
  • List and Get
  • Delete a user
  • - - -
  • Update a User
  • @@ -83,6 +82,13 @@

    Tutorial building an adressebook APIMake things generics +
  • Working with Pagination +
  • Linking ressource together
  • diff --git a/docs/build/using_user_endpoint.html b/docs/build/using_user_endpoint.html index 05e6be6..9d09ec5 100644 --- a/docs/build/using_user_endpoint.html +++ b/docs/build/using_user_endpoint.html @@ -61,9 +61,11 @@

    Playing with the newly created endpoint{"meta": {"filters": {}}, "object_list": []}

    Your endpoint is responding but does not have any data. Let’s add @@ -102,14 +104,14 @@

    List and GetShow data to users

    The list of users is also updated:

    -
    curl -i "http://localhost:5000/users/1/"
    +
    curl -i "http://localhost:5000/users/"
     HTTP/1.0 200 OK
     Content-Type: application/json
     Content-Length: 83
     Server: Werkzeug/0.8.3 Python/2.7.2
     Date: Mon, 14 Oct 2013 17:03:00 GMT
     
    -[{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]
    +{"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]}
     
    @@ -135,10 +137,31 @@

    Delete a user
    curl -i "http://localhost:5000/users/2/"
    +HTTP/1.0 404 NOT FOUND
    +Content-Type: application/json
    +Connection: close
    +Server: Werkzeug/0.8.3 Python/2.7.2
    +Date: Tue, 15 Oct 2013 11:16:33 GMT
    +
    +{"error": "<p>The requested URL was not found on the server.</p><p>If you entered the URL manually please check your spelling and try again.</p>"}
    +
    +

    +

    And the list is also updated:

    +
    curl -i "http://localhost:5000/users/"
    +HTTP/1.0 200 OK
    +Content-Type: application/json
    +Content-Length: 125
    +Server: Werkzeug/0.8.3 Python/2.7.2
    +Date: Tue, 15 Oct 2013 11:17:46 GMT
    +
    +{"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]}
    +
    -

    Update a User

    +

    Update a User

    Let’s go another time to the creation process:

    curl -i -H "Content-type: application/json" -X POST -d '{"first_name":"Steve", "last_name": "Roger"}'  http://localhost:5000/users/
     HTTP/1.0 201 CREATED
    @@ -151,23 +174,59 @@ 

    Update a User
    curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain", "last_name": "America"}'  http://localhost:5000/users/3/
    +
    curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Capitain", "last_name": "America"}'  http://localhost:5000/users/3/
     HTTP/1.0 200 OK
     Content-Type: application/json
     Content-Length: 58
     Server: Werkzeug/0.8.3 Python/2.7.2
     Date: Mon, 14 Oct 2013 20:57:47 GMT
    +
    +{"first_name": "Capitain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"}
     
    -

    Partial update is also possible:

    -
    curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Cap tain"}'  http://localhost:5000/users/3/
    +

    Argh! Thats a typo. the fist name is “Captain”, not “Capitain”. Let’s +correct this:

    +
    curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain"}'  http://localhost:5000/users/3/
     HTTP/1.0 200 OK
     Content-Type: application/json
     Content-Length: 59
     Server: Werkzeug/0.8.3 Python/2.7.2
     Date: Mon, 14 Oct 2013 21:08:04 GMT
    +
    +{"first_name": "Captain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"}
     
    +
    +
    +

    Filtering

    +

    Ressources can be filtered easily using parameters:

    +
    curl -i "http://localhost:5000/users/?last_name=America"
    +HTTP/1.0 200 OK
    +Content-Type: application/json
    +Content-Length: 236
    +Server: Werkzeug/0.8.3 Python/2.7.2
    +Date: Tue, 15 Oct 2013 12:07:21 GMT
    +
    +{"meta": {"filters": {"last_name": "America"}}, "object_list":
    +[{"first_name": "Joe", "last_name": "America", "id": 1,
    +"ressource_uri": "/users/1/"}, {"first_name": "Bob", "last_name":
    +"America", "id": 3, "ressource_uri": "/users/3/"}]
    +
    +
    +

    Multiple filters are allowed:

    +
    curl -i "http://localhost:5000/users/?last_name=America&first_name=Joe"
    +HTTP/1.0 200 OK
    +Content-Type: application/json
    +Content-Length: 171
    +Server: Werkzeug/0.8.3 Python/2.7.2
    +Date: Tue, 15 Oct 2013 12:09:32 GMT
    +
    +{"meta": {"filters": {"first_name": "Joe", "last_name": "America"}},
    +"object_list": [{"first_name": "Joe", "last_name": "America", "id": 1,
    +"ressource_uri": "/users/1/"}]}
    +
    +
    +

    Error handling

    Of course, If data is not formated as expected by the API, the base @@ -224,9 +283,8 @@

    Table Of Contents

  • Create a user
  • List and Get
  • Delete a user
  • - - -
  • Update a User
      +
    • Update a User
    • +
    • Filtering
    • Error handling
      • Missing data
      • Invalid Data
      • diff --git a/docs/build/work_with_pagination.html b/docs/build/work_with_pagination.html new file mode 100644 index 0000000..cb199ef --- /dev/null +++ b/docs/build/work_with_pagination.html @@ -0,0 +1,299 @@ + + + + + + + + Working with Pagination — Python Rest Api Framework 0.1 documentation + + + + + + + + + + + + + +
        +
        +
        +
        + +
        +

        Working with Pagination

        +
        +

        Creating fixtures

        +

        When your address book will be full of entry, you will need to add a +pagination on your API. As it is a common need, REST API Framework +implement a very easy way of doing so.

        +

        Before you can play with the pagination process, you will need to +create more data. You can create those records the way you want:

        +
          +
        • direct insert into the database
        • +
        +
        sqlite3 adress_book.db
        +INSERT INTO users VALUES ("Nick", "Furry", 6);
        +
        +
        +
          +
        • using the datastore directly
        • +
        +
        store = SQLiteDataStore({"name": "adress_book.db", "table": "users"}, UserModel)
        +store.create({"first_name": "Nick", "last_name": "Furry"})
        +
        +
        +
          +
        • using your API
        • +
        +
        curl -i -H "Content-type: application/json" -X POST -d '{"first_name": "Nick", "last_name": "Furry"}'  http://localhost:5000/users/
        +
        +
        +

        each on of those methods have advantages and disavantages but they all +make the work done. For this example, I propose to use the well know +requests package with a script to create a bunch of random records:

        +

        For this to work you need to install resquests : http://docs.python-requests.org/en/latest/user/install/#install

        +
        import json
        +import requests
        +import random
        +import string
        +
        +def get_random():
        +    return ''.join(
        +                   random.choice(
        +                     string.ascii_letters) for x in range(
        +                     int(random.random() * 20)
        +                     )
        +                   )
        +
        +for i in range(200):
        +    requests.post("http://localhost:5000/users/", data=json.dumps({"first_name": get_random(), "last_name": get_random()}))
        +
        +
        +
        + +
        +

        Browsering Through Paginated objects

        +

        Of course you get 20 records but the most usefull part is the meta +key:

        +
        {"meta":
        +    {"count": 20,
        +    "total_count": 802,
        +    "next": "?offset=20",
        +    "filters": {},
        +    "offset": 0,
        +    "previous": "null"}
        +}
        +
        +
        +

        You can use the “next” key to retreive the 20 next rows:

        +
        curl -i "http://localhost:5000/users/?offset=20"
        +HTTP/1.0 200 OK
        +Content-Type: application/json
        +Content-Length: 1849
        +Server: Werkzeug/0.8.3 Python/2.7.2
        +Date: Tue, 15 Oct 2013 11:38:59 GMT
        +
        +
        +
        {"meta": {"count": 20, "total_count": 802, "next": "?offset=40",
        +"filters": {}, "offset": 20, "previous": "?offset=0"}, "object_list":
        +[<snip for readability>]}
        +
        +
        +

        Note

        +

        The count and offset keywords can be easily changed to match your +needs. pagination class may take an offset_key and count_key +parameters. So if you prefer to use first_id and limit, you can +change your Paginator class to do so:

        +
        "options": {"pagination": Pagination(20,
        +                                 offset_key="first_id",
        +                                 count_key="limit")
        +
        +
        +

        Wich will results in the following:

        +
        curl -i "http://localhost:5000/users/"
        +{"meta": {"first_id": 0, "total_count": 802, "next": "?first_id=20",
        +"limit": 20, "filters": {}, "previous": "null"}, "object_list": [<snip
        +for readability>]
        +
        +
        +
        +
        +
        +

        Pagination and Filters

        +

        Pagination and filtering play nice together

        +
        curl -i "http://localhost:5000/users/?last_name=America"
        +HTTP/1.0 200 OK
        +Content-Type: application/json
        +Content-Length: 298
        +Server: Werkzeug/0.8.3 Python/2.7.2
        +Date: Tue, 15 Oct 2013 12:14:59 GMT
        +
        +{"meta": {"count": 20,
        +          "total_count": 2,
        +          "next": "null",
        +          "filters": {"last_name": "America"},
        +          "offset": 0,
        +          "previous": "null"},
        +          "object_list": [
        +              {"first_name": "Joe",
        +               "last_name": "America",
        +               "ressource_uri": "/users/1/"},
        +              {"first_name": "Bob",
        +               "last_name": "America",
        +               "ressource_uri": "/users/3/"}
        +          ]
        + }
        +
        +
        +
        +
        + + +
        +
        +
        +
        +
        +

        Table Of Contents

        + + +

        This Page

        + + + +
        +
        +
        +
        + + + + \ No newline at end of file diff --git a/docs/source/#related_ressources.rst# b/docs/source/#related_ressources.rst# new file mode 100644 index 0000000..84fc2b9 --- /dev/null +++ b/docs/source/#related_ressources.rst# @@ -0,0 +1,7 @@ +Linking ressource together +========================== + + + +Address Book need to link address to user as you can have multiple +users with a single address (a familliy for example) diff --git a/docs/source/.#related_ressources.rst b/docs/source/.#related_ressources.rst new file mode 120000 index 0000000..67b9448 --- /dev/null +++ b/docs/source/.#related_ressources.rst @@ -0,0 +1 @@ +yohann@MacBook-Air-de-Yohann.local.36008 \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index d4c2c38..d43f9fa 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,8 +3,15 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to Python Rest Api Framework's documentation! -===================================================== +Python Rest Api Framework's documentation +========================================= + +Python REST API framework is a set of utilities based on werkzeug to +easily build Restful API with a MVC pattern. Main features includes: +Pagination, Authentication, Authorization, Filters, Partials Response, +Error handling, data validators, data formaters... +and more... + Contents: @@ -13,12 +20,70 @@ Contents: introduction tutorial - datastore - controller - pagination - authentication - multiple_endpoint - references +.. datastore +.. controller +.. pagination +.. authentication +.. multiple_endpoint +.. references + + +A Full working example +---------------------- + +.. code-block:: python + + from rest_api_framework import models + from rest_api_framework.datastore import SQLiteDataStore + from rest_api_framework.views import JsonResponse + from rest_api_framework.controllers import Controller + from rest_api_framework.datastore.validators import UniqueTogether + from rest_api_framework.pagination import Pagination + + + class UserModel(models.Model): + """ + Define how to handle and validate your data. + """ + fields = [models.StringField(name="first_name", required=True), + models.StringField(name="last_name", required=True), + models.PkField(name="id", required=True) + ] + + + def remove_id(response, obj): + """ + Do not show the id in the response. + """ + obj.pop(response.model.pk_field.name) + return obj + + + class UserEndPoint(Controller): + ressource = { + "ressource_name": "users", + "ressource": {"name": "adress_book.db", "table": "users"}, + "model": UserModel, + "datastore": SQLiteDataStore, + "options": {"validators": [UniqueTogether("first_name", "last_name")]} + } + + controller = { + "list_verbs": ["GET", "POST"], + "unique_verbs": ["GET", "PUT", "DElETE"], + "options": {"pagination": Pagination(20)} + } + + view = {"response_class": JsonResponse, + "options": {"formaters": ["add_ressource_uri", remove_id]}} + + + if __name__ == '__main__': + + from werkzeug.serving import run_simple + from rest_api_framework.controllers import WSGIDispatcher + app = WSGIDispatcher([UserEndPoint]) + run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True) Indices and tables ================== @@ -26,4 +91,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index c3f3823..90ce8cb 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -346,5 +346,5 @@ is the same as with the PythonListDataStore. Where to go from here --------------------- -* :doc:`Authentication and Authorization ` -* :doc:`multiple_endpoint` +.. * :doc:`Authentication and Authorization ` +.. * :doc:`multiple_endpoint` diff --git a/docs/source/representing_data.rst b/docs/source/representing_data.rst index bb030d6..da59b3d 100644 --- a/docs/source/representing_data.rst +++ b/docs/source/representing_data.rst @@ -48,7 +48,7 @@ You can check that it work as expected: Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 23:41:55 GMT - {"first_name": "Cap tain", "last_name": "America", + {"first_name": "Captain", "last_name": "America", "ressource_uri": "/users/1/"} Make things generics @@ -77,6 +77,11 @@ Your code then become: obj.pop(response.model.pk_field.name) return obj -And reuse this formatter as long as you need +And reuse this formatter as long as you need. -Next :doc:`related_ressources` +Formaters are here to help you build clean and meaningful ressources +representations. It should hide internal representation of your +ressources and return all of the fields needed to manipulate and +represent your data. + +Next :doc:`work_with_pagination` diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index 6abcfa8..ccaefd5 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -8,4 +8,5 @@ Tutorial building an adressebook API using_user_endpoint adding_validator_datastore representing_data + work_with_pagination related_ressources diff --git a/docs/source/using_user_endpoint.rst b/docs/source/using_user_endpoint.rst index 28119fe..5994155 100644 --- a/docs/source/using_user_endpoint.rst +++ b/docs/source/using_user_endpoint.rst @@ -9,9 +9,11 @@ First you can check that your endpoint is up HTTP/1.0 200 OK Content-Type: application/json - Content-Length: 2 + Content-Length: 44 Server: Werkzeug/0.8.3 Python/2.7.2 - Date: Mon, 14 Oct 2013 12:52:22 GMT + Date: Tue, 15 Oct 2013 11:13:44 GMT + + {"meta": {"filters": {}}, "object_list": []} Your endpoint is responding but does not have any data. Let's add some: @@ -59,14 +61,14 @@ The list of users is also updated: .. code-block:: bash - curl -i "http://localhost:5000/users/1/" + curl -i "http://localhost:5000/users/" HTTP/1.0 200 OK Content-Type: application/json Content-Length: 83 Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 17:03:00 GMT - [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}] + {"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]} Delete a user ------------- @@ -94,8 +96,35 @@ and now delete it: Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 20:41:46 GMT +You can check that the user no longer exists: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/2/" + HTTP/1.0 404 NOT FOUND + Content-Type: application/json + Connection: close + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:16:33 GMT + + {"error": "

        The requested URL was not found on the server.

        If you entered the URL manually please check your spelling and try again.

        "} + +And the list is also updated: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 125 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:17:46 GMT + + {"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]} + + Update a User -============= +------------- Let's go another time to the creation process: @@ -114,24 +143,63 @@ America. Let's update this user: .. code-block:: bash - curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain", "last_name": "America"}' http://localhost:5000/users/3/ + curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Capitain", "last_name": "America"}' http://localhost:5000/users/3/ HTTP/1.0 200 OK Content-Type: application/json Content-Length: 58 Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 20:57:47 GMT -Partial update is also possible: + {"first_name": "Capitain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"} + +Argh! Thats a typo. the fist name is "Captain", not "Capitain". Let's +correct this: .. code-block:: bash - curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Cap tain"}' http://localhost:5000/users/3/ + curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain"}' http://localhost:5000/users/3/ HTTP/1.0 200 OK Content-Type: application/json Content-Length: 59 Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 21:08:04 GMT + {"first_name": "Captain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"} + + +Filtering +--------- + +Ressources can be filtered easily using parameters: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/?last_name=America" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 236 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 12:07:21 GMT + + {"meta": {"filters": {"last_name": "America"}}, "object_list": + [{"first_name": "Joe", "last_name": "America", "id": 1, + "ressource_uri": "/users/1/"}, {"first_name": "Bob", "last_name": + "America", "id": 3, "ressource_uri": "/users/3/"}] + +Multiple filters are allowed: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/?last_name=America&first_name=Joe" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 171 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 12:09:32 GMT + + {"meta": {"filters": {"first_name": "Joe", "last_name": "America"}}, + "object_list": [{"first_name": "Joe", "last_name": "America", "id": 1, + "ressource_uri": "/users/1/"}]} Error handling -------------- diff --git a/docs/source/work_with_pagination.rst b/docs/source/work_with_pagination.rst new file mode 100644 index 0000000..eebe89a --- /dev/null +++ b/docs/source/work_with_pagination.rst @@ -0,0 +1,215 @@ +Working with Pagination +======================= + +Creating fixtures +----------------- + +When your address book will be full of entry, you will need to add a +pagination on your API. As it is a common need, REST API Framework +implement a very easy way of doing so. + +Before you can play with the pagination process, you will need to +create more data. You can create those records the way you want: + +* direct insert into the database + +.. code-block:: bash + + sqlite3 adress_book.db + INSERT INTO users VALUES ("Nick", "Furry", 6); + +* using the datastore directly + +.. code-block:: python + + store = SQLiteDataStore({"name": "adress_book.db", "table": "users"}, UserModel) + store.create({"first_name": "Nick", "last_name": "Furry"}) + +* using your API + +.. code-block:: python + + curl -i -H "Content-type: application/json" -X POST -d '{"first_name": "Nick", "last_name": "Furry"}' http://localhost:5000/users/ + +each on of those methods have advantages and disavantages but they all +make the work done. For this example, I propose to use the well know +requests package with a script to create a bunch of random records: + +For this to work you need to install resquests : http://docs.python-requests.org/en/latest/user/install/#install + +.. code-block:: python + + import json + import requests + import random + import string + + def get_random(): + return ''.join( + random.choice( + string.ascii_letters) for x in range( + int(random.random() * 20) + ) + ) + + for i in range(200): + requests.post("http://localhost:5000/users/", data=json.dumps({"first_name": get_random(), "last_name": get_random()})) + +Pagination +---------- + +Now your datastore is filled with more than 200 records, it's time to +paginate. To do so import Pagination and change the controller part of +your app. + +.. code-block:: python + + from rest_api_framework.pagination import Pagination + + class UserEndPoint(Controller): + ressource = { + "ressource_name": "users", + "ressource": {"name": "adress_book.db", "table": "users"}, + "model": UserModel, + "datastore": SQLiteDataStore, + "options": {"validators": [UniqueTogether("first_name", "last_name")]} + } + + controller = { + "list_verbs": ["GET", "POST"], + "unique_verbs": ["GET", "PUT", "DElETE"], + "options": {"pagination": Pagination(20)} + } + + view = {"response_class": JsonResponse, + "options": {"formaters": ["add_ressource_uri", remove_id]}} + +and try your new pagination: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 1811 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:32:55 GMT + + {"meta": {"count": 20, "total_count": 802, "next": "?offset=20", + "filters": {}, "offset": 0, "previous": "null"}, "object_list": + [{"first_name": "Captain", "last_name": "America", + "ressource_uri": "/users/1/"}, {"first_name": "Captain", + "last_name": "America", "ressource_uri": "/users/3/"}, + {"first_name": "John", "last_name": "Doe", "ressource_uri": + "/users/4/"}, {"first_name": "arRFOSYZT", "last_name": "", + "ressource_uri": "/users/5/"}, {"first_name": "iUJsYORMuYeMUDy", + "last_name": "TqFpmcBQD", "ressource_uri": "/users/6/"}, + {"first_name": "EU", "last_name": "FMSAbcUJBSBDPaF", + "ressource_uri": "/users/7/"}, {"first_name": "mWAwamrMQARXW", + "last_name": "yMNpEnYOPzY", "ressource_uri": "/users/8/"}, + {"first_name": "y", "last_name": "yNiKP", "ressource_uri": + "/users/9/"}, {"first_name": "s", "last_name": "TRT", + "ressource_uri": "/users/10/"}, {"first_name": "", "last_name": + "zFUaBd", "ressource_uri": "/users/11/"}, {"first_name": "WA", + "last_name": "priJ", "ressource_uri": "/users/12/"}, + {"first_name": "XvpLttDqFmR", "last_name": "liU", "ressource_uri": + "/users/13/"}, {"first_name": "ZhJqTgYoEUzmcN", "last_name": + "KKDqHJwJMxPSaTX", "ressource_uri": "/users/14/"}, {"first_name": + "qvUxiKIATdKdkC", "last_name": "wIVzfDlKCkjkHIaC", + "ressource_uri": "/users/15/"}, {"first_name": "YSSMHxdDQQsW", + "last_name": "UaKCKgKsgEe", "ressource_uri": "/users/16/"}, + {"first_name": "EKLFTPJLKDINZio", "last_name": "nuilPTzHqattX", + "ressource_uri": "/users/17/"}, {"first_name": "SPcDBtmDIi", + "last_name": "MrytYqElXiIxA", "ressource_uri": "/users/18/"}, + {"first_name": "OHxNppXiYp", "last_name": "AUvUXFRPICsJIB", + "ressource_uri": "/users/19/"}, {"first_name": "WBFGxnoe", + "last_name": "KG", "ressource_uri": "/users/20/"}, {"first_name": + "i", "last_name": "ggLOcKPpMfgvVGtv", "ressource_uri": + "/users/21/"}]} + +Browsering Through Paginated objects +------------------------------------ + +Of course you get 20 records but the most usefull part is the meta +key: + +.. code-block:: json + + {"meta": + {"count": 20, + "total_count": 802, + "next": "?offset=20", + "filters": {}, + "offset": 0, + "previous": "null"} + } + +You can use the "next" key to retreive the 20 next rows: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/?offset=20" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 1849 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:38:59 GMT + +.. code-block:: json + + {"meta": {"count": 20, "total_count": 802, "next": "?offset=40", + "filters": {}, "offset": 20, "previous": "?offset=0"}, "object_list": + []} + +.. note:: + + The count and offset keywords can be easily changed to match your + needs. pagination class may take an offset_key and count_key + parameters. So if you prefer to use first_id and limit, you can + change your Paginator class to do so: + + .. code-block:: python + + "options": {"pagination": Pagination(20, + offset_key="first_id", + count_key="limit") + + Wich will results in the following: + + .. code-block:: bash + + curl -i "http://localhost:5000/users/" + {"meta": {"first_id": 0, "total_count": 802, "next": "?first_id=20", + "limit": 20, "filters": {}, "previous": "null"}, "object_list": [] + + +Pagination and Filters +---------------------- + +Pagination and filtering play nice together + +.. code-block:: bash + + curl -i "http://localhost:5000/users/?last_name=America" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 298 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 12:14:59 GMT + + {"meta": {"count": 20, + "total_count": 2, + "next": "null", + "filters": {"last_name": "America"}, + "offset": 0, + "previous": "null"}, + "object_list": [ + {"first_name": "Joe", + "last_name": "America", + "ressource_uri": "/users/1/"}, + {"first_name": "Bob", + "last_name": "America", + "ressource_uri": "/users/3/"} + ] + } diff --git a/rest_api_framework/controllers.py b/rest_api_framework/controllers.py index 145d4e7..cd2c317 100644 --- a/rest_api_framework/controllers.py +++ b/rest_api_framework/controllers.py @@ -130,11 +130,22 @@ def paginate(self, request): offset, count = None, None request_kwargs = request.values.to_dict() filters = request_kwargs + objs = self.datastore.get_list(offset=offset, count=count, **filters) + if self.pagination: + total = self.datastore.count(**filters) + meta = self.pagination.get_metadata(count=count, + offset=offset, + total=total, + **filters) + else: + meta = {"filters": {}} + for k, v in filters.iteritems(): + meta["filters"][k] = v - return self.view(objs=objs, status=200) + return self.view(objs=objs, meta=meta, status=200) def get_list(self, request): """ @@ -198,7 +209,11 @@ def update(self, request, identifier): """ obj = self.datastore.get(identifier=identifier) - obj = self.datastore.update(obj, json.loads(request.data)) + try: + obj = self.datastore.update(obj, json.loads(request.data)) + except ValueError: + raise BadRequest + return self.view( objs=obj, status=200) diff --git a/rest_api_framework/datastore/simple.py b/rest_api_framework/datastore/simple.py index 87fd66a..286a5d9 100644 --- a/rest_api_framework/datastore/simple.py +++ b/rest_api_framework/datastore/simple.py @@ -71,6 +71,9 @@ def create(self, data): self.data.append(obj) return obj[self.model.pk_field.name] + def count(self, **kwargs): + return len(self.filter(**kwargs)) + def update(self, obj, data): """ Update a single object diff --git a/rest_api_framework/datastore/sql.py b/rest_api_framework/datastore/sql.py index 9689571..db33f94 100644 --- a/rest_api_framework/datastore/sql.py +++ b/rest_api_framework/datastore/sql.py @@ -69,6 +69,7 @@ def __init__(self, ressource_config, model, **options): conn.close() self.fields = self.model.get_fields() + def get_connector(self): """ return a sqlite3 connection to communicate with the table @@ -86,6 +87,32 @@ def filter(self, **kwargs): kwargs['query'] += ' FROM {0}' return kwargs + def count(self, **data): + cdt = self.build_conditions(data) + if len(cdt) == 0: + query = "SELECT COUNT (*) FROM {0}".format( + self.ressource_config['table']) + else: + cdt = " AND ".join(cdt) + query = "SELECT COUNT (*) FROM {0} WHERE {1}".format( + self.ressource_config['table'], + cdt + ) + cursor = self.get_connector().cursor() + cursor.execute(query) + return cursor.fetchone()[0] + + def build_conditions(self, data): + return [ + ["{0}='{1}'".format( + e[0], e[1]) for e in condition.iteritems() + ][0] for condition in self.get_conditions(data)] + + def get_conditions(self, data): + return [ + {k: v} for k, v in data.iteritems() if k not in ["query", "fields"] + ] + def paginate(self, data, **kwargs): """ paginate the result of filter using ids limits. Obviously, to @@ -93,10 +120,9 @@ def paginate(self, data, **kwargs): receive from the last call on this method. The max number of row this method can give back depend on the paginate_by option. """ - print kwargs + + where_query = self.build_conditions(data) args = [] - where_query = [ - "{0}='{1}'".format(k, v) for k, v in data.iteritems() if k not in ["query", "fields"]] limit = kwargs.pop("end", None) if kwargs.get("start", None): where_query.append(" id >=?") @@ -105,14 +131,14 @@ def paginate(self, data, **kwargs): if len(where_query) > 0: data["query"] += " WHERE " data["query"] += " AND ".join(where_query) + cursor = self.get_connector().cursor() # a hook for ordering data["query"] += " ORDER BY id ASC" if limit: data["query"] += " LIMIT {0}".format(limit) - cursor = self.get_connector().cursor() - print data["query"] + cursor.execute(data["query"].format(self.ressource_config['table']), tuple(args) ) @@ -210,11 +236,12 @@ def update(self, obj, data): cursor = conn.cursor() update = " ,".join(["{0}='{1}'".format(f, v) for f, v in zip(fields, values)]) - query = "update {0} set {1}".format( + query = "update {0} set {1} WHERE {2}={3}".format( self.ressource_config["table"], - update + update, + self.model.pk_field.name, + obj[self.model.pk_field.name] ) - print query cursor.execute(query) conn.commit() conn.close() diff --git a/rest_api_framework/pagination.py b/rest_api_framework/pagination.py index f55cd14..4694979 100644 --- a/rest_api_framework/pagination.py +++ b/rest_api_framework/pagination.py @@ -27,3 +27,29 @@ def paginate(self, request): if count > self.max: count = self.max return offset, count, request_kwargs + + def get_metadata(self, total=0, offset=0, count=0, **filters): + meta = {self.offset_key: offset, + self.count_key: count, + "total_count": total, + "filters": {} + } + for k, v in filters.iteritems(): + meta["filters"][k] = v + if offset == 0: + meta['previous'] = "null" + else: + meta["previous"] = offset - count + if meta["previous"] < 0: + meta["previous"] = 0 + if meta['previous'] != "null": + meta["previous"] = "?{0}={1}".format(self.offset_key, + meta["previous"]) + + meta["next"] = offset + count + if meta["next"] > total: + meta["next"] = "null" + if meta['next'] != "null": + meta["next"] = "?{0}={1}".format(self.offset_key, + meta["next"]) + return meta diff --git a/rest_api_framework/views.py b/rest_api_framework/views.py index 47c37a3..eed2efd 100644 --- a/rest_api_framework/views.py +++ b/rest_api_framework/views.py @@ -28,18 +28,31 @@ def __init__(self, model, ressource_name, def __call__(self, *args, **kwargs): + meta = None + + if "meta" in kwargs: + meta = kwargs.pop("meta") + if "objs" in kwargs: objs = self.format(kwargs.pop('objs')) - return Response(json.dumps(objs), + if meta: + response = {"meta": meta, + "object_list": objs} + else: + response = objs + return Response(json.dumps(response), mimetype="application/json", **kwargs) else: - return Response(*args, - mimetype="application/json", - **kwargs) + response = "" + if args: + response = json.dumps(*args) + return Response(response, + mimetype="application/json", + **kwargs) def make_options(self, **options): - print options + pass def format(self, objs): diff --git a/setup.cfg b/setup.cfg index 7b58525..2268c04 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,4 +7,5 @@ with-coverage=1 cover-xml=1 cover-xml-file=report_coverage.xml cover-package=rest_api_framework -stop=1 \ No newline at end of file +stop=1 +nocapture=1 \ No newline at end of file diff --git a/tests/controllers_test.py b/tests/controllers_test.py index 14da42c..65d95cb 100644 --- a/tests/controllers_test.py +++ b/tests/controllers_test.py @@ -65,8 +65,7 @@ def test_get_list(self): response_wrapper=BaseResponse) resp = client.get("/address/") self.assertEqual(resp.status_code, 200) - print resp.data - self.assertIsInstance(json.loads(resp.data), list) + self.assertIsInstance(json.loads(resp.data)["object_list"], list) def test_get(self): client = Client(WSGIDispatcher([ApiApp]), @@ -230,7 +229,8 @@ def test_base_pagination(self): client = Client(WSGIDispatcher([ApiApp]), response_wrapper=BaseResponse) resp = client.get("/address/") - self.assertEqual(len(json.loads(resp.data)), 20) + + self.assertEqual(len(json.loads(resp.data)["object_list"]), 20) def test_base_pagination_count(self): client = Client(WSGIDispatcher([ApiApp]), @@ -242,13 +242,13 @@ def test_base_pagination_count_overflow(self): client = Client(WSGIDispatcher([ApiApp]), response_wrapper=BaseResponse) resp = client.get("/address/?count=200") - self.assertEqual(len(json.loads(resp.data)), 20) + self.assertEqual(len(json.loads(resp.data)["object_list"]), 20) def test_base_pagination_offset(self): client = Client(WSGIDispatcher([ApiApp]), response_wrapper=BaseResponse) resp = client.get("/address/?offset=2") - self.assertEqual(json.loads(resp.data)[0]['ressource_uri'], + self.assertEqual(json.loads(resp.data)["object_list"][0]['ressource_uri'], "/address/2/") @@ -261,7 +261,7 @@ def test_base_pagination(self): client.post("/address/", data=json.dumps({"name": "bob", "age": 34})) resp = client.get("/address/") - self.assertEqual(len(json.loads(resp.data)), 20) + self.assertEqual(len(json.loads(resp.data)["object_list"]), 20) os.remove("test.db") def test_base_pagination_offset(self): @@ -271,7 +271,7 @@ def test_base_pagination_offset(self): client.post("/address/", data=json.dumps({"name": "bob", "age": 34})) resp = client.get("/address/?offset=2") - self.assertEqual(json.loads(resp.data)[0]['id'], 2) + self.assertEqual(json.loads(resp.data)["object_list"][0]['id'], 2) os.remove("test.db") def test_base_pagination_count(self): @@ -292,7 +292,7 @@ def test_base_pagination_count_offset(self): data=json.dumps({"name": "bob", "age": 34})) resp = client.get("/address/?count=2&offset=4") self.assertEqual(len(json.loads(resp.data)), 2) - self.assertEqual(json.loads(resp.data)[0]['id'], 4) + self.assertEqual(json.loads(resp.data)["object_list"][0]['id'], 4) os.remove("test.db") @@ -303,7 +303,7 @@ def test_get_partial_list(self): response_wrapper=BaseResponse) resp = client.get("/address/?fields=age") # we only want "age". get_list add id, JsonResponse add ressource_uri - self.assertEqual(len(json.loads(resp.data)[0].keys()), 3) + self.assertEqual(len(json.loads(resp.data)["object_list"][0].keys()), 3) def test_get_partial_raise(self): client = Client(WSGIDispatcher([PartialApiApp]), @@ -322,8 +322,7 @@ def test_get_partial_sql(self): resp = client.get("/address/?fields=age") # we only want "age". get_list add id, JsonResponse add ressource_uri - print resp - self.assertEqual(len(json.loads(resp.data)[0].keys()), 3) + self.assertEqual(len(json.loads(resp.data)["object_list"][0].keys()), 3) os.remove("test.db") def test_get_partial_sql_raise(self): diff --git a/tests/datastore_test.py b/tests/datastore_test.py index 51c55e3..dfd2ba3 100644 --- a/tests/datastore_test.py +++ b/tests/datastore_test.py @@ -244,7 +244,6 @@ def test_pagination(self): ApiModel) for i in range(100): store.create({"name": "bob", "age": 34}) - print store.get_list(count=10) self.assertEqual(len(store.get_list(count=10)), 10) self.assertEqual(store.get_list(count=10)[-1]["id"], 10) self.assertEqual(store.get_list(offset=15)[0]["id"], 15) @@ -279,7 +278,6 @@ def test_create(self): self.assertEqual(store.create({"name": "bob", "age": 34}), 1) self.assertEqual(store.create({"name": "bob", "age": 35}), 2) self.assertEqual(store.create({"name": "bob", "age": 34}), 3) - print store.get(3) self.assertEqual(store.get(3)["id"], 3) os.remove("test.db")