PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/imunify360/venv/lib/python3.11/site-packages/geoip2
Viewing File: /opt/imunify360/venv/lib/python3.11/site-packages/geoip2/webservice.py
""" ============================ WebServices Client API ============================ This class provides a client API for all the GeoIP2 Precision web service end points. The end points are Country, City, and Insights. Each end point returns a different set of data about an IP address, with Country returning the least data and Insights the most. Each web service end point is represented by a different model class, and these model classes in turn contain multiple record classes. The record classes have attributes which contain data about the IP address. If the web service does not return a particular piece of data for an IP address, the associated attribute is not populated. The web service may not return any information for an entire record, in which case all of the attributes for that record class will be empty. SSL --- Requests to the web service are always made with SSL. """ import ipaddress import json from typing import Any, Dict, cast, List, Optional, Type, Union import aiohttp import aiohttp.http import requests import requests.utils import geoip2 import geoip2.models from geoip2.errors import ( AddressNotFoundError, AuthenticationError, GeoIP2Error, HTTPError, InvalidRequestError, OutOfQueriesError, PermissionRequiredError, ) from geoip2.models import City, Country, Insights from geoip2.types import IPAddress _AIOHTTP_UA = ( f"GeoIP2-Python-Client/{geoip2.__version__} {aiohttp.http.SERVER_SOFTWARE}" ) _REQUEST_UA = ( f"GeoIP2-Python-Client/{geoip2.__version__} {requests.utils.default_user_agent()}" ) class BaseClient: # pylint: disable=missing-class-docstring, too-few-public-methods _account_id: str _host: str _license_key: str _locales: List[str] _timeout: float def __init__( self, account_id: int, license_key: str, host: str, locales: Optional[List[str]], timeout: float, ) -> None: """Construct a Client.""" # pylint: disable=too-many-arguments if locales is None: locales = ["en"] self._locales = locales # requests 2.12.2 requires that the username passed to auth be bytes # or a string, with the former being preferred. self._account_id = ( account_id if isinstance(account_id, bytes) else str(account_id) ) self._license_key = license_key self._base_uri = f"https://{host}/geoip/v2.1" self._timeout = timeout def _uri(self, path: str, ip_address: IPAddress) -> str: if ip_address != "me": ip_address = ipaddress.ip_address(ip_address) return "/".join([self._base_uri, path, str(ip_address)]) @staticmethod def _handle_success(body: str, uri: str) -> Any: try: return json.loads(body) except ValueError as ex: raise GeoIP2Error( f"Received a 200 response for {uri}" " but could not decode the response as " "JSON: " + ", ".join(ex.args), 200, uri, ) from ex def _exception_for_error( self, status: int, content_type: str, body: str, uri: str ) -> GeoIP2Error: if 400 <= status < 500: return self._exception_for_4xx_status(status, content_type, body, uri) if 500 <= status < 600: return self._exception_for_5xx_status(status, uri, body) return self._exception_for_non_200_status(status, uri, body) def _exception_for_4xx_status( self, status: int, content_type: str, body: str, uri: str ) -> GeoIP2Error: if not body: return HTTPError( f"Received a {status} error for {uri} with no body.", status, uri, body, ) if content_type.find("json") == -1: return HTTPError( f"Received a {status} for {uri} with the following body: {body}", status, uri, body, ) try: decoded_body = json.loads(body) except ValueError as ex: return HTTPError( f"Received a {status} error for {uri} but it did not include " + "the expected JSON body: " + ", ".join(ex.args), status, uri, body, ) else: if "code" in decoded_body and "error" in decoded_body: return self._exception_for_web_service_error( decoded_body.get("error"), decoded_body.get("code"), status, uri ) return HTTPError( "Response contains JSON but it does not specify code or error keys", status, uri, body, ) @staticmethod def _exception_for_web_service_error( message: str, code: str, status: int, uri: str ) -> Union[ AuthenticationError, AddressNotFoundError, PermissionRequiredError, OutOfQueriesError, InvalidRequestError, ]: if code in ("IP_ADDRESS_NOT_FOUND", "IP_ADDRESS_RESERVED"): return AddressNotFoundError(message) if code in ( "ACCOUNT_ID_REQUIRED", "ACCOUNT_ID_UNKNOWN", "AUTHORIZATION_INVALID", "LICENSE_KEY_REQUIRED", "USER_ID_REQUIRED", "USER_ID_UNKNOWN", ): return AuthenticationError(message) if code in ("INSUFFICIENT_FUNDS", "OUT_OF_QUERIES"): return OutOfQueriesError(message) if code == "PERMISSION_REQUIRED": return PermissionRequiredError(message) return InvalidRequestError(message, code, status, uri) @staticmethod def _exception_for_5xx_status( status: int, uri: str, body: Optional[str] ) -> HTTPError: return HTTPError( f"Received a server error ({status}) for {uri}", status, uri, body, ) @staticmethod def _exception_for_non_200_status( status: int, uri: str, body: Optional[str] ) -> HTTPError: return HTTPError( f"Received a very surprising HTTP status ({status}) for {uri}", status, uri, body, ) class AsyncClient(BaseClient): """An async GeoIP2 client. It accepts the following required arguments: :param account_id: Your MaxMind account ID. :param license_key: Your MaxMind license key. Go to https://www.maxmind.com/en/my_license_key to see your MaxMind account ID and license key. The following keyword arguments are also accepted: :param host: The hostname to make a request against. This defaults to "geoip.maxmind.com". To use the GeoLite2 web service instead of GeoIP2 Precision, set this to "geolite.info". :param locales: This is list of locale codes. This argument will be passed on to record classes to use when their name properties are called. The default value is ['en']. The order of the locales is significant. When a record class has multiple names (country, city, etc.), its name property will return the name in the first locale that has one. Note that the only locale which is always present in the GeoIP2 data is "en". If you do not include this locale, the name property may end up returning None even when the record has an English name. Currently, the valid locale codes are: * de -- German * en -- English names may still include accented characters if that is the accepted spelling in English. In other words, English does not mean ASCII. * es -- Spanish * fr -- French * ja -- Japanese * pt-BR -- Brazilian Portuguese * ru -- Russian * zh-CN -- Simplified Chinese. :param timeout: The timeout in seconds to use when waiting on the request. This sets both the connect timeout and the read timeout. The default is 60. :param proxy: The URL of an HTTP proxy to use. It may optionally include a basic auth username and password, e.g., ``http://username:password@host:port``. """ _existing_session: aiohttp.ClientSession _proxy: Optional[str] def __init__( # pylint: disable=too-many-arguments self, account_id: int, license_key: str, host: str = "geoip.maxmind.com", locales: Optional[List[str]] = None, timeout: float = 60, proxy: Optional[str] = None, ) -> None: super().__init__( account_id, license_key, host, locales, timeout, ) self._proxy = proxy async def city(self, ip_address: IPAddress = "me") -> City: """Call City endpoint with the specified IP. :param ip_address: IPv4 or IPv6 address as a string. If no address is provided, the address that the web service is called from will be used. :returns: :py:class:`geoip2.models.City` object """ return cast( City, await self._response_for("city", geoip2.models.City, ip_address) ) async def country(self, ip_address: IPAddress = "me") -> Country: """Call the GeoIP2 Country endpoint with the specified IP. :param ip_address: IPv4 or IPv6 address as a string. If no address is provided, the address that the web service is called from will be used. :returns: :py:class:`geoip2.models.Country` object """ return cast( Country, await self._response_for("country", geoip2.models.Country, ip_address), ) async def insights(self, ip_address: IPAddress = "me") -> Insights: """Call the Insights endpoint with the specified IP. Insights is only supported by GeoIP2 Precision. The GeoLite2 web service does not support it. :param ip_address: IPv4 or IPv6 address as a string. If no address is provided, the address that the web service is called from will be used. :returns: :py:class:`geoip2.models.Insights` object """ return cast( Insights, await self._response_for("insights", geoip2.models.Insights, ip_address), ) async def _session(self) -> aiohttp.ClientSession: if not hasattr(self, "_existing_session"): self._existing_session = aiohttp.ClientSession( auth=aiohttp.BasicAuth(self._account_id, self._license_key), headers={"Accept": "application/json", "User-Agent": _AIOHTTP_UA}, timeout=aiohttp.ClientTimeout(total=self._timeout), ) return self._existing_session async def _response_for( self, path: str, model_class: Union[Type[Insights], Type[City], Type[Country]], ip_address: IPAddress, ) -> Union[Country, City, Insights]: uri = self._uri(path, ip_address) session = await self._session() async with await session.get(uri, proxy=self._proxy) as response: status = response.status content_type = response.content_type body = await response.text() if status != 200: raise self._exception_for_error(status, content_type, body, uri) decoded_body = self._handle_success(body, uri) return model_class(decoded_body, locales=self._locales) async def close(self): """Close underlying session This will close the session and any associated connections. """ if hasattr(self, "_existing_session"): await self._existing_session.close() async def __aenter__(self) -> "AsyncClient": return self async def __aexit__(self, exc_type: None, exc_value: None, traceback: None) -> None: await self.close() class Client(BaseClient): """A synchronous GeoIP2 client. It accepts the following required arguments: :param account_id: Your MaxMind account ID. :param license_key: Your MaxMind license key. Go to https://www.maxmind.com/en/my_license_key to see your MaxMind account ID and license key. The following keyword arguments are also accepted: :param host: The hostname to make a request against. This defaults to "geoip.maxmind.com". To use the GeoLite2 web service instead of GeoIP2 Precision, set this to "geolite.info". :param locales: This is list of locale codes. This argument will be passed on to record classes to use when their name properties are called. The default value is ['en']. The order of the locales is significant. When a record class has multiple names (country, city, etc.), its name property will return the name in the first locale that has one. Note that the only locale which is always present in the GeoIP2 data is "en". If you do not include this locale, the name property may end up returning None even when the record has an English name. Currently, the valid locale codes are: * de -- German * en -- English names may still include accented characters if that is the accepted spelling in English. In other words, English does not mean ASCII. * es -- Spanish * fr -- French * ja -- Japanese * pt-BR -- Brazilian Portuguese * ru -- Russian * zh-CN -- Simplified Chinese. :param timeout: The timeout in seconds to use when waiting on the request. This sets both the connect timeout and the read timeout. The default is 60. :param proxy: The URL of an HTTP proxy to use. It may optionally include a basic auth username and password, e.g., ``http://username:password@host:port``. """ _session: requests.Session _proxies: Optional[Dict[str, str]] def __init__( # pylint: disable=too-many-arguments self, account_id: int, license_key: str, host: str = "geoip.maxmind.com", locales: Optional[List[str]] = None, timeout: float = 60, proxy: Optional[str] = None, ) -> None: super().__init__(account_id, license_key, host, locales, timeout) self._session = requests.Session() self._session.auth = (self._account_id, self._license_key) self._session.headers["Accept"] = "application/json" self._session.headers["User-Agent"] = _REQUEST_UA if proxy is None: self._proxies = None else: self._proxies = {"https": proxy} def city(self, ip_address: IPAddress = "me") -> City: """Call City endpoint with the specified IP. :param ip_address: IPv4 or IPv6 address as a string. If no address is provided, the address that the web service is called from will be used. :returns: :py:class:`geoip2.models.City` object """ return cast(City, self._response_for("city", geoip2.models.City, ip_address)) def country(self, ip_address: IPAddress = "me") -> Country: """Call the GeoIP2 Country endpoint with the specified IP. :param ip_address: IPv4 or IPv6 address as a string. If no address is provided, the address that the web service is called from will be used. :returns: :py:class:`geoip2.models.Country` object """ return cast( Country, self._response_for("country", geoip2.models.Country, ip_address) ) def insights(self, ip_address: IPAddress = "me") -> Insights: """Call the Insights endpoint with the specified IP. Insights is only supported by GeoIP2 Precision. The GeoLite2 web service does not support it. :param ip_address: IPv4 or IPv6 address as a string. If no address is provided, the address that the web service is called from will be used. :returns: :py:class:`geoip2.models.Insights` object """ return cast( Insights, self._response_for("insights", geoip2.models.Insights, ip_address) ) def _response_for( self, path: str, model_class: Union[Type[Insights], Type[City], Type[Country]], ip_address: IPAddress, ) -> Union[Country, City, Insights]: uri = self._uri(path, ip_address) response = self._session.get(uri, proxies=self._proxies, timeout=self._timeout) status = response.status_code content_type = response.headers["Content-Type"] body = response.text if status != 200: raise self._exception_for_error(status, content_type, body, uri) decoded_body = self._handle_success(body, uri) return model_class(decoded_body, locales=self._locales) def close(self): """Close underlying session This will close the session and any associated connections. """ self._session.close() def __enter__(self) -> "Client": return self def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: self.close()