From 38cf36d5476c450e06c39625bb610e3ca53638f9 Mon Sep 17 00:00:00 2001 From: BadgerHobbs Date: Fri, 24 Dec 2021 18:34:26 +0000 Subject: [PATCH] Added full flask API, docker support and API documentation --- README.md | 501 +++++++++++++++++++++++++ __pycache__/flask_api.cpython-36.pyc | Bin 0 -> 4311 bytes __pycache__/parkrun_api.cpython-36.pyc | Bin 32238 -> 31076 bytes dockerfile | 13 + flask_api.py | 339 +++++++++++++++++ parkrun_api.py | 7 +- 6 files changed, 858 insertions(+), 2 deletions(-) create mode 100644 __pycache__/flask_api.cpython-36.pyc create mode 100644 dockerfile create mode 100644 flask_api.py diff --git a/README.md b/README.md index 8ee05d0..a17a05e 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,13 @@ Python API for Parkrun data ## Python Requirements ```python +# General pip install requests pip install beautifulsoup4 + +# For running web API +pip install flask +pip install flask-limiter ``` ## Example Usage (see also example.py) ```python @@ -61,3 +66,499 @@ def ExampleUsage(): ExampleUsage() ``` + +## Docker Deployment + +### Build Docker Container +``` +docker build -t parkrun-api:latest . +``` + +### Run Docker Container +``` +docker run -d \ + --name parkrun-api \ + -p 5000:5000 \ + --restart on-failure \ + parkrun-api:latest +``` + +## API Documentation + +### General Data + +```/``` +``` +Redirect to https://github.com/BadgerHobbs/Parkrun-API-Python +``` + + +```/v1/cache``` +```json +{ + "countries": [ + { + "id": "3", + "url": "https://www.parkrun.com.au" + }, + ], + "events": [ + { + "id": 193, + "name": "newfarm", + "longName": "New Farm parkrun", + "shortName": "New Farm", + "countryCode": 3, + "seriesId": 1, + "location": "New Farm, Brisbane", + "url": "https://www.parkrun.com.au/newfarm/" + }, + ], + "countries/3/historic-numbers": [], + "global/results/week-first-finishers": [], + "...": [], +} +``` + + +```/v1/countries``` +```json +[ + { + "id": "3", + "url": "https://www.parkrun.com.au" + }, + { + "id": "4", + "url": "https://www.parkrun.co.at" + }, +] +``` + +### Event Specific Data + +```/v1/events``` +```json +[ + { + "id": 2761, + "name": "centralplymouth", + "longName": "Central parkrun, Plymouth", + "shortName": "Central, Plymouth", + "countryCode": 97, + "seriesId": 1, + "location": "Central Park", + "url": "https://www.parkrun.org.uk/centralplymouth/" + }, +] +``` + + +```/v1/events//history``` +```json +[ + { + "eventNumber": "5", + "date": "18/12/2021", + "finishers": "169", + "volunteers": "21", + "male": "John SMITH", + "female": "Jasmine KING", + "maleTime": "1647", + "femaleTime": "2029" + }, +] +``` + + +```/v1/events//first-finishers``` +```json +[ + { + "parkRunner": "John SMITH", + "firstPlaceFinishes": "2", + "bestTime": "16:47", + "sex": "M" + }, +] +``` + + +```/v1/events//age-category-records``` +```json +[ + { + "ageCategory": "JM10", + "eventNumber": "4", + "date": "11/12/2021", + "parkRunner": "John SMITH", + "time": "00:24:16", + "ageGrade": "70.12 %" + }, +] +``` + + +```/v1/events//clubs``` +```json + { + "name": "Plymouth Musketeers RC", + "numberOfParkrunners": "53", + "numberOfRuns": "110", + "clubHomePage": "http://www.plymouthmusketeers.org.uk/" + }, +``` + + +```/v1/events//sub-20-women``` +```json +[ + { + "rank": "1", + "parkRunner": "Molly SMITH", + "numberOfRuns": "2", + "fastestTime": "18:36", + "club": null + } +] +``` + + +```/v1/events//sub-17-men``` +```json +[ + { + "rank": "1", + "parkRunner": "Matt SMITH", + "numberOfRuns": "1", + "fastestTime": "16:43", + "club": "Egdon Heath Harriers" + }, +] +``` + + +```/v1/events//age-graded-league-ranks?quantity=``` +```json +[ + { + "rank": "1", + "parkRunner": "Molly SMITH", + "ageGrade": "84.50 %" + }, +] +``` + + +```/v1/events//fastest-500``` +```json +[ + { + "rank": "1", + "parkRunner": "Matt SMITH", + "numberOfRuns": "1", + "sex": "M", + "fastestTime": "00:16:43", + "club": "Egdon Heath Harriers" + }, +] +``` + +### Country Specific Data + +```/v1/countries//week-first-finishers``` +```json +[ + { + "event": "Airlie Beach", + "maleParkRunner": "William SMITH", + "maleClub": "Run Crew", + "femaleParkRunner": "Aimee KING", + "femaleClub": null + }, +] +``` + + +```/v1/countries//week-sub-17-runs``` +```json +[ + { + "event": "Goolwa", + "parkRunner": "Unknown ATHLETE", + "time": "14:55", + "club": null + }, +] +``` + + +```/v1/countries//week-top-age-grades``` +```json +[ + { + "event": "Devonport", + "parkRunner": "Xavier SMITH", + "time": "18:08", + "ageGroup": "JM10", + "ageGrade": "93.84 %", + "club": null + }, +] +``` + + +```/v1/countries//week-new-category-records``` +```json +[ + { + "event": "Applecross", + "parkRunner": "Jen SMITH", + "time": "19:59", + "ageGroup": "VW50-54", + "ageGrade": "83.90 %", + "club": "Frontrunner TRC" + }, +] +``` + + +```/v1/countries//course-records``` +```json +[ + { + "event": "Airlie Beach", + "femaleParkRunner": "Alex KING", + "femaleTime": "18:56", + "femaleDate": "11/12/2021", + "maleParkRunner": "Tony SMITH", + "maleTime": "15:55", + "maleDate": "13/11/2021" + }, +] +``` + + +```/v1/countries//attendance-records``` +```json +[ + { + "event": "Airlie Beach", + "attendance": "120", + "week": "16/02/2019", + "thisWeek": "61" + }, +] +``` + + +```/v1/countries//most-events``` +```json +[ + { + "parkRunner": "Neil SMITH", + "events": "316", + "totalParkRuns": "444", + "totalParkRunsWorldwide": "444" + }, +] +``` + + +```/v1/countries//largest-clubs``` +```json +[ + { + "club": "Derek Zoolander Centre for Kids Who Can't Run Good", + "numberOfParkRunners": "1535", + "numberOfRuns": "75993", + "clubHomePage": "https://www.facebook.com/groups/650499288335106/" + }, +] +``` + + +```/v1/countries//joined-100-club``` +```json +[ + { + "parkRunner": "Adeline KING", + "numberOfRuns": "100" + }, +] +``` + + +```/v1/countries//most-first-finishes``` +```json +[ + { + "parkRunner": "Cory SMITH", + "numberOfRuns": "270" + }, +] +``` + + +```/v1/countries//freedom-runs``` +```json +[ + { + "parkRunner": "David SMITH", + "date": "24/12/2021", + "location": "Blue Gum Hills", + "runTime": "00:46:20" + }, +] +``` + + +```/v1/countries//historic-numbers``` +```json +[ + { + "date": "2011-04-02", + "events": "1", + "athletes": "108", + "volunteers": "7" + }, +] +``` + +### Global Results Data + +```/v1/global/results/week-first-finishers``` +```json +[ + { + "event": "Aberbeeg", + "maleParkRunner": "Martin SMITH", + "maleClub": "Parc Bryn Bach Running Club", + "femaleParkRunner": "Lisa KING", + "femaleClub": "Pont-y-pwl and District Runners" + }, +] +``` + + +```/v1/global/results/new-category-records``` +```json +[ + { + "event": "Aberdeen", + "parkRunner": "Kirsty SMITH", + "time": "18:22", + "ageGroup": "SW18-19", + "ageGrade": "81.49 %", + "club": "Aberdeen AAC" + }, +] +``` + + +```/v1/global/results/sub-17-runs``` +```json +[ + { + "event": "Hull", + "parkRunner": "Kris SMITH", + "time": "14:50", + "club": "City of Hull AC" + }, +] +``` + + +```/v1/global/results/top-age-grades``` +```json +[ + { + "event": "Rickmansworth", + "parkRunner": "Sarah SMITH", + "time": "22:34", + "ageGroup": "VW70-74", + "ageGrade": "101.99 %", + "club": "Dacorum AC" + }, +] +``` + + +```/v1/global/results/course-records``` +```json +[ + { + "event": "Aachener Weiher", + "femaleParkRunner": "Hannah KING", + "femaleTime": "18:44", + "femaleDate": "14/12/2019", + "maleParkRunner": "Declan SMITH", + "maleTime": "16:37", + "maleDate": "06/11/2021" + }, +] +``` + + +```/v1/global/results/freedom-runs``` +```json +[ + { + "parkRunner": "Maria KING", + "date": "24/12/2021", + "location": "Whangarei", + "runTime": "00:28:55" + }, +] +``` + +### Global Stats Data + +```/v1/global/stats/largest-clubs``` +```json +[ + { + "name": "Lonely Goat RC", + "numberOfParkrunners": "4443", + "numberOfRuns": "170799", + "clubHomePage": "https://lonelygoat.com" + }, +] +``` + + +```/v1/global/stats/attendance-records``` +```json +[ + { + "event": "Aberbeeg", + "attendance": "364", + "week": "01/01/2020", + "thisWeek": "30" + }, +] +``` + + +```/v1/global/stats/most-events``` +```json +[ + { + "parkRunner": "Paul SMITH", + "events": "541", + "totalParkRuns": "585", + "totalParkRunsWorldwide": null + }, +] +``` + + +```/v1/global/stats/most-first-finishes``` +```json +[ + { + "parkRunner": "Hannah SMITH", + "numberOfRuns": "368" + }, +] +``` diff --git a/__pycache__/flask_api.cpython-36.pyc b/__pycache__/flask_api.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb94920e23182a67beee4546091fb7fed9bf3aac GIT binary patch literal 4311 zcmcgv%Wm676dgV!QIaLUUvZw!lWAnyNz6vU5)Bh)mEvkRWw@zT&w7I%w=|*>pb?J*$E!! z33wXZ;7NEUd5WjunG%Xfw-tLtjFdBcq^m!GD^qL&yXn3xJ&E=Lnf3IY5@UMaaBJ^W_8eXGLy{M4KrLdYs`a{5HSCKmVYCX4(!j z@BW2yn@Skls5t(CsY(^vp&klMSPDJ;u_rRaQk0C5wNmSR@>|y*0iz?HHB)i zmeOKvuu6*sajvYgmI6<86|4p$YFlBl=rwAAbcG+r#cNRud@~Uy>W(~+jarykr?-dU z=wlJARVq;vJIB7p&f!ReQ^U7MU(~r12x`5M73fuTiu(d)q60~ZF7!K-Bk=h6Wc~i` z3tvcocdf>yc(dz!4OtYsQq(m?SsBQ9ISmKvV}Arq|Jz zB63$@uw!EClqzQd`KVY&+@N{P4LuHx%~h^_P=SHI)m+&Rg1Ud-vPy2S-zY2XCKoipLODP1zz~56;tIID4R*^^@>GP?xTxc zNJ?~LjOYOTF_KFQ^|J5P!WeH<>wcsijdxto$Od>E$#fS!(H-*~j+vn`yRqIk?vV*G zDmWPHAR%W0h=#J(CfQb5pqFDoJc^e#I3citsK7s(iHJ=c>N0@^`nX)c0^Ns9JX9r& zj-&PDOX%w3H<)HX@XGU0Yo?GhMV9~n5XJ?lk8lzIiE{rJ!pUwp`)lxzUbYvY-8^^9 z)>K(do_Nkpl;r8-Nujj;W06NN<^Ol&u}(cDF*SA#Ljh4=WswV#G03eE4JtO zfq=c{?(*`fhR*OUII$7Yp^)j+f!PT{26MC@2j-6$=Kod4t>yF0#PSmT+@B^Hdu~GR zxbH#Yuh%^pKn5uajgq+##^?}5N6aXb#gxrXcTRqIxRRKrMEL`rq#}}d0H9JpbaiD53$)g@X_(iAi}a`x<@#|gEu oTJ?CNA|7D!F^su~@uF@Fh|b^cExnAVo{EUNI?1)FUFIh*IRPDZ|U1AqZ%(3*K3f z;AY{dCF?I$tVVGjbracfVmo!()KSwmb(%+2SDzBQX{)+v>P}n7sZ%Fxoz`{J_WSr( zf4?&`ckT=Z4*_JX?`sy=JNMi>bN9}@=lssO=ia$m91W@V)eb& zYv-p6kEN`BYv5wa8n}?D7C{YKL!gF0l|XH?wu9OZst44twFA@+P-Rd%tzDpYf$9ad z+u8$a52y;LYplJX_JZmIwa>a1)U}}cL0xBE59)eQ1E5B%8$jIvY7o?o)=i*p0yPBc zX6qJEw}9FP>Q?JEP`82F4(fJm6x1lFVNiEicY?YT)DBSltuatzpmu^9w zfOQbmK~TFv-DMpDbqLfRQ17ws26Z>6Ye3y&-3#hoP(lMQ|?@fr_MJXIP=hf@%#4A&evw_iQ3uOQ-1WEeR5&{ z9TRs*{(C$9c602xZ>Rj6W2>2%ag5U)2{QzI(wd5m{B#$9($tc*!4wak3D0bS+t$D)9F8M z*T;_9?fU7lN04CB$6A(c(#L1p5t?ikrl*Yd+-2H#11ok0ZMbT>7f>Shq4JY1EY8qK(wSW#6%{g}Cl$)JKN^TlSznD^exbE_kllPxs6`d1DnwGN9op4%$W zvDM?PlP8@MPuJ9oYOy(X#+XV!5vlr6o)R}$Hr@Cx{!c^zh)9vo7_UrEpINLm zyc{*aU#vK7NOXPPMqgTreP+~&%%NB@xxZmV9QIWyQ!Yx1?m;A`O86ee*BM8kS-O2Vw7muy>D}f0R8jRcCwi!=q;I_kb{&yNjNl&UAAKv)w$xT(^KQ-z_36bV~?} zfdG^gD+Z`Qb5u)>R&!>Gu^#7iOSKtwd;QLsgb-It5Cd?)6PAl}J+Sk5phuNajxd6o zm&SE_ri0?p%3*nxX`?`#q)X)aevxD)qoJjZ*&he-b+#c8p-Dqn7s!5+X_BV`W8zJ|rUaeqV$jIuN8hgs7lnMI9^YSdWgCb*vXxRbDAB18pF584 zU+DTaE_4M?0^QOnyVeHWI?`R$@`7qD*s6`LsP@Py(tmx@1WyH>=(Wi4peL(APXgR^ zPtID^;3}`}M~j{`UyqhjBMfd}Kp<8(F}NAQXkHC5&aP0~8SG_15g5&?TX3kBy_!?D z-MqwO9qLv**_1jv--BzMYY)r}`r8T~?sl0wo~2g96%Gt41hEJVnz)jmi-ynZOF;-_z6JdboR{P^QkihzrQk5m(vqK zwye`uikKsvAbhX(fdr!QmIc<-4#sX{An>3u!X0>S!GczndOxQc&T}}&A!DYpW!&#@ z=DJL0hj)juGe=8P{s*12i$(8du!jMqLb2V{?F{Z@5dN_Kb?Ki8WQxiGLiJU4N!k^} zsKPV0v)H&gAjM{OOR!NMQ!wM)T)nY)vRZ&OW7*0rK@HE+JaKbSzK(w$tdweRt$upe zK5Nr38ucNHj1Ab)W#y+?m|L9oOY51|#pS1^JpW`A2o+(JXf(7&D2r{p3PKHd4HQCD zz`TTDs!)Nbi--WCGSu(@kBpVx1w&^p3-$$TTA2%($A1&i=humDX6D-!krruttgEf*mM#cokx>K?Ft2DZV#RA(m; z2VLq+{2&?;`e$+eq^*`GI=!@SX(<4_-Ar4#`Ff+~IGrKy4xO=f?PS9qQ>}BI9x#1v zjPTUylPDT~hMnl=u<<*+lH+xNe_GH9{%Pz8vjB8PXV5Fp=;D$R*t!c%s17lpYE*YK zxQD^L4Bm@iv}6Dnm8qMfC9l{cZt)bx1+X*Jex5F%RuYkRdyQsri+(FFPX$e9I<1=tH7bcu;&5f}-E#L`DDF zI!2=M;W3M^uoYFR$5JpG*}a*mY9B`v05$J7VaLf0AI6!Z=|l`0#IHOh*Z9BnQJm`t z8)rMayfoG%a?Zx!i@&OlFi3(gR_r9cA$;wX$|l3tjW|=ASZj?MDuTC<&pPcEhYnZ4 zx_SszadTi)*i>MvS?4sy5pMZxtAXK)jd(RTUu)Ra!YP|ax5V(-S=e1=FNJX*x_~|= z|K+DyENrW=+}`Q1V3=W2VsY9px<_a!yVp-6VYIhKu^(qsuYzKpj`G_q#Ik_tXzItY z3<@`fWf@>uMzAak#cN_&4p^2GEXxDS;3xr3vGXZ=)mp>%2TB0Hhvcg&t?{1Otx#)b}!Yior1kQwUbTnRRDzqVK8cAzKkcByidCH9lHiRsFa1%N0qrSvX z)$_QJA09LK3MSL4S#97J4A63JnJo9rB788ay6qH;eY#Pr+kV+jwR92~E@GW^>?N4y zG%DTR>F_dlJ1`=C9c&J@{Ck^DfpM^?2O#nN(7)aLllr(djlsJxqdX@o42#kEMyM4@SyD66~MWxkq5!Rr?}YF*b6CAg)JE zVQ|P3daMVxO?n@U8#-@B(!=<>hGxz6kxMA@kpyQn$05<^8uI=jwnmb?vzdOKRq*A# zM=>QL?{v2v<|#KK*pR#r9htEwp$ukVNIYZLTgsZMQwaFskv%K|!C~5JGH*I-0{Jgs z5EB-yXiq2>q`n^ktX699gNR~iC~}Vxig9|VKgd=v3FLe+gTtoL(*B1y#~~GQuVgyc zthl*ksi*xUH(3E!Z%OLEEK>i)HKhJ*MCxG)aE#>3KIGiThM)4KU64ZOQ`SA~_*Gx> zqxt>7gfAlpM}5rz(!Pc?9Cg|t(!PT<9KAGa=#@euc5IvKXzW1$z||t0qJ&eND0Zml z(TpK-sP~f;%j7RHM!-;21a1MQJA6Ir1XEr>aETm!i7Mt2IT+GGt~QJa!4`{YWPvqc zMSYMbiRn;d%tVzPGw{NOJmzt`Q{9u8EEtoVB~X)`C5SU@FtYa}NLJIRPIYKSmLch6 zV0Q-)V`U}3g)HYf1i`Q>K%QHHJqG&Rw&GGE(27I{{&V@Pg+sIWHKTSSb|P_X*gwEG zz@c8n6!w_@XuD$^Ng9VX>NL(xVr;z?(?h&l)$L6g_J7~0r@g5R*ddNv2@+wLfZ{0r z@z%WkG&;t&Kx8QH4o?RsnpgMfRJpuQTrk1)Mq^1k(Z$sRu8-RxeZ0M7AkyZ?@8Q}l z0nx__VqL<=u{O!()xs$y%dv{!WuCDu!K2 zt8zFkT(OT=crGewk4OQ&QMrbb{VeNrjzMTXg^14EDzXyPz8FdNRQ4#QY34&*Mu3NL z+_-I7KE8PJt^?1v=4;KVI*aCqM{QKnOkLZk49v+qeB`y}i();h7jc=}d#dKp3&;_q zG&hu4nHlhY^GvZB2mx?EnWr8?lF}mGiqMJ&(5;vZzH?n2ci$)4L7lrQmg1InvqsU}#_ZdUulxLA1xEvB78 zi#^vDp05o#U0$sj<0ZEbQhwZLe7pHHxr+XhxaBtN+ikZJ^LGaq)S8s>fU=vT`E~8I zW!`I&RI?Et;m`Zy#j;|`7G=7Gx*fdt*p-lIb)Gfna8LwVbr_@i^aXiU9x(1!OK&4l zdWVAAt|PlHesxQ-`*D%oSCeFSE(^)h5~`L=Dk85RMGkdmAmjA#d4v1oh9+ZqgK6K6SPwG2I@9z2&Z^ya^5l0#4KO|8P-L7f;pH@fpJ@cNt(lCGbWhldb|~^Vixz%DLYrTdQf6< z>7{NFnB|o&N=~QReOy`7xI3v1;R1J$*gPHj&{?=cYi-VtgtK7_EkM?8N)L zjN^9up~$TEMx8LCak1tl+M%QUZgv(cEit#(K8@81_H4t=!awSSsyK7unmy*`}T9kJU-;0-QkD(On5b7#WkzE@^NV~l3$ z98RmhjB?lV&HOdw8#N6r`InIKDyv2_8%8&=53kUmF3x&O0Y%nDDYj)vvxr9lg|mK&8FEmqBdF3iyIp z0k0=n0aw$4SAi`MSPt~{dRG0{VI^qFw-N?d@%0WFR=q!9y8FO^D`(ZK8MH6BI)t2r zZrzxD|1wI7SPbQ`!k9hsm=%@x?E8@R?E8=wkm!rho};$tr^UETjQgXy__xS!*P$+} z8TZ3!G4FTymr(4(t2g3={Sn@1k^#XM_%;4Kl{y5yS1~0b=Jcl@#&>focZZ(0?H40+ zS6d&$dkUeS3kT>`_2-6V=?MK9?x44Zz{v9#lQ1_J-WR;ZB<_GL3klD~yf6!vf`t&of%+6z!dFcW{6%{+$QvO2&e@Yfk@->XeEYXMuMh90EL0DRm=!E5J zRH13EIh}J-;PlP-pkEgs^yiX%(C{OZ$L3(P)88eO_A`^Kz5JJvuPL2Ruh%D$L+%cI z*yLpIn%_c>rgT0yS$zdL7DQx|y*781SHcw)+X=43T*^%Y_951?fYKKDIhtFrid zPV11ni|Qv>^iMMQDF%Oo!N(bh)B0~R7HS(}qkC;4a|EvIB1UeS4b5QQ@*h$fd^5OQxkfQfb28#G96pTW=JaEQ_atrs zsIe_gR^Em>cy6$hjEy4*rXr=!#S3aNiw@9O|Hwl6o7fBVeQUcHOY+V7B`Yn8^CZ5` z*AQ$;X}#8^>-s0IhSE}>@zqse+-lW?G4={Ywbml20j-F{RkUUOBXXJZR8O>zHl}r6 zL#s!$PTx6$r5tzABt_RmQbf%}QbfZHm{)1#QZZ9wEaYC}i+I4z2K4VIXI+De#x2|t zk#DhXZ+qUTb|BaZWoh9f-)Y|aT@ag8Bd>}Y`Qbk=HKKmSS0J96;BvbpHGw6&`q9MI z)WlX8FsdCek(mGzk#)5XpoJCs5%aqY9%K;mUQvC537=%}vkX4P;O7{KPWXAoVme`W zoM8gse$-@_mFhs0!7XdDOHk7KQ<|S<#Xb|QU8n(ggEz1?z6RiwCet(%A}({0hjHAT z1{mOtJlk3b{5x07ITBL{W2m7lmGe}C?;MHs`dvIpe6p)4i4UQ z+AE@2#9QJGZwhe{k9PLPw9RM9Am$iHRENR7j=TL_l%YlE#1lpB#1lpH$e_t6P%9r% zNk&E-P689tIK@ywFOqMQoGc^$42|z(8H@9=4^qjcY8;><(#-v5AU3Itz97oz_3w@{ z^2RUfE$^tuHeh7sB8?2~DJ}Z`l9U#FfBV-P+qAdL;ZfBkQ~iNca7{Q{SDl5-T~xn7 zM*mw3KF8n}8T@SqqR##fV=;BMH-S?EJ@gTi`&Mc+z3%(Y4RBvjrgr?TF~0dF*7NU1 zD;sJz-tPwu?bch_tC*&_8u2Y^HyJ-n+2^j#;&AUNJ(>GIiK?nAq~ku#Hc8NN*Rgk2 z+{|BSx_6-I_GwMmw9j3Ub>J}<$CXxipC@0L3U4RX+X^Od+&ziuqWqGxLGJ-un*OGAMr| z`tu*M=D!-PeyBg$0zb!E`lCbYPo`;hM_gPKhjHB8=&%B(JhmAKo~feSoF5+X!=B%5 z9Fdi4a8}y@Cs>=~JqfG+Ixbd65mfz2GCb2}t5=x%C{txxjcKr+=o|C~5i&C}1riTh zdb4eEnM|D=W3o4kCW+CG7f<6i*=U0}FEWVa9QzLQev>sTYcYwhvu~?oNLR*?U-@p1 zA^pj+uVTCyn+)?tka{E;j4|=B)vIZhL9Y4ORWWgaEgrktrS&Zy)8u{?TY2Ovu4+%C z7cLRfFOeVd2%ebVnm5!Jkm^mRktfuz@ubzC2taxsUS}x0xl-vuV;si5fQ8 zq?*fzJuctO1ld<*F!}a(b1>;ongvs1(TO+?EWc?EED`5Jg_pgfKiC|?K2k}8O>p=4 zdRo-$+pRJEY6{fzs%(hq*PB}l`Ms!q1D&salfiE>_$LhBU?8gWpE4FjRF{6#2ulST zu0LzAXp#n{dFOdk&FfdNXi(m0(RvuHJ?!u?F+o&FIe$5)w^%05Zc zypQ}XPN@wZX}7U6Qmt9nYBSGL@!=6a?yC<(R#TgSlFWJAMU-p0NH<5byjp5w3k@0` z<{TT8p>bk7_S$x~Z3j!a8~;8rEjSlSDtQNEIEmB31O% z_IL?;%r_X%iLsP>9iZTd=2gE+;S!9y$!*E=aM}em5x<^r5Kmi8YiXW*11+^l7wARN zTHVcPE%jk&4ld{jmThn;$H$NsTgst+0co58UUv@euJtrb*dtqG(x{dR=Hp^ax}F{h z`Ln3LL|*+egMZF|6Qt^2Fc3ZRFByyJk$p)W9bo^I$vRP8qFHAw&L)Yh6O_3A+}yun zCEt$LG*l(L**jP(UzKofj%js#^(TkeVSG0?`sz8>a=guUwzM=u9F(`97S+CXt6gjO z>j|Ce&ItK@OEs)>vzC5@oXY{QGoS1#sC=SvuvlBn%Smr?*A+O zGOYE>JUZ8F+f7cjanlK7`oZ74<||D7Hw=Cc!NwHJzvk(tVkxt7vdxp@_UX8&l8_V~ z5|W}kD*9xtHF|VXj5Xx3gk$=LLOr2OVUKzQwXolyFQk-fVl~Y`A)Un6;grNCHN%2v zhSOI^Ggy6ix+ZCy(LdF>b3HA<7TyvoX&VI((@^Y{=Aw0HtwP=@s(;H%f1km>WAIf5 z|DJ(>zROq)^tn?=*m(gq{|FgiH%{l^Hjeh*4n_K+X!i#ttdH*hfwlRM(JF=L&byjn zEev#LS{+~g(dd37lJvuEYi)VhYdkr>B}^a3FGlwU@z$X`-czUzWCufoLx{(A0twD- zB1`s`h;@sA_GfwfG0+}1>c0+b6MeQcEO-}h{;^Yo_|`UdsIvXF?0HzefHc@&>^1c@ z2D+0rhUNc>*BDq{t@w51GXqX_z@Uidkpv=oB!QS-?Kdt7P`%n~B*wVM-2U$R-iD7?)DG4bnh&U%=Pcg6t325Fy-zvdtFyG%G^+~fuKb(4N;K5=@{>T8zZL*`g1i`A}~ME^0j4b_5%hOMJvu>YVXqdqc{Hy17|2$H?wyO;H_%$9sJTO|kI~sS*7?PfWN;Eslp#ty zkuv9Qi(MW_ojVZAFt}Ufjy|qq@!Eu<_ige?vgnE7Iv%?=Fkd4#7Zz7XOUQ?=Ee+v( z*kzN0m=iLHd3NiA7~!L&0SowOUAGn6_^PnidM>Jvvx@4E(2DB6F!&~eKW6YJ3}m!& ziLnsbr0?mXiuIrE1VfG@kWTk}bkHI_zdv?mV$TPK$*XeKSXuX9S-bxhtzrniyvsi( zBl@G4BHlN_v^u`}Lts9P@8*0{<)g~Bt=9ZjpBwsq)WF+Pm8)Z`MYY)EURc54MRfGz zJm+Qvcy*AUAL|*hibmK0u>s5Wu$svTtzE`%q5Lf&^t);rV@M0?^q7mjv_ z`ct&p#>}U0u@jAP3AR4d90c!TNC=P{v|zYF3w-y5=WFCHhiNhPkU7eu;m?KmI!c~d zeLy0DdsBENWj3S?J4s6x`APHC8z44`)900yHJNNZWDohPsQx>7?SC-% zHiQ4k;5!Ti+5Z<~F=XEv?brZ@A2!(|is_*zGGvGtdjzGd?~M90R_T97>lMN|Z|yqP z#D{ZEH84#x9pd4v_01r(HElYhit^sRaHpk`+wZ*$V7BhPTV_ihsY(2-_wuO{w%x%? z74W*ON01gExqvv`ikOntCp^iEqFo_S{sQkd2Iae=m0bzvRMA@Wf=#e3?7h^*yR^Jl z_VJZI{tJCF&5ER94PH^;ZbDwSYz^#6b$k8a9+cmxw0pW0#%@2$(oL+bz)^EuB9~b8z>b(DI{fhUhkMWviQ3c%ofll^?(Ffdg z*-c$e_gzl+TuzU4GB51EoZc_nbYD*2emOlV!~VLvyn?RXKHf$XKY_PuEmg5tOp+dLT3!N{p!w&>C3u^hkWb;q z`1VF2&@9yfsoi9lz*j)oQgXG(C`uH2tJ)u}S4<3D+PYDRGt}KF%I5*xIvfn?Tqc(p z&Q&tG^tN;*yD9k1}|T!DktKo`I~jdXq6uCy3`!JR@SC3#xl8 z6@sF^Be(RR_qNRM!7Zm8a%~9>UdoiDiTV0X^RJxk-`jtvf1>}6{+s)6?|-QOR{V`3 Vb{O$V{2j&dLFA3}-`oGt{{!W{qqP74 literal 32238 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 diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..68ae230 --- /dev/null +++ b/dockerfile @@ -0,0 +1,13 @@ +FROM python:3 + +COPY . /app +WORKDIR /app + +# Install Python Libraries +RUN pip3 install flask && \ + pip3 install Flask-Limiter && \ + pip3 install requests && \ + pip3 install gunicorn && \ + pip3 install gevent + +CMD ["gunicorn", "-k", "gevent", "-b", "0.0.0.0:5000", "flask_api:app"] \ No newline at end of file diff --git a/flask_api.py b/flask_api.py new file mode 100644 index 0000000..b0e2a44 --- /dev/null +++ b/flask_api.py @@ -0,0 +1,339 @@ +from flask import Flask, redirect, request +from flask_limiter import Limiter +from flask_limiter.util import get_remote_address + +import json +import parkrun_api as parkrun + +cache = {} + +def Setup(): + + countries = parkrun.Country.GetAllCountries() + events = parkrun.Event.GetAllEvents() + parkrun.Event.UpdateEventUrls(events, countries) + + cache["countries"] = countries + cache["events"] = events + +def GetEventById(eventId): + + for event in cache["events"]: + if str(event.id) == (eventId): + return event + + return None + +def GetCountryById(countryId): + + for country in cache["countries"]: + if str(country.id) == (countryId): + return country + + return None + +def ObjectListToDictList(objectList): + + objectDictList = [] + + for object in objectList: + objectDictList.append(object.__dict__) + + return objectDictList + +def CacheToDict(): + + cacheDict = {} + print(cache) + + for cacheItem in cache: + + cacheDict[cacheItem] = ObjectListToDictList(cache[cacheItem]) + + return cacheDict + +app = Flask(__name__) + +limiter = Limiter( + app, + key_func=get_remote_address, + default_limits=["15000 per day", "600 per hour"] +) + +@app.route("/") +def GithubRedirect(): + return redirect("https://github.com/BadgerHobbs/Parkrun-API-Python", code=302) + +@app.route("/v1/cache") +def GetCache(): + return json.dumps(CacheToDict()) + +@app.route("/v1/countries") +def GetCountries(): + + if "countries" not in cache: + cache["countries"] = parkrun.Country.GetAllCountries() + + return json.dumps(ObjectListToDictList(cache["countries"])) + +# Event Specific Data +@app.route("/v1/events") +def GetEvents(): + + if "events" not in cache: + cache["events"] = parkrun.Event.GetAllEvents() + + return json.dumps(ObjectListToDictList(cache["events"])) + +@app.route("/v1/events//history") +def GetEventHistory(event_id): + + if f"events/{event_id}/history" not in cache: + cache[f"events/{event_id}/history"] = parkrun.EventHistory.GetEventHistorys(GetEventById(event_id)) + + return json.dumps(ObjectListToDictList(cache[f"events/{event_id}/history"])) + +@app.route("/v1/events//first-finishers") +def GetFirstFinishers(event_id): + + if f"events/{event_id}/first-finishers" not in cache: + cache[f"events/{event_id}/first-finishers"] = parkrun.FirstFinisher.GetFirstFinishers(GetEventById(event_id)) + + return json.dumps(ObjectListToDictList(cache[f"events/{event_id}/first-finishers"])) + +@app.route("/v1/events//age-category-records") +def GetAgeCategoryRecords(event_id): + + if f"events/{event_id}/age-category-records" not in cache: + cache[f"events/{event_id}/age-category-records"] = parkrun.AgeCategoryRecord.GetAgeCategoryRecords(GetEventById(event_id)) + + return json.dumps(ObjectListToDictList(cache[f"events/{event_id}/age-category-records"])) + +@app.route("/v1/events//clubs") +def GetClubs(event_id): + + if f"events/{event_id}/clubs" not in cache: + cache[f"events/{event_id}/clubs"] = parkrun.Club.GetClubs(GetEventById(event_id)) + + return json.dumps(ObjectListToDictList(cache[f"events/{event_id}/clubs"])) + +@app.route("/v1/events//sub-20-women") +def GetSub20Women(event_id): + + if f"events/{event_id}/sub-20-women" not in cache: + cache[f"events/{event_id}/sub-20-women"] = parkrun.Sub20Woman.GetSub20Women(GetEventById(event_id)) + + return json.dumps(ObjectListToDictList(cache[f"events/{event_id}/sub-20-women"])) + +@app.route("/v1/events//sub-17-men") +def GetSub17Men(event_id): + + if f"events/{event_id}/sub-17-men" not in cache: + cache[f"events/{event_id}/sub-17-men"] = parkrun.Sub17Man.GetSub17Men(GetEventById(event_id)) + + return json.dumps(ObjectListToDictList(cache[f"events/{event_id}/sub-17-men"])) + +@app.route("/v1/events//age-graded-league-ranks") +def GetAgeGradedLeagueRanks(event_id): + + quantity = 1000 + + if request.args.get('quantity'): + quantity = int(request.args.get('quantity')) + + if f"events/{event_id}/age-graded-league-ranks" not in cache: + cache[f"events/{event_id}/age-graded-league-ranks"] = parkrun.AgeGradedLeagueRank.GetAgeGradedLeagueRanks(GetEventById(event_id), quantity=quantity) + + return json.dumps(ObjectListToDictList(cache[f"events/{event_id}/age-graded-league-ranks"])) + +@app.route("/v1/events//fastest-500") +def GetFastest500(event_id): + + if f"events/{event_id}/fastest-500" not in cache: + cache[f"events/{event_id}/fastest-500"] = parkrun.Fastest.GetFastest500(GetEventById(event_id)) + + return json.dumps(ObjectListToDictList(cache[f"events/{event_id}/fastest-500"])) + +# Country Specific Data +@app.route("/v1/countries//week-first-finishers") +def GetWeekFirstFinishersForCountry(country_id): + + if f"countries/{country_id}/week-first-finishers" not in cache: + cache[f"countries/{country_id}/week-first-finishers"] = parkrun.WeekFirstFinisher.GetWeekFirstFinishersForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/week-first-finishers"])) + +@app.route("/v1/countries//week-sub-17-runs") +def GetWeekSub17RunsForCountry(country_id): + + if f"countries/{country_id}/week-sub-17-runs" not in cache: + cache[f"countries/{country_id}/week-sub-17-runs"] = parkrun.WeekSub17Run.GetWeekSub17RunsForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/week-sub-17-runs"])) + +@app.route("/v1/countries//week-top-age-grades") +def GetWeekTopAgeGradesForCountry(country_id): + + if f"countries/{country_id}/week-top-age-grades" not in cache: + cache[f"countries/{country_id}/week-top-age-grades"] = parkrun.WeekTopAgeGrade.GetWeekTopAgeGradesForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/week-top-age-grades"])) + +@app.route("/v1/countries//week-new-category-records") +def GetWeekNewCategoryRecordsForCountry(country_id): + + if f"countries/{country_id}/week-new-category-records" not in cache: + cache[f"countries/{country_id}/week-new-category-records"] = parkrun.WeekNewCategoryRecord.GetWeekNewCategoryRecordsForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/week-new-category-records"])) + +@app.route("/v1/countries//course-records") +def GetCourseRecordsForCountry(country_id): + + if f"countries/{country_id}/course-records" not in cache: + cache[f"countries/{country_id}/course-records"] = parkrun.CourseRecord.GetCourseRecordsForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/course-records"])) + +@app.route("/v1/countries//attendance-records") +def GetAttendanceRecordsForCountry(country_id): + + if f"countries/{country_id}/attendance-records" not in cache: + cache[f"countries/{country_id}/attendance-records"] = parkrun.AttendanceRecord.GetAttendanceRecordsForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/attendance-records"])) + +@app.route("/v1/countries//most-events") +def GetMostEventsForCountry(country_id): + + if f"countries/{country_id}/most-events" not in cache: + cache[f"countries/{country_id}/most-events"] = parkrun.MostEvent.GetMostEventsForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/most-events"])) + +@app.route("/v1/countries//largest-clubs") +def GetLargestClubsForCountry(country_id): + + if f"countries/{country_id}/largest-clubs" not in cache: + cache[f"countries/{country_id}/largest-clubs"] = parkrun.LargestClub.GetLargestClubsForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/largest-clubs"])) + +@app.route("/v1/countries//joined-100-club") +def GetJoined100ClubsForCountry(country_id): + + if f"countries/{country_id}/joined-100-club" not in cache: + cache[f"countries/{country_id}/joined-100-club"] = parkrun.Joined100Club.GetJoined100ClubsForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/joined-100-club"])) + +@app.route("/v1/countries//most-first-finishes") +def GetMostFirstFinishesForCountry(country_id): + + if f"countries/{country_id}/most-first-finishes" not in cache: + cache[f"countries/{country_id}/most-first-finishes"] = parkrun.MostFirstFinish.GetMostFirstFinishesForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/most-first-finishes"])) + +@app.route("/v1/countries//freedom-runs") +def GetFreedomRunsForCountry(country_id): + + if f"countries/{country_id}/freedom-runs" not in cache: + cache[f"countries/{country_id}/freedom-runs"] = parkrun.FreedomRun.GetFreedomRunsForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/freedom-runs"])) + +@app.route("/v1/countries//historic-numbers") +def GetHistoricNumbersForCountry(country_id): + + if f"countries/{country_id}/historic-numbers" not in cache: + cache[f"countries/{country_id}/historic-numbers"] = parkrun.HistoricNumber.GetHistoricNumbersForCountry(GetCountryById(country_id)) + + return json.dumps(ObjectListToDictList(cache[f"countries/{country_id}/historic-numbers"])) + +# Global Results Data +@app.route("/v1/global/results/week-first-finishers") +def GetWeekFirstFinishersGlobally(): + + if f"global/results/week-first-finishers" not in cache: + cache[f"global/results/week-first-finishers"] = parkrun.WeekFirstFinisher.GetWeekFirstFinishersGlobally() + + return json.dumps(ObjectListToDictList(cache[f"global/results/week-first-finishers"])) + +@app.route("/v1/global/results/new-category-records") +def GetWeekNewCategoryRecordsGlobally(): + + if f"global/results/new-category-records" not in cache: + cache[f"global/results/new-category-records"] = parkrun.WeekNewCategoryRecord.GetWeekNewCategoryRecordsGlobally() + + return json.dumps(ObjectListToDictList(cache[f"global/results/new-category-records"])) + +@app.route("/v1/global/results/sub-17-runs") +def GetWeekSub17RunsGlobally(): + + if f"global/results/sub-17-runs" not in cache: + cache[f"global/results/sub-17-runs"] = parkrun.WeekSub17Run.GetWeekSub17RunsGlobally() + + return json.dumps(ObjectListToDictList(cache[f"global/results/sub-17-runs"])) + +@app.route("/v1/global/results/top-age-grades") +def GetWeekTopAgeGradesGlobally(): + + if f"global/results/top-age-grades" not in cache: + cache[f"global/results/top-age-grades"] = parkrun.WeekTopAgeGrade.GetWeekTopAgeGradesGlobally() + + return json.dumps(ObjectListToDictList(cache[f"global/results/top-age-grades"])) + +@app.route("/v1/global/results/course-records") +def GetCourseRecordsGlobally(): + + if f"global/results/course-records" not in cache: + cache[f"global/results/course-records"] = parkrun.CourseRecord.GetCourseRecordsGlobally() + + return json.dumps(ObjectListToDictList(cache[f"global/results/course-records"])) + +@app.route("/v1/global/results/freedom-runs") +def GetFreedomRunsGlobally(): + + if f"global/results/freedom-runs" not in cache: + cache[f"global/results/freedom-runs"] = parkrun.FreedomRun.GetFreedomRunsGlobally() + + return json.dumps(ObjectListToDictList(cache[f"global/results/freedom-runs"])) + +# Global Stats Data +@app.route("/v1/global/stats/largest-clubs") +def GetLargestClubsGlobally(): + + if f"global/stats/largest-clubs" not in cache: + cache[f"global/stats/largest-clubs"] = parkrun.Club.GetLargestClubsGlobally() + + return json.dumps(ObjectListToDictList(cache[f"global/stats/largest-clubs"])) + +@app.route("/v1/global/stats/attendance-records") +def GetAttendanceRecordsGlobally(): + + if f"global/stats/attendance-records" not in cache: + cache[f"global/stats/attendance-records"] = parkrun.AttendanceRecord.GetAttendanceRecordsGlobally() + + return json.dumps(ObjectListToDictList(cache[f"global/stats/attendance-records"])) + +@app.route("/v1/global/stats/most-events") +def GetMostEventsGlobally(): + + if f"global/stats/most-events" not in cache: + cache[f"global/stats/most-events"] = parkrun.MostEvent.GetMostEventsGlobally() + + return json.dumps(ObjectListToDictList(cache[f"global/stats/most-events"])) + +@app.route("/v1/global/stats/most-first-finishes") +def GetMostFirstFinishesGlobally(): + + if f"global/stats/most-first-finishes" not in cache: + cache[f"global/stats/most-first-finishes"] = parkrun.MostFirstFinish.GetMostFirstFinishesGlobally() + + return json.dumps(ObjectListToDictList(cache[f"global/stats/most-first-finishes"])) + +if __name__ == '__main__': + + Setup() + app.run(host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/parkrun_api.py b/parkrun_api.py index a9f2ae9..c4b10ec 100644 --- a/parkrun_api.py +++ b/parkrun_api.py @@ -91,7 +91,7 @@ def UpdateEventUrls(events, countries): if str(event.countryCode) == str(country.id): - event.url = f"{country.url}/{event.name}/".replace("//", "/") + event.url = f"{country.url}/{event.name}/" return events @@ -433,6 +433,9 @@ def GetAgeGradedLeagueRanks(event, quantity=1000): ageGradedLeagueRanks.append(ageGradedLeagueRank) + if len(ageGradedLeagueRanks) == quantity: + return ageGradedLeagueRanks + return ageGradedLeagueRanks class Fastest(): @@ -1150,7 +1153,7 @@ def GetHistoricNumbersForCountry(country=None): splitContentRow = contentRow.split(",") dateStart = splitContentRow[0].find("(") dateEnd = splitContentRow[0].find(")") - date = splitContentRow[0][dateStart + 1:dateEnd] + date = splitContentRow[0][dateStart + 1:dateEnd].replace("\"","") historicNumber = HistoricNumber( _date=date,