From 649a0dec6c82d0d0f7631b5ce2bf796aeff53e0c Mon Sep 17 00:00:00 2001
From: Alex Duan <51781608+DuanKuanJun@users.noreply.github.com>
Date: Fri, 7 Feb 2025 04:45:55 +0800
Subject: [PATCH] feat: add TDengine.py driver to db_engine (#32041)
Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
---
README.md | 1 +
docs/docs/configuration/databases.mdx | 19 ++++++
docs/static/img/tdengine.png | Bin 0 -> 43540 bytes
pyproject.toml | 4 ++
superset/db_engine_specs/tdengine.py | 57 ++++++++++++++++++
.../db_engine_specs/test_tdengine.py | 34 +++++++++++
6 files changed, 115 insertions(+)
create mode 100644 docs/static/img/tdengine.png
create mode 100644 superset/db_engine_specs/tdengine.py
create mode 100644 tests/unit_tests/db_engine_specs/test_tdengine.py
diff --git a/README.md b/README.md
index 7928904a2..3c03e9ac1 100644
--- a/README.md
+++ b/README.md
@@ -137,6 +137,7 @@ Here are some of the major database solutions that are supported:
+
MwPSw7@;BdeVH_$~46=%%7`V+(nqGfE%}UzwwTgJ)V)Le(!r@_P8!UjdYwGQWvk&q-iPO#3 z9z{gA(GK4P`F%% 6ZggP_RO^V oDbN{W1pgy8I%j}Hvu=$#ANlc(#qg;z` a5x-?_RGcxU26oq4x9dEzahn6jdI+o^Qgq*m@Xw)OTu8*>fGv%vM(? zV(`ti7V98XC~CCC)1Ap47ozGlq_zxNpIY_#>6_t43}k4N@zI%Q{7q=8+wt}rz(DXq zYZ{@YCK&bhnK=r1DAAkwcAe?;Rbz;;_~Wv%S_=3C)xX&?w!faHH3&O58%xnDMS02o z_@n`Pq eoAq=Ap9BZN3pQDF&ST9%6pkfw*7vz=~x9Y7u{^ZVfY$7 zJzbiioKAnepcrTXD>Ofh9Q|LDAhn}4^bHLb$!5Ts1Sp>wJ)?^8Zs|(sEn)Ry)n?xb z62D!Oe4BaF6xWaT)gr@y?;$$S+Z%MBzQuDvE!;(f0`{|##_RA~To5UYYi@AbyPiz> z7 ^zXudKf{K7KdD?GN`*DXq zCyzMLAaR=3HbvRz;G1>us1kl+km_QD`b-OFrFjo@N(OFJD})|7NVvsu#Dg>g`t`^! zA#(0HWV|4lvjej)u#vZACv!d^>8(Ri-+?f4r6xhf=5<52SbzP&{L90_L{p92soP4T zM=Hp=*t>(d12()JK4U0T@kj3(!D&rF)BT!`zT*4sVowuGwe^Otxx@jA@O)2fpq93x zdq)8zTCp!bY23^HRX+(-?yRu!FYpjA*2m lF*<5k zm}b7e;FOQJ?r@P`-nlppd2ZJ( 9QPqG$I<|diHMkf;|rf7GKf0;Re{Q|%o(iBAbi-HAu^J$@WTzsJR~`- zLaCbS796_B(z+Okpow(g-4Cue1^87Wn`-{OcQ}oGRQUTGP$8W2UfDLcmO-=ZYdbc> zLEVfI7SOpW(kQe5eOIc*Z6kvWs+7fG9NXXIpj#JYMta7HbS3GB)y!9rbSg#-^R!P+ z|K6WATeo*AJsyXhLFjmuTM#<*QWihGAONOf`6mvGPmv&?s#eHm^QibB>A8EvScRe2 zv{IhwAr(1JvW=!;Jhn=dGPoZx-ZjFI91y+C^&vlUI_JfxP0d1n+VeWlx6x}kG%35G z!dqBd;U45JYl%>QE1WWvyczX=Y;>cZp!|oWmRDDV)aJn4mg2c$aqt8gMz;O>W6I{= z-#LGtD5#&ohQ>ABBT7~`WRcD`7< Z!t?1PfYixUZ@_6gmt@(|CZUuupb&<^U8mE)Mze_>q+;?EVwh_6) zeGypr7v@{6$^_z=cJKCKes-`^mtX%tbEAhHJB%@Q;H0TA4oN$ZHGM2fp=`30K>~Gj z4R7nYef~#Vj+9-9nEd|XKsXIWte}YJwkd0w)N@5hIpd_0cE7O9Sf;C~A|)76TlE2w z-%RITEne*X%jR)BBBQzTc#cg*GZxh>kn7=RRiR?=DhmL#Ju0r$ZDu}d*V33k`!P3g z&zl1jqDf6Ds9}PcQ`lS$gT3O(p#;&Ql6FGlZ)c-3qN{0Ku(Ft;@x)KoMPRpszk~Q( z5(rgJbNPBSLJQWWdPDk9=vP|sq!8KYLd4@+AD>pl>A$P-(Temb&)CRuu-6jhuON)+ zdVV2B1mmgz1yne=xYxcSWJpEPZU_f3H7`eeI~n1Nk(qo4arci01vS=QEm0%-nTfSJ z+pga0?5WEn9^RSX(x%-92jBZ&M6)Edsgau2QbazUv}fl~gUkrPG|RI-q_@;ub%hV{ zIQ-WHD(;W#@q*6VP`o9|!Bbw ~9zogy1UdJ_d7L4fgvki=x3(RE06&pP{2f5BE8fL`P*Fw_AOcntW=}%UJ#o zn15pY*TKt29qsbK@V-Q3sI_E(_)I{zqPg8D!0sDv-EU)~@zMk2^W<-BklU~cuyb+# za4%{;Ci0rM9RW2MCi9uYWp^0|JFb3@ZY7EWvN?Bnb+e>=Q4OsXf}!@ z_QeD-V#>_}JM-!Lx5i}@t)#)SN>zCk#4~WrG6%;CW+N>|9Dxw7SEt`-_Y;O+w^V*T z*pawCwm?(Iz*xdNrQWgCuH5uLBqI(YI!k5Jtk(xG;|q=-gWTMNXJ9_4oi Jv;Z)d zjQekZ@Uq5oC8fva>J#6^-}fF-%DSnQ*&8wqNU{nv7awHv?1CFUW6OaTJTwu _F_@NH*N`RTo0{Rq vtQ3A-tzl)AE^<{taUMKX-x>gT63`w!}t0CiYg4$aV(_${M)2r1v^6CBc=_)yq zvrWAuC<@a+JM;${mFHQ&CMx9S-uSdN(IGwqHS=I(np52t%l1PS68ibS3F$=6m6bm( zE^ajGC8`nKt8>)scTpDdkMl3X4+%As`YUDn>0N6rH?;=s#aDvgulL|pFVk ~^mKk_dcnQ_G`E4rEl_ z{e?B!5%M$!c2%|pNyTO=j~|koJfRf4gB1)71H+Ykf8C$eVxV)4a>3Bs(|U6R%p%=X z&O^6a{TwjBM8MQvu&+q=?^kPrEyCYg-b%*P45}NtbaDrpL=n7&)kdL=X*bvEvUD+; z4w-k261TWqPYN*2F!u$Mz#yXGE+JLzN@zxd$74mNDZDcLs|R?cx^WPM+5!wYJt2hC z3WQOFnFsNP&u$($6rd9 BaM7P`-6#NE%V5-S?X zs}NwAXd5&Y3(DG`D>RShw~hXAg>i+cT*?n*u? R4H z0%>_Ut$fc`qS_So>DpRi;i+<3zl BMY@=bGSyVa}f3Mgs0K>r);e5rhfBgwj(|zBf2xzP4^W-1ve~!D0e-~ zK~d*xwhX8T@Si_c73*Bl6gqD0tI!R1W;Ei%%0+Hv=KdM((Fh0Sw@K+hXp<#Y|*9V>xtnh(?I>_Q|1=x zDG!KZ6i6u{%M&3yv_e>)HtLa%(L^R@#-35XW9YNCi8yPKb*}4-6- xSfbr5CB>ml5OMg sGoRWbJvRwEKG;{P%V9P z>Zq@Q@}$JIs+sQ%#wI`M(zsJZXUFr2mEmdVGff1~<{>$q52s|NVFp5x@7b_-#O(UT zuV*f$cxK^NquNY5js)}k1z3>-5Oy5m@HglvF(#|7`y4?M5g!kE1ODO$YcK6=HC8LR z7`PXcmv(!bD^;+Cp+P{RBtLa$sHS&K{XFrpKciOOD*UY3kAfME79z*&5S=~%tu~BT zY4Qh&%W+38tQQ|Bk#R9jqb%xa$)c@QeGicP)!#ps u+)plO7WU52*G1&C?)>MOlUm6AQjf zg#|Hcf>+(2jSkh@!m9<6e6e~JA_ZEt{P-D-#t_`2-?8~R(byE1^!b$dJxKCZ=dPDZ zTE$ |dzIQ#!2R7UMb#`-8Pqyh2IZ{ }}?sA}#uE z0o!*LW=ch4{-oFaUrS4sF5fZP^TZ}iw1%wsXORNd!O3|D?wE`tOx99Up`mJ;I?U0W zU~1{Ybv!k%fEz72x=m~I+>&{DpCr;UULV1{jtc$!bH>3B0(I`H_Y1(owB5l4U;dJu z?~;ac9P>QO4bMNrM`Gl^_a}@HI*F~!{a%8qyX;n5T<%iM(7>`|PQH&U%Z6m u`nJ^h>3zu_?3TT$(j^xQ|O-|%@A^CEUdm(ji} zgQxUq*0oDuZAQt@rm66lJxgKr1Bllz^LL*Mun+v;qpsRbo3V-wrAEX`ur=LCm*eel z*+~%MxE%Ppg${%l?<3FCoF&5G>RI(=Eo<^XxmD0nJyOZ+N!riK-Mhaqw~`e6r+#;- zh{&3zTGPoDURW&M*!5DlsXs_aZ=G*Pcehuz)xu?K^ako_!q)l(CfU5hID{lzwDY zY5*4(j}{9W6Cx@nmYNO=;ZBhi8S@-T7`b*0{LlEya)n2i&2Gg_fB+xY^N9s!KQmPJ za9NK`oUD*WE~d$6b0p23GbwIW2k8TwZ-EBYlK_+=Psa7ouSl3=pF;K}P6aT`odq<| zelp}?tAc9lL>I0RzkD(hLC)gK^G{dQ8~BzjJCz&R)|7fB{OoL(r^TR`EZD7iPOE7b zN1}pQW!Kjy;VU%sMGE=7<_Rj00e_Ocf@ GWZ4_fS1HBiW?_e^2)@pkRG#D*o{ z<1F3MfEzFiT3s6t>a(*XiRjv$g8MXNLNg3!FBljD(#!}s0wXR3mb1d& cav5>kf#tk1V!7XxGhC5wG?rk(|2qOAG2XvA&dcZ6L4hx@8mu^{* z{j-|WR2sgjYr7cv2ap*NJK@zTs 5mPZjc!jjk&z9U)gJd(h$#Am1To_V zx}RtTkxk2pt6Wz+v4#p}g*T96`LTSyUo;`tal6>0xSO2Mls`MPqBDckgvIgU?ckj> zo>j(zw~?+Je+K-xq`J4;Z`A2-KYnxuZL3VC88sK9)eRp~UY^UhJdR!sCwVuzeewBN zC!8MMf|f-rdLFs2(_&M0ZH#ljWr(05d?3so-9*;fgt+JahJ*@MR@evz{nzSwv=eUV zBfZts^4z?X`F@xRLin>edT{h8iWPiIT^4hnKYVN)K4cRy*&?1<5~*~({GC0V&USYM z>mthtraPUzo2->3&j_E%-%RCl)40Lil02n8BD}b9<+==_r)hn$x?o77RKBYP-rYh5 zp(6la`bB;CS8{w@ac(E9B6qd)CV&uTOYki8^LDyoPg}^X_Kj}$Hc(^Tb;ZIr ;@2kwil0?Th`1|BL8x%T4Fq)q~b(OTuQjHTD#QZTjOYjSk zdWrMsw@CBGE`D^s%|bCkq~|Mf7s>l( =TBpRyV?$C3Wu~Ms+i0%Ke&h_sNMU^ z|7d@b*ibb#>c_~H_)XKHO0W6+^gFGiOvN@l@<$dd#YFVQ+L}JUe+)1Q0JXISG-!l< z;?gx>b{+m8UtAO}a~GY+t y>UOdHX_QQnYk6?bjLUUymIS!9%z{Arg84V A~tdH5i~il~H9NC@XnKSDW<_0$*zvqky_ z;zjhEM0Kqx?HvvTv@Q+af(bRN7)0r|2Un>1h2nQzmr)==(X(|goIwRYzWJTVSR8`T zCQgrVs=7HAr2Li~i8(&Jhys0e?qSI9PXKM-i=6ICtjC-(gMrcl$~|jjd}piEauFjs z`~%M@hc3 BfA_vx(GY)l@gZb0$$E6BhV2uC-r%;-QxSWC2}(QcWyq zrRyF0HtB*}Ug^TLbC)f!%hBgGOD{AoBzm2N3#nFpU=8I~Pdc7IRb#*TkO8l-Hrl%W zuU{6R7 !eJxId}KrV9*n;6Erc8=as&(jQdnIOjB6Wc)q1^;o5^ zENupM)HF+aNSS^E`omUvgt0?Ufd?c$nn_~k^^TrO*G2*el(Wt|6_=u7`@-K`R!rPj zy-*X5yI`JJKZL1zK{71AccNW yB8 z_N(4vst(&K=SoO>Tw?tb*!BpeFej_TF7LL(s0*{NEX)a`VAEylMR;+MtM)vRggZ$0 zn(hjpdLhL!beFd o7jm1P-&vNHk}08b2g6j2zBZns_MyY?Q K?mT2;sZukxI4k$rKvrKlHXMNPEM&zB+K=4I`}`{VStQpHq`aalO$Za#vB{;3Jh zeCu_gI4B>-gA%fEUONHbhaQ|NjD{7VGxP426p|Ly?)cu#bBcb*Ce)S3eu2s$t%{1c zTwO#Zv=xH$6TGS+O~x^!Bjz!?GNuf_dm(6!5Hu5@!E#apTlcYz6}NY@#fc@Em*|Nd zH5fymr}F)2BaU|opcTdC><&;f-W2EjjIq_dKJ~i+t;y;ID(*^aVQmwvq=ZA~&>ofk z(XIiGvadOIi@K} dl4O8dk*UNVx zB?Ky#)`0QyiRNA8kTF`FMAOUM188}b4Ro%1GP+Q5o=YPaA`+TUTxKjCYb`55y9~yGm z3*aUD?0^Nctq#-|&PSOGlN42i!j4Fw0H=^T6-JGybAf>1DVI?@1jb{=xYM1QrK$_N zr!*C@^vn^3^ph7-*?RG@o$!}Co4G=3qw?%9qQe;jGuEi)RnA$@9nG!FDNM@`TwMVY zP+QK2t@Udz&H-Dr{p;6WEz&-Tpw;|UI_a37jrK2Pf<~Z4m+^S(QWt%MDU|wiBc7 ^~mCq`b74aMpg7j8~H++~8-t?;l}5{Fm64w^YtT@;tTGlAF+ z$inS0!HXie>+H2}vVQ`y*1}=wW~Le&KKHU&A?yZ2d%yPOB#A(VlBaXy9(@(V{Q_JF zUe2vlB@cK1)1`lYU)Fq(()`K)XV!U$m*$7dWXKYZ_*9Kpbpb{}=WFjszGSi{ks zAvwD7F5@u!6Vn7S!nTjrgl-@sMTg}_7#1J_BM2?$UV2q#B%^_S?OqZvej+ErS5W_9 zYB+A>P=veo&r!)2zrKSH@ +*mwbb?eUXN7jsV+R^RH#!KN3ox%2h$Ym7H zesUka)ZJwH{^i`m3zgrKqvX;|UtNWgvKXno Th7=Ev4=Z z-oK)T63O4BmXEMb*S21Ws3{5$A;SWO=oI@m9-Glp)ZOXSm$}el3s&2_553A(!@b=$ z=6_#fxGxdDVf(iXW9ABOK2pF{thso-;&7zJHdS~9<`lHFbcgw;f$Z@y2iY=Fali3{ zj&MWf@WU<9oq_~$^)kQv_wze&?lkRdEn TT);3j6 K3c#Z zCjvbY?wnhb(f{=VBq#lD=^%*|L1=v^^5R%Y?}!fDRZ>0qAXa^_+u3NtTpO3 q+- z-GijW8bk!5_`2}8UgcY6+0|;m$@J&}!q=zPWSHHP7MH023$zh?EKhtu3&|Fu)2FSz zJRZ2hnAI(53%|5+^EveOe_+58n^h{KN2hf;Y8?#B5_EPi&4BmIZyHijq9Vh5&Nh#L zod--|o3J+wTkmu9fMw%O{v#3O${X3ta&(sJ94c$o^A0m$4xaOi+yoky(rGRYJ~Q)= z(u(6GJ6zMI9VfI5&3@qQdCu$nCcn_@`T0^f^XHpy-+ZdHabDxLRw?UfPpaZo0P|nA ztEcAGUggd&EMF}zY$akHJytg@U%Bb9(Bc*^UroRJISm#EildCxCN-_z#+F%FF;Nj! zAD-W)T|;A>gyTVQ-v-UUqw Hy?mw;%B|;Pqdgo=sMd>W% zqZ3vt&US5yb@>3P#cEAu81@-$=Rohh1_?`15@7^}_PH4m>F=V|ymE%WtWV4fP^$h* zFJ1l!E$D}`-;%XgsgISw6TeP02l?+ B!!hG@OrMHHB(3=hw4bGxe2!Hvk?+uPAz zRoY8$D^72?#^3zprT#WP$%&QTfRoNk(Xi(i!; 162%Avt4 z?=%~&V>rNt7N@Mu9`53XrdLQ?;AEgyJ;F9jR@ANyN5pWHyFN89l{SmjS;PaM#9G>B z62srE&PMLrjy6j<{xdy&v9_E<=Glgu*qa^r+MXm~OiTm@4IUBS1s(co_^-p1Z=8qU zG3@caxnax<`oqqd`7@*CAS^fA?C*LIg0^#l?a*bPgD6~C=w1o=W#&@Iu-FyaM=84u zo-S%Xw1lZetlp0*c&4)(OZVxWHy6=&shBirU)rjXWxc7T%E@Py?c4}|XiVYckJ5<^ z;ik}DZr@+=K9ixA6Rl<`W|V#0HFxM)j^eE`u`Wg&9YuNUa?NrmUczCFctTtCyKM9L zrt4n^F&INe>R2^K5bZb-gD&f7KDFN1+NS0-m3-FA{rdrHuyT7-Ucip2?6J0AyF7Fu znZf9FR45ISz1HQ2rE869JI$wW!lZs1c$C*c s81SZ4+(81kC1AW$~P;Pe>Lw$ksnVXRCnjQJ{P9h{p+#q z5_mo+t$7cYm_d*hIO`OpjO0%(%hgZQ(p5VvtKkZs8HWDmsJn2n8}Uh&o)%@~5LtU? ze~LQaP~)O=)@EFk@XUfyaSWSZRUYz@MDZKTj~$RNK5(MGQqTGrc^^;*1AH5g%EOb8 z^me78nl;HhGOGp;ct66voh%)E5^4EaJDV5mvdg+`aTGa&dApeBNBK&Z4pRCj-!H#i zE^_q2{)j$dV-^pN9fLIzupn}E?yp8AIGwv|@l}mSfItomc_2q&Y=$g+!wiMZ3a|8J zzyCx8F9pPgwhqGl--(<}CPBZa3g=#ZNGFXv7?9QPEHo^_VEOwKF2CsZ@hc5E8)s;! zR`)sLW0r%BWX~xsM<#S5KtGJ6vl3!t^g!~0`vj9Qy;2Zb7LtN5x<>|w;h}K$zh=C{ zi#^r0{HA6#iKS1Ef1Nvju# Uw!x1`brm{0Q=oq`9c4z84*I}2Dgukr#6DE zxJci!?q(>OzOc5NAk!h-n_56;)5{YC(E6?RL$`XO2v^G_w{#G%IMzbMY1s}=7WvGZ zjKeZ`^xTIt%ZDB32U{=38a&b8{|=@&AY>^>86Q&3r(Ao=ZN|Ph@B#lnn$9{Z%J1vq z&kRF12n^jIJxC)R(qYiuE!{OV2ntdX64D*g-6@g+N_Pm-o$v5{*Y97}Fszx&z31$` zKj%Jd^LUvewd?wiV*T1@Z_=YhSu}Z>@3*~CajBuG*;9uHWLUfXXz*~82t?1%z)tOH zt)t-{FBgl-0cn=`5qXWN3`~|)XC(I4CGc^yr=XKa<8=4yuH8gY3n8IN4m)NBklVmX zAuDl&W#Dn=eH&Vs7$L{o5EFBW#U*h+ha|rf;MdqKoIaZ7`F!DZA9w|$I|TlP+!jqV zV_yTSET>$nEP$uhdybq&S^nggph2w_v)bQ8xdJ4?mFS~Hszf
D8~7te!TrVu`2B+w2Gbrbx(7a9 zE?3E7j_9{JIBW)l4&W9aZMaS1+wuN`T$xfk5C r{?jsEK!R!+FLvR&U o?!)mtK!X_sg9|)&$$!n#%9v)NNc@$;gsrDw)ZT{_3U<$x0T}F(>HtIuO2qz z+~FmMv{VZEvaukk@L9iwvpO|?z?PVqrICktt4QUus-;}qeBt`GfO{Le`OMk3_CJ{2 z>I~ 4Fc{aRj*zol}oDl%7kLfiD^V8e3GhtF~9uk^Rb+EJum;TeAs uqUR4w)q`e)~Exe z445`dAo44Y5Aw4%Qka+KQJ8*b3tE{eGL^j3i_Ih#?Onygb6klMRRY8|)LMpZI=)sn zuN%7%cT+f82H0=+CpqF=-)|B{(9e|)j7zkYqavFODfyB8HDu#Du+#pN%u-DI!GjX- zBb+UNA-@Jc-}WU&{my #Xsh;L~Af_`CkMg%$-y(yK>sHx@P1J3bbChd-GwcPsyr zDO#Ud2b{Ue0Tc?=+wFL)<(ZX_EIMnRD(7sMSg*A%o+WB^$j5}y=(A=}%{7~B9=!Fp zjJug(9yzh0yRpjZW)5|+?RFKbwSYlYsYO5awEz^C3u2-v#tC1^D}K7n>5WaHzU8}{ z-VVES{qQdjmDurmGp;*N6yRxw5{RPDCqM5W-!l0nKP#H^aUAO|bvIbhIG9{iZF#6Y zs-6c&7Lopck(Ol;0IMDdZ^&GQldNq`9(q$W;AU0N9QShVEEr{WQd~ zlC1Vz_VsI(>}uyGq=sgkU}7+IkLC#fw}-~W+^(kC`bqmoHsAA=JBrxF % ZXAxOKP^Kc7JoSV#B9}WPMq0-sry;KH!H#Z}&&=21F_? zU6wB}t_$#F3o;FY3uqPg`JmyLuK->wE>jb9n96Xh=mH{bA%kPSDVo{Z%oU1DVl45U z7f=4?VNi)|uh+~tO`;910RbcAY1H=%_^ksov^XIR<2L%XE!GSk-aqGnvP1A7s1Z`% zR)9akeCLg= 6M=&R?wVj@~@ z#~4b(HUzvgcEVxAgvE@(@fx;KV%y2zLKIrpLtJBs-4gK=hpN&GFwP32w^^jbG>(+% zBjzqYBC{WQ?xh3I<+oQ(Cd N*tN* zG;zg#Qf97a+RH5-^(Tfm_DRWVSfq@rVeNj=LzT9)mv88DdhxWOET404H)-e9RPo+$ zDYr^y2` rT0YF!#V&nsa6If7kpz?oql!sm0p;HW +s}skphOuWFl{;CI^G^ z=?8};B4}u6F&e8zQQ|h=mM*0T%5(a04Q?_A4q;Q_xF2!CMu*Q@9l}Q<3>3t|W^Q6L znS+B?o>>abbR z`jPylWfpGZsS0LRoS&Sf*Lm<3{Olp!@tPZxeP)Om@K^;t+wJfFAh^Fsn%KKw4E2ta z@f`lhz?wO!bK`vYWp*rctH;sf;_;tC0D7jR>vyp9Z5mo%T$MY!jd*KWy4$9-&YQ*6 zFD10dr7}CITN4&g=uM_s6br)}j0B}ub&|D(Nph4Zai&Y7ZrN;M{dAneB@YE1-??|N zt;F7_{-R${c}+gPu3t;}w+aWmvn{ChxG;;>Xbo{%G`OV=ZZDfh&*PUz;l@trT0wo< zVQThzf2&3hBZf;!V-ELtJ{cBGa${<$`XB>ncv|iaR)M7T9LCHMR=WliTsGwnxEX_l zpU`{hDqI`?5`5nQFLo5_&S)NJ-IL`oNEud8biW#78?Ez-A^^^tXeBWl>Y48t{|)Z? zTKck|C`u}*2%%roz-!J$S1+8$Xm&HJYmHk~Uii0OS8vYaSJe4bn!%tbq#4#<-yPHb zt4n+n62r3bZ|` 75vIAbSNi(V&US`*Vm%C&96`?J)eaR zdg{c~rxe0r=)nULlf1+tIGhFew5VL_V&A=DFxdo*O|VwBe8zaui)~fV4(>JO(nVN& zg>Q~u!py0<3D(^XUMz39&`lJ~ph)ZM|AR2Fh6OKAM!fi*g%Xm5XC~abpI6+~+!lf^ z_aVS1Yz%kylkHyQcI{iefN^KJ&qN~$I4)tE$|jNt=%%f18w> H@En#u^eUns{X*pvu3R*Pzm z28JVL`V#ZTKvVthqE627PpQmT4Fy@Y6yp#~l1zcmO>7uzITyul7DXGG^J%loCx4F* z)dYk)^s6apx=sCeI0{yn5FNulj=@z74q_4HlHXMlhF$~xEtAc}om?oQSb{_3jvn6U z4#CHdory;3x^*r2?Jgfra3&4E3Wxed_}?yr{9HVA&X|EfeK;{lgyUG>{0z^elA!`u zUSz!XeOQUUU?SBwlpZJP!?Tub^f~0t<*&MlK6)Q{29$WgRo20kLvy4-n8}Mgb@Lx% zao{cns7+Gi@4k5FplO@^`TTZh&6&Z5jBp!nqo4lcdhlQbQ!yq8FBfF4muTKtL?B~J zOdv1pMNv4Hojp>fXaltsk^E|qDfJiU*lffWS0Vrv_(f)dCZM4f%BfAmxWp6)!4IKb zH`#IyNLHoI36cBhU%{t{rX02rMc@MdoPyT1PRjdH`xAJoapq#i4``C?7ewPu`}30t z(-@Bi6b@muWEcLkKb`f{J7IPTmctFsHo3}eYrg2CbGU_Pfv*0PO&Y~iUmdvtP72z8 zUQ}8A2p?-;5=+2~iHGoV5DZC7UqjN6Hv0pJLgx|f zM6(Ts<9U5=I;gpQkz*W%bB?WjS7m-f45tYp23_^7XxyK;@V#2l$-h(~UN|K*l_4k$ zyQ0~j_GIfiNO3ZrCkCn(D}G}c^K|mIwEh+Opr@lF8KLhqFyn4?WtYRD22hn$*@c)u zrSWv;O#UoeA)R(5&W|_k$)8wfwbF>J=kOj3mO`TZh6dmEW6?wc#UDwT{$a{8H RUgt zpzVbY4bsz2C&)an#FFsNOZZ~YIpUAfll^qSq)M`{G4*D9tF-pC@510ca&647p>*uG z&&F!ye|--!OsUDcA=u492{i@QB~J>&ET*-#%`F0tle9NhvZLStd(fL?R->@>ljg2Q z%Z~9V{|3YM29~omoj~vpHX5%9Mu7$K1H_As=X&4{dYvfr#Lk#XCB!`EWuUIowOxX# zhP0O+%d25*)UzaG(Omka#F*SJ4YMY*&a>#Vx+$~0fA3Qhs}O||W#=bQzp>#af}OIP z@l0>L2}uh-DosC&0uYrp$Lcf_SuQlgN;P}*FT-EI&p0;pv6qSG`5*&jH(_rL%*O%2 zEutD?n_m@tL&lG&UVed4ZusD!`x&GY$*+M0V~DTAsSjZY@I-F-5h}|ig&j`0mU0Ct zZWjExfjVSq_*g~7>6FT^ET2cEzg!p98euIjUciqQ+4PRf=b+#vP6h~1MFn;q4swCa z15trrXW<}M?`c9vb1=L(!$5Qv6q(P5rENr_EgPm(VxN5WbICw_Zn&bzddcYr)A=J| zl!syU7Y5qd+l-3`BRb+;PaMLKu5p3nUWTli0G0@F^jy%}cUU5fy{ Z+B-O*T-`r3_U!YXRk(>&4q S{nK$6MsJqvZ?w=ap14dlz#XcX97qN|*QZPBzu#p*GxAaw9 z1vvVO`6B(rTW-qN_|mLQQfxTE^D|cCfu6oQP*g;^tBbzs$Pr*SF(HL(`*g$Ws_f=f zyK<~)qjnbAcSE9T;vdxB92t4oA9d%cUHDFbExFC5dik2}u}}ghsQ3s{3OE2GY!Ld= zww{^He$7PSD>Bi6wHLzS&A2F!CIs<*6^ByUCit0h!7Wce>RF*JNNGB(RTpQELXvPC zE}%U~=o)v7adCip_tJ4nh`3LT$z@d$WPIV_)VFic>o5&LzC=ge$9y|@L!nE^P=nE1i9$gBfns8&f2rpk2e^IY9^$;4Kh z`|*Ec-@*kXI|(0gxObA1&xr&Miw+~MtmaK!j?+YVe}BTi#|r1o$--xF^3a|0tft=K z;5UW(L2zCAJbRA%PHKi#Z;ZQI5?ta)v8)7p%=9fOlIQMN=iEDYSi()hnoO|iA!?DP ztk26I3e~;c7q5)B9aS@PRU;z8?aB+>*)D5;Y52(yyhr_olm$^_0FQ+ZmsDQL^vQ@d zy_=wI$1==$=r }q2++#n}t8b z8cAg!raW3_3ooz=V+lNiI&j_Puuwv}jG+vz@Nj-}7uB~ N% z&}-LFsrR5*IlHV7Acp-n)k>0+_Na-*uaC$DM`AyN2Z|N&?ZkeRwJk6n!gTYe%CMu6 z-3>p^biJJCoqYGVZwH8v0z#b70+7sgzST }z z%*85cDHS^h*S6YaTT-B5>7_t+02x+z`$Sj@`(L3i@Z+1Jtg)(|c=OWOf21gYf)=SH z6V|*_xN%?DA9chhVGai1+9gd~971E*;b$g@^L|j$H&UCOAJ~Ba_12_LWNWcF= XL)|69A&TlPt58FxTp z#TAtbWhB(}C0x4~+rV>H=iB$c5%S}Y+nZ7hIl=?ZB(-Hm$Kx#00SOaJ3B(o#Xh)1C z YgF=@V?oN}Mgye49;eCR{|agaJkPOvamUx(-Zu*O#d_}~In zy{A9P9eoWVXsymFc#-UU39a6eI;9!XX5!5nzeLXG%SS_bL}TcVdKq;z?ch$N {5Rt5?hk1aNFJ2p72VQXgd)X@ zZo5Mt$*|hjiFo2&+QruPcw*SAe~w**lQO?BRlPV_Qc~bj8>~5Fi#u~XWQ;e2WMg)M zQ0|i=`4#XNkUG6gGELBgI?pJXgW&od^UA2~=encDP<-{RIg){d;f082+wJcGRL~t# zr;>R!3)}*l{3PJNBfgBJLS1H{Gp7$h4a5%${QkuJif>lSPY&1wJa%i4iH8QH^Cuo9 zswt0L&@x-wG=_6OZbqDcR;ANi3L|an$%BkOsqe_FcLQFdCSSOa;s%jt1eBwssH1bj z-rv3?M_yefF!YhRq{Z`c{1L9l&xld>wSqaXEe$Zu`6@)CUxYI(URzfF+p#qQE8J~` z2xxMAk@6Hr>AF70m006|$EMTocdR&nFQqi=J)4bljy;wUoQV|r^TU@5$r{yKAkq+Z zB8$|EDh8YV7!7{gbw Q6>@m ~g7XPq!QJT=<1(E;1^&a;RcR7p^u=Nn8@hmHH5=im=nD)9X{v zfX*7e KU9|don3*rtj09;fmmaET@%0n$^TKhuxP_I&)I03=0^A zlI^r2cKB*|+lL^H+Vk(zFQvIu?I32L6cWSQBHQ88iw_N=x#wqdwfO|qrlxl6Cs(a~ zmxc%0$1T7O1Wps#K{=cuY?OF!-Z!JYQK`6(@ngNhY#z)%)E!?!%APG^$&k4$?9*Mb ziubTbx;nWkZ1Nfzkc^21d) dm?)f|R zg7!g|9~r(#&=hrd?9y(MHtI6`!y>~%5l0( -KPS=SufA+zcFyTWlPDEz;P@_vI%4O(Q2i0Lc**W`)P+x@KTl#8l~4S z4lN?3aoR=JDt4uU-DPfQ=)mT%UU svYb|5$)4Wi5>5DJPWIZsx*#U_9cr$?eq7 z5~-rIf9t?VZ_hdt`*pgV3VMjw#2lCNTo3kUPM7gTFDXI0LUBdd+5P}E3W9`nhA=GC zMoF{pP)aTkeaRu**`;D+s^8v3X~~S16-IsFV?%j(9b$s^Bzr<7%i4}9I4c9b!jS )tpdLn%Jq?)Sc8#ur@&d}{HhkXB%D zPS_kEJQA!&>kLsoj0=-?@)i%7j|z4S`FcRp^zRd!a1Dd7aM@i2rMJ$%d)9Eb7l=>$ zT1fAf_3!8Xs7$nLzRW-?y7<+o?f|bn7L%WwQNVbkTSIg|$i5MWfDZl@%gC7&s~sP3 zS6D8vUU}x4g+w#jX##FebqZx@KBxHo6Wp@;<|TUoIVW{>Xx;MX?*{6|l>(RULlSDL z>b34}gB1pt9@GkdB)jYTUN?2B@m+#^9)65$f9Y~!eeztrpGoI{@RV`6ufzd}wsA25 z@Hq{d_#E$hn(qDwK38J7tc2t;lpPm1leCkAwZhqGh-hwm`)qp?WuFHP{zd7dWvNeU zX2eU$TK=Z7)Q=~5f9#eQ#45@Ps8$qFFmPKM@j5Lx@gZ91KI&m&iaD6VzRjh87i};* zsb}xY@BakI#HKc*>X1%uPRBEozR_Qfgmf}CR|eYaHn@yc9X(ZFO64jARZhbM)GZhP zajrvgF38T$veskA61$+cKCgIpZ0lpBg$du3MEh*=$<%(NrDn(u$v@|>v8P{gAdbYe zp)_aljr7HFRn=6ZvASC=(9UDdFJ&U =IJ^Me|N%*mm(MwM4QkEG#T2!}F8#0`2> zKT%x}d@=I{%_}SJY9(wFH|8 *IxY9@a?B+wt6Fc^M(!Md+_S=EhW_Dy=ShlZ@0NXmr4;{j^lacujSLQe7Ky5I zRoNBC^g&C{+SpS{b(#+ty%37|1d_mOn|v01=5;Oo)@B^jJ2XsMj8yXP?F;eQ@9+Br zRQD1JhY9y4zR*rl)1$oV<@pMBkl@MvDB?}y{*D}+u 9dZ)b rIVVAOiQ zVk)7E0?_UbnGingeRCz>f>3$YN6cRUA{h`xt7prP{{)y~KOwq%>Dfhh;@~b@OLmo; zjh^m&y@vNizcY?L)cOw-@m5CRo0*cHi+sNL;dOa99UNp(g9d~86+j8Fdu67xE=(Wp z+aXBlceX!Hy@I3fbqka=Qq 84Q zo#dJ@Nv*C>qA?46`Nk(B4@~>BTHe8W!hp+##)}!G1Z)sqF^A<(AnR1zt(8^}&cF9Q zL;2^s1B)JrzZDiyH|Y$lb`()pC-4u3h@l%BYO3Bz_0n;LBP1KD6Eoa%57!NUN}yp{ z>kd!8dR)%P`f#ZGdkuzm7%30jDvi419|srj?<`DGEEI`Ylb7t6KMITnR~UUpu6OU5 z0XO`O8o1i^qX<&i4u`|zmFZc{20FqjsdBg!9CI=2g}xvXEnpw=%}+vW+I}_=MxrDl zWPKikR5jLta~q~;029zp->jz=92m0->WZ6?5BM?mMnHKmqt08(Nv`MLqU8Y(^abwQ zKf|yW#~v9cOz%NEd_~j$a+KaT2I|wrW;x;^))HaArwJ@w`1~O9t+485H6S8XzNk+G zU$%gvFyYTG1h- >^Jloeb-G*>Vle!b zKQ~1>%+OK4luO^$78V~6>$fa#3iuE}us&=IGsk&4Jk;Mooe%=2hk<$uwb0$;Tpx9Z zLNkCcy3z1tFS&WxF=4|FRJ&7C6V}Ovj?mC=CVLRyyR0l `qW-x(enW@+Ze|&6~8@>$w%`LC+0F3b_ g9#W5+#MB zfdtlz8w?)ty4;70_n~GFWhQ4e5>gKu)W?hvXs3>u9CAVCS{owbe_^ul3kg`ds8-l~ zGjK*l^V*MjJrdKI6V5H>B}#^s`Z4o1GQ%IhxpC{@fjf5=)db~9(W8XWnhg#P%^E|W zeb8_rw>&P|{)pO@-^o%UpC9a?1c$Eb+Rfh(VgJD~LhnYpDNFw?N_dB1uUE40F7?av z+q_USFwN5U)zfmBS{3T^c4@X0N85_htA!`5tgvryOd4?)rQ!ijka`|5W39LEVtGrr zh6z!KiS~+UVTyhM_1|ao0kPbZS|>spAtzqL$w8uk?)k*KZ2*M^;)-^Z8I96&wA$pY zvPk2V(@1Tm!$?Zl2$5eGw#&AbjMp|Sj!oeUULAQg=I{MS$2Xz- {g^;?<79$_nH GrnasyI)>kfwMQIMel#56 z0b_zTN{@{0MxVrvO}1BEn07CRHo-jsL@ec*>IR7+<7#q`>U;Xm4R7*WS;Rd?Xq_+5 z1K$!c>jN~=OQ=x#LD*OICO?z;cxcO+q 9&Ynbjg?{Zi0ye4rxS-&f_Xc-MDl}} z%m^h1Ia|HA_-Snfzd+d}O5<6{BNgv|Di%Q@T32Q0FixK;tcm-N-fnjRG!W76p6fsV zv8sd^jo~>!_%wS#_OqyOiAdZz8T89bXe$(B3v0UKaL!QD*a+9VxIl5BkxYq<;g`?N zPC|;Yhq}YW(+4|!&$~tpTuHISda{|a(&zxZky$TQkXW4^;>hntRIbk-ms08a*V|CC zT1~1rmjzv0hyCRm6{!DO=AaTf8ZTflKbRMl-Fy#4vz|}od!OKPMU*J>m;n)h!L+g* zP>$knn2CtkE1JRB^$8b;#`9nrPWIw7P`@7ltS9|*Z#YnF+5XlAiyvPzGEtbu`jLYQ z#7tV<@$VzUspKZPK#y%02uMYpsz>Ih7@rLY2;t)L3;5eYXsChG1CzpQ<)s2;r6mFo z)Xf((AtwNFWj(-@I_=E?dOKMgWdbwj?B%+eCIBprHH+HL-@sXNDWO>5`0|#@ttdU} z-#iPz*M$6_-{^phHgD+}iP fKg~!s`y#+@MghK zTmbT>&rq3@Go*{U_DbN%`TWlKx69A)O;JTw(TCkmp846u6RS;!ea9=u^^xN@MhcGF zS8Mm{U??N47>aq^Zxh&83`!hKfhDx;d7i)i);gQViEGk49}dS}XI7Kqq3C0w;}IT_ z`uED9Ltj1fIgR#D>1Y#y>&Tp;6S|urqB~1&TUaQ>JCHcGWk+X6f}@fV3$V`oNnC^9~2_sUjNLcfRATvSQ3uMsD{ zaztzP!pVN)e^JG-u=KtCB)sF-qE{#Ky@D1=NLBA-PL4I~Uo^;}6>sB7=I`blY*4>| zV%>MwhcQ3=G|=Z!d*dXUf_a`JPC2IhZ4xaTgGQ4aO{@@aJlIsCkt<|;D`DkZH=KhV zm*dtZpg)olrKfmYC_deH$(vCA8Tc6K8$bG0&g-T{t&*c#+NH#)*I3Amd|yJU#+mVZ zjiFRK=U&Nn4zr(7H>5GJHeZk34S7c&W@KkF|5sFzdAdlk&^L6)rg|;dkx^KjU%qx6 z Gg$cbKZeC2j*CB?x2O|OOlzrkpCbqhsdxkIp zAJjMY>VQ+1A64G1PLijGJBpVhIf8(# |(GXwq zg}k~O_G0|CtRBB#J5lKD+3Ub9G2?jp1um=5aYq=GAf?M1W^Fb(oPLEx*oKpIX=ySs zX QX5&-R_-~-HCwY=34NUn0wg-PP~UKZCZQ;0 zuGi)Y?DC 5*yc_ z=s*^FI!d$OgxiszY8~n`Y3pYIgVOc+DM`7ImAXrI0TI*s5VzEk)J*;nM4}_$kQh(^ zGlr!XCgIwA=HZI4(V3P*(~b}SyNM`rUHRBz^#!f|$brtP-qXmWMk7awDNSQeTd)|C zZzv%~MHvTAn TxQ(!Y4QC7hVCBA?#JkGSIm2%sOka-cYaK^; z;TlMGj=5uk se2<_6C3K`Zrs`5&@>o#g2UQ6z`Xl`b%iy!pJXEohu z^z9g13EizF;l)8g4WLc}?RZT!k&GV^Ag((BPKIK2+k}W-;~iAp{mV?2dJg>P#wW`5 zMGM1?2AgpkWDCQA-IWa6GD9uS(BiK*>>pLDp$UF+CjQ32CEfkNGbL?4v{_wSdeanA zf+4!9KQg4GJMYK{?(#;FtgpY4$EF@3B_YiF{vtSG_AKi*m#!;LNPzjt1L-T_k-KNW z0JFcuPv;nsp|!v#2fX|=1()kz*NA^OPuwaW8g{tbx3&rGVYxP08sts6gt2@i0>8F) zD#9OfH(sc@T^@K9bsnOxovr7aht=?3BO|iWeW~cHFm;7 W-K8%81GqDZ7CA zgBoh2PH-=TS%LyX0U~$MgX@gvn?D%&P(>S>5CKP0{!bDGJICrQIB;oUdo(Q}^T>ix zQ@?6Ivl!lrVT*k7qCaN7Vzm54LW4=@+CM|)HhVn=;)WqEjtk$h#6K2%Uq*BY+Hzk? zaG`g3?y#n2evVV6RT7PsT>kw!(ARTL)T^#Bg#>R5>8eGoDQ+c9`m=;W*JmVg%~#{L z!(P(Qub0N@j-CW;7NVDNENZcYLe7{rKHYrCFmBy1*YLA7*api4tM|xDet)d zd`et7jdeOeP9A9(P*SW^H3a=yka@6giRW RZjbt+Fm6?0$Lt{e>5aGQ zCaR?u`4F}t_}(kF%V{eYyQAmpEQ~lKkp8G?q_xW0hPQ#6cvl^z#weVb&~#(;dAm63 zPLr-$Er)@D&=~XO=FY?Jhu#a@IZLfyp^69InXB(UXI~ mpyUV>3 zk;SlHWs %~zUz&q7${{{$B%>gl#Z&qpyQ11^v~d};mBX$n|>^Wd&rgJa?P{2oIrFGm3@WY zLy8ZT?A@@W2ly9?q8a-nK5L~eW`QELEC>@W9jp~TTq<*#9M!=1K`8&G>;ju_fFbjT zg7~5O&>}55{( 3|P)h#1^xk`&dzi$jKj)d5jfn^WYpIRHCs|FJ z0@2e+ccVY`5=am-`$A(Iw>MU;x3qX$g+d<@ifk1x_cu=Kgs~?PXR%L8D(A)P{RW^h z_;^D7fLFlL=B(ypwLI#Ff4~P?!U Q4#G->U}P qGf@QY=y@3-;;L=mfX=`7;SrPz=&l4-l~7E11QfM>?%k_kAbMPPD9@ zkb%svEv*ZdQWc51BpLFL7rPZ59Qm~6GdNu3JflA?WTSmDBS|oh0yB-!_b*V1y_ljK zF{iKqH~Q3=!eY$1&;!}}eA{zw+I;Xh5i`9(MxQq; >an5 z^Ly_Xk$>;-g==D)ro4`ysHuz^mr(uwT)!&suYr?I5bzL%9*rD_X&JYIg~x^+BE`)> z7HJ?8XceRd8a#JK3@o4mI;5<{7|MfLUcPih!>vpgRw6sMtsrpyKOin_lkd=KNF13y z133l@!Gs&q|DZay!#J7+ApZ6Sv95P-(|Zmx^>pjL7YFoZjl*NU-by{0A!TK_u&Z3W z#!p@Apibwo)LSeo2ttQKMF_l8bM`>*fU3xi7eKmyIZD|)T|D)qoHpL2_;H0Q-DCKD zDkosX`Mh{eMyd?tC7i-l56ML1D8BYNH9Ir=y)rdlZ2AUl(TmQ}yeCpyrzvUdyjCXT zpqbow|3oUKA7NGPZqQ|VTrLx1KJvZOyr+<01UNG^bVWVoMES6_^Y%**X)?VX+}P3e z(P2=p;`}{o72EHqm%HDrVdnfxHOvB3PPPaqof65q%d{U7^qgBu32oRt^yMq38S9}G zFbGb%l3RZ#!~&7W`X0m%g3uf#ZGCKL`*idUvtCrN<=@YG8#;+ExB5iy zijHhpiDI^`zQ{bL+qk*O-K#`Qqx7)~&(VPW@Ff5Wiz7gAW(SmlfynQEzA+pkPLezU z( j~Q~ zl{ArrF;RK96h11Rwa#2z+8$%S4jOM6Ew9Ib?;o$MVAf~aj$H@I!P|(7h;9EJZeOXd ze}yeGA|6kra31(EUjOuOKanG~z$NHN CcNwt|FsYy36cnh z&7|kl*8@ab_zqzS!@PRC321N{FW z#sP!Bg0qxV8KW>O{Gdiq)Dg|Y0V80@T>&su~2A}L`fIKu2 z^IpD*XhrSI=<{c-xMoh$j1w?K%5&uAUyl^uKJ&eOZFK4K)z>M)Uy)HcxIC^bQ14{M z3M4PK`rf 7V23KLf(eI1evl^j5F($>$^*d?rf``*lJP{ z9!N{m+;}$vU>cF}JMGPTX#(4~t#iQ+FSXwoAV>X}Qm2sc1_v`?c6q|*#K~DdK1_Zd zt@L=GO=%6!#iG0S!=BYepBQC(H>o8_&*i7k(Xcb w_Nx6~Lwk$gt4gL63-UPs?40bJgUx(t#1e7hc>FT+gL>`(VbD z4=o*S=PsZkKOdPrGDHu?;Bt_m8c4o^=*9lU$Sy&=6pCtZN@;Fz;OwRkRhWC BKe^0g~Wsa z{rIN@@PHWTEg?#obP|IW2jd&`euFQ6ea5a`5SGkyXVmAfC|e>6^Ey~UWGMeRNK`hv zoRza0m2gE3s>OzDa2n^<{rsphSf^Q7ECHAI(s@A=eDn`70Z0Fi*#84mO$blz1AJlP zO>Ilv*tWy|FB0ps2Si7Od_6VrjXdFbCoLXPP*e8LO>bZ<44$34Nuh=(rU~I+RinrM zN7UkO&_{@X1qy*?VU HTfcCFuE^kT0JT2VieAeNm(>$E${sH9sd&>mAnD zwPSVrP6}Uwtaw)S;CH%`I_FHLwm=y($KRztygn!GVgiq OY(T7l1QhfM$k=@9v !YIei hix!tOjELo%ZScKK?% zTjaa=uh6RIl9uqq3Tjd$rL9seuU1s{w*$CQY358!G|~d25jbH-R)W4dwp8JS&grE$ zwr)8(ZNA5d>_CswME6dS-%sGgbUa8f7I+@V%vcaF+ceqd_T393qyT-(liM=`=|~3; z%x P55Y3`Kk2TxwAD~ zXm-DCaz>OJ*1VOMK+FWk0cgD*;cawG|0^^MssVv%P@gTLZN1UJf<%KVBztT>#|M>* zvekSyr@Pa7!OzQF@)(c)$OnBv!u9MB%~-a+{MJuNksK$$xYG1D-oe(vExWnaU+g5e zP0Z7nPq` k7(+YIX)y{kUYk z@ZY3Ev7s86(PlJjyDps~m @K$?UgtWHsJmDCKsT zN=|1Y9xJHUsEnDm#} hvo!2!JQAIK z?){zM1wCqA_mkS*lWd%^4JbPako4n2O9z0^D69aQcZTksMH>eeiy*}4m3V P2oV+gOXRulH+i-UZrdZ$1f3x#3Xx zI%cJf$EBCsYTKfbqK=*A?rerX=A|)HuOa}Scaz?_AX+LAOi%6ae3zerlEG#JVV~5b zNvMFt=Gph1*P<-3pUn*ldvZ}PL?#VW J1RUYY ^2n%2ld zyi5DV5v6M22^3EadRnV?1Z `{%+Idovhuc&B+JLBHk05oiSFFl=@F;RhXgTl1bMsTfr8W z${OJpyM|g}k55a^%yQRYA$4%I4XP}P?lK+(1X(^eN-Hlv`J&D4(tQUbM320}6eSkY zn^`(*>o>k}+@XjC;1b`mx|AH=BR$h{)W>xnLy1J9>5NHBK2O?{x%_tAbJYUHdRWar z(CJvi{!)$$5-h4idWye$pifKX|E&j3sU?POEDpfD6b_rF@}LNuPJ*CRKR9Mag61eD z3mNu_Uf-^(esjbw*Gw7_sQ@?F@KX>(`=sWnU*iOFK@X8cX!Zztm*olnOinm7$IJaM zA4=ln-w7f7N~Hsaee*is)gYZ(?GO?&I7B+d0^r%_V|{cPOm~RcS2RHdSPj~5_8>b% zEbJ#xzKe4A%J*$US*1SemKvi5$ n&DJ01m3Zn!|WjbK&o%}+H-7uOCf zn0p@w{d*`DRXxXRdsL5K%O@f=R}eV;lkXfZ@Kp6~qz--lEi|5}BzvI)G)_7QMtjvJ z0yJL_cle<`+OkF9!~#@h7aIDWCAZn2KWfYpA{?^r6W Hq9PDi7fVPaIQ0!&BI*St4RKK8A3;Pxw?U|u`Jm*R&2s|n z97_6;1uGcV+b7ZW6^-&Q8@mr0&^egPuKU&Rz-?|KDYOREX{xApRe5m};()ljf4YIY zB^}T&XpU_&4o%- R~cLJmvUk;*&mk4EN|Gh|=+V8EoUOJ)o2P{N3z2<$}q%eJT z0pERW`g)$3d_yFTusp5|P5fXAk?^DrMgm5_gK1NO9DR&}l-cg;ehF*0iyRlhr20!8 zgv!E;R7?7IH*ZIHMS@-c-ApWA69|Z VaUp7~*5{uuV{Hx3Q4^LBE1 zDlu#6L!I#gy74mv66@o#cACET_;_(3DpIo7dU 53W8Ai xK?g*e>1c`1}Q5JxVM1zDql}n=w>7C^q`O6_P}Lp7s%CqyXeNOv3Tn_u?mM z)Fmgy4DX8lO(pLxwoBy~22Xdl#p7>n%8$sZDcVFgNpiQ0@y+G| G>kUhNwHcO&j{9d%kL}VO%D5iTP?`F@c&a;QgQ)j| -jp-eo_+x2zX#F6bVy^z#} z`!?z%=qW{hD&L+(@-}EW!UjuuJNc0@J_Tv4+MBJW+8u?tUwMH msq)6tR{!?zg(h?eLT0tX_&M1LN31fW*b}Zpq zW;{tf ZX;9!B91kFXN(vj*ikdp4?Rd(ymUg`6wN96s_Ik zYC4r0W&H;eIo1cA7m^!%yV)#0YXhOk @gXq$(<_7O2*JN@$8sBab+ z9-e~BhY~8q#@p=!1+glBXhDI QgR~m`>TB}A4#hbum>(`IV+e(7k_rlrr%K^@C~mYpmb2z zc@k6ER8XurzjLs{%&y+e2o0?Q%ad34+6ndW)6R1ybghi?^J!HSW5@ajo^ARoQyx9b zZt=o57LJ&hisKsYudnLcj_Qo|$OC5Qys0r{7*563NqT3^(IR&$mqk(w|dp1ER zi3-(g azv9kzjftR$K))MLOq3ghi$$RBH>8+Ar!}r zc_npxjXHj->wgaL`-iQZ&7Cu;z-Auk_ny!O`qi`%G!D0S+fCHuvFpRTNmzwoxYt zMUU_o}-U|{RQ_pYibKSywZg@eP4fAlN1m#iw9D|2Mdy<%pN@+6gYaV;p|Lvf?` z&C@(db>FR+5#|p0>$ojxVCExumKu3x@4q92S&|yMlt0PTD^VZx1?!|D(x2FU-&$b) z0bXlW^{#y5 1G0SNuR Nk2As;8jkUF&dt *gc>~`6XJ8m`EF<;311*`OU%Oj!%b$GcJszliu zhe;3}gYtdJb0ZB1@5>=4PtG^!t0$L =tyGUvzG1-URAVw(^Wd#C6!WGGD9+9q}>FC#m~wPM~0 z7!YpEbd)AUB!>8<4Ol#u+E7r@mL!e^Wy}QzU5dad5V@C`ByyXq%(J{ASNu#VIp|A- z S>^{OCGDecR&irbl%LmT+Ug2bt@$C`Wt?7&F6+4c!iB6P4!P{p()*-H>FZh5 zxs|i79i+pJcxo8jqheN@iXHQhQ2f~x;hi^1<};UEX*!})V0&cenwtsHe4(whr8YF~ zkwu&^_Z5LqAY&kgt*QX0J`3wNvTR+;gF9&7Lsc9nf{%47xcK{omm(+BHJ(8pOu}s0 z-F9pHsldBYI%ZT;5i3lf4}LUC`+0wq;3nZn@kf693UXO?O%SOn@J0-opzK{A+W27t zUfxBuRMYZocU@JM s9x}+~!e{xRUJ2@yG(a}HgqoH%7|rhz>HAT3Z&{$d zv~QD;mQDU;_M 2*)$^LKu2ckEv<2& z==f$f)Xg9=i11hd8LK=c!81{-)*C^s$!+%C>6zbApO)_Kznrt-vjeg`HnaS^ISW@Q zyr=AQCR8Nq5JQqIh{P0w>}_sGj{2F0@CeYke~+z{7#k8~v+j%*%4MWKyPeNp;+IzZ zM3lNT_O1I{1oS&oW7@m^2<>&&tzr(;(w)>@~4EGXUlp%Oy$WzGrkPP z;AAWyck^VYht;Pa$YU%x66Tg(gS@R2)x!ho`L0NiKLp!7C~>Z61J}{v#e;F(r{L>- zX!LbpP(~C{Ty!$ebA=$y=TJQE2~-1#4s3+&ts*bDDK!c!Q=W`CBr{*xnM%#LBI`Zw zj$1z`6PvlA3p=AT53LEG`6%g#6WItou>R(3W@ln)iAlBQ?YER`_P9tA%0Cdf^PZ&T zF4`9rgIdoBj{0pnt(JNg=_yB&fty0CuT3ERMGarLm^fsY+|eJ}u{xLYU ?>*YaC% zYIj83#%18xz)qE)kesD3 l4FEWInwKYiv~U{OcZx_gC#C$I%bv$W7U zjLS0jj3TY;E^(eR@w-|UrJW|=y}~E&2Zg58WCVNdFI*tkrxuK;=BTUFlb#y}>}DhT zpB+^pp@wi50R $z^SlyI?tC;>S4PY2@vFOjtyjQj= zj>smG)N!hTA>*H4;sS0*$)5p+#r1@Jwj=Zh=g*b&MLlVH8!3-EAs~BGmd{QdsPJ<_ zW{lH6WaO^4#Iy-gOLS_OXK_en=7uPNJG91j^VdsTnFMi3+3ikZslvIt^3-~!(3J;_ zw!RrFuDHjTV LIQ-6Dn$Eqs$(bYJ$fuQ|BUG2+*r$4T4cjV;fTGC zL>ZB*0X}vI0Rp0c)A9e>ZWl6p>M;eX=L*XE#cQJeBqfst6oaJ`p1!kepNpNB^lAm+ zDE?q;6>>_s^qCw~0|e2lbHk#< JdBJLBPIf=K9&X z5 iNP>@6WYM zE>HDQiUTiauXRUy8TH20f31#6waZu2@rfo{InEK6=)aOJEI#)r>Aqp>x0R%7{!-R5 z`Z;Jcw){TDD%jK$IV-lMB7NH2Ye3IvFyBb_iYuujb#V-`&aUD nF}65vUq3Ko=H(4s-G=u%c9J{iPyz^_?rELLMq8DOE2UkGz_(sbB+AsZc!ih> zfK})+GTb>cay#j>lQnx?U%WTDEG({DV%#PU+;`c4o}S6tMW1xmY^DNzjtRo{Wusac zXUePK Z)^GkWyq_T#G&QzzbY zPa7{t#EmBK_3T|OA2;SHy8YjYv{_$}4bFW9SKhJhXmRphJ!ak8hl%6lZJ7L PkeI|&tLx_H#YJVZd;J#kg`G}fHZWeus(!Qx+ z`B^Cs6}E%@?ZOb3;TcUYE;O17X>5$r7clvyW@r%?jp9heeQ}IhGUcgM6}))ofm!o_ zZEJ)xd_}tnG1B7s{J_1Ml{k5R{_6R=Zc;52a}NtDF>Pq3WXvR4akVtiUgYY$Fou@z z`M?W%*Z(~{rHFQ`V9)ds hO_5rUUps20H3q zrSLl&9KE=)(Q6$3j!z!7$+HrXTAXWlJl6iT474wqq{UY3>{l4&+UnHtZf!RoB@M1N zO&7UUrd$F9(c_a3Yj622zN&7`;$iq% F;9?7lcN#)Az@h=Cfxb>*$)%P6mTp!E|lSNoYy&r`*F3)f|9>?)027z!_6$fGir z6E5gp8_iPD=Y;{f-&Fg* %7D$ZHHj%wRdB==qL82BLg!7k(T~`$3>39D3gVa(v2tP>;S;~N61}V z;beHmR;0M`;CiBdB b}e!SFjA92XUk(64ljA{u;MGXO^5 S&RAiRHccnyp3keM5Vml41?k1oqkYJ57z6|R)=I(&$l6w z^p^J;l}PmH%OJj*@T$15`eKlDvObilH|F6G$;zAJJ}2*1XjEeXji;^mP5}Vt43Pc- z=-X79MR;;ZUhz}xAG~4w*B3yEF|^9AT|?1QQSz;m7XXUSE&xv6NFPq _Z9w;51NSlk2;_XdzPXR51J<^SJ1s@JY&Qh=HZS z7I@6;EPx+m#m3LyB>Sn{1{%6sI9p;g6+|fRef5|R; HKVbaTa9L zD4a&XZ 9wvY9F%Mh V>5$F5n1zJT}p;XeRCt~~#VGv`2#rt60Y^;|)@&^QAOHrC#0)br;R zG0jz49xpk;YxLN?hmnCooZzK?soX=Y_`TUz$8U1l3&}8}zY&r^){kwT8!5Dq T1=TlPZU}ZuAN(m ALphp=ES>POvOXj0WST@A zLy8b>TaAE6)OWk`fB&bCYp_tNEz&k}tn~)(E 1>t zIvn{|UL;C@=O)pMxHBhC0B(O^Xgu;X8q#v2WdbAmirEK!BeS~?#MTTi!EWzfp%3J0 zA7o+2f6Y{27FWDPck|bj#!KH)&tP-J GaS< 8rX;+@33W4zgKE~11v@`hVPs&$V+AF*U^Fd zW9l!5lh)c0Rhcb^*}kNK=SgeU`6OkyeYlMX%ehT1rN(4d_a7Ocv&58%*LsIHWqIo# z8?u4=S2ECEz!XnDx&yWU;$@kmSzXzxW{Dk()0;GX>2TXhhQaOgW6+9p*brxayv}SJ zvdpr7>%YH~HhKS7` *QKy^&uZ|o$f37 x`)VvlM$cpS1M| z^WPQm3FJXRXbqV0MqpB+PkDzAps2?BY;3Q7*`Q-YCsJ=(vo2%xxj=e`>J`a{TcMy< z9hn$t*kefF)cY6a9as#PZfbzOe4TP}};~XmqO6t)4xP*vNZG1kS4w}!^ zvAT!v(DtE$47*-*tEZ-wd >zjPxip|&A(V9uMR)^ zt{Hrt2=)Ia*`i(b5VKhQ+sSz@etXna#XJvnl>F4;-0~S!id-lW8LW%7?e}9zeas 0N=uN0cWl~XsGS?&M^npNrnbTZ9m?ncdGrKAEm*34m-PF zcfw3lbx3tK;uY4l-pT7>&OQGL_+4fJ(r=f^?nh!22;CIYtPa(K|D_yDTz{4JPSc)E z%8)LJbpVceKdzBC Lwe>E#o$MzD6X#2G{aEE{9 z5EWD v($0G6z{O#a?Cfbx;jWO zOL2SK8WeoM!SYbo1a=s tkXy|w zs rU#@eM_HHC?PpoU^6ks*n>(xveO>6+)h;DQ8ih$e;aotb%qkb{t^O{i&@|=SP zbw3~8xxLL#Swc`8U?ih0p=xqV2&8fkZM)~e`-jJ%R$M{E_8qf#NY^!4fa5c){XWZR zqQV_h)F#6x!>yPc)|z5JAqDJ5*4wR9AvIuTFg U!)n8CBok $rI1fab@&4{(0b z)wG)w6~H&Cw=9xU?i)N^!CR$4c$^1ysHW2+^~0Y!t*W=~I_uewHKOrrr>Yr|t^G;8 zbNDLH3Vt0=4-AfaHTIfiq^|!|2t;N6`wKR}pXKuZ!6GF*t0y2o#zEM;rumcQqumPl zaEgl7frgoKN+HNJtD(P0_(|cxrK$O^zfeU!OA|Ue 1{Nbv zF_k+M-ihTE@P}=g1g+r)(>ivo35?Ol3;xi*g&LO#lG#(CsXL&_Ho312z&dphV MmOU3#PyN@mpgWyKP;pL^qXj|S8sZ9e{aMTYsltpPz>r_>k?@KwWG7rVi ztd(xxXRS2HlVDu-^>%3Z+B9jR?ZP5?H6!)=)@5C)g79-&WFzg;cZ_I&Ig?@df$~3i zh;47*!V}*W*_u?h-{Hpm@5qLB>8}=DABG6wZegg*#l@i~wQ4bDj7-^s?)Syjgx5~; zlsqS`-VT>3A(0u) BeP>uS30D#gPC+|5GvjD5d_u&%*kG JyO z8rQF6RxjXF9{2H{NsSMI4r{n=P0{Cvw{hCeEV=Non8_FSu}2}H8AFl+xvKwD;Woaf s#q{xNN7{!aq`A4=fmCiVE#KOKg;atFN*fAlE|<0Q|eBU;qFB literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml index 0d93f9e96..892a2fea5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -173,6 +173,10 @@ spark = [ "tableschema", "thrift>=0.14.1, <1", ] +tdengine = [ + "taospy>=2.7.21", + "taos-ws-py>=0.3.8" +] teradata = ["teradatasql>=16.20.0.23"] thumbnails = ["Pillow>=10.0.1, <11"] vertica = ["sqlalchemy-vertica-python>=0.5.9, < 0.6"] diff --git a/superset/db_engine_specs/tdengine.py b/superset/db_engine_specs/tdengine.py new file mode 100644 index 000000000..f2910e422 --- /dev/null +++ b/superset/db_engine_specs/tdengine.py @@ -0,0 +1,57 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from __future__ import annotations + +from typing import Any +from urllib import parse + +from sqlalchemy.engine.url import make_url, URL # noqa: F401 + +from superset.db_engine_specs.base import BaseEngineSpec + + +class TDengineEngineSpec(BaseEngineSpec): + engine = "taosws" + engine_name = "TDengine" + max_column_name_length = 64 + default_driver = "taosws" + sqlalchemy_uri_placeholder = ( + "taosws://user:******@host:port/dbname[?key=value&key=value...]" + ) + + # time grain + _time_grain_expressions = { + None: "{col}", + "PT1S": "TIMETRUNCATE({col}, 1s, 0)", + "PT1M": "TIMETRUNCATE({col}, 1m, 0)", + "PT1H": "TIMETRUNCATE({col}, 1h, 0)", + "P1D": "TIMETRUNCATE({col}, 1d, 0)", + "P1W": "TIMETRUNCATE({col}, 1w, 0)", + } + + @classmethod + def get_schema_from_engine_params( + cls, + sqlalchemy_uri: URL, + connect_args: dict[str, Any], + ) -> str | None: + """ + Return the configured schema. + + A TDengine database is a SQLAlchemy schema. + """ + return parse.unquote(sqlalchemy_uri.database) diff --git a/tests/unit_tests/db_engine_specs/test_tdengine.py b/tests/unit_tests/db_engine_specs/test_tdengine.py new file mode 100644 index 000000000..f33cef5d6 --- /dev/null +++ b/tests/unit_tests/db_engine_specs/test_tdengine.py @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + + +from sqlalchemy.engine.url import make_url, URL # noqa: F401 + + +# test get schema +def test_get_schema_from_engine_params() -> None: + """ + Test the ``get_schema_from_engine_params`` method. + """ + from superset.db_engine_specs.tdengine import TDengineEngineSpec + + assert ( + TDengineEngineSpec.get_schema_from_engine_params( + make_url("taosws://root:taosdata@127.0.0.1:6041/dbname"), {} + ) + == "dbname" + )