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/statrep/venv/lib/python3.13/site-packages/pip/_vendor/rich
Viewing File: /opt/statrep/venv/lib/python3.13/site-packages/pip/_vendor/rich/table.py
from dataclasses import dataclass, field, replace from typing import ( TYPE_CHECKING, Dict, Iterable, List, NamedTuple, Optional, Sequence, Tuple, Union, ) from . import box, errors from ._loop import loop_first_last, loop_last from ._pick import pick_bool from ._ratio import ratio_distribute, ratio_reduce from .align import VerticalAlignMethod from .jupyter import JupyterMixin from .measure import Measurement from .padding import Padding, PaddingDimensions from .protocol import is_renderable from .segment import Segment from .style import Style, StyleType from .text import Text, TextType if TYPE_CHECKING: from .console import ( Console, ConsoleOptions, JustifyMethod, OverflowMethod, RenderableType, RenderResult, ) @dataclass class Column: """Defines a column within a ~Table. Args: title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None. caption (Union[str, Text], optional): The table caption rendered below. Defaults to None. width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None. min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None. box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD. safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1). collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False. pad_edge (bool, optional): Enable padding of edge cells. Defaults to True. expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. show_header (bool, optional): Show a header row. Defaults to True. show_footer (bool, optional): Show a footer row. Defaults to False. show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True. show_lines (bool, optional): Draw lines between every row. Defaults to False. leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0. style (Union[str, Style], optional): Default style for the table. Defaults to "none". row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None. header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header". footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer". border_style (Union[str, Style], optional): Style of the border. Defaults to None. title_style (Union[str, Style], optional): Style of the title. Defaults to None. caption_style (Union[str, Style], optional): Style of the caption. Defaults to None. title_justify (str, optional): Justify method for title. Defaults to "center". caption_justify (str, optional): Justify method for caption. Defaults to "center". highlight (bool, optional): Highlight cell contents (if str). Defaults to False. """ header: "RenderableType" = "" """RenderableType: Renderable for the header (typically a string)""" footer: "RenderableType" = "" """RenderableType: Renderable for the footer (typically a string)""" header_style: StyleType = "" """StyleType: The style of the header.""" footer_style: StyleType = "" """StyleType: The style of the footer.""" style: StyleType = "" """StyleType: The style of the column.""" justify: "JustifyMethod" = "left" """str: How to justify text within the column ("left", "center", "right", or "full")""" vertical: "VerticalAlignMethod" = "top" """str: How to vertically align content ("top", "middle", or "bottom")""" overflow: "OverflowMethod" = "ellipsis" """str: Overflow method.""" width: Optional[int] = None """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width.""" min_width: Optional[int] = None """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None.""" max_width: Optional[int] = None """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None.""" ratio: Optional[int] = None """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents.""" no_wrap: bool = False """bool: Prevent wrapping of text within the column. Defaults to ``False``.""" highlight: bool = False """bool: Apply highlighter to column. Defaults to ``False``.""" _index: int = 0 """Index of column.""" _cells: List["RenderableType"] = field(default_factory=list) def copy(self) -> "Column": """Return a copy of this Column.""" return replace(self, _cells=[]) @property def cells(self) -> Iterable["RenderableType"]: """Get all cells in the column, not including header.""" yield from self._cells @property def flexible(self) -> bool: """Check if this column is flexible.""" return self.ratio is not None @dataclass class Row: """Information regarding a row.""" style: Optional[StyleType] = None """Style to apply to row.""" end_section: bool = False """Indicated end of section, which will force a line beneath the row.""" class _Cell(NamedTuple): """A single cell in a table.""" style: StyleType """Style to apply to cell.""" renderable: "RenderableType" """Cell renderable.""" vertical: VerticalAlignMethod """Cell vertical alignment.""" class Table(JupyterMixin): """A console renderable to draw a table. Args: *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance. title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None. caption (Union[str, Text], optional): The table caption rendered below. Defaults to None. width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None. min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None. box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD. safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1). collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False. pad_edge (bool, optional): Enable padding of edge cells. Defaults to True. expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. show_header (bool, optional): Show a header row. Defaults to True. show_footer (bool, optional): Show a footer row. Defaults to False. show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True. show_lines (bool, optional): Draw lines between every row. Defaults to False. leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0. style (Union[str, Style], optional): Default style for the table. Defaults to "none". row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None. header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header". footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer". border_style (Union[str, Style], optional): Style of the border. Defaults to None. title_style (Union[str, Style], optional): Style of the title. Defaults to None. caption_style (Union[str, Style], optional): Style of the caption. Defaults to None. title_justify (str, optional): Justify method for title. Defaults to "center". caption_justify (str, optional): Justify method for caption. Defaults to "center". highlight (bool, optional): Highlight cell contents (if str). Defaults to False. """ columns: List[Column] rows: List[Row] def __init__( self, *headers: Union[Column, str], title: Optional[TextType] = None, caption: Optional[TextType] = None, width: Optional[int] = None, min_width: Optional[int] = None, box: Optional[box.Box] = box.HEAVY_HEAD, safe_box: Optional[bool] = None, padding: PaddingDimensions = (0, 1), collapse_padding: bool = False, pad_edge: bool = True, expand: bool = False, show_header: bool = True, show_footer: bool = False, show_edge: bool = True, show_lines: bool = False, leading: int = 0, style: StyleType = "none", row_styles: Optional[Iterable[StyleType]] = None, header_style: Optional[StyleType] = "table.header", footer_style: Optional[StyleType] = "table.footer", border_style: Optional[StyleType] = None, title_style: Optional[StyleType] = None, caption_style: Optional[StyleType] = None, title_justify: "JustifyMethod" = "center", caption_justify: "JustifyMethod" = "center", highlight: bool = False, ) -> None: self.columns: List[Column] = [] self.rows: List[Row] = [] self.title = title self.caption = caption self.width = width self.min_width = min_width self.box = box self.safe_box = safe_box self._padding = Padding.unpack(padding) self.pad_edge = pad_edge self._expand = expand self.show_header = show_header self.show_footer = show_footer self.show_edge = show_edge self.show_lines = show_lines self.leading = leading self.collapse_padding = collapse_padding self.style = style self.header_style = header_style or "" self.footer_style = footer_style or "" self.border_style = border_style self.title_style = title_style self.caption_style = caption_style self.title_justify: "JustifyMethod" = title_justify self.caption_justify: "JustifyMethod" = caption_justify self.highlight = highlight self.row_styles: Sequence[StyleType] = list(row_styles or []) append_column = self.columns.append for header in headers: if isinstance(header, str): self.add_column(header=header) else: header._index = len(self.columns) append_column(header) @classmethod def grid( cls, *headers: Union[Column, str], padding: PaddingDimensions = 0, collapse_padding: bool = True, pad_edge: bool = False, expand: bool = False, ) -> "Table": """Get a table with no lines, headers, or footer. Args: *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance. padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0. collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True. pad_edge (bool, optional): Enable padding around edges of table. Defaults to False. expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. Returns: Table: A table instance. """ return cls( *headers, box=None, padding=padding, collapse_padding=collapse_padding, show_header=False, show_footer=False, show_edge=False, pad_edge=pad_edge, expand=expand, ) @property def expand(self) -> bool: """Setting a non-None self.width implies expand.""" return self._expand or self.width is not None @expand.setter def expand(self, expand: bool) -> None: """Set expand.""" self._expand = expand @property def _extra_width(self) -> int: """Get extra width to add to cell content.""" width = 0 if self.box and self.show_edge: width += 2 if self.box: width += len(self.columns) - 1 return width @property def row_count(self) -> int: """Get the current number of rows.""" return len(self.rows) def get_row_style(self, console: "Console", index: int) -> StyleType: """Get the current row style.""" style = Style.null() if self.row_styles: style += console.get_style(self.row_styles[index % len(self.row_styles)]) row_style = self.rows[index].style if row_style is not None: style += console.get_style(row_style) return style def __rich_measure__( self, console: "Console", options: "ConsoleOptions" ) -> Measurement: max_width = options.max_width if self.width is not None: max_width = self.width if max_width < 0: return Measurement(0, 0) extra_width = self._extra_width max_width = sum( self._calculate_column_widths( console, options.update_width(max_width - extra_width) ) ) _measure_column = self._measure_column measurements = [ _measure_column(console, options.update_width(max_width), column) for column in self.columns ] minimum_width = ( sum(measurement.minimum for measurement in measurements) + extra_width ) maximum_width = ( sum(measurement.maximum for measurement in measurements) + extra_width if (self.width is None) else self.width ) measurement = Measurement(minimum_width, maximum_width) measurement = measurement.clamp(self.min_width) return measurement @property def padding(self) -> Tuple[int, int, int, int]: """Get cell padding.""" return self._padding @padding.setter def padding(self, padding: PaddingDimensions) -> "Table": """Set cell padding.""" self._padding = Padding.unpack(padding) return self def add_column( self, header: "RenderableType" = "", footer: "RenderableType" = "", *, header_style: Optional[StyleType] = None, highlight: Optional[bool] = None, footer_style: Optional[StyleType] = None, style: Optional[StyleType] = None, justify: "JustifyMethod" = "left", vertical: "VerticalAlignMethod" = "top", overflow: "OverflowMethod" = "ellipsis", width: Optional[int] = None, min_width: Optional[int] = None, max_width: Optional[int] = None, ratio: Optional[int] = None, no_wrap: bool = False, ) -> None: """Add a column to the table. Args: header (RenderableType, optional): Text or renderable for the header. Defaults to "". footer (RenderableType, optional): Text or renderable for the footer. Defaults to "". header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None. highlight (bool, optional): Whether to highlight the text. The default of None uses the value of the table (self) object. footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None. style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None. justify (JustifyMethod, optional): Alignment for cells. Defaults to "left". vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top". overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis". width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None. min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None. max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None. ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None. no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column. """ column = Column( _index=len(self.columns), header=header, footer=footer, header_style=header_style or "", highlight=highlight if highlight is not None else self.highlight, footer_style=footer_style or "", style=style or "", justify=justify, vertical=vertical, overflow=overflow, width=width, min_width=min_width, max_width=max_width, ratio=ratio, no_wrap=no_wrap, ) self.columns.append(column) def add_row( self, *renderables: Optional["RenderableType"], style: Optional[StyleType] = None, end_section: bool = False, ) -> None: """Add a row of renderables. Args: *renderables (None or renderable): Each cell in a row must be a renderable object (including str), or ``None`` for a blank cell. style (StyleType, optional): An optional style to apply to the entire row. Defaults to None. end_section (bool, optional): End a section and draw a line. Defaults to False. Raises: errors.NotRenderableError: If you add something that can't be rendered. """ def add_cell(column: Column, renderable: "RenderableType") -> None: column._cells.append(renderable) cell_renderables: List[Optional["RenderableType"]] = list(renderables) columns = self.columns if len(cell_renderables) < len(columns): cell_renderables = [ *cell_renderables, *[None] * (len(columns) - len(cell_renderables)), ] for index, renderable in enumerate(cell_renderables): if index == len(columns): column = Column(_index=index, highlight=self.highlight) for _ in self.rows: add_cell(column, Text("")) self.columns.append(column) else: column = columns[index] if renderable is None: add_cell(column, "") elif is_renderable(renderable): add_cell(column, renderable) else: raise errors.NotRenderableError( f"unable to render {type(renderable).__name__}; a string or other renderable object is required" ) self.rows.append(Row(style=style, end_section=end_section)) def add_section(self) -> None: """Add a new section (draw a line after current row).""" if self.rows: self.rows[-1].end_section = True def __rich_console__( self, console: "Console", options: "ConsoleOptions" ) -> "RenderResult": if not self.columns: yield Segment("\n") return max_width = options.max_width if self.width is not None: max_width = self.width extra_width = self._extra_width widths = self._calculate_column_widths( console, options.update_width(max_width - extra_width) ) table_width = sum(widths) + extra_width render_options = options.update( width=table_width, highlight=self.highlight, height=None ) def render_annotation( text: TextType, style: StyleType, justify: "JustifyMethod" = "center" ) -> "RenderResult": render_text = ( console.render_str(text, style=style, highlight=False) if isinstance(text, str) else text ) return console.render( render_text, options=render_options.update(justify=justify) ) if self.title: yield from render_annotation( self.title, style=Style.pick_first(self.title_style, "table.title"), justify=self.title_justify, ) yield from self._render(console, render_options, widths) if self.caption: yield from render_annotation( self.caption, style=Style.pick_first(self.caption_style, "table.caption"), justify=self.caption_justify, ) def _calculate_column_widths( self, console: "Console", options: "ConsoleOptions" ) -> List[int]: """Calculate the widths of each column, including padding, not including borders.""" max_width = options.max_width columns = self.columns width_ranges = [ self._measure_column(console, options, column) for column in columns ] widths = [_range.maximum or 1 for _range in width_ranges] get_padding_width = self._get_padding_width extra_width = self._extra_width if self.expand: ratios = [col.ratio or 0 for col in columns if col.flexible] if any(ratios): fixed_widths = [ 0 if column.flexible else _range.maximum for _range, column in zip(width_ranges, columns) ] flex_minimum = [ (column.width or 1) + get_padding_width(column._index) for column in columns if column.flexible ] flexible_width = max_width - sum(fixed_widths) flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum) iter_flex_widths = iter(flex_widths) for index, column in enumerate(columns): if column.flexible: widths[index] = fixed_widths[index] + next(iter_flex_widths) table_width = sum(widths) if table_width > max_width: widths = self._collapse_widths( widths, [(column.width is None and not column.no_wrap) for column in columns], max_width, ) table_width = sum(widths) # last resort, reduce columns evenly if table_width > max_width: excess_width = table_width - max_width widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths) table_width = sum(widths) width_ranges = [ self._measure_column(console, options.update_width(width), column) for width, column in zip(widths, columns) ] widths = [_range.maximum or 0 for _range in width_ranges] if (table_width < max_width and self.expand) or ( self.min_width is not None and table_width < (self.min_width - extra_width) ): _max_width = ( max_width if self.min_width is None else min(self.min_width - extra_width, max_width) ) pad_widths = ratio_distribute(_max_width - table_width, widths) widths = [_width + pad for _width, pad in zip(widths, pad_widths)] return widths @classmethod def _collapse_widths( cls, widths: List[int], wrapable: List[bool], max_width: int ) -> List[int]: """Reduce widths so that the total is under max_width. Args: widths (List[int]): List of widths. wrapable (List[bool]): List of booleans that indicate if a column may shrink. max_width (int): Maximum width to reduce to. Returns: List[int]: A new list of widths. """ total_width = sum(widths) excess_width = total_width - max_width if any(wrapable): while total_width and excess_width > 0: max_column = max( width for width, allow_wrap in zip(widths, wrapable) if allow_wrap ) second_max_column = max( width if allow_wrap and width != max_column else 0 for width, allow_wrap in zip(widths, wrapable) ) column_difference = max_column - second_max_column ratios = [ (1 if (width == max_column and allow_wrap) else 0) for width, allow_wrap in zip(widths, wrapable) ] if not any(ratios) or not column_difference: break max_reduce = [min(excess_width, column_difference)] * len(widths) widths = ratio_reduce(excess_width, ratios, max_reduce, widths) total_width = sum(widths) excess_width = total_width - max_width return widths def _get_cells( self, console: "Console", column_index: int, column: Column ) -> Iterable[_Cell]: """Get all the cells with padding and optional header.""" collapse_padding = self.collapse_padding pad_edge = self.pad_edge padding = self.padding any_padding = any(padding) first_column = column_index == 0 last_column = column_index == len(self.columns) - 1 _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {} def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]: cached = _padding_cache.get((first_row, last_row)) if cached: return cached top, right, bottom, left = padding if collapse_padding: if not first_column: left = max(0, left - right) if not last_row: bottom = max(0, top - bottom) if not pad_edge: if first_column: left = 0 if last_column: right = 0 if first_row: top = 0 if last_row: bottom = 0 _padding = (top, right, bottom, left) _padding_cache[(first_row, last_row)] = _padding return _padding raw_cells: List[Tuple[StyleType, "RenderableType"]] = [] _append = raw_cells.append get_style = console.get_style if self.show_header: header_style = get_style(self.header_style or "") + get_style( column.header_style ) _append((header_style, column.header)) cell_style = get_style(column.style or "") for cell in column.cells: _append((cell_style, cell)) if self.show_footer: footer_style = get_style(self.footer_style or "") + get_style( column.footer_style ) _append((footer_style, column.footer)) if any_padding: _Padding = Padding for first, last, (style, renderable) in loop_first_last(raw_cells): yield _Cell( style, _Padding(renderable, get_padding(first, last)), getattr(renderable, "vertical", None) or column.vertical, ) else: for style, renderable in raw_cells: yield _Cell( style, renderable, getattr(renderable, "vertical", None) or column.vertical, ) def _get_padding_width(self, column_index: int) -> int: """Get extra width from padding.""" _, pad_right, _, pad_left = self.padding if self.collapse_padding: if column_index > 0: pad_left = max(0, pad_left - pad_right) return pad_left + pad_right def _measure_column( self, console: "Console", options: "ConsoleOptions", column: Column, ) -> Measurement: """Get the minimum and maximum width of the column.""" max_width = options.max_width if max_width < 1: return Measurement(0, 0) padding_width = self._get_padding_width(column._index) if column.width is not None: # Fixed width column return Measurement( column.width + padding_width, column.width + padding_width ).with_maximum(max_width) # Flexible column, we need to measure contents min_widths: List[int] = [] max_widths: List[int] = [] append_min = min_widths.append append_max = max_widths.append get_render_width = Measurement.get for cell in self._get_cells(console, column._index, column): _min, _max = get_render_width(console, options, cell.renderable) append_min(_min) append_max(_max) measurement = Measurement( max(min_widths) if min_widths else 1, max(max_widths) if max_widths else max_width, ).with_maximum(max_width) measurement = measurement.clamp( None if column.min_width is None else column.min_width + padding_width, None if column.max_width is None else column.max_width + padding_width, ) return measurement def _render( self, console: "Console", options: "ConsoleOptions", widths: List[int] ) -> "RenderResult": table_style = console.get_style(self.style or "") border_style = table_style + console.get_style(self.border_style or "") _column_cells = ( self._get_cells(console, column_index, column) for column_index, column in enumerate(self.columns) ) row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells)) _box = ( self.box.substitute( options, safe=pick_bool(self.safe_box, console.safe_box) ) if self.box else None ) _box = _box.get_plain_headed_box() if _box and not self.show_header else _box new_line = Segment.line() columns = self.columns show_header = self.show_header show_footer = self.show_footer show_edge = self.show_edge show_lines = self.show_lines leading = self.leading _Segment = Segment if _box: box_segments = [ ( _Segment(_box.head_left, border_style), _Segment(_box.head_right, border_style), _Segment(_box.head_vertical, border_style), ), ( _Segment(_box.mid_left, border_style), _Segment(_box.mid_right, border_style), _Segment(_box.mid_vertical, border_style), ), ( _Segment(_box.foot_left, border_style), _Segment(_box.foot_right, border_style), _Segment(_box.foot_vertical, border_style), ), ] if show_edge: yield _Segment(_box.get_top(widths), border_style) yield new_line else: box_segments = [] get_row_style = self.get_row_style get_style = console.get_style for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)): header_row = first and show_header footer_row = last and show_footer row = ( self.rows[index - show_header] if (not header_row and not footer_row) else None ) max_height = 1 cells: List[List[List[Segment]]] = [] if header_row or footer_row: row_style = Style.null() else: row_style = get_style( get_row_style(console, index - 1 if show_header else index) ) for width, cell, column in zip(widths, row_cell, columns): render_options = options.update( width=width, justify=column.justify, no_wrap=column.no_wrap, overflow=column.overflow, height=None, highlight=column.highlight, ) lines = console.render_lines( cell.renderable, render_options, style=get_style(cell.style) + row_style, ) max_height = max(max_height, len(lines)) cells.append(lines) row_height = max(len(cell) for cell in cells) def align_cell( cell: List[List[Segment]], vertical: "VerticalAlignMethod", width: int, style: Style, ) -> List[List[Segment]]: if header_row: vertical = "bottom" elif footer_row: vertical = "top" if vertical == "top": return _Segment.align_top(cell, width, row_height, style) elif vertical == "middle": return _Segment.align_middle(cell, width, row_height, style) return _Segment.align_bottom(cell, width, row_height, style) cells[:] = [ _Segment.set_shape( align_cell( cell, _cell.vertical, width, get_style(_cell.style) + row_style, ), width, max_height, ) for width, _cell, cell, column in zip(widths, row_cell, cells, columns) ] if _box: if last and show_footer: yield _Segment( _box.get_row(widths, "foot", edge=show_edge), border_style ) yield new_line left, right, _divider = box_segments[0 if first else (2 if last else 1)] # If the column divider is whitespace also style it with the row background divider = ( _divider if _divider.text.strip() else _Segment( _divider.text, row_style.background_style + _divider.style ) ) for line_no in range(max_height): if show_edge: yield left for last_cell, rendered_cell in loop_last(cells): yield from rendered_cell[line_no] if not last_cell: yield divider if show_edge: yield right yield new_line else: for line_no in range(max_height): for rendered_cell in cells: yield from rendered_cell[line_no] yield new_line if _box and first and show_header: yield _Segment( _box.get_row(widths, "head", edge=show_edge), border_style ) yield new_line end_section = row and row.end_section if _box and (show_lines or leading or end_section): if ( not last and not (show_footer and index >= len(row_cells) - 2) and not (show_header and header_row) ): if leading: yield _Segment( _box.get_row(widths, "mid", edge=show_edge) * leading, border_style, ) else: yield _Segment( _box.get_row(widths, "row", edge=show_edge), border_style ) yield new_line if _box and show_edge: yield _Segment(_box.get_bottom(widths), border_style) yield new_line if __name__ == "__main__": # pragma: no cover from pip._vendor.rich.console import Console from pip._vendor.rich.highlighter import ReprHighlighter from ._timer import timer with timer("Table render"): table = Table( title="Star Wars Movies", caption="Rich example table", caption_justify="right", ) table.add_column( "Released", header_style="bright_cyan", style="cyan", no_wrap=True ) table.add_column("Title", style="magenta") table.add_column("Box Office", justify="right", style="green") table.add_row( "Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690", ) table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") table.add_row( "Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889", style="on black", end_section=True, ) table.add_row( "Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889", ) def header(text: str) -> None: console.print() console.rule(highlight(text)) console.print() console = Console() highlight = ReprHighlighter() header("Example Table") console.print(table, justify="center") table.expand = True header("expand=True") console.print(table) table.width = 50 header("width=50") console.print(table, justify="center") table.width = None table.expand = False table.row_styles = ["dim", "none"] header("row_styles=['dim', 'none']") console.print(table, justify="center") table.width = None table.expand = False table.row_styles = ["dim", "none"] table.leading = 1 header("leading=1, row_styles=['dim', 'none']") console.print(table, justify="center") table.width = None table.expand = False table.row_styles = ["dim", "none"] table.show_lines = True table.leading = 0 header("show_lines=True, row_styles=['dim', 'none']") console.print(table, justify="center")