From 6b1381d88fa3db188502dc89dfe07189697b1691 Mon Sep 17 00:00:00 2001 From: "manh6264@gmail.com" Date: Wed, 15 Apr 2020 14:49:19 +0700 Subject: [PATCH 1/4] Add tqdm module --- tqdm/__init__.py | 40 + tqdm/__init__.pyc | Bin 0 -> 1995 bytes tqdm/__main__.py | 2 + tqdm/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1613 bytes tqdm/__pycache__/_monitor.cpython-37.pyc | Bin 0 -> 2745 bytes tqdm/__pycache__/_tqdm_pandas.cpython-37.pyc | Bin 0 -> 1671 bytes tqdm/__pycache__/_version.cpython-37.pyc | Bin 0 -> 1163 bytes tqdm/__pycache__/cli.cpython-37.pyc | Bin 0 -> 5681 bytes tqdm/__pycache__/gui.cpython-37.pyc | Bin 0 -> 5904 bytes tqdm/__pycache__/std.cpython-37.pyc | Bin 0 -> 40765 bytes tqdm/__pycache__/utils.cpython-37.pyc | Bin 0 -> 11566 bytes tqdm/_main.py | 7 + tqdm/_monitor.py | 99 + tqdm/_monitor.pyc | Bin 0 -> 3366 bytes tqdm/_tqdm.py | 7 + tqdm/_tqdm_gui.py | 7 + tqdm/_tqdm_notebook.py | 7 + tqdm/_tqdm_pandas.py | 46 + tqdm/_tqdm_pandas.pyc | Bin 0 -> 1903 bytes tqdm/_utils.py | 6 + tqdm/_version.py | 59 + tqdm/_version.pyc | Bin 0 -> 1471 bytes tqdm/auto.py | 6 + tqdm/autonotebook.py | 18 + tqdm/cli.py | 228 +++ tqdm/cli.pyc | Bin 0 -> 7243 bytes tqdm/contrib/__init__.py | 80 + tqdm/contrib/concurrent.py | 65 + tqdm/contrib/itertools.py | 34 + tqdm/gui.py | 321 +++ tqdm/gui.pyc | Bin 0 -> 7373 bytes tqdm/keras.py | 93 + tqdm/notebook.py | 277 +++ tqdm/std.py | 1470 ++++++++++++++ tqdm/std.pyc | Bin 0 -> 47485 bytes tqdm/tests/tests_concurrent.py | 36 + tqdm/tests/tests_contrib.py | 61 + tqdm/tests/tests_itertools.py | 27 + tqdm/tests/tests_keras.py | 97 + tqdm/tests/tests_main.py | 144 ++ tqdm/tests/tests_notebook.py | 9 + tqdm/tests/tests_pandas.py | 264 +++ tqdm/tests/tests_perf.py | 367 ++++ tqdm/tests/tests_synchronisation.py | 213 ++ tqdm/tests/tests_tqdm.py | 1892 ++++++++++++++++++ tqdm/tests/tests_version.py | 12 + tqdm/tqdm.1 | 257 +++ tqdm/utils.py | 369 ++++ tqdm/utils.pyc | Bin 0 -> 15087 bytes 49 files changed, 6620 insertions(+) create mode 100644 tqdm/__init__.py create mode 100644 tqdm/__init__.pyc create mode 100644 tqdm/__main__.py create mode 100644 tqdm/__pycache__/__init__.cpython-37.pyc create mode 100644 tqdm/__pycache__/_monitor.cpython-37.pyc create mode 100644 tqdm/__pycache__/_tqdm_pandas.cpython-37.pyc create mode 100644 tqdm/__pycache__/_version.cpython-37.pyc create mode 100644 tqdm/__pycache__/cli.cpython-37.pyc create mode 100644 tqdm/__pycache__/gui.cpython-37.pyc create mode 100644 tqdm/__pycache__/std.cpython-37.pyc create mode 100644 tqdm/__pycache__/utils.cpython-37.pyc create mode 100644 tqdm/_main.py create mode 100644 tqdm/_monitor.py create mode 100644 tqdm/_monitor.pyc create mode 100644 tqdm/_tqdm.py create mode 100644 tqdm/_tqdm_gui.py create mode 100644 tqdm/_tqdm_notebook.py create mode 100644 tqdm/_tqdm_pandas.py create mode 100644 tqdm/_tqdm_pandas.pyc create mode 100644 tqdm/_utils.py create mode 100644 tqdm/_version.py create mode 100644 tqdm/_version.pyc create mode 100644 tqdm/auto.py create mode 100644 tqdm/autonotebook.py create mode 100644 tqdm/cli.py create mode 100644 tqdm/cli.pyc create mode 100644 tqdm/contrib/__init__.py create mode 100644 tqdm/contrib/concurrent.py create mode 100644 tqdm/contrib/itertools.py create mode 100644 tqdm/gui.py create mode 100644 tqdm/gui.pyc create mode 100644 tqdm/keras.py create mode 100644 tqdm/notebook.py create mode 100644 tqdm/std.py create mode 100644 tqdm/std.pyc create mode 100644 tqdm/tests/tests_concurrent.py create mode 100644 tqdm/tests/tests_contrib.py create mode 100644 tqdm/tests/tests_itertools.py create mode 100644 tqdm/tests/tests_keras.py create mode 100644 tqdm/tests/tests_main.py create mode 100644 tqdm/tests/tests_notebook.py create mode 100644 tqdm/tests/tests_pandas.py create mode 100644 tqdm/tests/tests_perf.py create mode 100644 tqdm/tests/tests_synchronisation.py create mode 100644 tqdm/tests/tests_tqdm.py create mode 100644 tqdm/tests/tests_version.py create mode 100644 tqdm/tqdm.1 create mode 100644 tqdm/utils.py create mode 100644 tqdm/utils.pyc diff --git a/tqdm/__init__.py b/tqdm/__init__.py new file mode 100644 index 0000000..670d645 --- /dev/null +++ b/tqdm/__init__.py @@ -0,0 +1,40 @@ +from .std import tqdm, trange +from .gui import tqdm as tqdm_gui # TODO: remove in v5.0.0 +from .gui import trange as tgrange # TODO: remove in v5.0.0 +from ._tqdm_pandas import tqdm_pandas +from .cli import main # TODO: remove in v5.0.0 +from ._monitor import TMonitor, TqdmSynchronisationWarning +from ._version import __version__ # NOQA +from .std import TqdmTypeError, TqdmKeyError, TqdmWarning, \ + TqdmDeprecationWarning, TqdmExperimentalWarning, \ + TqdmMonitorWarning + +__all__ = ['tqdm', 'tqdm_gui', 'trange', 'tgrange', 'tqdm_pandas', + 'tqdm_notebook', 'tnrange', 'main', 'TMonitor', + 'TqdmTypeError', 'TqdmKeyError', + 'TqdmWarning', 'TqdmDeprecationWarning', + 'TqdmExperimentalWarning', + 'TqdmMonitorWarning', 'TqdmSynchronisationWarning', + '__version__'] + + +def tqdm_notebook(*args, **kwargs): # pragma: no cover + """See tqdm.notebook.tqdm for full documentation""" + from .notebook import tqdm as _tqdm_notebook + from warnings import warn + warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.notebook.tqdm` instead of `tqdm.tqdm_notebook`", + TqdmDeprecationWarning, stacklevel=2) + return _tqdm_notebook(*args, **kwargs) + + +def tnrange(*args, **kwargs): # pragma: no cover + """ + A shortcut for `tqdm.notebook.tqdm(xrange(*args), **kwargs)`. + On Python3+, `range` is used instead of `xrange`. + """ + from .notebook import trange as _tnrange + from warnings import warn + warn("Please use `tqdm.notebook.trange` instead of `tqdm.tnrange`", + TqdmDeprecationWarning, stacklevel=2) + return _tnrange(*args, **kwargs) diff --git a/tqdm/__init__.pyc b/tqdm/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0433944822e75eb8c60f1ebec1cc560012457634 GIT binary patch literal 1995 zcmcIkOK;mo5FUz_WXX~xKjNfmi}J|031uMZsZE;z$*D~ftX&k~p%lYi%Y?b4%3V1T zV4vD^e^vicPd&9Wvs7dwMNW;R9qr7{%+BN6@&6k3|NP;_FBvqS9sGZS%lwT*;?IKu zKzk5-pj!~Pz$0l33ZJDLj`x|z^S0wRpy+_^LfmzH8;VWPJ&1db??BN9y#?_W=mEq7 zC+$Kp1icOMw&OSX?h(Wz&|`?lpm!kN0lf?HF6cdo_dxGMypP&FkbRI_AO|3aAh$t| zK#oD~fZPSS2XY_eEszHwZ-cx8@)MY$FO=Mc-vMHjf65y?{S1!7&oTSlLlU--*bO4T zN^Lu|snc?%LOK<=!0lL>h}|NB%E>$}Wor1CJ?Jzqoxe>~IeAf)xvgq@ml)JPUY6Ne zjY5;!yefZA>oPBA&YXNE$%U#7o+L@gaSR*7ljU4JuWNKQXe8gOr4x`H`EH!qgNA>m z=C#VM-(zn#N6+8RRh?^9+O$}i?KWpk$c>n@x0DN~J6q&}3(zIWjH@KLd=x0Fl$BMl ztLogcO6gR5JQY-7K~h2fM2|?H2xTBBFVQAKi8ENqfFv(*nI}jPsRT;~>lMM&2Z=Cj zf%f6b2k#R3HnmcAKz8A`mgilz;1b}{gFgU!P8XX8NRE{XDBNfjZbV#gTGhenq9_7c zWectkwaKC69T@U|@Uw3D8*HM%nBe3rH)vgEWD~r}(amcW)Jj(uN(On!7e0RcRdf^` z^%6xEGYsSK*q=KMx+rOvJc^&q5Laj#ZMt-D7-BGQbY z$In#{8Sq5XdS)2)=ct9-ZImT9u4AhZ#lXaThr5X`?IfH0WDu~Er;}&OoZD(XA-e~U zqDK#-qxbfcB+2nTBuO-1iV^y_h09PT&>DMVuhSZOAG7gScu_yV&(px1;Zw>MmOK6* z4G7=532;aixpzM}JaommH;vfC%QASiv}aZM@XPzbly$LZhWbgZUiZ%VX{@dRiO{qL z5*OHfjj|8mVs*>c4<6hY{gOLWy*#OHg)&2eNN@4|tc>EboiDY|w_~Ya)uiH8PO8&MePC%Jg${HvY!0xCuBv=ZniKK<3jiiI5fg}Jj zfoTVZMekQI^*#_rpdO$;F0eK{<_lMP7anoWJyt7yR%eY5D7oj(Ir)PCGQrus1I{DN zY`Ds7h*ES^sOV@eM8t~8++H7FTUj70;4a}nK+pK^0D?1dc${hQ$R}tQz01J$EsqqJ z#VKb|mfJ%fJ^E(2Kim&r3!Z8o&0&r|KYR@4y5uQ~ile3e+S`pUtHLp%F4M`0;HO-~ zH506Euml71g3{1x0}{2b8T-ah)l{4M2`HzUc9Ls>t>vGNx(N>dKK$|9(HjjFHF`37 z%JoTE%tmN^@4@iF{o(#+{3uB>sM91F&K3&Wb`z$yo$aB^`vKPh*-wXn6;ZjrEu-H~oJIGzZQQg3eT_7|_ zR-I(^h@Ayc-9l~vR$>@9ykwn@L-%%+hfsSrtG3&kc`<$OJR6MQENHW&PR>^mI+=|Lq` SA99f&l7RT|xiITq5Pk%u1(Z<$ literal 0 HcmV?d00001 diff --git a/tqdm/__pycache__/_monitor.cpython-37.pyc b/tqdm/__pycache__/_monitor.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b604a5d7bb4580406ef88d5153421273ee44b1ec GIT binary patch literal 2745 zcmZuz&2t+?7Vr7cXf&3ckOg-6sI5LwRJ2887IL6M$`v5M1+)wCRw$KW7*DsYkw!Ce zcPqA4%E`G>6#s#Jgv0&?d*rX^E2sPmoc8y6ECq+mRKI@x_4>Wv`W#!8jHzj9b#jcteB_Sg-RGx3FGdTc19Xm9f$8NXbmtt$eN& zCQ{?ZJFXep4L!kzzVL>D@S#YcVoWba2EzDsUYg=obM^MEnW}TCHJ3`&O7r7Uk&pN^JLP#c z*OC`iUPIU1@JecfTN93Fe{OhfMp7LY*i+R!6OW_E{rC&%bx(Gdyy zj@S@g^oc`tQ?(%``wrMUR4#o6ka&YGKd2RVnD1vw_y4#_^Wxyy63>+6CY!jUbqyE6 z2^13C)LayLR%WN}bXEyYAvB(AqxrOe-2jSbb5l#ETA356Ku@5p6@q4?lorW zQp#CcRJig{R+8X@vd)CNjs)*s8t3*wo;jAX;+Kvj81j+KCzqHF@&G{7Lp`-m(DWk^ z!_K`WyYP^q=u_VWXAC`@3EEoHT(l&=9VTPe9+wdiz|%BE~&MxGSL#!4O*8i?A^ja%Ae%AmZ2T9yauFF2!K zCbG^Yh5Ela=&PPc}K^-vR?%fIT6y zWX3-uWZwivK{In6UJy>+px06_VYdymG>VeahU2=ZY^3C@hW4I%1C!6I{2J!oL(`PY zq;dk47n^JK$o?y;t%KrK>=&N!pEw1_-noCl?z1z`IcfOn z{^B2Nz^HY>)H{>~n2pZc7wpFqmmTgL z&c`_KpM19T@3U`r9@E+8%=hk0fGqSTp&CGbFi!r)KsP2WH9-CbX$PaO@I~+gGVW;b zkWucE#7UrjF`FVZTU45V2t4)>nfMj^ntj8L65`>h_TVXn=`Q>fRW)#(EdKRC8q|`k zI+TLzVj;PhE2=M4INjHj-_%m_kufuUXM6jwFr)c?AO72>L~Tf+^<3)h+aLV1_mqHz z`~>dHCnXZJ>|Il@U{c*6;$j_N@xr)Nv{9i`ihSqKnEXU)skgD$#;f{myC}LQok21D z6WEPucCxCRwyorWl6s^f5>M&S3th_C7XBhqGweKzk$A;E7nC+o^J)i2^-U0lRPuw! z-|`bL47y(I$EX6tQtOnBdcpx(jKi{n-O z9ZUOYO_X|zYtJrfio%AXpj7l9r#6VtJq^l~->#x5su2!Ib(=U6`UksR4Rqy?KGMXv zU!%Ai1|f`#Z^RMaw|ZUMX+G8n3RkpUL3fp^slqY+l3`Id9{4LHS^T%)ON(m1_4T}> jf_?c*=KSLd`HM8M@!jSdxkDIgvgu_Y4G#eI>(T!KV{V@V literal 0 HcmV?d00001 diff --git a/tqdm/__pycache__/_tqdm_pandas.cpython-37.pyc b/tqdm/__pycache__/_tqdm_pandas.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50a04b921ed60b1b2ae2eb012c4240def37baa71 GIT binary patch literal 1671 zcmbtVTW{Mo6s9EK>LkNZ3|)Z%1%bXufi1fibVIOaTbtd0Ei1Zq0}QV+BT|YINn}YX zjtlQ8eeHfj`?&wHU!m7M?Jw+Uhm@VQ-s1@Hh?n!7`=K7}?DP?gKfe3^**6`8{xqBQ zaAEN|OmzbYMbydB1W|Y5P%m{RE^SfoC7O8D`4I)}zkmlFzW5-`^l^0*#=IECLTsdbD-ny>8R? z8%O@M^k@f8-Pax3rM)*Uy`sA(y^|{!9__z!9kfIz{iU;Xe{&pE?gICgk#VxM$)~*~ zx^RFO(d}2jOMBqR4*A7HsE=VhW@)B0lM3r&hSThfm3U@6nc=Kdnv^lab8y!(&l>N* zamm6|@=6>n!ULkoSCSO$Yjf~m5emsu$&`wS5cy&jTIjbL#N;_jm@n|K$C|+6L$Xc_mznCWYK1lyJDSjzJ2_Esn zezH>g;{m=k2I2#SF~IkNpz+_>YILls*l+aZjGVFiPh`acjBnzYls2B0F|D!z=E7u( zkqY+ege9F(5OmaTAjr_b~%DACMw?4TyzH|HI@yH_9 z#tyTRW--xR4rwK`G95N47;@-;Vv$3!(8s*AFlk^T8_E|CA3>y{P))JU%u(2A@WU_+ns!`A$pTilW<9``C+yd)B+J=h-JZ)# zGtl2lL}b_hN4&KlfKCR1+jQj&qmd4_>z2_k;WT6L%C>ADTLRMp*8Ls~_@Bl#g9{{HOY-t?)0 zH(~n4^nj^T&BfGMGrSw#y%UcAvz|t8#Vr&IX&!&0TU`Z$KH7G#IUUD!Z#eI{u6x}H xTJn83ulrF%D#)yaY;|Fs=TT&_){M8vb;I5JwBWSL*}ka&1^*k=b6ZZw`vaSX@I-~LC(YL>}J;wgD zi|w#GU(%n|Ib(_`h?ul9k6_8A157!mAcYdLqAF__t60OzIWItZ$ez{L4Q!I%D*bk8 zU2G{=RSSpWoieh6odRBS?JAI6bS_aHRV%pi&iSjqWKY$xx4u@eh5H(`dI!_E#PsAY z?yj#FY~3%I>^>o--?8u62~$n%mRTp*RV@X-^C6Q19IW?LTXiUVht|XQ^mTAh*b4T` z=m)q@`uO@o)m80+<6z;ChMv_LACr>(QsY?Oz#ISZ-^7~=RIlXf;!s^Ha~_d`3JwSC z)Atm{r0@!TrQAjrp1qfsC+}3etFEg)dIMI7b9gnDxA4}McYDg!c_r@~^wa>mC9ijN z{u36D1;c|yoj3$Ac?fK^tM;gahv%@UQk8%I#w?y{J^cCbg#E-~PB#iau%qF|zL3X9 zU__E)Y#P3QhB^zA*!Pc|jiAlV9Wolo7#OYiXtWZe(=eCrG>K>7sR3<7{;OI_@y){r z$19<6c7IbL_yyeC*tE?*G%jGWsnJ5Z%OJ~9ON+~-$b-|&z}W@dT{Ryk2if!K{9qPF zcy!zFBr`lrG`(0Rt6}1o3ah>!M3L{C=2mDJ&k`e^C1GrwWsn*t%e8hW$%r(_=ccMc z9S2J^HQ%>K`@ZzDsSeYeHcT~mnpv21sVEz$gc(h;f-iC%NN+axFAF8bfs$1U%*in| zyNtpZO@#=a*Wv`Ptu9)$KCO0{K@F! z|M`<`hezp4Q~P|GsOJ%WVJ|s5CR2kZcOBvEh$ieo8wBq|oBlqp^Cs-@zT=YbI!&-& T9ri@usRJiu8*als^l1JU+ZR1* literal 0 HcmV?d00001 diff --git a/tqdm/__pycache__/cli.cpython-37.pyc b/tqdm/__pycache__/cli.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c5fa83706cf8af60bf388bc4fce95215e7ed630 GIT binary patch literal 5681 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 Ne`f0_xX+o!{{Y~abf5qL literal 0 HcmV?d00001 diff --git a/tqdm/__pycache__/gui.cpython-37.pyc b/tqdm/__pycache__/gui.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d86c37e95eb528c41fcbd9a7c101834353d37e3f GIT binary patch literal 5904 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 literal 0 HcmV?d00001 diff --git a/tqdm/__pycache__/std.cpython-37.pyc b/tqdm/__pycache__/std.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f6b89fe589bb49be506e493a9428be39194689d GIT binary patch literal 40765 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 literal 0 HcmV?d00001 diff --git a/tqdm/__pycache__/utils.cpython-37.pyc b/tqdm/__pycache__/utils.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e2aaf426d61449e47373fecbaec745110b94c5a GIT binary patch literal 11566 zcmb7KUvL}8dA~axfCC7UAVpE4Y%7Rk*@7d{f1)Igt!N}umaW91Qd73~ z{%G$g5irm?W>PE7#7#PFrb*ij*-kr?nNDB&(3ei1^3b>TsnfaXOlQ(3zo+d?9_s#n zdxrx+Q;7r2?cUygxBGqHe*gB{dvS8IsNwhXSFe5Z#FLu#Uu+D16S%pEEBHr4(}X5; zUu)>LuJhjT4ckDS@iPt6&L+=tc24!>?Yx)W)ojbNgt?Wo3tpk5wQhJtkrlabYa)MH z+tqhwm@v*K~_1nMt{)1r#{oTBYX|BH<;* zGjY{$1;DFa0*a}1&Q?Xd0#i|x*@ z3g_3ornl3U7dTK5<0h$}Rm;vTz4h|VtIM`|ZRM>MJNs(AxxHiOF1zwxy=iAxwwu>i z?EDq!c`vS9wsXr~$U1+$?KN@Jox8R3%<8ST-+tkic<0HtFP*jBv)y;DkX1n_>uucR zZ+h<5iWlN`;<^-`^u*m=sf}3u0Q7Q5U_5&5~V0IcZO#oU*4-PTSKc z%XS&%j6EY}#4KPTxMB|V5pNDcNin6tIVz4~#TKHHGeSwa<{6+$yss1Q=TU@O4{r&5 z%akWV;2yVkjraA?=)w5y8o`t6kPIi&(>BeX-ZMT_<`Hw4%O6pcx+k%0UM=)Q#SJUZ zR9v~f-SC>>naX-Ateg^2*4gqpK{Xp?e6JZ5Zgkq-OH#IEl;3azHw>km#V}EByDi+% zt7alI@cgyN#6Ur0Zru}s60W$QEsre6sWUCEe7E_~-QQ(NWSD2e2s* zh&1`Igey3K0+i9=gO)skKw%oXvv51>}wp9 z941EH*)dGiyERVDAL}eGg~sE!0duvjyqqICu?p&1XzYRUWcz-G9w=5uGvOG7(rXI& z5MGWZ94GL?Iuzb3gd+X{GnX7^ z0OmOM$m|OT)@2BlVnAV{)D4A63#?FkA0o90iQd;>*E5uCE>+*4K&{bjK)%h$`nuOq z8W}H5rrl~QnM%Y+s>P{b^aCpteFuF3xvfD7sfG}yxiO>@z)#>yyo2XZe4wqt z3c+?-_cex2^h5Vi@9DtZ=oxzkEUHE^yRU6R$ks?fMp>bBP%aiV>Ies=L!;rMOY7

jxKbGk7mvZZk6Px)Qdm)@pa;Y0g)0oSN?j!BAJTWMp6$>dhc@XloR+2UroI zs5qQV5FrbNrq6=g;PE=$!=PRdV*E$uPe)m++uU2!Bx!I~WqfEnXM|sC-v;+`*3RB2M6x5`q+W==0 zCAYI}w=NGe=F)3J;Ur#4bbbDlDi_0~)QlN%atAcv+D{Z+S11g)$B-hHT|&yUDDL7K zX!pz*>$phEsgPrt0aQ|g;NqnGKK=e0Yfn_7?Sy?~=||c;QA%$glA6kSukl*j+})MO*==whYU%Oe>2kgMZ%2=isuFzrm-5F8M|c zSNfxwxQHuw6vZ&qLYQT#KuaGDw6F$+X!1%+Hr((vukE`vulq)^g5QM;7bkc zN>=6boa6-*G#H{)gVrk{eTmNx6P}Cc_!ce|r&w7y7gO?C4OjXjsTXktEXKqGUIP)b zDG-OF#@1ZqQRg5w1=M*_6ceZ|Q4*7=3t|c^nU0D}F#4|2+U_%#f&lSsP^oRyeNjQk zT48v)=+H65Fo=6Y`P9X>Y_&ZZb`BQ_MycRA#9~w1O5!+dH%5jT@IBiKHxQ!?xj_Ml za*pF8;{7Asr*>xx0KW!+LA1>+kBSuz!6t!soSy;cf!L%_W2|Acs919bH=WXO=e7bw zLj$?N$K#AssCJxW8gfp`M|8h@sTLxvsN7bjFQN8!MY^<9m2kstCPp#g>S6-I-9@gY zR!iQ%w9(WhaKFB~9mXjD`8#+xun3Cq16FT39#9TA{I`Hl!XpDwFI>+f<7;sMer!D$ z%P9`T>w!HsoiujVkShHEjR_Bb9gPVH(C1cBzClvrjkkQ@8m9ClFaJI1QBq|*h>`?y zY%NpCicR|-!wj_tSGmAU_neAO81mdeTvJJyg49fFyNNVSWxamaYgXcgNkJv0kPh1> zCGo~Q$|oy&Kp2(R{)ZHKvpYY&41=W0a@9I$Whe)VS@JF3ak4lD7%u(LwtNGBau`_C8$uPhgh5;Wy;_Vz(Jr^BWsW;lbcf;EWuU@|y zTa@mP6;GBR*6UuS)$}`++l%)k0{PR`+m+TD1l~({DiS7ERy!51S!)SIk*d3m&7CLI zI4X9oxL`aV4gjPVAkwactwTn8A{d=8j;|NhSYz@`DIncJ1x1Lx9QG#ID90HX?&%;E z2Di*f_!(IP6rq;cU>n9$-omRf`_-x^^7}TQa#s&&Smgb1MHsIA99I(jmG$n!hp_PQ zaFko^CB7SHo8vSWvl2vgU;VKtM)w=3Ouj^(daygUQwILFETb00w3tf5nIXqgMmfa{ zPf6iG+Vz9GQ|SQRee-mEbK&_5xUEZt-AqNjK(U92Bh`Vl>&y~%7prb%hNFnuvrCb_Oc4r3K5v^>hJ5q0?LK%yQu(BB`X7ib$hMud$-T}^)0BS z@+#C6(xMboR&J+S3cbQ6t0lv9BqX2L!;N4dKCubB&7Bu{b327oi}E`cILR`cUs@?kmsEvp!@%6VI;8BLIhznaF6nlF}aKw(hBfb_R+M*KZ9bj?A^~ zW^F{lC~^PJ*|;p4QD$6>OMxQnX8fmr`NLputOFPe~;9Jso%(3U4F9JB`p4 z-Wi3lTm!uHP(V92cwc8Rv;>^#KXE09s;uAXPlG5ItXo+^(4cH9Jq#Ah6;CM&DzUx8 z5msJ)lQWW<*hDc}kW|FjYdnEFY%H1TRtVz6E5taBD3_!p$V$bKM8(DkQR+sX{41_t z7KN5ABjsZ0^VzAmG`c4qOb^B+2ddeFYOgeY625H|w;*u`O1K;;8V_PR(nF z^;R>=`mK9#Bpd@Qq0<=BEmatucj|$IVLw7&z)Xau8wlN|uvM7C-8|AiMMJjme2fig z!1@?^I>lioKOy*JLJ~q$kP~NEt{?*AKrtR~;tq#+##;LzN;3}JAI$?0i>doxz$O`0 z15z-h%R6|IB1Ea9CaHDd-i7Fd*jmUlI}i7evVLa{DQo!NtzGdOwz_Scuc$3eR_gz({@HH>o zmd%P=p?Ts$SMF`pYa10r_Ce)dU4$FTBX4;1^^K50QLWVsTE3@VKJD{}_Dn0-IfElw z9zCC_b=GlPjl-B_xTW0H6$gj9(NV{1-mS}46R8kCa1!`vcKmVJ-VW6(%U9owj*R!I z$*TCmV(jt3B|kV%9P8!5rJziHA-BXo`N+TQ<5{}=N73M6wBqI8;imKFJgwm8B7MDq z6BPtm$nyD_J{%-I88I8#zS+q2`Pqos&%x-@6`mK_ey&+UD~r}EB8MJ*Grwo`a!9@B zdj_3g-%`&Cy?oEYSxuo=kU!~V_KL#lS(_94#;u8du|Lr-^(XsN{po(WKhvM>AL-Ba zW_r`dv4SuXrC2lKPac$J6w=&djtutwXs!lR8F~)Sz?h{{2C*hcID2^bOrc|_W zI{(X$KmNG>KP>(m#YapG+Y=l3y~1K+qgt|awGcw9Y@zDdIp>~A37EdO z7TTtRZx*7gq*U8^XSEfEtp-3F?hfBT)j1$ok}3OniV<~;rmYMiDjO7~9L33?a>Cdd zkKvA)ZuLfu!2D%!(Read5D%C#tb6--m7CQP{wiC(Da=75lGPzbZR8 zZD>?ZCt6fh8RzA0f$9Sd^?3}R4K{plC(6{DA(H;vtH@i{ydbcPwGFSfrE<}LY2&yS z#`H1}lHWxEcH;Gew0F_-6lv#{S~Cn#ItR)XmF!Cu$V4MmpbydLoE|EWQl5-&G%|@I znNT3JpHW}L%)#7e;qlHV<{l!Bua@Q!U1kxLBAOAoUBsp$zkr??rZXGn!u+NstOeLz zk*8WGgP_zZswu4FF_Q3h}TqR1Ho&{Z%Mb|j-WMkQj-pufHSGPp0nM)Ml9)u zG+i>23|5vs7iXMqPS6D37#G+SFCO2K$PCZCMEfvNfBooS8jg9H`M|%=kX&n;CJ(=Y@@a@LYRsZDY|bB zC^7NP3uxR4W9)|!c=`}Uq321qj|(FJC3Ii2FtkW~{S!`$TXR1=yI`4iMBjbuxj zyrQ$>1`Q|T2f^;hJz)c1hIiQw5#0`V2U6?_Z@R$g+nCSYBK~)b8e9?g5Ha3ep564hA z-7w`9!U=w;9x( zHg=5op6Vbw3yPia-VH&1su;$^ZqZ<5UDjufd0qY$MytLM6;up|d4qWOL`{w?96^RW zr;}tm+#tLmjFBNd`8_<46gas<$c)?U$ai^{=b;z&;0k{`wz z^@QFVF{1>8y1|L~hP3mEPRJi~1}?MwV-`O^VVnH0OU~gIsYiU3C%rW}$yNq=P`kM5 z23{<@I650`z#{oL6F{6ANctjmhHOtDq~_Or>U)Z5K7NSBO%@kf(CMmVVw?!8v8nbC z4E|L(t>QrCu_YavvZ%_C*%JvJYL8F*5w9{eS1q;T{>Z8g)!sTEAJ|`@Vh8t8Fht-L zhYT!>MlnlwI)%GW);I?>27P53e!#GfVdI)@_0%XCCoh$!%SX!#<(cvcYYNBi1#8+` Suu9gfWg5sa@C_WwTmKKCR`){y literal 0 HcmV?d00001 diff --git a/tqdm/_main.py b/tqdm/_main.py new file mode 100644 index 0000000..07b6730 --- /dev/null +++ b/tqdm/_main.py @@ -0,0 +1,7 @@ +from .cli import * # NOQA +from .cli import __all__ # NOQA +from .std import TqdmDeprecationWarning +from warnings import warn +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.cli.*` instead of `tqdm._main.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/tqdm/_monitor.py b/tqdm/_monitor.py new file mode 100644 index 0000000..e1e2570 --- /dev/null +++ b/tqdm/_monitor.py @@ -0,0 +1,99 @@ +from threading import Event, Thread, current_thread +from time import time +from warnings import warn +import atexit +__all__ = ["TMonitor", "TqdmSynchronisationWarning"] + + +class TqdmSynchronisationWarning(RuntimeWarning): + """tqdm multi-thread/-process errors which may cause incorrect nesting + but otherwise no adverse effects""" + pass + + +class TMonitor(Thread): + """ + Monitoring thread for tqdm bars. + Monitors if tqdm bars are taking too much time to display + and readjusts miniters automatically if necessary. + + Parameters + ---------- + tqdm_cls : class + tqdm class to use (can be core tqdm or a submodule). + sleep_interval : fload + Time to sleep between monitoring checks. + """ + + # internal vars for unit testing + _time = None + _event = None + + def __init__(self, tqdm_cls, sleep_interval): + Thread.__init__(self) + self.daemon = True # kill thread when main killed (KeyboardInterrupt) + self.was_killed = Event() + self.woken = 0 # last time woken up, to sync with monitor + self.tqdm_cls = tqdm_cls + self.sleep_interval = sleep_interval + if TMonitor._time is not None: + self._time = TMonitor._time + else: + self._time = time + if TMonitor._event is not None: + self._event = TMonitor._event + else: + self._event = Event + atexit.register(self.exit) + self.start() + + def exit(self): + self.was_killed.set() + if self is not current_thread(): + self.join() + return self.report() + + def get_instances(self): + # returns a copy of started `tqdm_cls` instances + return [i for i in self.tqdm_cls._instances.copy() + # Avoid race by checking that the instance started + if hasattr(i, 'start_t')] + + def run(self): + cur_t = self._time() + while True: + # After processing and before sleeping, notify that we woke + # Need to be done just before sleeping + self.woken = cur_t + # Sleep some time... + self.was_killed.wait(self.sleep_interval) + # Quit if killed + if self.was_killed.is_set(): + return + # Then monitor! + # Acquire lock (to access _instances) + with self.tqdm_cls.get_lock(): + cur_t = self._time() + # Check tqdm instances are waiting too long to print + instances = self.get_instances() + for instance in instances: + # Check event in loop to reduce blocking time on exit + if self.was_killed.is_set(): + return + # Only if mininterval > 1 (else iterations are just slow) + # and last refresh exceeded maxinterval + if instance.miniters > 1 and \ + (cur_t - instance.last_print_t) >= \ + instance.maxinterval: + # force bypassing miniters on next iteration + # (dynamic_miniters adjusts mininterval automatically) + instance.miniters = 1 + # Refresh now! (works only for manual tqdm) + instance.refresh(nolock=True) + if instances != self.get_instances(): # pragma: nocover + warn("Set changed size during iteration" + + " (see https://github.com/tqdm/tqdm/issues/481)", + TqdmSynchronisationWarning, stacklevel=2) + + def report(self): + return not self.was_killed.is_set() diff --git a/tqdm/_monitor.pyc b/tqdm/_monitor.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e542e86c2bd5049a4235a1a20b9c6076a6377078 GIT binary patch literal 3366 zcmcImO>ZN|5v?9ll*pw*c{i{f+lLO209*qrI`JM1BXB-;ja*_O8LhF20`Fin(-McA z5AJkxwF=gkD8~T#9lrQi_#gS=i;=unLrNR?DwFJHb$4}jb=B+F-TQWb^v$0>dzQ)O z)5HJoQ2f78<@gyXMJgjFMyg0oBB^?E(i6k6NXouceL3lO?Vgl7QVrx}(6#$g?#juo zx z4MkHwg=SHoUq^-p|A6ATm=gvBF+f1#V>R#R=nt@Ct92QQ2b~|Y2dlQpocGqXZPR*t zF)y;YtM+Y@pLwq)VOJG)NVaL4?@Q z*u3zoGQHH_A%X^V7FSDO#6 zKiZ^=NmhDmKekz!`c8=nSLd~ho)8$*EUoRSvw#P@Bd)N=)cW;lm*e~0Hht;bDk*9# z`64Z8_^fQwT$iKaC!5Ww9_)n+=W4sjB*T!+UAElBEaMSliBKLf=3k)-A`2t0a9E9) z5K&5cJX}|zvZj-r19_!-$*#QW)b=C{Wr6c8vK&hL_f3<^BdLcH#%f`t{rg+Be{Pyo z+gG)|s_hF-`eH(PaiHE%dV_wDkB+Bw`=~%A)4Ql*C4uggFoQT2eRMx**G}0ZPaOgg zQ(uqIMe37fQI;-Onz?9}AQVpD=1qy_|05ogkW@V(j&y;ergkyIsH-IIny%8|UKT-3 zw(h*}NSaR2UK;WtZ9{js>8W?+Sv-LdAE3JBBTT}uDE>WEV)o6@j8XWVm`Sv)4@|}| zvRtF$Lo^tA1qP#!27m#HS-!7hJxxp9DJoVvo;;dzqwzkfz!-TKVj9*!EE*u0+|s%g zs9A|MejDvu-us&{`D+x<>~ilD6x}CXp@WZ5JXOwL+v-C1`1p%= zpnc4PeM}p>ve;wSf4)}C06?8hjty0g;$ny^!ZARdF}GO*2rWDH z&e?egEC2Cqc3y<}`t&esD!p*Ks_=g8{OsXJzq+r0L87u{2_7j6;&Vzih~Hp!hZSEW z-GO4_F47_>U#NS9Pk6#eBFa0=JROedu=Z>(H*m2CE~SvH;58mnfyS%!<@Rz>jJEEq zb$%XmC4B{S*yb5k7Q{0QGxgE7#h0$KMcXZdxrcSzb^Qtc{QIbc9M&5~_o9jE_YTZB z8k>EUCuU^E77`lJ2zog=~mu+q(#Qa6Mlp7pf@msXn1!x z!1otZqDS-IJ;{K3y;$`QR)X(tMDB!-aE$v7L_g(3?lMX6s=^(ua9-EEPB)Km1)AQp cKf=Bgp1>> import pandas as pd + >>> import numpy as np + >>> from tqdm import tqdm, tqdm_pandas + >>> + >>> df = pd.DataFrame(np.random.randint(0, 100, (100000, 6))) + >>> tqdm_pandas(tqdm, leave=True) # can use tqdm_gui, optional kwargs, etc + >>> # Now you can use `progress_apply` instead of `apply` + >>> df.groupby(0).progress_apply(lambda x: x**2) + + References + ---------- + https://stackoverflow.com/questions/18603270/ + progress-indicator-during-pandas-operations-python + """ + from tqdm import TqdmDeprecationWarning + + if isinstance(tclass, type) or (getattr(tclass, '__name__', '').startswith( + 'tqdm_')): # delayed adapter case + TqdmDeprecationWarning("""\ +Please use `tqdm.pandas(...)` instead of `tqdm_pandas(tqdm, ...)`. +""", fp_write=getattr(tkwargs.get('file', None), 'write', sys.stderr.write)) + tclass.pandas(*targs, **tkwargs) + else: + TqdmDeprecationWarning("""\ +Please use `tqdm.pandas(...)` instead of `tqdm_pandas(tqdm(...))`. +""", fp_write=getattr(tclass.fp, 'write', sys.stderr.write)) + type(tclass).pandas(deprecated_t=tclass) diff --git a/tqdm/_tqdm_pandas.pyc b/tqdm/_tqdm_pandas.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6183c56319fb6165903255515cc7a62942ba9f7 GIT binary patch literal 1903 zcmb_dTW{P%6h5|-wArRo1&9}32KwSHyY}{?f&z`W1P~I0YFkt(5_l(`S;vVzGo2Y{ zvm!i&XMPaR{3CtZB;P-F7{jr4A6XW+2d|ir*z+VIn zz#$Sb97Z4#s3JIwL2SS;c%B4-XXz#i*MH)3;NC*As{L_yl$E;8OYV%cqHO#vWcZh& zEeuzJyE4N42EI4&{SIIE8!l)Kfe^8c;O7J)AHxNzC2$#o+XcT7w&(=HUnX#}31Sm2 zBd|Zg1s-mJxIr>8oFIJ@#7L!mP|Ht2=YF%=WqGUs~SEufoA+%gk6^S?OHCjcJy%j8wn&Xr^hH3;<=L zolJ)Wc^#}X8U=J~8tH4TvH__-}u8cpK2Yo+pz&9x<$31pp}-oG!-dGPpk z@5tuf-X5!4qpfFs_851}i0eGnwPqPu%3QJLR=0gbD+2*|<$Z5!ExP_PF=tQE`m3U( z%48NJ(k*>z<6RJsuAG_=B$jW7Ya;kJm_kCZ9aFog6ZDmz7*mK1s0Z z>Jif>REjsOPZ}lk%A>pNKrg5mYsJ~M2BAiiTrfRnv%Y7pw8ebgXX#{kt+UdGw?~4r z^Idj+@7{x9Ux}%lODnN$U$4b@Og4|bH*Po2v9rrleJ1U^(F+;?FFWZ%7%sp6;p54} z2Omsw(pWn?u9c`u?zJ6@&ep0L_azu>oEw}p$7bn|wIVf|GHI9{{%u^IV^;U2u@WsH z$O~>2n!wu*P!tMVyC^);;KPKX%(~B!dWyNn)(!1D3#VC+nPyow>@Vm#Z=|#0271Iu zCL0jO+!PC2dx>)iU|!?Z3OIE1A92W)AzpI7gBHaqL@5e?K$C_LgF{ow#Q=qBx4t_) zb7`ax!?lv0dvAS^!TMP5!r;M4Ml6y&&`Q#QTROi*aza`=4eGS-Gl0_=!!&7#mX7-Y z(I+?DYrhe!`_Y?m62BLbbJIJ>qJWEnci21@`yeRcwrPrjjJ)#ge|3-= W HEAD points to file containing last commit + if 'ref:' in extra: + # reference file path + ref_file = extra[5:] + branch_name = ref_file.rsplit('/', 1)[-1] + + ref_file_path = os.path.abspath(os.path.join(gitdir, ref_file)) + # check that we are in git folder + # (by stripping the git folder from the ref file path) + if os.path.relpath( + ref_file_path, gitdir).replace('\\', '/') != ref_file: + # out of git folder + extra = None + else: + # open the ref file + with io_open(ref_file_path, 'r') as fh_branch: + commit_hash = fh_branch.readline().strip() + extra = commit_hash[:8] + if branch_name != "master": + extra += '.' + branch_name + + # detached HEAD mode, already have commit hash + else: + extra = extra[:8] + + # Append commit hash (and branch) to version string if not tagged + if extra is not None: + try: + with io_open(os.path.join(gitdir, "refs", "tags", + 'v' + __version__)) as fdv: + if fdv.readline().strip()[:8] != extra[:8]: + __version__ += '-' + extra + except Exception as e: + if "No such file" not in str(e): + raise diff --git a/tqdm/_version.pyc b/tqdm/_version.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71b32e0e2e4a73d1d3768abbdf3fd4fbfa1e1ef3 GIT binary patch literal 1471 zcmZWoQEMAV5dQW~mMvMTTq(9?xwYk_4}uUWkU&c*Z2}Io&>EUj5{{DNa=WrtI^CUa zHzp}G&?*mo%b#jr`&{VH=nrUTRyxN?gl}$c=9_Q7nH~H8HtTtBQ3_vkUd zdw`@rfdmLvzK29$?ji6Yq>pofpaR)LumQP=X%%V{Y71%uOAkxR)}VlC9n)>7hKn@I z$OolERZYk($QqWu!?eoO4pfUH#Ve0=1Z^n4Oi@(}vd#y|ZMLs(i=YG9fa+j+4NG8F zMe&b#x1@QcOh1Ms)*bc>^ zxRuwRA?QQ(-_pXYYjSSm8;|gkM%88wwKp0bEq?VX>IAboieEetT&FEMa0_#txo4Xw zUV6U)i3P)!ba_*e3AKX86*g}mz*BgX&sF7we!{#Tw#Zji2 zN5-lZ0m03RDLOZ<sDV#`cccz(FeNL^jC<+qA$`DZlF?b{ zx@A8Mqa+DK%cN!BIvr>3=xL@?%k1+gcW+~hhnEw}BRg??QWt47SC-odLzYB@HTp1d zp>s=V2aBE>SHg|b+&IkR(1typ=2@017gEn{5jlqOBwUY{0fmaBqoVMHeLj(ciC$Nd zKss?19y=*+u7$}Hx+%Wir$v;;lh6rny7Cn+ylkgX$rV3OqF9w$R>h7d&gOG%!%1W& zZZuEfR4Z$W4@x#(LA-Ic=V&8JsaI0ohmc4Sw7;GxpjAV_s;P4 zd;X|g%3=P(wfDt5lg|?MC10^QCDRZ)Ud^xi-AY@0BHE(rorpF)U9aKQ#h!QK*TfMa Z+LWt{hUir~ena?TS9C;Q9Eujj{|Aw$B(VSh literal 0 HcmV?d00001 diff --git a/tqdm/auto.py b/tqdm/auto.py new file mode 100644 index 0000000..4dd1717 --- /dev/null +++ b/tqdm/auto.py @@ -0,0 +1,6 @@ +import warnings +from .std import TqdmExperimentalWarning +with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=TqdmExperimentalWarning) + from .autonotebook import tqdm, trange +__all__ = ["tqdm", "trange"] diff --git a/tqdm/autonotebook.py b/tqdm/autonotebook.py new file mode 100644 index 0000000..0bcd42a --- /dev/null +++ b/tqdm/autonotebook.py @@ -0,0 +1,18 @@ +import os + +try: + from IPython import get_ipython + if 'IPKernelApp' not in get_ipython().config: # pragma: no cover + raise ImportError("console") + if 'VSCODE_PID' in os.environ: # pragma: no cover + raise ImportError("vscode") +except: + from .std import tqdm, trange +else: # pragma: no cover + from .notebook import tqdm, trange + from .std import TqdmExperimentalWarning + from warnings import warn + warn("Using `tqdm.autonotebook.tqdm` in notebook mode." + " Use `tqdm.tqdm` instead to force console mode" + " (e.g. in jupyter console)", TqdmExperimentalWarning, stacklevel=2) +__all__ = ["tqdm", "trange"] diff --git a/tqdm/cli.py b/tqdm/cli.py new file mode 100644 index 0000000..2335c04 --- /dev/null +++ b/tqdm/cli.py @@ -0,0 +1,228 @@ +from .std import tqdm, TqdmTypeError, TqdmKeyError +from ._version import __version__ # NOQA +import sys +import re +import logging +__all__ = ["main"] + + +def cast(val, typ): + log = logging.getLogger(__name__) + log.debug((val, typ)) + if " or " in typ: + for t in typ.split(" or "): + try: + return cast(val, t) + except TqdmTypeError: + pass + raise TqdmTypeError(val + ' : ' + typ) + + # sys.stderr.write('\ndebug | `val:type`: `' + val + ':' + typ + '`.\n') + if typ == 'bool': + if (val == 'True') or (val == ''): + return True + elif val == 'False': + return False + else: + raise TqdmTypeError(val + ' : ' + typ) + try: + return eval(typ + '("' + val + '")') + except: + if typ == 'chr': + return chr(ord(eval('"' + val + '"'))) + else: + raise TqdmTypeError(val + ' : ' + typ) + + +def posix_pipe(fin, fout, delim='\n', buf_size=256, + callback=lambda int: None # pragma: no cover + ): + """ + Params + ------ + fin : file with `read(buf_size : int)` method + fout : file with `write` (and optionally `flush`) methods. + callback : function(int), e.g.: `tqdm.update` + """ + fp_write = fout.write + + # tmp = '' + if not delim: + while True: + tmp = fin.read(buf_size) + + # flush at EOF + if not tmp: + getattr(fout, 'flush', lambda: None)() # pragma: no cover + return + + fp_write(tmp) + callback(len(tmp)) + # return + + buf = '' + # n = 0 + while True: + tmp = fin.read(buf_size) + + # flush at EOF + if not tmp: + if buf: + fp_write(buf) + callback(1 + buf.count(delim)) # n += 1 + buf.count(delim) + getattr(fout, 'flush', lambda: None)() # pragma: no cover + return # n + + while True: + try: + i = tmp.index(delim) + except ValueError: + buf += tmp + break + else: + fp_write(buf + tmp[:i + len(delim)]) + callback(1) # n += 1 + buf = '' + tmp = tmp[i + len(delim):] + + +# ((opt, type), ... ) +RE_OPTS = re.compile(r'\n {8}(\S+)\s{2,}:\s*([^,]+)') +# better split method assuming no positional args +RE_SHLEX = re.compile(r'\s*(? : \2', d) + split = RE_OPTS.split(d) + opt_types_desc = zip(split[1::3], split[2::3], split[3::3]) + d = ''.join('\n --{0}=<{0}> : {1}{2}'.format(*otd) + for otd in opt_types_desc if otd[0] not in UNSUPPORTED_OPTS) + + d = """Usage: + tqdm [--help | options] + +Options: + -h, --help Print this help and exit + -v, --version Print version and exit + +""" + d.strip('\n') + '\n' + + # opts = docopt(d, version=__version__) + if any(v in argv for v in ('-v', '--version')): + sys.stdout.write(__version__ + '\n') + sys.exit(0) + elif any(v in argv for v in ('-h', '--help')): + sys.stdout.write(d + '\n') + sys.exit(0) + + argv = RE_SHLEX.split(' '.join(["tqdm"] + argv)) + opts = dict(zip(argv[1::3], argv[3::3])) + + log.debug(opts) + opts.pop('log', True) + + tqdm_args = {'file': fp} + try: + for (o, v) in opts.items(): + try: + tqdm_args[o] = cast(v, opt_types[o]) + except KeyError as e: + raise TqdmKeyError(str(e)) + log.debug('args:' + str(tqdm_args)) + except: + fp.write('\nError:\nUsage:\n tqdm [--help | options]\n') + for i in sys.stdin: + sys.stdout.write(i) + raise + else: + buf_size = tqdm_args.pop('buf_size', 256) + delim = tqdm_args.pop('delim', '\n') + delim_per_char = tqdm_args.pop('bytes', False) + manpath = tqdm_args.pop('manpath', None) + stdin = getattr(sys.stdin, 'buffer', sys.stdin) + stdout = getattr(sys.stdout, 'buffer', sys.stdout) + if manpath is not None: + from os import path + from shutil import copyfile + from pkg_resources import resource_filename, Requirement + fi = resource_filename(Requirement.parse('tqdm'), 'tqdm/tqdm.1') + fo = path.join(manpath, 'tqdm.1') + copyfile(fi, fo) + log.info("written:" + fo) + sys.exit(0) + if delim_per_char: + tqdm_args.setdefault('unit', 'B') + tqdm_args.setdefault('unit_scale', True) + tqdm_args.setdefault('unit_divisor', 1024) + log.debug(tqdm_args) + with tqdm(**tqdm_args) as t: + posix_pipe(stdin, stdout, '', buf_size, t.update) + elif delim == '\n': + log.debug(tqdm_args) + for i in tqdm(stdin, **tqdm_args): + stdout.write(i) + else: + log.debug(tqdm_args) + with tqdm(**tqdm_args) as t: + posix_pipe(stdin, stdout, delim, buf_size, t.update) diff --git a/tqdm/cli.pyc b/tqdm/cli.pyc new file mode 100644 index 0000000000000000000000000000000000000000..813d1ffa30faac52a0bcecf58b386b34ce3cf305 GIT binary patch literal 7243 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 TH{LszGzM-C#85*!k&OQv40q6a literal 0 HcmV?d00001 diff --git a/tqdm/contrib/__init__.py b/tqdm/contrib/__init__.py new file mode 100644 index 0000000..01312cd --- /dev/null +++ b/tqdm/contrib/__init__.py @@ -0,0 +1,80 @@ +""" +Thin wrappers around common functions. + +Subpackages contain potentially unstable extensions. +""" +from tqdm import tqdm +from tqdm.auto import tqdm as tqdm_auto +from tqdm.utils import ObjectWrapper +from copy import deepcopy +import functools +import sys +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['tenumerate', 'tzip', 'tmap'] + + +class DummyTqdmFile(ObjectWrapper): + """Dummy file-like that will write to tqdm""" + def write(self, x, nolock=False): + # Avoid print() second call (useless \n) + if len(x.rstrip()) > 0: + tqdm.write(x, file=self._wrapped, nolock=nolock) + + +def tenumerate(iterable, start=0, total=None, tqdm_class=tqdm_auto, + **tqdm_kwargs): + """ + Equivalent of `numpy.ndenumerate` or builtin `enumerate`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + try: + import numpy as np + except ImportError: + pass + else: + if isinstance(iterable, np.ndarray): + return tqdm_class(np.ndenumerate(iterable), + total=total or iterable.size, **tqdm_kwargs) + return enumerate(tqdm_class(iterable, **tqdm_kwargs), start) + + +def _tzip(iter1, *iter2plus, **tqdm_kwargs): + """ + Equivalent of builtin `zip`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + kwargs = deepcopy(tqdm_kwargs) + tqdm_class = kwargs.pop("tqdm_class", tqdm_auto) + for i in zip(tqdm_class(iter1, **tqdm_kwargs), *iter2plus): + yield i + + +def _tmap(function, *sequences, **tqdm_kwargs): + """ + Equivalent of builtin `map`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + for i in _tzip(*sequences, **tqdm_kwargs): + yield function(*i) + + +if sys.version_info[:1] < (3,): + @functools.wraps(_tzip) + def tzip(*args, **kwargs): + return list(_tzip(*args, **kwargs)) + + @functools.wraps(_tmap) + def tmap(*args, **kwargs): + return list(_tmap(*args, **kwargs)) +else: + tzip = _tzip + tmap = _tmap diff --git a/tqdm/contrib/concurrent.py b/tqdm/contrib/concurrent.py new file mode 100644 index 0000000..139c89d --- /dev/null +++ b/tqdm/contrib/concurrent.py @@ -0,0 +1,65 @@ +""" +Thin wrappers around `concurrent.futures`. +""" +from __future__ import absolute_import +from tqdm.auto import tqdm as tqdm_auto +from copy import deepcopy +try: + from os import cpu_count +except ImportError: + try: + from multiprocessing import cpu_count + except ImportError: + def cpu_count(): + return 4 +import sys +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['thread_map', 'process_map'] + + +def _executor_map(PoolExecutor, fn, *iterables, **tqdm_kwargs): + """ + Implementation of `thread_map` and `process_map`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + kwargs = deepcopy(tqdm_kwargs) + if "total" not in kwargs: + kwargs["total"] = len(iterables[0]) + tqdm_class = kwargs.pop("tqdm_class", tqdm_auto) + max_workers = kwargs.pop("max_workers", min(32, cpu_count() + 4)) + pool_kwargs = dict(max_workers=max_workers) + if sys.version_info[:2] >= (3, 7): + # share lock in case workers are already using `tqdm` + pool_kwargs.update( + initializer=tqdm_class.set_lock, initargs=(tqdm_class.get_lock(),)) + with PoolExecutor(**pool_kwargs) as ex: + return list(tqdm_class(ex.map(fn, *iterables), **kwargs)) + + +def thread_map(fn, *iterables, **tqdm_kwargs): + """ + Equivalent of `list(map(fn, *iterables))` + driven by `concurrent.futures.ThreadPoolExecutor`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + from concurrent.futures import ThreadPoolExecutor + return _executor_map(ThreadPoolExecutor, fn, *iterables, **tqdm_kwargs) + + +def process_map(fn, *iterables, **tqdm_kwargs): + """ + Equivalent of `list(map(fn, *iterables))` + driven by `concurrent.futures.ProcessPoolExecutor`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + from concurrent.futures import ProcessPoolExecutor + return _executor_map(ProcessPoolExecutor, fn, *iterables, **tqdm_kwargs) diff --git a/tqdm/contrib/itertools.py b/tqdm/contrib/itertools.py new file mode 100644 index 0000000..0f2a2a4 --- /dev/null +++ b/tqdm/contrib/itertools.py @@ -0,0 +1,34 @@ +""" +Thin wrappers around `itertools`. +""" +from __future__ import absolute_import +from tqdm.auto import tqdm as tqdm_auto +from copy import deepcopy +import itertools +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['product'] + + +def product(*iterables, **tqdm_kwargs): + """ + Equivalent of `itertools.product`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + kwargs = deepcopy(tqdm_kwargs) + tqdm_class = kwargs.pop("tqdm_class", tqdm_auto) + try: + lens = list(map(len, iterables)) + except TypeError: + total = None + else: + total = 1 + for i in lens: + total *= i + kwargs.setdefault("total", total) + with tqdm_class(**kwargs) as t: + for i in itertools.product(*iterables): + yield i + t.update() diff --git a/tqdm/gui.py b/tqdm/gui.py new file mode 100644 index 0000000..35f5c5e --- /dev/null +++ b/tqdm/gui.py @@ -0,0 +1,321 @@ +""" +GUI progressbar decorator for iterators. +Includes a default (x)range iterator printing to stderr. + +Usage: + >>> from tqdm.gui import trange[, tqdm] + >>> for i in trange(10): #same as: for i in tqdm(xrange(10)) + ... ... +""" +# future division is important to divide integers and get as +# a result precise floating numbers (instead of truncated int) +from __future__ import division, absolute_import +# import compatibility functions and utilities +from .utils import _range +# to inherit from the tqdm class +from .std import tqdm as std_tqdm +from .std import TqdmExperimentalWarning +from warnings import warn + + +__author__ = {"github.com/": ["casperdcl", "lrq3000"]} +__all__ = ['tqdm_gui', 'tgrange', 'tqdm', 'trange'] + + +class tqdm_gui(std_tqdm): # pragma: no cover + """ + Experimental GUI version of tqdm! + """ + + # TODO: @classmethod: write() on GUI? + + def __init__(self, *args, **kwargs): + import matplotlib as mpl + import matplotlib.pyplot as plt + from collections import deque + kwargs['gui'] = True + + super(tqdm_gui, self).__init__(*args, **kwargs) + + # Initialize the GUI display + if self.disable or not kwargs['gui']: + return + + warn('GUI is experimental/alpha', TqdmExperimentalWarning, stacklevel=2) + self.mpl = mpl + self.plt = plt + self.sp = None + + # Remember if external environment uses toolbars + self.toolbar = self.mpl.rcParams['toolbar'] + self.mpl.rcParams['toolbar'] = 'None' + + self.mininterval = max(self.mininterval, 0.5) + self.fig, ax = plt.subplots(figsize=(9, 2.2)) + # self.fig.subplots_adjust(bottom=0.2) + total = len(self) + if total is not None: + self.xdata = [] + self.ydata = [] + self.zdata = [] + else: + self.xdata = deque([]) + self.ydata = deque([]) + self.zdata = deque([]) + self.line1, = ax.plot(self.xdata, self.ydata, color='b') + self.line2, = ax.plot(self.xdata, self.zdata, color='k') + ax.set_ylim(0, 0.001) + if total is not None: + ax.set_xlim(0, 100) + ax.set_xlabel('percent') + self.fig.legend((self.line1, self.line2), ('cur', 'est'), + loc='center right') + # progressbar + self.hspan = plt.axhspan(0, 0.001, + xmin=0, xmax=0, color='g') + else: + # ax.set_xlim(-60, 0) + ax.set_xlim(0, 60) + ax.invert_xaxis() + ax.set_xlabel('seconds') + ax.legend(('cur', 'est'), loc='lower left') + ax.grid() + # ax.set_xlabel('seconds') + ax.set_ylabel((self.unit if self.unit else 'it') + '/s') + if self.unit_scale: + plt.ticklabel_format(style='sci', axis='y', + scilimits=(0, 0)) + ax.yaxis.get_offset_text().set_x(-0.15) + + # Remember if external environment is interactive + self.wasion = plt.isinteractive() + plt.ion() + self.ax = ax + + def __iter__(self): + # TODO: somehow allow the following: + # if not self.gui: + # return super(tqdm_gui, self).__iter__() + iterable = self.iterable + if self.disable: + for obj in iterable: + yield obj + return + + # ncols = self.ncols + mininterval = self.mininterval + maxinterval = self.maxinterval + miniters = self.miniters + dynamic_miniters = self.dynamic_miniters + last_print_t = self.last_print_t + last_print_n = self.last_print_n + n = self.n + # dynamic_ncols = self.dynamic_ncols + smoothing = self.smoothing + avg_time = self.avg_time + time = self._time + + for obj in iterable: + yield obj + # Update and possibly print the progressbar. + # Note: does not call self.update(1) for speed optimisation. + n += 1 + # check counter first to avoid calls to time() + if n - last_print_n >= self.miniters: + miniters = self.miniters # watch monitoring thread changes + delta_t = time() - last_print_t + if delta_t >= mininterval: + cur_t = time() + delta_it = n - last_print_n + # EMA (not just overall average) + if smoothing and delta_t and delta_it: + rate = delta_t / delta_it + avg_time = self.ema(rate, avg_time, smoothing) + self.avg_time = avg_time + + self.n = n + self.display() + + # If no `miniters` was specified, adjust automatically + # to the max iteration rate seen so far between 2 prints + if dynamic_miniters: + if maxinterval and delta_t >= maxinterval: + # Adjust miniters to time interval by rule of 3 + if mininterval: + # Set miniters to correspond to mininterval + miniters = delta_it * mininterval / delta_t + else: + # Set miniters to correspond to maxinterval + miniters = delta_it * maxinterval / delta_t + elif smoothing: + # EMA-weight miniters to converge + # towards the timeframe of mininterval + rate = delta_it + if mininterval and delta_t: + rate *= mininterval / delta_t + miniters = self.ema(rate, miniters, smoothing) + else: + # Maximum nb of iterations between 2 prints + miniters = max(miniters, delta_it) + + # Store old values for next call + self.n = self.last_print_n = last_print_n = n + self.last_print_t = last_print_t = cur_t + self.miniters = miniters + + # Closing the progress bar. + # Update some internal variables for close(). + self.last_print_n = last_print_n + self.n = n + self.miniters = miniters + self.close() + + def update(self, n=1): + # if not self.gui: + # return super(tqdm_gui, self).close() + if self.disable: + return + + if n < 0: + self.last_print_n += n # for auto-refresh logic to work + self.n += n + + # check counter first to reduce calls to time() + if self.n - self.last_print_n >= self.miniters: + delta_t = self._time() - self.last_print_t + if delta_t >= self.mininterval: + cur_t = self._time() + delta_it = self.n - self.last_print_n # >= n + # elapsed = cur_t - self.start_t + # EMA (not just overall average) + if self.smoothing and delta_t and delta_it: + rate = delta_t / delta_it + self.avg_time = self.ema( + rate, self.avg_time, self.smoothing) + + self.display() + + # If no `miniters` was specified, adjust automatically to the + # maximum iteration rate seen so far between two prints. + # e.g.: After running `tqdm.update(5)`, subsequent + # calls to `tqdm.update()` will only cause an update after + # at least 5 more iterations. + if self.dynamic_miniters: + if self.maxinterval and delta_t >= self.maxinterval: + if self.mininterval: + self.miniters = delta_it * self.mininterval \ + / delta_t + else: + self.miniters = delta_it * self.maxinterval \ + / delta_t + elif self.smoothing: + self.miniters = self.smoothing * delta_it * \ + (self.mininterval / delta_t + if self.mininterval and delta_t + else 1) + \ + (1 - self.smoothing) * self.miniters + else: + self.miniters = max(self.miniters, delta_it) + + # Store old values for next call + self.last_print_n = self.n + self.last_print_t = cur_t + + def close(self): + # if not self.gui: + # return super(tqdm_gui, self).close() + if self.disable: + return + + self.disable = True + + with self.get_lock(): + self._instances.remove(self) + + # Restore toolbars + self.mpl.rcParams['toolbar'] = self.toolbar + # Return to non-interactive mode + if not self.wasion: + self.plt.ioff() + if not self.leave: + self.plt.close(self.fig) + + def display(self): + n = self.n + cur_t = self._time() + elapsed = cur_t - self.start_t + delta_it = n - self.last_print_n + delta_t = cur_t - self.last_print_t + + # Inline due to multiple calls + total = self.total + xdata = self.xdata + ydata = self.ydata + zdata = self.zdata + ax = self.ax + line1 = self.line1 + line2 = self.line2 + # instantaneous rate + y = delta_it / delta_t + # overall rate + z = n / elapsed + # update line data + xdata.append(n * 100.0 / total if total else cur_t) + ydata.append(y) + zdata.append(z) + + # Discard old values + # xmin, xmax = ax.get_xlim() + # if (not total) and elapsed > xmin * 1.1: + if (not total) and elapsed > 66: + xdata.popleft() + ydata.popleft() + zdata.popleft() + + ymin, ymax = ax.get_ylim() + if y > ymax or z > ymax: + ymax = 1.1 * y + ax.set_ylim(ymin, ymax) + ax.figure.canvas.draw() + + if total: + line1.set_data(xdata, ydata) + line2.set_data(xdata, zdata) + try: + poly_lims = self.hspan.get_xy() + except AttributeError: + self.hspan = self.plt.axhspan( + 0, 0.001, xmin=0, xmax=0, color='g') + poly_lims = self.hspan.get_xy() + poly_lims[0, 1] = ymin + poly_lims[1, 1] = ymax + poly_lims[2] = [n / total, ymax] + poly_lims[3] = [poly_lims[2, 0], ymin] + if len(poly_lims) > 4: + poly_lims[4, 1] = ymin + self.hspan.set_xy(poly_lims) + else: + t_ago = [cur_t - i for i in xdata] + line1.set_data(t_ago, ydata) + line2.set_data(t_ago, zdata) + + ax.set_title(self.format_meter( + n, total, elapsed, 0, + self.desc, self.ascii, self.unit, self.unit_scale, + 1 / self.avg_time if self.avg_time else None, self.bar_format, + self.postfix, self.unit_divisor), + fontname="DejaVu Sans Mono", fontsize=11) + self.plt.pause(1e-9) + + +def tgrange(*args, **kwargs): + """ + A shortcut for `tqdm.gui.tqdm(xrange(*args), **kwargs)`. + On Python3+, `range` is used instead of `xrange`. + """ + return tqdm_gui(_range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_gui +trange = tgrange diff --git a/tqdm/gui.pyc b/tqdm/gui.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0b0fe2d4b82421d0f882bf91c3ee7011377ec4a GIT binary patch literal 7373 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 literal 0 HcmV?d00001 diff --git a/tqdm/keras.py b/tqdm/keras.py new file mode 100644 index 0000000..080aef6 --- /dev/null +++ b/tqdm/keras.py @@ -0,0 +1,93 @@ +from __future__ import absolute_import, division +from .auto import tqdm as tqdm_auto +from copy import deepcopy +try: + import keras +except ImportError as e: + try: + from tensorflow import keras + except ImportError: + raise e +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['TqdmCallback'] + + +class TqdmCallback(keras.callbacks.Callback): + """`keras` callback for epoch and batch progress""" + @staticmethod + def bar2callback(bar, pop=None, delta=(lambda logs: 1)): + def callback(_, logs=None): + n = delta(logs) + if logs: + if pop: + logs = deepcopy(logs) + [logs.pop(i, 0) for i in pop] + bar.set_postfix(logs, refresh=False) + bar.update(n) + + return callback + + def __init__(self, epochs=None, data_size=None, batch_size=None, verbose=1, + tqdm_class=tqdm_auto): + """ + Parameters + ---------- + epochs : int, optional + data_size : int, optional + Number of training pairs. + batch_size : int, optional + Number of training pairs per batch. + verbose : int + 0: epoch, 1: batch (transient), 2: batch. [default: 1]. + Will be set to `0` unless both `data_size` and `batch_size` + are given. + tqdm_class : optional + `tqdm` class to use for bars [default: `tqdm.auto.tqdm`]. + """ + self.tqdm_class = tqdm_class + self.epoch_bar = tqdm_class(total=epochs, unit='epoch') + self.on_epoch_end = self.bar2callback(self.epoch_bar) + if data_size and batch_size: + self.batches = batches = (data_size + batch_size - 1) // batch_size + else: + self.batches = batches = None + self.verbose = verbose + if verbose == 1: + self.batch_bar = tqdm_class(total=batches, unit='batch', + leave=False) + self.on_batch_end = self.bar2callback( + self.batch_bar, + pop=['batch', 'size'], + delta=lambda logs: logs.get('size', 1)) + + def on_train_begin(self, *_, **__): + params = self.params.get + auto_total = params('epochs', params('nb_epoch', None)) + if auto_total is not None: + self.epoch_bar.reset(total=auto_total) + + def on_epoch_begin(self, *_, **__): + if self.verbose: + params = self.params.get + total = params('samples', params( + 'nb_sample', params('steps', None))) or self.batches + if self.verbose == 2: + if hasattr(self, 'batch_bar'): + self.batch_bar.close() + self.batch_bar = self.tqdm_class( + total=total, unit='batch', leave=True, + unit_scale=1 / (params('batch_size', 1) or 1)) + self.on_batch_end = self.bar2callback( + self.batch_bar, + pop=['batch', 'size'], + delta=lambda logs: logs.get('size', 1)) + elif self.verbose == 1: + self.batch_bar.unit_scale = 1 / (params('batch_size', 1) or 1) + self.batch_bar.reset(total=total) + else: + raise KeyError('Unknown verbosity') + + def on_train_end(self, *_, **__): + if self.verbose: + self.batch_bar.close() + self.epoch_bar.close() diff --git a/tqdm/notebook.py b/tqdm/notebook.py new file mode 100644 index 0000000..1d3204f --- /dev/null +++ b/tqdm/notebook.py @@ -0,0 +1,277 @@ +""" +IPython/Jupyter Notebook progressbar decorator for iterators. +Includes a default (x)range iterator printing to stderr. + +Usage: + >>> from tqdm.notebook import trange[, tqdm] + >>> for i in trange(10): #same as: for i in tqdm(xrange(10)) + ... ... +""" +# future division is important to divide integers and get as +# a result precise floating numbers (instead of truncated int) +from __future__ import division, absolute_import +# import compatibility functions and utilities +import sys +from .utils import _range +# to inherit from the tqdm class +from .std import tqdm as std_tqdm + + +if True: # pragma: no cover + # import IPython/Jupyter base widget and display utilities + IPY = 0 + IPYW = 0 + try: # IPython 4.x + import ipywidgets + IPY = 4 + try: + IPYW = int(ipywidgets.__version__.split('.')[0]) + except AttributeError: # __version__ may not exist in old versions + pass + except ImportError: # IPython 3.x / 2.x + IPY = 32 + import warnings + with warnings.catch_warnings(): + warnings.filterwarnings( + 'ignore', + message=".*The `IPython.html` package has been deprecated.*") + try: + import IPython.html.widgets as ipywidgets + except ImportError: + pass + + try: # IPython 4.x / 3.x + if IPY == 32: + from IPython.html.widgets import FloatProgress as IProgress + from IPython.html.widgets import HBox, HTML + IPY = 3 + else: + from ipywidgets import FloatProgress as IProgress + from ipywidgets import HBox, HTML + except ImportError: + try: # IPython 2.x + from IPython.html.widgets import FloatProgressWidget as IProgress + from IPython.html.widgets import ContainerWidget as HBox + from IPython.html.widgets import HTML + IPY = 2 + except ImportError: + IPY = 0 + + try: + from IPython.display import display # , clear_output + except ImportError: + pass + + # HTML encoding + try: # Py3 + from html import escape + except ImportError: # Py2 + from cgi import escape + + +__author__ = {"github.com/": ["lrq3000", "casperdcl", "alexanderkuk"]} +__all__ = ['tqdm_notebook', 'tnrange', 'tqdm', 'trange'] + + +class tqdm_notebook(std_tqdm): + """ + Experimental IPython/Jupyter Notebook widget using tqdm! + """ + + @staticmethod + def status_printer(_, total=None, desc=None, ncols=None): + """ + Manage the printing of an IPython/Jupyter Notebook progress bar widget. + """ + # Fallback to text bar if there's no total + # DEPRECATED: replaced with an 'info' style bar + # if not total: + # return super(tqdm_notebook, tqdm_notebook).status_printer(file) + + # fp = file + + # Prepare IPython progress bar + try: + if total: + pbar = IProgress(min=0, max=total) + else: # No total? Show info style bar with no progress tqdm status + pbar = IProgress(min=0, max=1) + pbar.value = 1 + pbar.bar_style = 'info' + except NameError: + # #187 #451 #558 + raise ImportError( + "FloatProgress not found. Please update jupyter and ipywidgets." + " See https://ipywidgets.readthedocs.io/en/stable" + "/user_install.html") + + if desc: + pbar.description = desc + if IPYW >= 7: + pbar.style.description_width = 'initial' + # Prepare status text + ptext = HTML() + # Only way to place text to the right of the bar is to use a container + container = HBox(children=[pbar, ptext]) + # Prepare layout + if ncols is not None: # use default style of ipywidgets + # ncols could be 100, "100px", "100%" + ncols = str(ncols) # ipywidgets only accepts string + try: + if int(ncols) > 0: # isnumeric and positive + ncols += 'px' + except ValueError: + pass + pbar.layout.flex = '2' + container.layout.width = ncols + container.layout.display = 'inline-flex' + container.layout.flex_flow = 'row wrap' + display(container) + + return container + + def display(self, msg=None, pos=None, + # additional signals + close=False, bar_style=None): + # Note: contrary to native tqdm, msg='' does NOT clear bar + # goal is to keep all infos if error happens so user knows + # at which iteration the loop failed. + + # Clear previous output (really necessary?) + # clear_output(wait=1) + + if not msg and not close: + msg = self.__repr__() + + pbar, ptext = self.container.children + pbar.value = self.n + + if msg: + # html escape special characters (like '&') + if '' in msg: + left, right = map(escape, msg.split('', 1)) + else: + left, right = '', escape(msg) + + # remove inesthetical pipes + if left and left[-1] == '|': + left = left[:-1] + if right and right[0] == '|': + right = right[1:] + + # Update description + pbar.description = left + if IPYW >= 7: + pbar.style.description_width = 'initial' + + # never clear the bar (signal: msg='') + if right: + ptext.value = right + + # Change bar style + if bar_style: + # Hack-ish way to avoid the danger bar_style being overridden by + # success because the bar gets closed after the error... + if not (pbar.bar_style == 'danger' and bar_style == 'success'): + pbar.bar_style = bar_style + + # Special signal to close the bar + if close and pbar.bar_style != 'danger': # hide only if no error + try: + self.container.close() + except AttributeError: + self.container.visible = False + + def __init__(self, *args, **kwargs): + # Setup default output + file_kwarg = kwargs.get('file', sys.stderr) + if file_kwarg is sys.stderr or file_kwarg is None: + kwargs['file'] = sys.stdout # avoid the red block in IPython + + # Initialize parent class + avoid printing by using gui=True + kwargs['gui'] = True + kwargs.setdefault('bar_format', '{l_bar}{bar}{r_bar}') + kwargs['bar_format'] = kwargs['bar_format'].replace('{bar}', '') + # convert disable = None to False + kwargs['disable'] = bool(kwargs.get('disable', False)) + super(tqdm_notebook, self).__init__(*args, **kwargs) + if self.disable or not kwargs['gui']: + self.sp = lambda *_, **__: None + return + + # Get bar width + self.ncols = '100%' if self.dynamic_ncols else kwargs.get("ncols", None) + + # Replace with IPython progress bar display (with correct total) + unit_scale = 1 if self.unit_scale is True else self.unit_scale or 1 + total = self.total * unit_scale if self.total else self.total + self.container = self.status_printer( + self.fp, total, self.desc, self.ncols) + self.sp = self.display + + # Print initial bar state + if not self.disable: + self.display() + + def __iter__(self, *args, **kwargs): + try: + for obj in super(tqdm_notebook, self).__iter__(*args, **kwargs): + # return super(tqdm...) will not catch exception + yield obj + # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt + except: # NOQA + self.sp(bar_style='danger') + raise + + def update(self, *args, **kwargs): + try: + super(tqdm_notebook, self).update(*args, **kwargs) + except Exception as exc: + # cannot catch KeyboardInterrupt when using manual tqdm + # as the interrupt will most likely happen on another statement + self.sp(bar_style='danger') + raise exc + + def close(self, *args, **kwargs): + super(tqdm_notebook, self).close(*args, **kwargs) + # Try to detect if there was an error or KeyboardInterrupt + # in manual mode: if n < total, things probably got wrong + if self.total and self.n < self.total: + self.sp(bar_style='danger') + else: + if self.leave: + self.sp(bar_style='success') + else: + self.sp(close=True) + + def moveto(self, *args, **kwargs): + # void -> avoid extraneous `\n` in IPython output cell + return + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Consider combining with `leave=True`. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + if total is not None: + pbar, _ = self.container.children + pbar.max = total + return super(tqdm_notebook, self).reset(total=total) + + +def tnrange(*args, **kwargs): + """ + A shortcut for `tqdm.notebook.tqdm(xrange(*args), **kwargs)`. + On Python3+, `range` is used instead of `xrange`. + """ + return tqdm_notebook(_range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_notebook +trange = tnrange diff --git a/tqdm/std.py b/tqdm/std.py new file mode 100644 index 0000000..0a94953 --- /dev/null +++ b/tqdm/std.py @@ -0,0 +1,1470 @@ +""" +Customisable progressbar decorator for iterators. +Includes a default (x)range iterator printing to stderr. + +Usage: + >>> from tqdm import trange[, tqdm] + >>> for i in trange(10): #same as: for i in tqdm(xrange(10)) + ... ... +""" +from __future__ import absolute_import, division +# compatibility functions and utilities +from .utils import _supports_unicode, _environ_cols_wrapper, _range, _unich, \ + _term_move_up, _unicode, WeakSet, _basestring, _OrderedDict, \ + Comparable, _is_ascii, FormatReplace, disp_len, disp_trim, \ + SimpleTextIOWrapper, CallbackIOWrapper +from ._monitor import TMonitor +# native libraries +from contextlib import contextmanager +import sys +from numbers import Number +from time import time +# For parallelism safety +import threading as th +from warnings import warn + +__author__ = {"github.com/": ["noamraph", "obiwanus", "kmike", "hadim", + "casperdcl", "lrq3000"]} +__all__ = ['tqdm', 'trange', + 'TqdmTypeError', 'TqdmKeyError', 'TqdmWarning', + 'TqdmExperimentalWarning', 'TqdmDeprecationWarning', + 'TqdmMonitorWarning'] + + +class TqdmTypeError(TypeError): + pass + + +class TqdmKeyError(KeyError): + pass + + +class TqdmWarning(Warning): + """base class for all tqdm warnings. + + Used for non-external-code-breaking errors, such as garbled printing. + """ + def __init__(self, msg, fp_write=None, *a, **k): + if fp_write is not None: + fp_write("\n" + self.__class__.__name__ + ": " + + str(msg).rstrip() + '\n') + else: + super(TqdmWarning, self).__init__(msg, *a, **k) + + +class TqdmExperimentalWarning(TqdmWarning, FutureWarning): + """beta feature, unstable API and behaviour""" + pass + + +class TqdmDeprecationWarning(TqdmWarning, DeprecationWarning): + # not suppressed if raised + pass + + +class TqdmMonitorWarning(TqdmWarning, RuntimeWarning): + """tqdm monitor errors which do not affect external functionality""" + pass + + +class TqdmDefaultWriteLock(object): + """ + Provide a default write lock for thread and multiprocessing safety. + Works only on platforms supporting `fork` (so Windows is excluded). + You must initialise a `tqdm` or `TqdmDefaultWriteLock` instance + before forking in order for the write lock to work. + On Windows, you need to supply the lock from the parent to the children as + an argument to joblib or the parallelism lib you use. + """ + def __init__(self): + # Create global parallelism locks to avoid racing issues with parallel + # bars works only if fork available (Linux/MacOSX, but not Windows) + self.create_mp_lock() + self.create_th_lock() + cls = type(self) + self.locks = [lk for lk in [cls.mp_lock, cls.th_lock] if lk is not None] + + def acquire(self, *a, **k): + for lock in self.locks: + lock.acquire(*a, **k) + + def release(self): + for lock in self.locks[::-1]: # Release in inverse order of acquisition + lock.release() + + def __enter__(self): + self.acquire() + + def __exit__(self, *exc): + self.release() + + @classmethod + def create_mp_lock(cls): + if not hasattr(cls, 'mp_lock'): + try: + from multiprocessing import RLock + cls.mp_lock = RLock() # multiprocessing lock + except ImportError: # pragma: no cover + cls.mp_lock = None + except OSError: # pragma: no cover + cls.mp_lock = None + + @classmethod + def create_th_lock(cls): + if not hasattr(cls, 'th_lock'): + try: + cls.th_lock = th.RLock() # thread lock + except OSError: # pragma: no cover + cls.th_lock = None + + +# Create a thread lock before instantiation so that no setup needs to be done +# before running in a multithreaded environment. +# Do not create the multiprocessing lock because it sets the multiprocessing +# context and does not allow the user to use 'spawn' or 'forkserver' methods. +TqdmDefaultWriteLock.create_th_lock() + + +class Bar(object): + """ + `str.format`-able bar with format specifiers: `[width][type]` + + - `width` + + unspecified (default): use `self.default_len` + + `int >= 0`: overrides `self.default_len` + + `int < 0`: subtract from `self.default_len` + - `type` + + `a`: ascii (`charset=self.ASCII` override) + + `u`: unicode (`charset=self.UTF` override) + + `b`: blank (`charset=" "` override) + """ + ASCII = " 123456789#" + UTF = u" " + u''.join(map(_unich, range(0x258F, 0x2587, -1))) + BLANK = " " + + def __init__(self, frac, default_len=10, charset=UTF): + if not (0 <= frac <= 1): + warn("clamping frac to range [0, 1]", TqdmWarning, stacklevel=2) + frac = max(0, min(1, frac)) + assert default_len > 0 + self.frac = frac + self.default_len = default_len + self.charset = charset + + def __format__(self, format_spec): + if format_spec: + _type = format_spec[-1].lower() + try: + charset = dict(a=self.ASCII, u=self.UTF, b=self.BLANK)[_type] + except KeyError: + charset = self.charset + else: + format_spec = format_spec[:-1] + if format_spec: + N_BARS = int(format_spec) + if N_BARS < 0: + N_BARS += self.default_len + else: + N_BARS = self.default_len + else: + charset = self.charset + N_BARS = self.default_len + + nsyms = len(charset) - 1 + bar_length, frac_bar_length = divmod( + int(self.frac * N_BARS * nsyms), nsyms) + + bar = charset[-1] * bar_length + frac_bar = charset[frac_bar_length] + + # whitespace padding + if bar_length < N_BARS: + return bar + frac_bar + \ + charset[0] * (N_BARS - bar_length - 1) + return bar + + +class tqdm(Comparable): + """ + Decorate an iterable object, returning an iterator which acts exactly + like the original iterable, but prints a dynamically updating + progressbar every time a value is requested. + """ + + monitor_interval = 10 # set to 0 to disable the thread + monitor = None + + @staticmethod + def format_sizeof(num, suffix='', divisor=1000): + """ + Formats a number (greater than unity) with SI Order of Magnitude + prefixes. + + Parameters + ---------- + num : float + Number ( >= 1) to format. + suffix : str, optional + Post-postfix [default: '']. + divisor : float, optional + Divisor between prefixes [default: 1000]. + + Returns + ------- + out : str + Number with Order of Magnitude SI unit postfix. + """ + for unit in ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z']: + if abs(num) < 999.5: + if abs(num) < 99.95: + if abs(num) < 9.995: + return '{0:1.2f}'.format(num) + unit + suffix + return '{0:2.1f}'.format(num) + unit + suffix + return '{0:3.0f}'.format(num) + unit + suffix + num /= divisor + return '{0:3.1f}Y'.format(num) + suffix + + @staticmethod + def format_interval(t): + """ + Formats a number of seconds as a clock time, [H:]MM:SS + + Parameters + ---------- + t : int + Number of seconds. + + Returns + ------- + out : str + [H:]MM:SS + """ + mins, s = divmod(int(t), 60) + h, m = divmod(mins, 60) + if h: + return '{0:d}:{1:02d}:{2:02d}'.format(h, m, s) + else: + return '{0:02d}:{1:02d}'.format(m, s) + + @staticmethod + def format_num(n): + """ + Intelligent scientific notation (.3g). + + Parameters + ---------- + n : int or float or Numeric + A Number. + + Returns + ------- + out : str + Formatted number. + """ + f = '{0:.3g}'.format(n).replace('+0', '+').replace('-0', '-') + n = str(n) + return f if len(f) < len(n) else n + + @staticmethod + def ema(x, mu=None, alpha=0.3): + """ + Exponential moving average: smoothing to give progressively lower + weights to older values. + + Parameters + ---------- + x : float + New value to include in EMA. + mu : float, optional + Previous EMA value. + alpha : float, optional + Smoothing factor in range [0, 1], [default: 0.3]. + Increase to give more weight to recent values. + Ranges from 0 (yields mu) to 1 (yields x). + """ + return x if mu is None else (alpha * x) + (1 - alpha) * mu + + @staticmethod + def status_printer(file): + """ + Manage the printing and in-place updating of a line of characters. + Note that if the string is longer than a line, then in-place + updating may not work (it will print a new line at each refresh). + """ + fp = file + fp_flush = getattr(fp, 'flush', lambda: None) # pragma: no cover + + def fp_write(s): + fp.write(_unicode(s)) + fp_flush() + + last_len = [0] + + def print_status(s): + len_s = len(s) + fp_write('\r' + s + (' ' * max(last_len[0] - len_s, 0))) + last_len[0] = len_s + + return print_status + + @staticmethod + def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False, + unit='it', unit_scale=False, rate=None, bar_format=None, + postfix=None, unit_divisor=1000, **extra_kwargs): + """ + Return a string-based progress bar given some parameters + + Parameters + ---------- + n : int or float + Number of finished iterations. + total : int or float + The expected total number of iterations. If meaningless (None), + only basic progress statistics are displayed (no ETA). + elapsed : float + Number of seconds passed since start. + ncols : int, optional + The width of the entire output message. If specified, + dynamically resizes `{bar}` to stay within this bound + [default: None]. If `0`, will not print any bar (only stats). + The fallback is `{bar:10}`. + prefix : str, optional + Prefix message (included in total width) [default: '']. + Use as {desc} in bar_format string. + ascii : bool, optional or str, optional + If not set, use unicode (smooth blocks) to fill the meter + [default: False]. The fallback is to use ASCII characters + " 123456789#". + unit : str, optional + The iteration unit [default: 'it']. + unit_scale : bool or int or float, optional + If 1 or True, the number of iterations will be printed with an + appropriate SI metric prefix (k = 10^3, M = 10^6, etc.) + [default: False]. If any other non-zero number, will scale + `total` and `n`. + rate : float, optional + Manual override for iteration rate. + If [default: None], uses n/elapsed. + bar_format : str, optional + Specify a custom bar string formatting. May impact performance. + [default: '{l_bar}{bar}{r_bar}'], where + l_bar='{desc}: {percentage:3.0f}%|' and + r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' + '{rate_fmt}{postfix}]' + Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, + percentage, elapsed, elapsed_s, ncols, desc, unit, + rate, rate_fmt, rate_noinv, rate_noinv_fmt, + rate_inv, rate_inv_fmt, postfix, unit_divisor, + remaining, remaining_s. + Note that a trailing ": " is automatically removed after {desc} + if the latter is empty. + postfix : *, optional + Similar to `prefix`, but placed at the end + (e.g. for additional stats). + Note: postfix is usually a string (not a dict) for this method, + and will if possible be set to postfix = ', ' + postfix. + However other types are supported (#382). + unit_divisor : float, optional + [default: 1000], ignored unless `unit_scale` is True. + + Returns + ------- + out : Formatted meter and stats, ready to display. + """ + + # sanity check: total + if total and n >= (total + 0.5): # allow float imprecision (#849) + total = None + + # apply custom scale if necessary + if unit_scale and unit_scale not in (True, 1): + if total: + total *= unit_scale + n *= unit_scale + if rate: + rate *= unit_scale # by default rate = 1 / self.avg_time + unit_scale = False + + elapsed_str = tqdm.format_interval(elapsed) + + # if unspecified, attempt to use rate = average speed + # (we allow manual override since predicting time is an arcane art) + if rate is None and elapsed: + rate = n / elapsed + inv_rate = 1 / rate if rate else None + format_sizeof = tqdm.format_sizeof + rate_noinv_fmt = ((format_sizeof(rate) if unit_scale else + '{0:5.2f}'.format(rate)) + if rate else '?') + unit + '/s' + rate_inv_fmt = ((format_sizeof(inv_rate) if unit_scale else + '{0:5.2f}'.format(inv_rate)) + if inv_rate else '?') + 's/' + unit + rate_fmt = rate_inv_fmt if inv_rate and inv_rate > 1 else rate_noinv_fmt + + if unit_scale: + n_fmt = format_sizeof(n, divisor=unit_divisor) + total_fmt = format_sizeof(total, divisor=unit_divisor) \ + if total is not None else '?' + else: + n_fmt = str(n) + total_fmt = str(total) if total is not None else '?' + + try: + postfix = ', ' + postfix if postfix else '' + except TypeError: + pass + + remaining = (total - n) / rate if rate and total else 0 + remaining_str = tqdm.format_interval(remaining) if rate else '?' + + # format the stats displayed to the left and right sides of the bar + if prefix: + # old prefix setup work around + bool_prefix_colon_already = (prefix[-2:] == ": ") + l_bar = prefix if bool_prefix_colon_already else prefix + ": " + else: + l_bar = '' + + r_bar = '| {0}/{1} [{2}<{3}, {4}{5}]'.format( + n_fmt, total_fmt, elapsed_str, remaining_str, rate_fmt, postfix) + + # Custom bar formatting + # Populate a dict with all available progress indicators + format_dict = dict( + # slight extension of self.format_dict + n=n, n_fmt=n_fmt, total=total, total_fmt=total_fmt, + elapsed=elapsed_str, elapsed_s=elapsed, + ncols=ncols, desc=prefix or '', unit=unit, + rate=inv_rate if inv_rate and inv_rate > 1 else rate, + rate_fmt=rate_fmt, rate_noinv=rate, + rate_noinv_fmt=rate_noinv_fmt, rate_inv=inv_rate, + rate_inv_fmt=rate_inv_fmt, + postfix=postfix, unit_divisor=unit_divisor, + # plus more useful definitions + remaining=remaining_str, remaining_s=remaining, + l_bar=l_bar, r_bar=r_bar, + **extra_kwargs) + + # total is known: we can predict some stats + if total: + # fractional and percentage progress + frac = n / total + percentage = frac * 100 + + l_bar += '{0:3.0f}%|'.format(percentage) + + if ncols == 0: + return l_bar[:-1] + r_bar[1:] + + format_dict.update(l_bar=l_bar) + if bar_format: + format_dict.update(percentage=percentage) + + # auto-remove colon for empty `desc` + if not prefix: + bar_format = bar_format.replace("{desc}: ", '') + else: + bar_format = "{l_bar}{bar}{r_bar}" + + full_bar = FormatReplace() + try: + nobar = bar_format.format(bar=full_bar, **format_dict) + except UnicodeEncodeError: + bar_format = _unicode(bar_format) + nobar = bar_format.format(bar=full_bar, **format_dict) + if not full_bar.format_called: + # no {bar}, we can just format and return + return nobar + + # Formatting progress bar space available for bar's display + full_bar = Bar( + frac, + max(1, ncols - disp_len(nobar)) + if ncols else 10, + charset=Bar.ASCII if ascii is True else ascii or Bar.UTF) + if not _is_ascii(full_bar.charset) and _is_ascii(bar_format): + bar_format = _unicode(bar_format) + res = bar_format.format(bar=full_bar, **format_dict) + return disp_trim(res, ncols) if ncols else res + + elif bar_format: + # user-specified bar_format but no total + l_bar += '|' + format_dict.update(l_bar=l_bar, percentage=0) + full_bar = FormatReplace() + nobar = bar_format.format(bar=full_bar, **format_dict) + if not full_bar.format_called: + return nobar + full_bar = Bar( + 0, + max(1, ncols - disp_len(nobar)) + if ncols else 10, + charset=Bar.BLANK) + res = bar_format.format(bar=full_bar, **format_dict) + return disp_trim(res, ncols) if ncols else res + else: + # no total: no progressbar, ETA, just progress stats + return ((prefix + ": ") if prefix else '') + \ + '{0}{1} [{2}, {3}{4}]'.format( + n_fmt, unit, elapsed_str, rate_fmt, postfix) + + def __new__(cls, *args, **kwargs): + # Create a new instance + instance = object.__new__(cls) + # Construct the lock if it does not exist + with cls.get_lock(): + # Add to the list of instances + if not hasattr(cls, '_instances'): + cls._instances = WeakSet() + cls._instances.add(instance) + # Create the monitoring thread + if cls.monitor_interval and (cls.monitor is None or not + cls.monitor.report()): + try: + cls.monitor = TMonitor(cls, cls.monitor_interval) + except Exception as e: # pragma: nocover + warn("tqdm:disabling monitor support" + " (monitor_interval = 0) due to:\n" + str(e), + TqdmMonitorWarning, stacklevel=2) + cls.monitor_interval = 0 + # Return the instance + return instance + + @classmethod + def _get_free_pos(cls, instance=None): + """Skips specified instance.""" + positions = set(abs(inst.pos) for inst in cls._instances + if inst is not instance and hasattr(inst, "pos")) + return min(set(range(len(positions) + 1)).difference(positions)) + + @classmethod + def _decr_instances(cls, instance): + """ + Remove from list and reposition other bars + so that newer bars won't overlap previous bars + """ + with cls._lock: + try: + cls._instances.remove(instance) + except KeyError: + # if not instance.gui: # pragma: no cover + # raise + pass # py2: maybe magically removed already + # else: + if not instance.gui: + for inst in cls._instances: + # negative `pos` means fixed + if hasattr(inst, "pos") and inst.pos > abs(instance.pos): + inst.clear(nolock=True) + inst.pos -= 1 + # TODO: check this doesn't overwrite another fixed bar + # Kill monitor if no instances are left + if not cls._instances and cls.monitor: + try: + cls.monitor.exit() + del cls.monitor + except AttributeError: # pragma: nocover + pass + else: + cls.monitor = None + + @classmethod + def write(cls, s, file=None, end="\n", nolock=False): + """Print a message via tqdm (without overlap with bars).""" + fp = file if file is not None else sys.stdout + with cls.external_write_mode(file=file, nolock=nolock): + # Write the message + fp.write(s) + fp.write(end) + + @classmethod + @contextmanager + def external_write_mode(cls, file=None, nolock=False): + """ + Disable tqdm within context and refresh tqdm when exits. + Useful when writing to standard output stream + """ + fp = file if file is not None else sys.stdout + + if not nolock: + cls.get_lock().acquire() + # Clear all bars + inst_cleared = [] + for inst in getattr(cls, '_instances', []): + # Clear instance if in the target output file + # or if write output + tqdm output are both either + # sys.stdout or sys.stderr (because both are mixed in terminal) + if hasattr(inst, "start_t") and (inst.fp == fp or all( + f in (sys.stdout, sys.stderr) for f in (fp, inst.fp))): + inst.clear(nolock=True) + inst_cleared.append(inst) + yield + # Force refresh display of bars we cleared + for inst in inst_cleared: + inst.refresh(nolock=True) + if not nolock: + cls._lock.release() + + @classmethod + def set_lock(cls, lock): + """Set the global lock.""" + cls._lock = lock + + @classmethod + def get_lock(cls): + """Get the global lock. Construct it if it does not exist.""" + if not hasattr(cls, '_lock'): + cls._lock = TqdmDefaultWriteLock() + return cls._lock + + @classmethod + def pandas(tclass, *targs, **tkwargs): + """ + Registers the given `tqdm` class with + pandas.core. + ( frame.DataFrame + | series.Series + | groupby.(generic.)DataFrameGroupBy + | groupby.(generic.)SeriesGroupBy + ).progress_apply + + A new instance will be create every time `progress_apply` is called, + and each instance will automatically close() upon completion. + + Parameters + ---------- + targs, tkwargs : arguments for the tqdm instance + + Examples + -------- + >>> import pandas as pd + >>> import numpy as np + >>> from tqdm import tqdm + >>> from tqdm.gui import tqdm as tqdm_gui + >>> + >>> df = pd.DataFrame(np.random.randint(0, 100, (100000, 6))) + >>> tqdm.pandas(ncols=50) # can use tqdm_gui, optional kwargs, etc + >>> # Now you can use `progress_apply` instead of `apply` + >>> df.groupby(0).progress_apply(lambda x: x**2) + + References + ---------- + https://stackoverflow.com/questions/18603270/ + progress-indicator-during-pandas-operations-python + """ + from pandas.core.frame import DataFrame + from pandas.core.series import Series + try: + from pandas import Panel + except ImportError: # TODO: pandas>0.25.2 + Panel = None + try: # pandas>=1.0.0 + from pandas.core.window.rolling import _Rolling_and_Expanding + except ImportError: + try: # pandas>=0.18.0 + from pandas.core.window import _Rolling_and_Expanding + except ImportError: # pragma: no cover + _Rolling_and_Expanding = None + try: # pandas>=0.25.0 + from pandas.core.groupby.generic import DataFrameGroupBy, \ + SeriesGroupBy # , NDFrameGroupBy + except ImportError: + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import DataFrameGroupBy, \ + SeriesGroupBy + except ImportError: + from pandas.core.groupby import DataFrameGroupBy, \ + SeriesGroupBy + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import GroupBy + except ImportError: + from pandas.core.groupby import GroupBy + + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import PanelGroupBy + except ImportError: + try: + from pandas.core.groupby import PanelGroupBy + except ImportError: # pandas>=0.25.0 + PanelGroupBy = None + + deprecated_t = [tkwargs.pop('deprecated_t', None)] + + def inner_generator(df_function='apply'): + def inner(df, func, *args, **kwargs): + """ + Parameters + ---------- + df : (DataFrame|Series)[GroupBy] + Data (may be grouped). + func : function + To be applied on the (grouped) data. + **kwargs : optional + Transmitted to `df.apply()`. + """ + + # Precompute total iterations + total = tkwargs.pop("total", getattr(df, 'ngroups', None)) + if total is None: # not grouped + if df_function == 'applymap': + total = df.size + elif isinstance(df, Series): + total = len(df) + elif _Rolling_and_Expanding is None or \ + not isinstance(df, _Rolling_and_Expanding): + # DataFrame or Panel + axis = kwargs.get('axis', 0) + if axis == 'index': + axis = 0 + elif axis == 'columns': + axis = 1 + # when axis=0, total is shape[axis1] + total = df.size // df.shape[axis] + + # Init bar + if deprecated_t[0] is not None: + t = deprecated_t[0] + deprecated_t[0] = None + else: + t = tclass(*targs, total=total, **tkwargs) + + if len(args) > 0: + # *args intentionally not supported (see #244, #299) + TqdmDeprecationWarning( + "Except func, normal arguments are intentionally" + + " not supported by" + + " `(DataFrame|Series|GroupBy).progress_apply`." + + " Use keyword arguments instead.", + fp_write=getattr(t.fp, 'write', sys.stderr.write)) + + try: + func = df._is_builtin_func(func) + except TypeError: + pass + + # Define bar updating wrapper + def wrapper(*args, **kwargs): + # update tbar correctly + # it seems `pandas apply` calls `func` twice + # on the first column/row to decide whether it can + # take a fast or slow code path; so stop when t.total==t.n + t.update(n=1 if not t.total or t.n < t.total else 0) + return func(*args, **kwargs) + + # Apply the provided function (in **kwargs) + # on the df using our wrapper (which provides bar updating) + result = getattr(df, df_function)(wrapper, **kwargs) + + # Close bar and return pandas calculation result + t.close() + return result + + return inner + + # Monkeypatch pandas to provide easy methods + # Enable custom tqdm progress in pandas! + Series.progress_apply = inner_generator() + SeriesGroupBy.progress_apply = inner_generator() + Series.progress_map = inner_generator('map') + SeriesGroupBy.progress_map = inner_generator('map') + + DataFrame.progress_apply = inner_generator() + DataFrameGroupBy.progress_apply = inner_generator() + DataFrame.progress_applymap = inner_generator('applymap') + + if Panel is not None: + Panel.progress_apply = inner_generator() + if PanelGroupBy is not None: + PanelGroupBy.progress_apply = inner_generator() + + GroupBy.progress_apply = inner_generator() + GroupBy.progress_aggregate = inner_generator('aggregate') + GroupBy.progress_transform = inner_generator('transform') + + if _Rolling_and_Expanding is not None: # pragma: no cover + _Rolling_and_Expanding.progress_apply = inner_generator() + + def __init__(self, iterable=None, desc=None, total=None, leave=True, + file=None, ncols=None, mininterval=0.1, maxinterval=10.0, + miniters=None, ascii=None, disable=False, unit='it', + unit_scale=False, dynamic_ncols=False, smoothing=0.3, + bar_format=None, initial=0, position=None, postfix=None, + unit_divisor=1000, write_bytes=None, lock_args=None, + gui=False, **kwargs): + """ + Parameters + ---------- + iterable : iterable, optional + Iterable to decorate with a progressbar. + Leave blank to manually manage the updates. + desc : str, optional + Prefix for the progressbar. + total : int or float, optional + The number of expected iterations. If unspecified, + len(iterable) is used if possible. If float("inf") or as a last + resort, only basic progress statistics are displayed + (no ETA, no progressbar). + If `gui` is True and this parameter needs subsequent updating, + specify an initial arbitrary large positive number, + e.g. 9e9. + leave : bool, optional + If [default: True], keeps all traces of the progressbar + upon termination of iteration. + If `None`, will leave only if `position` is `0`. + file : `io.TextIOWrapper` or `io.StringIO`, optional + Specifies where to output the progress messages + (default: sys.stderr). Uses `file.write(str)` and `file.flush()` + methods. For encoding, see `write_bytes`. + ncols : int, optional + The width of the entire output message. If specified, + dynamically resizes the progressbar to stay within this bound. + If unspecified, attempts to use environment width. The + fallback is a meter width of 10 and no limit for the counter and + statistics. If 0, will not print any meter (only stats). + mininterval : float, optional + Minimum progress display update interval [default: 0.1] seconds. + maxinterval : float, optional + Maximum progress display update interval [default: 10] seconds. + Automatically adjusts `miniters` to correspond to `mininterval` + after long display update lag. Only works if `dynamic_miniters` + or monitor thread is enabled. + miniters : int or float, optional + Minimum progress display update interval, in iterations. + If 0 and `dynamic_miniters`, will automatically adjust to equal + `mininterval` (more CPU efficient, good for tight loops). + If > 0, will skip display of specified number of iterations. + Tweak this and `mininterval` to get very efficient loops. + If your progress is erratic with both fast and slow iterations + (network, skipping items, etc) you should set miniters=1. + ascii : bool or str, optional + If unspecified or False, use unicode (smooth blocks) to fill + the meter. The fallback is to use ASCII characters " 123456789#". + disable : bool, optional + Whether to disable the entire progressbar wrapper + [default: False]. If set to None, disable on non-TTY. + unit : str, optional + String that will be used to define the unit of each iteration + [default: it]. + unit_scale : bool or int or float, optional + If 1 or True, the number of iterations will be reduced/scaled + automatically and a metric prefix following the + International System of Units standard will be added + (kilo, mega, etc.) [default: False]. If any other non-zero + number, will scale `total` and `n`. + dynamic_ncols : bool, optional + If set, constantly alters `ncols` to the environment (allowing + for window resizes) [default: False]. + smoothing : float, optional + Exponential moving average smoothing factor for speed estimates + (ignored in GUI mode). Ranges from 0 (average speed) to 1 + (current/instantaneous speed) [default: 0.3]. + bar_format : str, optional + Specify a custom bar string formatting. May impact performance. + [default: '{l_bar}{bar}{r_bar}'], where + l_bar='{desc}: {percentage:3.0f}%|' and + r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' + '{rate_fmt}{postfix}]' + Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, + percentage, elapsed, elapsed_s, ncols, desc, unit, + rate, rate_fmt, rate_noinv, rate_noinv_fmt, + rate_inv, rate_inv_fmt, postfix, unit_divisor, + remaining, remaining_s. + Note that a trailing ": " is automatically removed after {desc} + if the latter is empty. + initial : int or float, optional + The initial counter value. Useful when restarting a progress + bar [default: 0]. If using float, consider specifying `{n:.3f}` + or similar in `bar_format`, or specifying `unit_scale`. + position : int, optional + Specify the line offset to print this bar (starting from 0) + Automatic if unspecified. + Useful to manage multiple bars at once (eg, from threads). + postfix : dict or *, optional + Specify additional stats to display at the end of the bar. + Calls `set_postfix(**postfix)` if possible (dict). + unit_divisor : float, optional + [default: 1000], ignored unless `unit_scale` is True. + write_bytes : bool, optional + If (default: None) and `file` is unspecified, + bytes will be written in Python 2. If `True` will also write + bytes. In all other cases will default to unicode. + lock_args : tuple, optional + Passed to `refresh` for intermediate output + (initialisation, iterating, and updating). + gui : bool, optional + WARNING: internal parameter - do not use. + Use tqdm.gui.tqdm(...) instead. If set, will attempt to use + matplotlib animations for a graphical output [default: False]. + + Returns + ------- + out : decorated iterator. + """ + if write_bytes is None: + write_bytes = file is None and sys.version_info < (3,) + + if file is None: + file = sys.stderr + + if write_bytes: + # Despite coercing unicode into bytes, py2 sys.std* streams + # should have bytes written to them. + file = SimpleTextIOWrapper( + file, encoding=getattr(file, 'encoding', None) or 'utf-8') + + if disable is None and hasattr(file, "isatty") and not file.isatty(): + disable = True + + if total is None and iterable is not None: + try: + total = len(iterable) + except (TypeError, AttributeError): + total = None + if total == float("inf"): + # Infinite iterations, behave same as unknown + total = None + + if disable: + self.iterable = iterable + self.disable = disable + with self._lock: + self.pos = self._get_free_pos(self) + self._instances.remove(self) + self.n = initial + self.total = total + self.leave = leave + return + + if kwargs: + self.disable = True + with self._lock: + self.pos = self._get_free_pos(self) + self._instances.remove(self) + raise ( + TqdmDeprecationWarning( + "`nested` is deprecated and automated.\n" + "Use `position` instead for manual control.\n", + fp_write=getattr(file, 'write', sys.stderr.write)) + if "nested" in kwargs else + TqdmKeyError("Unknown argument(s): " + str(kwargs))) + + # Preprocess the arguments + if ((ncols is None) and (file in (sys.stderr, sys.stdout))) or \ + dynamic_ncols: # pragma: no cover + if dynamic_ncols: + dynamic_ncols = _environ_cols_wrapper() + if dynamic_ncols: + ncols = dynamic_ncols(file) + else: + _dynamic_ncols = _environ_cols_wrapper() + if _dynamic_ncols: + ncols = _dynamic_ncols(file) + + if miniters is None: + miniters = 0 + dynamic_miniters = True + else: + dynamic_miniters = False + + if mininterval is None: + mininterval = 0 + + if maxinterval is None: + maxinterval = 0 + + if ascii is None: + ascii = not _supports_unicode(file) + + if bar_format and not ((ascii is True) or _is_ascii(ascii)): + # Convert bar format into unicode since terminal uses unicode + bar_format = _unicode(bar_format) + + if smoothing is None: + smoothing = 0 + + # Store the arguments + self.iterable = iterable + self.desc = desc or '' + self.total = total + self.leave = leave + self.fp = file + self.ncols = ncols + self.mininterval = mininterval + self.maxinterval = maxinterval + self.miniters = miniters + self.dynamic_miniters = dynamic_miniters + self.ascii = ascii + self.disable = disable + self.unit = unit + self.unit_scale = unit_scale + self.unit_divisor = unit_divisor + self.lock_args = lock_args + self.gui = gui + self.dynamic_ncols = dynamic_ncols + self.smoothing = smoothing + self.avg_time = None + self._time = time + self.bar_format = bar_format + self.postfix = None + if postfix: + try: + self.set_postfix(refresh=False, **postfix) + except TypeError: + self.postfix = postfix + + # Init the iterations counters + self.last_print_n = initial + self.n = initial + + # if nested, at initial sp() call we replace '\r' by '\n' to + # not overwrite the outer progress bar + with self._lock: + if position is None: + self.pos = self._get_free_pos(self) + else: # mark fixed positions as negative + self.pos = -position + + if not gui: + # Initialize the screen printer + self.sp = self.status_printer(self.fp) + self.refresh(lock_args=self.lock_args) + + # Init the time counter + self.last_print_t = self._time() + # NB: Avoid race conditions by setting start_t at the very end of init + self.start_t = self.last_print_t + + def __bool__(self): + if self.total is not None: + return self.total > 0 + if self.iterable is None: + raise TypeError('bool() undefined when iterable == total == None') + return bool(self.iterable) + + def __nonzero__(self): + return self.__bool__() + + def __len__(self): + return self.total if self.iterable is None else \ + (self.iterable.shape[0] if hasattr(self.iterable, "shape") + else len(self.iterable) if hasattr(self.iterable, "__len__") + else getattr(self, "total", None)) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + try: + self.close() + except AttributeError: + # maybe eager thread cleanup upon external error + if (exc_type, exc_value, traceback) == (None, None, None): + raise + warn("AttributeError ignored", TqdmWarning, stacklevel=2) + + def __del__(self): + self.close() + + def __repr__(self): + return self.format_meter(**self.format_dict) + + @property + def _comparable(self): + return abs(getattr(self, "pos", 1 << 31)) + + def __hash__(self): + return id(self) + + def __iter__(self): + """Backward-compatibility to use: for x in tqdm(iterable)""" + + # Inlining instance variables as locals (speed optimisation) + iterable = self.iterable + + # If the bar is disabled, then just walk the iterable + # (note: keep this check outside the loop for performance) + if self.disable: + for obj in iterable: + yield obj + return + + mininterval = self.mininterval + maxinterval = self.maxinterval + miniters = self.miniters + dynamic_miniters = self.dynamic_miniters + last_print_t = self.last_print_t + last_print_n = self.last_print_n + n = self.n + smoothing = self.smoothing + avg_time = self.avg_time + time = self._time + + if not hasattr(self, 'sp'): + raise TqdmDeprecationWarning( + "Please use `tqdm.gui.tqdm(...)` instead of" + " `tqdm(..., gui=True)`\n", + fp_write=getattr(self.fp, 'write', sys.stderr.write)) + + for obj in iterable: + yield obj + # Update and possibly print the progressbar. + # Note: does not call self.update(1) for speed optimisation. + n += 1 + # check counter first to avoid calls to time() + if n - last_print_n >= self.miniters: + miniters = self.miniters # watch monitoring thread changes + delta_t = time() - last_print_t + if delta_t >= mininterval: + cur_t = time() + delta_it = n - last_print_n + # EMA (not just overall average) + if smoothing and delta_t and delta_it: + rate = delta_t / delta_it + avg_time = self.ema(rate, avg_time, smoothing) + self.avg_time = avg_time + + self.n = n + self.refresh(lock_args=self.lock_args) + + # If no `miniters` was specified, adjust automatically + # to the max iteration rate seen so far between 2 prints + if dynamic_miniters: + if maxinterval and delta_t >= maxinterval: + # Adjust miniters to time interval by rule of 3 + if mininterval: + # Set miniters to correspond to mininterval + miniters = delta_it * mininterval / delta_t + else: + # Set miniters to correspond to maxinterval + miniters = delta_it * maxinterval / delta_t + elif smoothing: + # EMA-weight miniters to converge + # towards the timeframe of mininterval + rate = delta_it + if mininterval and delta_t: + rate *= mininterval / delta_t + miniters = self.ema(rate, miniters, smoothing) + else: + # Maximum nb of iterations between 2 prints + miniters = max(miniters, delta_it) + + # Store old values for next call + self.n = self.last_print_n = last_print_n = n + self.last_print_t = last_print_t = cur_t + self.miniters = miniters + + # Closing the progress bar. + # Update some internal variables for close(). + self.last_print_n = last_print_n + self.n = n + self.miniters = miniters + self.close() + + def update(self, n=1): + """ + Manually update the progress bar, useful for streams + such as reading files. + E.g.: + >>> t = tqdm(total=filesize) # Initialise + >>> for current_buffer in stream: + ... ... + ... t.update(len(current_buffer)) + >>> t.close() + The last line is highly recommended, but possibly not necessary if + `t.update()` will be called in such a way that `filesize` will be + exactly reached and printed. + + Parameters + ---------- + n : int or float, optional + Increment to add to the internal counter of iterations + [default: 1]. If using float, consider specifying `{n:.3f}` + or similar in `bar_format`, or specifying `unit_scale`. + """ + # N.B.: see __iter__() for more comments. + if self.disable: + return + + if n < 0: + self.last_print_n += n # for auto-refresh logic to work + self.n += n + + # check counter first to reduce calls to time() + if self.n - self.last_print_n >= self.miniters: + delta_t = self._time() - self.last_print_t + if delta_t >= self.mininterval: + cur_t = self._time() + delta_it = self.n - self.last_print_n # >= n + # elapsed = cur_t - self.start_t + # EMA (not just overall average) + if self.smoothing and delta_t and delta_it: + rate = delta_t / delta_it + self.avg_time = self.ema( + rate, self.avg_time, self.smoothing) + + if not hasattr(self, "sp"): + raise TqdmDeprecationWarning( + "Please use `tqdm.gui.tqdm(...)`" + " instead of `tqdm(..., gui=True)`\n", + fp_write=getattr(self.fp, 'write', sys.stderr.write)) + + self.refresh(lock_args=self.lock_args) + + # If no `miniters` was specified, adjust automatically to the + # maximum iteration rate seen so far between two prints. + # e.g.: After running `tqdm.update(5)`, subsequent + # calls to `tqdm.update()` will only cause an update after + # at least 5 more iterations. + if self.dynamic_miniters: + if self.maxinterval and delta_t >= self.maxinterval: + if self.mininterval: + self.miniters = delta_it * self.mininterval \ + / delta_t + else: + self.miniters = delta_it * self.maxinterval \ + / delta_t + elif self.smoothing: + self.miniters = self.smoothing * delta_it * \ + (self.mininterval / delta_t + if self.mininterval and delta_t + else 1) + \ + (1 - self.smoothing) * self.miniters + else: + self.miniters = max(self.miniters, delta_it) + + # Store old values for next call + self.last_print_n = self.n + self.last_print_t = cur_t + + def close(self): + """Cleanup and (if leave=False) close the progressbar.""" + if self.disable: + return + + # Prevent multiple closures + self.disable = True + + # decrement instance pos and remove from internal set + pos = abs(self.pos) + self._decr_instances(self) + + # GUI mode + if not hasattr(self, "sp"): + return + + # annoyingly, _supports_unicode isn't good enough + def fp_write(s): + self.fp.write(_unicode(s)) + + try: + fp_write('') + except ValueError as e: + if 'closed' in str(e): + return + raise # pragma: no cover + + leave = pos == 0 if self.leave is None else self.leave + + with self._lock: + if leave: + # stats for overall rate (no weighted average) + self.avg_time = None + self.display(pos=0) + fp_write('\n') + else: + self.display(msg='', pos=pos) + if not pos: + fp_write('\r') + + def clear(self, nolock=False): + """Clear current bar display.""" + if self.disable: + return + + if not nolock: + self._lock.acquire() + self.moveto(abs(self.pos)) + self.sp('') + self.fp.write('\r') # place cursor back at the beginning of line + self.moveto(-abs(self.pos)) + if not nolock: + self._lock.release() + + def refresh(self, nolock=False, lock_args=None): + """ + Force refresh the display of this bar. + + Parameters + ---------- + nolock : bool, optional + If `True`, does not lock. + If [default: `False`]: calls `acquire()` on internal lock. + lock_args : tuple, optional + Passed to internal lock's `acquire()`. + If specified, will only `display()` if `acquire()` returns `True`. + """ + if self.disable: + return + + if not nolock: + if lock_args: + if not self._lock.acquire(*lock_args): + return False + else: + self._lock.acquire() + self.display() + if not nolock: + self._lock.release() + return True + + def unpause(self): + """Restart tqdm timer from last print time.""" + cur_t = self._time() + self.start_t += cur_t - self.last_print_t + self.last_print_t = cur_t + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Consider combining with `leave=True`. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + self.last_print_n = self.n = 0 + self.last_print_t = self.start_t = self._time() + if total is not None: + self.total = total + self.refresh() + + def set_description(self, desc=None, refresh=True): + """ + Set/modify description of the progress bar. + + Parameters + ---------- + desc : str, optional + refresh : bool, optional + Forces refresh [default: True]. + """ + self.desc = desc + ': ' if desc else '' + if refresh: + self.refresh() + + def set_description_str(self, desc=None, refresh=True): + """Set/modify description without ': ' appended.""" + self.desc = desc or '' + if refresh: + self.refresh() + + def set_postfix(self, ordered_dict=None, refresh=True, **kwargs): + """ + Set/modify postfix (additional stats) + with automatic formatting based on datatype. + + Parameters + ---------- + ordered_dict : dict or OrderedDict, optional + refresh : bool, optional + Forces refresh [default: True]. + kwargs : dict, optional + """ + # Sort in alphabetical order to be more deterministic + postfix = _OrderedDict([] if ordered_dict is None else ordered_dict) + for key in sorted(kwargs.keys()): + postfix[key] = kwargs[key] + # Preprocess stats according to datatype + for key in postfix.keys(): + # Number: limit the length of the string + if isinstance(postfix[key], Number): + postfix[key] = self.format_num(postfix[key]) + # Else for any other type, try to get the string conversion + elif not isinstance(postfix[key], _basestring): + postfix[key] = str(postfix[key]) + # Else if it's a string, don't need to preprocess anything + # Stitch together to get the final postfix + self.postfix = ', '.join(key + '=' + postfix[key].strip() + for key in postfix.keys()) + if refresh: + self.refresh() + + def set_postfix_str(self, s='', refresh=True): + """ + Postfix without dictionary expansion, similar to prefix handling. + """ + self.postfix = str(s) + if refresh: + self.refresh() + + def moveto(self, n): + # TODO: private method + self.fp.write(_unicode('\n' * n + _term_move_up() * -n)) + self.fp.flush() + + @property + def format_dict(self): + """Public API for read-only member access.""" + return dict( + n=self.n, total=self.total, + elapsed=self._time() - self.start_t + if hasattr(self, 'start_t') else 0, + ncols=self.dynamic_ncols(self.fp) + if self.dynamic_ncols else self.ncols, + prefix=self.desc, ascii=self.ascii, unit=self.unit, + unit_scale=self.unit_scale, + rate=1 / self.avg_time if self.avg_time else None, + bar_format=self.bar_format, postfix=self.postfix, + unit_divisor=self.unit_divisor) + + def display(self, msg=None, pos=None): + """ + Use `self.sp` to display `msg` in the specified `pos`. + + Consider overloading this function when inheriting to use e.g.: + `self.some_frontend(**self.format_dict)` instead of `self.sp`. + + Parameters + ---------- + msg : str, optional. What to display (default: `repr(self)`). + pos : int, optional. Position to `moveto` + (default: `abs(self.pos)`). + """ + if pos is None: + pos = abs(self.pos) + + if pos: + self.moveto(pos) + self.sp(self.__repr__() if msg is None else msg) + if pos: + self.moveto(-pos) + + @classmethod + @contextmanager + def wrapattr(tclass, stream, method, total=None, bytes=True, **tkwargs): + """ + stream : file-like object. + method : str, "read" or "write". The result of `read()` and + the first argument of `write()` should have a `len()`. + + >>> with tqdm.wrapattr(file_obj, "read", total=file_obj.size) as fobj: + ... while True: + ... chunk = fobj.read(chunk_size) + ... if not chunk: + ... break + """ + with tclass(total=total, **tkwargs) as t: + if bytes: + t.unit = "B" + t.unit_scale = True + t.unit_divisor = 1024 + yield CallbackIOWrapper(t.update, stream, method) + + +def trange(*args, **kwargs): + """ + A shortcut for tqdm(xrange(*args), **kwargs). + On Python3+ range is used instead of xrange. + """ + return tqdm(_range(*args), **kwargs) diff --git a/tqdm/std.pyc b/tqdm/std.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4bc3e74492e219c6db39547395e1df88f0a532bd GIT binary patch literal 47485 zcmeIb3y@q_df#`tdoaKZ9)tJbK@yh$x#%GWg8{(D63fQ|19BHy5~S{ci`}8I-96ns zGmYuTpl=U>5dd2fxl67S?MfBLF0E2}cqKcsRgNP+N-E2dVkxU+?NnK^Rg$erN-BFT z+3QqXv7(gX*qh(~JNMBI3`j@-ol2EK_MLOj`|+LceCK=J!oS@*`YW%#_Jyj8|83&$ zC(F)7JMzxeoSWmBbG5u{=3Fc9=JKvpaB~Hhs})>r$Tf#tYlEBH;9A3OZrHUpy19+6 zwaLwGa;*_JH{x2OZf?}IHoLjauC>L@ZE>xwZf>h$6?R2eOZf=)r?RIm!eM=i$bB}B7b#r@pr$584Iqq8f+}u9@zR@-JySe?=*G;Z@ zz_kv#xr07!#5Esst%uy)LuuZJ-P|LVchofxxmMuj0+(x0?`Buq;%Zx6ZJVo&x!QKO zIP7XW+%t2Jy4p@x+vRGzU2Tu6?RB+rSKH@m``sd=J*@cp5w}1U2i&{Pz2n^H=N?nm z`r{URFpd3~O4pBC>_cg6Q5DvUu0G)w@~-x9n(&00QNoj!@JO04sm7IX%n}Zz2~(~X zxcYHx;L$X8+SRAsl@sb&u68&{Ka!@O)Bx+-tkh#^>?v1!TrYD^x!OiITl^T{5&a&I z(V5k#*J(AP%6zjPtaLkz-Fg(wSGqy1UhQ-%y-qh+;MeHY?IoHTJ>RZ2S8MessE}u& zvfAtg#p@H@N_(-MW}~J?yVqzh2E9%Y^=kEQcWQL>&8V_iKRp@*FTVIP}#Pr1J;7C+y)q_fO+IP*8si=6}YEYgDicd{V z1^UO|(FQ|xMYe+Ljq#|=N1f(suU_&^_q1DTjkg<7qtga_$huuwckn1ht1IeWR9bB} zs-0TBw@0z{_S=nar(LRcno;Rmx3aQQ@Aih3Vtw#Im00R+RV>rjDz!Rq*GsD_)?8ZM zMv^bpE6cO>UT?Fq&R3#()MNe@dt3DWdY9>|*UmMnz1}DhGo98-rK|PojVQell`2uS z(XfVI>2zC_URYmgR;u-QT+vFYS#MkBT7wtrZuNFibe7>a>lf?Sd*@%j6puk;o~bmO z^Ofpy8e7zySZxS)z*AH&N!sK>g`&k z+qN3pol1+gm+Yl8-?&z31CkravfOGc*DbVEsnKPPwpxjp=~}hf)4VmiS5Hk(Pe+=! z#YS&wb$+VaX&vubb3lYGgNG}dor}a>TwAHX-0iZYnq9?wroQGQRE;7o(Kk*1c>MnI zb=su&?Ovssr0tE<&ed1C^=hRDqvc*_<+yg%v8Se< zIyE(YNBB59a%yEQJVf!ssz)1RICrl-3?C?+`I0d_K-t#(B%NA98>_z)SXb^6tt;WxGk{A@>!M zsnd`3LL^ukwdFe<6a_d2`WHIw7&nw`5=tdoGMb}fJ&&ilLNP10yTEYuZuqE5d)RBR z#Q`>r$Lfpf%>^smiWV))!U{N#BcZ4EEB0K@VBvnja*|4q^Ro@&a$9nR++_ZObteNo z+yA9fty5)mN68(kSk?&(-EU7cCr_v+TGrvA^|pvmU_VF#hMJT}?a$YHm0+P>>8*C_ zlfh~`>e(rK=8f|~rCkf=>r0il8=cjz0d6+5@FZm)@JO`HpH;tXB)RoQvWa9rl74iQ zb?X85=m^a{uO8WE&sPcBKh56RGVynt*%!$m>=v~AQ50{{;Mx+WZBXk3?M^SKEG*Qk zy&&1t!NO|0DjZ#DHhOFS5$5|bI(dd)I^VtByy|uXQ!Mv+a`uCi zP#owELOpMRAy+F{V1r0^B|+S7u)s!F-z2J+Je+;Kq6M55QQ3}%0j)j<`LkfPIoy9I_>5fj{xeU z2i4e$K$}KADSMfi<#JGrI>DtzyVkiD1&xRmGHS9m;p_Q)XEkVX{zKC>dJR?!L|zHX zTJLfIEm&S}3d`hXZ?&s+8`FH9mg?%gVQ`4Yj!39@IQ5`!gixo~$mZLAy`6MyGFYP@ z?K*f~^ebavT)m}wJS|4K@&*|OO`t4Ft1dN~H6l1AtQ!?V-NjXr4a#z*Gv92?YYfUP z3a;6#Grm@!XtlQ*)#KAdd!WE?h~G?^_4*tEogH=~$@x>{9c}=$-+@$ zX9@?(V&0v*ywBYtM|upMQuqsbca;D{24JJ3M6F#Yik|8jLC1na8n>Vt0fPRh-ja_} zw|bCKhJ%xQR^DeYd~8RHYKZ)HJElS!RJ9q=ZHTuAd}O>%o&LxujOIo;HmVw!`l_ED z%0gk+AB|`7+~vLQ7PD2*k*otY@4mv6ULAFp`ldxa4WHv-gRNArt~R>$qG}8^qHtP| z6G~T{#+%{E`#pYy9xw4TB;s=0{fN{fwKY>FMd#%8Vmu(-lfC?_9lqkDm@4{kid+@g!(@jUFuqgB#xrc8)-ivN;!XzzSSpoh#118w&9_?rRQW^urvYZOXV4CQdvN{SME#{w z{kq7rdBV4MQxw}wnfNAwp3NlC00|i_xwXK^0yJ{K27n<hbTN7p&AbeF@!jOX=OO>e7g9Ozr{I>HX zJWHHaVfWzk#uc!Ona3w8{`#y%_&L_?#|k~L2@ap4N{(I^>HhEXW00QP`EM)OWNQYk z=`_DWrh#cb>(+KT=wT@QzL`Cn&g_eW6L{986PTx}3*pCk7Ik?JXH>N!drN*|loYB# zn+?AUGy@NA@K00Q3cpBP20!>+47;dnaYhCjb!x=6ZO2eBAHJl=86LKDcIjxfSFNkEHt>fpAO1zU2D{OOPAl$(R#V; zuc*g@vc;CI!XS83*NC`KEhxqeICx?Z4?$TMwW&BxJfEyo8FpOo;tRoa`E&pms@sL= zi$1i#^Oiqaori}4HEk@ryEH;a)$hI*D>P+{si0V{E>*fwz4wB3@XYMY`SVb7aW}-P z)4wN!@+w7QHrwrm-n{tAx<%$GGT*GUm-`AF4uZqCXEq*DaN^{tr=EW1*^fVWWK|d{ zSmogwUwxd%dwS?lG^82?4ef&J5D)#*Eq{X-a4M*CsIZ)K9oWZtN1&Z|`CRl}LI;2n zD8NI|JNcZ5G)e%I;;xdw5DgqzBN4=oVaW(B&g%+a?U}u>7^Z zK&Vx@Zri2>r05hr6Twl^mB}!!Q8%jIHh3S18CeuBZy#B7L3N8F3*X?8?fv`R_PXT% z5I-XnT<)RVuH3=g&fEz9RW&j#nhuJYGI>t2#!PtkFX_oV@AvdGB=Op$Es;$zxSADX ziXoj@GzG+tDX;+hp${*$4}iWm*!lMbp>Jgv7UkI9>^|o!4m_dN{hFvmEinW+H5m?B zcCPz%g;j2odzOT&Uw2o3E`H~=z0<;0r_V$rGzkk&Mc|-)x8MeiTHVyKS(WX+xxwuw zl|KN}p8}pJPTLHDao!wuyKikW$l1sss0uLRY^N(zxtk=6xN&9OKvp_N3uZ+&_MWEUPE0Y{GkmN z>D!MF?yOLJ~iM;MkUO7{e9@ddf`mmQ)S~wku0*sx=w1`b++<|@W-Eotq z?pMuo2V$Wn0@a$yF_EO!OmsQ-a4f2nIMqaxbBA2bM3G2T>yNrxko4`atC=uz?ubMD zsA>_95=Pd@Fdt9kj32WZ_?Sf=P3HWlCS(o~Y0(N!SkEUc58_cKjln))d7eyOpR`wG zq&{`hUXR&olKGguA}F1*3XfY?k0)K7w)&ZzY0Gw^Z*?epLXj+uR_CN>H&K+)-{WEQ z$T=_mti$>8QccN6JM&jSI+H=S4og=eqBK=YNB@u#_FH8qi98PBKFZ7eq6 zK&JIg2J@>uFG(Ka0yMisu$TCloOL#EVTv>N)wvYg>vcu`2Bf=cjqrMZgGHKIdT zR}mW4Yo38*v>peO1qDJ;dh)Tcl}mE7JlyX7j?$@ z+YNJ68?obq6VC31iW#+-FZRW3wwM)dB=J;i2XfAgb0m(>vYpn6+_p|61vz{lzUAE2 zL+~ud`m(4Z6V67znNZoH9bYkWT9eo|sbf$?jtv(}%r+JfnzFYx;VJLg^MOfJgU&+m zT4j+GgxNODst94;LgRWpkq{~B4On$tW}wm2T*uOXX%y`S0Rrh}r_xI!Rosg~gQBSK z6BDA&{7K+fN;#s{1=>_;?hBJaXT>wB2Wokv6ZMX*@KDaTV*2ZJaP;WqtR9nK1BXdB z?o!n`3A)LKbmCeac4ac;zG_b(RHfpy2cg0H9pg{aJAkq8ej3#`bi44}Ip+Z@p@j+J zVPyjemjF-CR1=l{n$aGgvgbv6zG2Uo?K!td7DNeO{p8~R{15o||9tL~i!Y4+=GRyL z{gF=^JMhNz=@U~Y7j7jlC#OziFNjBZ8KDBQol?r@jk#clDQW^ni}(dI>ek{}Ym4^_ zXB2Qe_jVX-P!EoJkorJOJ4B7|sdpCs2O=WLnBDHjA$|4V9{X>b=TPYQ*Jht7lkYMC zb}TdHcOyGKrWYYm^R{)_$!TQ4=+=Vko)=<(GG_ORLIhJ5+$@MfQT{#8`w-Nknv4D= zk96tp*o^G72z1)6u|c&*t74NH4x0?#`t<3`uf2A9b~b~yd)sgZaZo*JvwaYkHF*C( zoOMtQ=wIV`4S5d_V5WBK^oQ; zZn$|x2GahyWZ~9>gCzAUh9u^#-sSZ8XGD2+&)7j=Xc+Rdst!S=nE+y@O!_P@fC!f- z4?|#ae`)|p3fnb@FqfcA9>Jgh9XnnXu%kftkJlAB%e%kIM*o`1(PXY6s7s^4N}^|O^jHw7sJmAfR@qesHRnEVb8?Ky03*lA?EX2 zfb%#$k22gA>2?ZqFp^uwX?Swl*7-?$Ip$xE`J1$W#IQ@(>+^&YM1*2tc^svcYFp`r z2L~m(Y{gIlp8F%-B3-N8*imjLi>K`0KYGbX3P_6#N*?2dr3EsUH@WV1E%_{XgFm|g z9zy#_<%(%LA^I2eJ)L{>YtUF=v7wF7kutgpGDpf{LDcGWdP|DpELA?Ng4uLZ7N9eMX8O?2^8u}C)G7IiYWP?yfs+x0OF1Yvd?!AH~ZBUZu{=^7PC^d!0*GwzUOMDZJNU44_ z+Q&>oFQxNEjaHBZx4C32AWDYjtcv8!0OLZZr)vaEYujuCD|o z$|Xp=m!8)?-ot(J0*zbqJgm?8TII!~G-YI?EwkD!@Y_|U zH;)itbveF(I@X)33;*Zd=MOxXzJBV^vtgNnKqy4*#VpqqvQ#}5yaFdWP&Ryn+4~$y zc$|7C=yfa^-IZ$);Zl$l0=BW7A&_3c06P!03`+WB439xUr0oaO?&B|K@@`WoXY-$(&Sv}O-@CSI@=NcJP z+;^>H{j7Hnsf*%yxsAiQJ^7tOBl(>h45>AGRmBJVCi#7)Ovb+erOZNf4+#hfa4L>3 zZ_qpJ3)Zqt>_*pJ(UWCiLD3*z_AwOuE|nqaxj3ukevUgbJlpP~o|d>t_0R{H!3vmE zOXNG`{#Iq8EuGIlsuNjH%IDodn8j+IgukYS2^?(0Nupodtd0%t)|e&#bL-=8xSRG$ ztyh0f5C=n?GQ6skag@j5Cyd{M4A|zn`*RxDXikN;Dc9A9a&aMh^@ZT;jl+r_zqP{( zy_6#_eL*pb-$bPWW;vC;Yn6d!;CXMR%G|A8Hm8gZb;}!;${Z%CzJ*)6UHgfc zjjjplFY;gKQr3N%dF_C^i8%bf&%s?JFs_6VcMAoP_6}qMn@lF~+lT~m?Hr;2X73Lr zQ}<71c3(1&qwdxoo4y_Sct!SPlXYvau2kJu@;u>29)fwY%=(SHo8zwgoMPJygS0Ko z%|6$Lsf}ZuEKQ!a^ET_`EeIe7eI0d(ZvCGg;uSNcaYN{9Z# zG76*+wX9dd;NxL;Wz0n*LpL9Bvpv>>!E)1{8p6&%dk$tB3-gGo*+-`YY;Z-Nl!ym( zpiO&zh+8+>4`vC?lDE6CL6be{m!NN&Yai2cyo2B~ow9YR1`hnl3mf8*Q%J29H+;y*HQIgg z@Ybo=_j!K9oqc9Iy}cpcGJey-Dvmb`TZYZzcM2$K79=-Y)FBa#ZtkPp{KYS*h$jb` zbxbPd()#uWGJyp7x{b7hr~@lj>U=SeFr~HrgqPE_yol$Rc?e0kfI?)nL>Igz4szK( z+OWq3xq167cc7w+=p5EDV}PiXPNTAOyTd+1!(N?kD30}T^vc({|XHIF{sb0n4 zaBF>Rd5BX)M(Y@jL<4KBQghS?apIXmxE9-evM%sWcF>01ZYkgcNnOb@elv2AKV7X|I(F~Z97_W}9`uKlbjYOQgI zRL#oLHg|k&_ZvkbzA3~|Ychi5#vfpbV<{lJti8cGJ$j=l-Gy6*f^KvzeDpH2$lPXv zES0f*FC6td^mK58%Ej3cU(MtmkKa71$sL$-Y^@1q%mn-H7Mn z*7G;Iuva*f+Kaa)gQEjg1i{f8nqVt)BbM&mx;&T(fmYOzO!aLv!%q9IA`h(~0JAPk z25tUV6#%r(tN4$_+}2z+E}+q$E@_~I?Uo%AlUO)JRhi*5dV>f)fOpno-z0sGLv3^_ z-tG^t*R++%@)7b~jz{gAE5*{GJDN?VkXDjlsqa|V8fI*tifmvTuw@p5!>5D8LjRRj zYJP z5fqsBKw(sS6IP=gy5K3aO$R4fc*EFQjGLi(a0C5ups4@JXkQ490>P*s#u0=2?$hv> z;YG)&OR7em!if!A7;^E*sgIxRJEskc-=i!xLpdgLoeUa_ZJ4n&pw!5(a&{zOI9vHV#@}<2uaMp-+9UF8Ezts*y3qPQjKZ!lJQlK zNA@Q=9!6xa@MK_&IO8cb{zIZr2{m$%9N?8{aLS2W;F6PIkyBug8&BQ3@${|B;h!Ph zcultLEo3|2)bmp;WIUjFms?64hHWHKr0sRDjmCBlVU~FfDQ#3^S|z*O)UxcwHoCEQ zvRCcmEIZrU_*0f1?{Vu#3^JD3zwXwtbWfYwTKX1u)R41v+3;Nh&YTy;G0QP_Hg#0% zXNKXt6x(PnrgRsD);SKsdE!k8#OuY0cXl({t<2N6lF4V{Pu*hfFJCC`C&eq-i=QSc z!a8QBcPI_hP-?HCv}H|lX@V#l<3%=v*U`U7qDjS#$6OEpv@%P0&8^nCH@)%v%PdbV zE+Af>S!Mg9up}f_uZ8DTP7KI!n;uee^D1m&sM&DBF@>bjRtyvp+=Tz79)CfPALd~l z@%YI`V)Um62FbUwT^0U_aw{%7)We_PHT;W8P@C8gb}OZ2Y=;*kTgeRIyvCpaLXhF- z=SNle$MyKH^!Rgn{CPdT&cnvr4+-6#=3b;8OhwUH@+g2`flAYMX82Po`Il6Z%V{E) z6Yew3$nY;KMO$-WwV44_yTf2$G$C%SN1iE4_`VsSnv8g@439h`_UGSJ&1d)KMhg27 zA8*C*O3#t}*w9Y?D*oxh)450T!?~ULqxmg_3b_NM4CRB|!NM?lj{Eb&r0yFU&hLZM zyD#?$&n?ulh1_UDk_U6HJbID2?c_#hvOsRijG!U8pPIJt9LWzA3b{i5aX7^f<;D} zJ6u~>Bk+>JF(i-!U2XAH$hyIJ4Dj7$Tf;ki%5Pc%Tr%vyH@T=Azrv}a@D`HcF|oAd z_64Y$Kxq^9INyZF1S=2*mn0J8@rbZ^p| zwrG{aksd**0mR|BZwQ4~3<?+`8Uup&na=Ni!o%zpc1XS|p^`SMps`S<%%=$eiSM!9 zOib`44;?voj%DY6Dejo=4s*xIJbNF|kgSLQfkPifI2N}H$;>V{Rxrp-jYpE9PgS)Q z)Co<}sWKgSjsVbf`FJeaeBXO9@alkciTwc&mRI(QH+IUo&*rWIjA~I;*{X6n#)>a9 zM1>tazRAPqP`0UQTN!o5i`xE2CG)#qWMwK{&!b2J)2sX9KH4(T+OR9gKM-vQ4y=<< zg9eA;tHgw&dQ8K@_DijS8964s)wIL$jpag3PsE zqLn>K_g&#o1};KeGi3($zY`Gl08Ral%dNrD=f#$V#Q_F#UR+TyTyjDt491Qo2h8vv zan~=$1DlR;DP{^{JNL4(_`z>|z>bKnIBu!#EMulT9@$%h@ ziM7r|a;6uY&~86oH373~?g)_o!W4dyVgjCnF;ip265Ryq;3z13C1DNTK8m6c6hHS! zu|2$^#y~NHVWTkGoYmE31E>qdZS=;74Ugpvw|Q)(Ygza?C2N~6t~NXh)K;)fVWq0r z-gd*ER@^Tut2U!7J&iCugTS-FH7h2aj*@m8M27!Bar_?0_Ax7o5-y;+*^%*(pzyF- z7#`&~-JRQ77~&A#o<9mo+s~m|0G$b?4duOGlBy7zW?P^~By`Ma!lI{nydm~hP)WI)Z#OD| zj=G>IHm!73ll3*tRITmARQQ9G41bMB>}f=E>=DG8*8;_94rR@8QsP#^dqI(Vo_hE` z`Mv3;VF?M&txl_>-mY2Q;g^+*-ve1CYx5IYmQAX%a1dGwGgSeZrd-osEtC2O1eiQX zgm~JK)27o7RRcZqR6P0bumncf|6iiRpUquI zel$dr80*suD#GR=Ep_#m+$DAY-&pq%h$2r0pU`*8#tJ##%ji=qaW3|BX7CVm?PCvC zF~TrK7Y{FR)Y?c*8ofTA3|wz$Z86*0=14+>6W3?1)9qTNTTA2$aQW($R@$tmbT-lx zGIo2V-oTj~m5CR;G0B%7j?Y|Ait6bV0fyrfHq)!oI*0#6kAKR;5R!Mb=G8R*zEZxI z#{+}6!9(@40z7`eTD))UUZ!zmy$XLxug~l8RXt|(&?c~L!HqBc4aErU*#P87tz9#+ zFV;Bs)VFXq)W#S(v0Q}MS_|o%;P&NBYqXBbU0O*Xz z^eovwwa%D=8Dy2c?ke1E^vvMJ@vL=VqluBn?uvR-w#NQIGXOb7RX)y$Dow%z8^1wh zUE>yxbOn*+Huh28v_r?rr2TC&FlvxnlUoTScL7x~-f^E`t}_8)QCf;hyl3i)cBwti zt<5`2cugb>dKbit-imYM0n>bHe%a`AQQ%k81+9Lg+lPW*Ll5i(73Ufr8Z`n*Fq<7_ze?9WCn z-=ZHio;s$0OH-43i$4?%L#(A#(WZJtQ zv$|<>6XW;2d7lFTO%zU&?K*~}o(b(R8Y^#lJAboP9P;gM^ml6D%D<$qw1`AU8^t_0maUq6-47Iy%{(LB^pcKXy8*=1;x zdVFJQ3uT>vWlS*;ufrwpEv30l5}?y8n!*FUliMz1MO2IwKswNf5&P`v5NLSq4qt;Y+&xn=lPl;_VH&Z6^$iMQuy4VO&IHQ6 z_`<>=B~CUa^5|pS-*tu_){``6U8XkuEfL>e zqCXte7T`Fo)G}ZzwpXUmbFXz;_6IV*C?5yYJn;Ief0My86XyNZ!;l7{W~TfQizXd- z;c2*7!I7XU%Rs5Y$Nk7eUV)!+lWh<5=m_$-Yv!ygDSRi;aM;OX8tNuxgT(Sc4Yh@- z7_5uax5I4FD>DbzPY2hZc;e(lwwz%-=0n`MpvR7-^)K~$E79rWxc9`{gYGy8$gi=9 zTgS~hR60Y)Pkj8D=~E}4oj#tHN}&E2PT7zoVoq?ZwrV<0e$tM0kUp6GO>}H!jSEye zE!qIpcpYhDh@{)uE9>ff^&8f|>pt>ErCo3OSGk`kg&pjD+KVL?wS*Z6e|Yo!q3xun zHO5=s_~!#iz}Ka7JI>-GL=jqBNkK)_VqdNGTFLtrw^sBGxyz!@2@*%C z@7En?7<7fS8qX>xS70(+{dHsQQIEf0mE%ho$Ba*XpPRDP5Zz32TIE})k%LIA7}_xR z5dPu>k)Srdu1I3qoK!Tt(IS6N2K0*bG@a*sbD6_I!~x$NDeqf-`_(Fk+aD$GP$NY7 z#OJB?eVZf(`2Mga;VUjj4&5Lb2wAy=qMmm#r~4rX8)~&2%i~gRt!>p@y$?5lc=V2q zJ;ZfSlg7zU4{)xe3EHajnYtjtJZo#4v9`WPr#-3X7)MEnV%nE z9yeNJ9IawO$t|`WoflcW6v90kS=R_SNlGwl-$qQ5Kwu#4VhY7KJ?Kun6+@-GT;KUm z*W*bq%#BKXkTeiqp3{fo7Dcq z6WNKgt~GL!@gm4LYQdwTZZ5%Pkeh9(;zXaePLkO~bau0ATlc%Fr7i)Aw7_F27~vfyukqg>Z<%wzWSju zU4MxP!MEWB1>lQv%~B|DPnfHSH}HPo^?w0;NPg-9c)3M>MR$L(xh|N@)273t-~y51 z&*))t&_6@S-ygrBxL-|PZIT5q;oFMhcfWjgTb|he`M=VoO5JlCPUbFnd5}7`(pmAB z|D(KE7o8wIniicBke-nO7DZi)3zgDt@ zueAkhLoj7c7DsjI95~s)pgq(w6iRk!SDfHwXJr^#hB?&f+u&6BJ&S6m+93zSn){!9k zLUEs7wYfd1;x8@`S&yM*by_yXW+`B;zx$Q&ae_RKvIR_&-(6HXaED=kG11 zw2(##wb}#?+-($VbCbrVGdvKTPPO;JWRPGmA#kuM_H&mGtFLos`sX&@4#2DPPpKjx#mMcB|HgUPH;4uh;oCVH1~un3nCw(kmAf3w)jeZJmJtwL%?-^ zT&3X9zazy1(jfRYTdNR~zQDIlN9Aza8Fihv6WEM(T#eDUs*OPiH#Yg5YQ5!l9mLPtNk*S{Jq5{|%4P&sf5871JrK0{M^~-2%pIaVw-QUS! zS^{AVof^+M!{@Aqy2dQU=Ddx&Pb`o&yB?-YM530#4?lhyG9y)UWfL7Ty0 z7afs#Rd?B*-<9vlL`fERid<48rKk;d-qof2)#W4uR!RG<6oE9ua$YS$IQtgAl!v00 zEfcGJz@$CM73dFxEg-g3KJwS|Jw!0p&=zdp)Xa9*#XlaC zfeG9uxAMQ`d)VS2$B~%V6?%bksN>r$H)<-rrac99YRn>2K*+n5qXkHRE!U&#tSK!k z>9Eo51deDBig`@zGzA{lxD~+4iy;y?s@^D2>;p86rij6k%eTd51cZor(oE+FOv>U; zF~_t53QU=uBZ1=@f?}p!?}PgxsDcRX|j}FDc?WgSygVoqBht(K-+@nL< zKGNqoGQ>qihIjQnPnk~Jt36AfP|NYn=$Y}po6)_o zu*~#9YTjH_l5oan8Mv~XheK&jQl*WWc;7M!Z3RJm`eCBM>&mP33N9h$%>gLo_zI0z z7xtf{vD=Qw{=rUwPE#n7P-sNE^_J0%&k&8tkfEn)1@;i7DNXu}xFgrp%W@k*zjSHW=`+-gCl4 z#u`;+UWcE(Aa`!q>7gnNGmua3)a!Ub*7q_>^J~3&)HizL=f<1xF2mE_d+-K;{ZoSi zNvr~}jv-*SQW_#q)xXOzm*~j`!yML#6&-(8%)S|6oi=N=0ifzP|C5dj@Rg14#I#K^ zTeFFw1|FMZ3|7S^Q5oT{4phe7J2u-i6axF)#3Pc-W+sWgjrENZ0qs;f6e#>ai!8FX z@RFB8RBR;bx4q}s$R}0bzMGypahc^e*J0U2;;Hza+a&8Bv&|FJ>olpgJ2PNxQ>k6S zrVA+73^28k8G|A)1HmHp@%*s9*%<_NZ=xf=Es5bpG7HTLhBB`UImDFELLLwnBt4{9kOZ@X!r1V=Lljz z8ml~#F+yn1p1g+fJV<#zj7 zdh}wDKzW2hY&!jjYckc??5yrPq6fNj@fzO;gE(NS*;5`0(%vxum{XA+28tk}Mo>NjxjThlvjl?P@wrZW8+d3)`$ zT?b+kr{7jN!QCmP^1(DG&@_q4fHB2!APljovjy{Kc{;Eo#>1<9ebmT=BJ`+D*FYC_ z@OzddI?(2`V4b0+B{C;_A998xq6N;^^VHYps4wMBPrpTP8j1Yn>p&wAEy|BwgW;IYFK zR|OxuBma{H_pSeA!Hrjj|DRZJix!m^O@JB_SqJk6QREjnOp+4G4Z*xV+6^vMa|VG( zX;~OKp^HJcpcF<;7m`ih%7!-IlkrR3eFSm(Cum8$Hki~ZCq!!qCBtgOsG zH5GTY(NB=pZKn1y)g#9|eRlc@6N8=%c6x?`g_DO_kqwd>FFx@^9O5$1czN+g^+mJB z&x+f^e55AunpOSlV+$lC&0SU;nl07-)29z-!Qi4n^qFSJP&l{!< z6`VA1FN;Z3_StYW!-plsu^8MY;$D!M4@1I3^3G7I_?40ZCK0jARxJ|~C`0LNMx`BI zk~*M9*;_@m?=Hi9!@EnOj&iI(Up6j)-I!bTnmKmy{GEZm;R47|2L!Bca@^}YVJQZT zZa4{Vu8+?rl|Fj&0R+vZGvS5v7d~ak$Ud%&s$uLXD>#;Xsuc=18>iNk-Qtz@Xkbdp zc||_q4&O6Yun=xBGRY<_=AhW5MT64~$+6Px^z?a699qdw21v*zqk>uupB$IpjD**f zp|6+@>{q_i*B-L#R;=)oPM<7_v@K(vb>gC{)3=`*1tfwDY z9$V;yrn4b!zCII>-I5>wwBqLUklsf4n>z~M}+@Sk8kQB zL%Z-F=^?cjuVtjkH5RJZA9-I1fgTfj{8K%&BH>@r1G%var5|W6U&F5{^kqGMn1`u; z=(-%=?)`hlfwFEU*J)EqNhtm#GW_eR3quMQ{ zUwM16q)&c?dsIS$^RGXvs(wOoQc&r`)V2ubp4CpXQak)1<uVX;k{c5Gy_mOr17tw#G+In(Ufc!r{jBA0% z@1}N!zM_%B~6U3)?9-GNkX_?#XYb<~_N+g|U27UJ%8PlhD zk(3WbCU4D+hfV)#7QJ%Gj}n^F&{V*^0a^j3u<2#lsBQTS$Pwd@&o`*hYvbtzkh(ki zdSX)Gb;q1f>j07H?}Zn@&hj`&KnJfk%QvW!x{Zf4VF1$&v&z(S1H%WlxCV24HC3KA zN?TFO@SiZXeum!3Z0@XMQ#`#cly=I4>!RQvj|rW&yrY#$yVXl= zS?3Lu)HYFWrc8XDK)il_$*>c#6`W<|;slJfP{Kf>Z0n3&Co8(YFC7gA(%&@Y2Fs$w z<>(>5bcfB>)0*!oY{hLOm*n=jR5E1i73K_G*mBy!&XC>8c>FHgzmH(X!fEDY zT+_6P1|0h^$Q{ZiOA|IzCRMY>pc04VynQ6Sd5L--R&U}(Q?vRBVb7F_zer%sR14UK zKvir3#Rfzx;B~3fNI?b_Wt_{i|M+4b>+_uZe%u;_>qIB&@vHiFi!zKgvYRkn{nSgaLp!BUL{!I9dmH}fiQ2$kP7GL0#s#|GjgS)yVPm^RxBPdD7{=M?4Bb51;b&{vOs^D|6 zvRm3qR?7F18prZIj%Q-9HDOu3Qs;=1f)=ix?2li-ZT`hCtF3hjQ=pgfSHI!-|5B{* z#>l+F8)Ho}X?n&rp)%i{wGaG8&j5aBLCv@>s~xj#)N9P+L9%Ba|G0M}u!oOyUAzpt zXD8RKi3_1Vs1co^f;YHFajztIP&QM#fm=B@()gAsd$;7Wjx2Vy@q!p4sCZ;oO+z9^ z)tlnBC;aTzaNBi4WPPX&8-7d=yVu!G;)q@!(_@CN`OBSXi}fr7!Y?Y}clEH_)@Ky5 zBSBj;yX4rV8=qCT-)TO$6Lu5R{^l6yRbqQGWD$gf6?|-?Xa40L*wGjhv0A25=@}uM zCE9ZO?m1+v?jP&i9PBIh!}&+id)`pMksA5rG>w}JHUpzLMAHXncMa)|GMwL0*pVB? z!P=JGj@-V&&Vux#cTv0kv*oMm32>LLf0GSARafa~S1~X(KVF+YL z)U)}L*NRmP=+0vJOG*F4?Y&++df-C}gv}F)0vkXNh9dQXCM27;FGq|g(`v9-Q$F0U z{hH9Kp6q-J6VM)4a=x zKGqENuPmdVAu!q>nvWG>ni1e2`Xits<%hkNC}DcdOOshAqIEI6O-(@jaFY<4qm+!f z7OnERAygOT{!l^!DUtQ5mAs4}+0$uDX5Nd9u;CrMW4>VdF)y0H!1sK@(#-gRDxqd$ zsUg-w^6(`Dh*%zvEm__09L0>}Pug4@)tmAID@reOu%g>dkEdc&@U#kvT*P(ppI1WN zV+maq$Ne6PdTSSGV!lLD z(YEo~NO7*jtZ2BmX=k)F8O!#><^L}wP8aN6{E?iPc5)fsE!y484$JIJG~SyX zhjv)nDQf4W9es9K-orVW9g%kA8COq-rMCz(W}uzGr*-arLXYS5_z`8{=O6cL!Sgdz z^q=WO`=qMetFvqioVBsSe)w%>;a1qgX*HZXn4V|5HjdyfbR<7s7`M~xA>4c(%2^`nomj}JP^MN5>V42#dPs5I;l0G20n1Cndt4hJ&K1UV*=`XzSgnL&V)?iGxgmQTNq3i~u;pzOCFul{Q-fwl-$aVW$k;uum}M+7n44<{+F} zv@f?kdC~9DZXFF+U8}!fcilGdpC&}E%T{!X#|#u-dv(RoK(Voa`Z-D%FPKwc zc!r&tpmYfiJ-t|ElI;du;P0+7)g2(95%52(3p++=SZ&v`yRdS50*^N2aes)tc>4phgY+ND?sxQ}et%P= z*RUL9-%w$s00)%cP+<%<_cpfLPPWm;{9|~}m5<%+@Lu+c_aeGaKdshGnL5}BMlqyY zO5k;>0=9qrbRsJRUp^g(MnbWnQoXv$ zmw@n9AioGH-7#2AD(_T>?oIAKP}9+YhB8tz?p~^L9P2}y!AChk4fG83+uzZy{}6jT z_^i4Mo(Vq)(O$awNkOpS*2Jye$yy4${xVMDgMr}RrJ}8zyT(&=TRjAGu>_yUBwf&4 zr8@?{+Ojic@(Sa57zW%tdn~>(DwgjC+szT=!Ygj)?5xrTFt@v**G;k;fPGOMjVFDa z)@bZllLx^mj`g%}JnKM$PU9~0?PQTP4{RqJeqU{`RA8D2zk5$}&@+42L8GtI8^&Zz zAe#uT$$sEWhsF9!7YNkft1-@fQDIHTu-Q^FO=i}c1E0nYpCwCA_X3kt_ckWeeYCXQ zgWwO#4Ho(x(!xkf%p~es+)Cyp{9fdSfhv00(*maI(wF_-cJ_x@%%K8qQbMV}Kw{{Y zCNe|cqBT6CEu(*SMqe-vS08)mF@d>J!M411;F=^#Fxj`zihFPiEgBQ`xO#6&%dF$y zjS6GSZ2V4aJxwwTr;lT^l$Z+ZRt|bLI#f4yEKm(K$9R|HFm`ME4^^=reh|#od&g0h zMT-udlc?HlnD6sMk1HX|{zS<4gH0n7RR6(BKXxqW0`Jd-3D)rzSO0lF~u<}yY8 zkbp!j05RM;?k;=e7#LtC214!2&Rs7WwU41Z6rJ6r&RyPZey_+hyY9g6 zgLGXmWXdIuntUf?-^ALDF#l-v|FBlQhjIwnY&U)!T(jP8jFlsU;vFA3N^^UA=ERar zNDjR;Q>~qc%WfW; zBY&SHW9#Fl$zLFUf|w$|`_&N|bb;K|P7y6KjK3pEt3w?D-igex8pG7OUIUy_(WtLQ z;s2#nQTO5Z=`o_mgdTN0)JKz7WOqW-C9PmAw{40{SZ^ds8Mlu3J*fR1@P>EVg!JjuD;z$+6)XtRB@otdrwKvGBZA1B;OHH8*;_VKUe*nEH^22tf>q!p zEF8yPq=CB7SEFNR^!k`Ks1dl2DrBo~`j|!~-B$k*Wzi5!0W;IH z^!P#q%7X)opQE-vEwE769RkFi`C|UD+(8(#4{`rXX<_ZgnKJn_4Il}CL zLkPaA7)L+|K2?YWW;~-PW+(ZpkS`fp4r5iM1c*t$RUSGauFOYP=@bkJe&-^gzh zzvVJ{E)f7$0Wk?lH=yg4`|3-krUTm^e>+_Ba3T(3Bm)6Mh61hH_4pw@{)!&IpvN!j z@o7E8)bPTS_Z8A@)IPNl{xgMsNsmp;kv9{W)Tjt(9_RFUSq~$FUsdRg9y;4XW21gIuQvVJ z<31O{zOMOqXdrr<$M6Py3UN4J$nAW1Z131E{_Y$bFW7?Y{Km-MLT+q$>`)=MXA_?@ z%x90O{7=C@hc`^brjx}90CR23TL&~Xhz)cwhY zMu_Wx@E;}Q>uTatD}X*O6K5y6m8;vXYEl(|i4=*H&# zyk2e1wJg~pTj)Ml+9u+8xTmYl`iq(;^QJq@XBe*x>GPs^(#4mq{OR(sMni@m#KPVo Y{@c2LgZ|t5>}X-{C-#2FCI1!vUzX8_{Qv*} literal 0 HcmV?d00001 diff --git a/tqdm/tests/tests_concurrent.py b/tqdm/tests/tests_concurrent.py new file mode 100644 index 0000000..1e88754 --- /dev/null +++ b/tqdm/tests/tests_concurrent.py @@ -0,0 +1,36 @@ +""" +Tests for `tqdm.contrib.concurrent`. +""" +from tqdm.contrib.concurrent import thread_map, process_map +from tests_tqdm import with_setup, pretest, posttest, SkipTest, StringIO, \ + closing + + +def incr(x): + """Dummy function""" + return x + 1 + + +@with_setup(pretest, posttest) +def test_thread_map(): + """Test contrib.concurrent.thread_map""" + with closing(StringIO()) as our_file: + a = range(9) + b = [i + 1 for i in a] + try: + assert thread_map(lambda x: x + 1, a, file=our_file) == b + except ImportError: + raise SkipTest + assert thread_map(incr, a, file=our_file) == b + + +@with_setup(pretest, posttest) +def test_process_map(): + """Test contrib.concurrent.process_map""" + with closing(StringIO()) as our_file: + a = range(9) + b = [i + 1 for i in a] + try: + assert process_map(incr, a, file=our_file) == b + except ImportError: + raise SkipTest diff --git a/tqdm/tests/tests_contrib.py b/tqdm/tests/tests_contrib.py new file mode 100644 index 0000000..e79fad2 --- /dev/null +++ b/tqdm/tests/tests_contrib.py @@ -0,0 +1,61 @@ +""" +Tests for `tqdm.contrib`. +""" +import sys +from tqdm.contrib import tenumerate, tzip, tmap +from tests_tqdm import with_setup, pretest, posttest, SkipTest, StringIO, \ + closing + + +def incr(x): + """Dummy function""" + return x + 1 + + +@with_setup(pretest, posttest) +def test_enumerate(): + """Test contrib.tenumerate""" + with closing(StringIO()) as our_file: + a = range(9) + assert list(tenumerate(a, file=our_file)) == list(enumerate(a)) + assert list(tenumerate(a, 42, file=our_file)) == list(enumerate(a, 42)) + + +@with_setup(pretest, posttest) +def test_enumerate_numpy(): + """Test contrib.tenumerate(numpy.ndarray)""" + try: + import numpy as np + except ImportError: + raise SkipTest + with closing(StringIO()) as our_file: + a = np.random.random((42, 1337)) + assert list(tenumerate(a, file=our_file)) == list(np.ndenumerate(a)) + + +@with_setup(pretest, posttest) +def test_zip(): + """Test contrib.tzip""" + with closing(StringIO()) as our_file: + a = range(9) + b = [i + 1 for i in a] + if sys.version_info[:1] < (3,): + assert tzip(a, b, file=our_file) == zip(a, b) + else: + gen = tzip(a, b, file=our_file) + assert gen != list(zip(a, b)) + assert list(gen) == list(zip(a, b)) + + +@with_setup(pretest, posttest) +def test_map(): + """Test contrib.tmap""" + with closing(StringIO()) as our_file: + a = range(9) + b = [i + 1 for i in a] + if sys.version_info[:1] < (3,): + assert tmap(lambda x: x + 1, a, file=our_file) == map(incr, a) + else: + gen = tmap(lambda x: x + 1, a, file=our_file) + assert gen != b + assert list(gen) == b diff --git a/tqdm/tests/tests_itertools.py b/tqdm/tests/tests_itertools.py new file mode 100644 index 0000000..c55e07d --- /dev/null +++ b/tqdm/tests/tests_itertools.py @@ -0,0 +1,27 @@ +""" +Tests for `tqdm.contrib.itertools`. +""" +from tqdm.contrib.itertools import product +from tests_tqdm import with_setup, pretest, posttest, StringIO, closing +import itertools + + +class NoLenIter(object): + def __init__(self, iterable): + self._it = iterable + + def __iter__(self): + for i in self._it: + yield i + + +@with_setup(pretest, posttest) +def test_product(): + """Test contrib.itertools.product""" + with closing(StringIO()) as our_file: + a = range(9) + assert list(product(a, a[::-1], file=our_file)) == \ + list(itertools.product(a, a[::-1])) + + assert list(product(a, NoLenIter(a), file=our_file)) == \ + list(itertools.product(a, NoLenIter(a))) diff --git a/tqdm/tests/tests_keras.py b/tqdm/tests/tests_keras.py new file mode 100644 index 0000000..11684c4 --- /dev/null +++ b/tqdm/tests/tests_keras.py @@ -0,0 +1,97 @@ +from __future__ import division +from tqdm import tqdm +from tests_tqdm import with_setup, pretest, posttest, SkipTest, StringIO, \ + closing + + +@with_setup(pretest, posttest) +def test_keras(): + """Test tqdm.keras.TqdmCallback""" + try: + from tqdm.keras import TqdmCallback + import numpy as np + try: + import keras as K + except ImportError: + from tensorflow import keras as K + except ImportError: + raise SkipTest + + # 1D autoencoder + dtype = np.float32 + model = K.models.Sequential( + [K.layers.InputLayer((1, 1), dtype=dtype), K.layers.Conv1D(1, 1)] + ) + model.compile("adam", "mse") + x = np.random.rand(100, 1, 1).astype(dtype) + batch_size = 10 + batches = len(x) / batch_size + epochs = 5 + + with closing(StringIO()) as our_file: + + class Tqdm(tqdm): + """redirected I/O class""" + + def __init__(self, *a, **k): + k.setdefault("file", our_file) + super(Tqdm, self).__init__(*a, **k) + + # just epoch (no batch) progress + model.fit( + x, + x, + epochs=epochs, + batch_size=batch_size, + verbose=False, + callbacks=[ + TqdmCallback( + epochs, + data_size=len(x), + batch_size=batch_size, + verbose=0, + tqdm_class=Tqdm, + ) + ], + ) + res = our_file.getvalue() + assert "{epochs}/{epochs}".format(epochs=epochs) in res + assert "{batches}/{batches}".format(batches=batches) not in res + + # full (epoch and batch) progress + our_file.seek(0) + our_file.truncate() + model.fit( + x, + x, + epochs=epochs, + batch_size=batch_size, + verbose=False, + callbacks=[ + TqdmCallback( + epochs, + data_size=len(x), + batch_size=batch_size, + verbose=2, + tqdm_class=Tqdm, + ) + ], + ) + res = our_file.getvalue() + assert "{epochs}/{epochs}".format(epochs=epochs) in res + assert "{batches}/{batches}".format(batches=batches) in res + + # auto-detect epochs and batches + our_file.seek(0) + our_file.truncate() + model.fit( + x, + x, + epochs=epochs, + batch_size=batch_size, + verbose=False, + callbacks=[TqdmCallback(verbose=2, tqdm_class=Tqdm)], + ) + res = our_file.getvalue() + assert "{epochs}/{epochs}".format(epochs=epochs) in res + assert "{batches}/{batches}".format(batches=batches) in res diff --git a/tqdm/tests/tests_main.py b/tqdm/tests/tests_main.py new file mode 100644 index 0000000..774823f --- /dev/null +++ b/tqdm/tests/tests_main.py @@ -0,0 +1,144 @@ +import sys +import subprocess +from os import path +from shutil import rmtree +from tempfile import mkdtemp +from tqdm.cli import main, TqdmKeyError, TqdmTypeError +from tqdm.utils import IS_WIN + +from tests_tqdm import with_setup, pretest, posttest, _range, closing, \ + UnicodeIO, StringIO, SkipTest + + +def _sh(*cmd, **kwargs): + return subprocess.Popen(cmd, stdout=subprocess.PIPE, + **kwargs).communicate()[0].decode('utf-8') + + +class Null(object): + def __call__(self, *_, **__): + return self + + def __getattr__(self, _): + return self + + +IN_DATA_LIST = map(str, _range(int(123))) +NULL = Null() + + +# WARNING: this should be the last test as it messes with sys.stdin, argv +@with_setup(pretest, posttest) +def test_main(): + """Test command line pipes""" + ls_out = _sh('ls').replace('\r\n', '\n') + ls = subprocess.Popen('ls', stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + res = _sh(sys.executable, '-c', 'from tqdm.cli import main; main()', + stdin=ls.stdout, stderr=subprocess.STDOUT) + ls.wait() + + # actual test: + + assert ls_out in res.replace('\r\n', '\n') + + # semi-fake test which gets coverage: + _SYS = sys.stdin, sys.argv + + with closing(StringIO()) as sys.stdin: + sys.argv = ['', '--desc', 'Test CLI --delim', + '--ascii', 'True', '--delim', r'\0', '--buf_size', '64'] + sys.stdin.write('\0'.join(map(str, _range(int(123))))) + # sys.stdin.write(b'\xff') # TODO + sys.stdin.seek(0) + main() + sys.stdin = IN_DATA_LIST + + sys.argv = ['', '--desc', 'Test CLI pipes', + '--ascii', 'True', '--unit_scale', 'True'] + import tqdm.__main__ # NOQA + + with closing(StringIO()) as sys.stdin: + IN_DATA = '\0'.join(IN_DATA_LIST) + sys.stdin.write(IN_DATA) + sys.stdin.seek(0) + sys.argv = ['', '--ascii', '--bytes=True', '--unit_scale', 'False'] + with closing(UnicodeIO()) as fp: + main(fp=fp) + assert str(len(IN_DATA)) in fp.getvalue() + sys.stdin = IN_DATA_LIST + + # test --log + with closing(StringIO()) as sys.stdin: + sys.stdin.write('\0'.join(map(str, _range(int(123))))) + sys.stdin.seek(0) + # with closing(UnicodeIO()) as fp: + main(argv=['--log', 'DEBUG'], fp=NULL) + # assert "DEBUG:" in sys.stdout.getvalue() + sys.stdin = IN_DATA_LIST + + # clean up + sys.stdin, sys.argv = _SYS + + +def test_manpath(): + """Test CLI --manpath""" + if IS_WIN: + raise SkipTest + tmp = mkdtemp() + man = path.join(tmp, "tqdm.1") + assert not path.exists(man) + try: + main(argv=['--manpath', tmp], fp=NULL) + except SystemExit: + pass + else: + raise SystemExit("Expected system exit") + assert path.exists(man) + rmtree(tmp, True) + + +def test_exceptions(): + """Test CLI Exceptions""" + _SYS = sys.stdin, sys.argv + sys.stdin = IN_DATA_LIST + + sys.argv = ['', '-ascii', '-unit_scale', '--bad_arg_u_ment', 'foo'] + try: + main(fp=NULL) + except TqdmKeyError as e: + if 'bad_arg_u_ment' not in str(e): + raise + else: + raise TqdmKeyError('bad_arg_u_ment') + + sys.argv = ['', '-ascii', '-unit_scale', 'invalid_bool_value'] + try: + main(fp=NULL) + except TqdmTypeError as e: + if 'invalid_bool_value' not in str(e): + raise + else: + raise TqdmTypeError('invalid_bool_value') + + sys.argv = ['', '-ascii', '--total', 'invalid_int_value'] + try: + main(fp=NULL) + except TqdmTypeError as e: + if 'invalid_int_value' not in str(e): + raise + else: + raise TqdmTypeError('invalid_int_value') + + # test SystemExits + for i in ('-h', '--help', '-v', '--version'): + sys.argv = ['', i] + try: + main(fp=NULL) + except SystemExit: + pass + else: + raise ValueError('expected SystemExit') + + # clean up + sys.stdin, sys.argv = _SYS diff --git a/tqdm/tests/tests_notebook.py b/tqdm/tests/tests_notebook.py new file mode 100644 index 0000000..3af992f --- /dev/null +++ b/tqdm/tests/tests_notebook.py @@ -0,0 +1,9 @@ +from tqdm.notebook import tqdm as tqdm_notebook +from tests_tqdm import with_setup, pretest, posttest + + +@with_setup(pretest, posttest) +def test_notebook_disabled_description(): + """Test that set_description works for disabled tqdm_notebook""" + with tqdm_notebook(1, disable=True) as t: + t.set_description("description") diff --git a/tqdm/tests/tests_pandas.py b/tqdm/tests/tests_pandas.py new file mode 100644 index 0000000..8719a7c --- /dev/null +++ b/tqdm/tests/tests_pandas.py @@ -0,0 +1,264 @@ +from tqdm import tqdm +from tests_tqdm import with_setup, pretest, posttest, SkipTest, \ + StringIO, closing + + +@with_setup(pretest, posttest) +def test_pandas_setup(): + """Test tqdm.pandas()""" + try: + from numpy.random import randint + import pandas as pd + except ImportError: + raise SkipTest + + with closing(StringIO()) as our_file: + tqdm.pandas(file=our_file, leave=True, ascii=True, total=123) + series = pd.Series(randint(0, 50, (100,))) + series.progress_apply(lambda x: x + 10) + res = our_file.getvalue() + assert '100/123' in res + + +@with_setup(pretest, posttest) +def test_pandas_rolling_expanding(): + """Test pandas.(Series|DataFrame).(rolling|expanding)""" + try: + from numpy.random import randint + import pandas as pd + except ImportError: + raise SkipTest + + with closing(StringIO()) as our_file: + tqdm.pandas(file=our_file, leave=True, ascii=True) + + series = pd.Series(randint(0, 50, (123,))) + res1 = series.rolling(10).progress_apply(lambda x: 1, raw=True) + res2 = series.rolling(10).apply(lambda x: 1, raw=True) + assert res1.equals(res2) + + res3 = series.expanding(10).progress_apply(lambda x: 2, raw=True) + res4 = series.expanding(10).apply(lambda x: 2, raw=True) + assert res3.equals(res4) + + expects = ['114it'] # 123-10+1 + for exres in expects: + our_file.seek(0) + if our_file.getvalue().count(exres) < 2: + our_file.seek(0) + raise AssertionError( + "\nExpected:\n{0}\nIn:\n{1}\n".format( + exres + " at least twice.", our_file.read())) + + +@with_setup(pretest, posttest) +def test_pandas_series(): + """Test pandas.Series.progress_apply and .progress_map""" + try: + from numpy.random import randint + import pandas as pd + except ImportError: + raise SkipTest + + with closing(StringIO()) as our_file: + tqdm.pandas(file=our_file, leave=True, ascii=True) + + series = pd.Series(randint(0, 50, (123,))) + res1 = series.progress_apply(lambda x: x + 10) + res2 = series.apply(lambda x: x + 10) + assert res1.equals(res2) + + res3 = series.progress_map(lambda x: x + 10) + res4 = series.map(lambda x: x + 10) + assert res3.equals(res4) + + expects = ['100%', '123/123'] + for exres in expects: + our_file.seek(0) + if our_file.getvalue().count(exres) < 2: + our_file.seek(0) + raise AssertionError( + "\nExpected:\n{0}\nIn:\n{1}\n".format( + exres + " at least twice.", our_file.read())) + + +@with_setup(pretest, posttest) +def test_pandas_data_frame(): + """Test pandas.DataFrame.progress_apply and .progress_applymap""" + try: + from numpy.random import randint + import pandas as pd + except ImportError: + raise SkipTest + + with closing(StringIO()) as our_file: + tqdm.pandas(file=our_file, leave=True, ascii=True) + df = pd.DataFrame(randint(0, 50, (100, 200))) + + def task_func(x): + return x + 1 + + # applymap + res1 = df.progress_applymap(task_func) + res2 = df.applymap(task_func) + assert res1.equals(res2) + + # apply unhashable + res1 = [] + df.progress_apply(res1.extend) + assert len(res1) == df.size + + # apply + for axis in [0, 1, 'index', 'columns']: + res3 = df.progress_apply(task_func, axis=axis) + res4 = df.apply(task_func, axis=axis) + assert res3.equals(res4) + + our_file.seek(0) + if our_file.read().count('100%') < 3: + our_file.seek(0) + raise AssertionError("\nExpected:\n{0}\nIn:\n{1}\n".format( + '100% at least three times', our_file.read())) + + # apply_map, apply axis=0, apply axis=1 + expects = ['20000/20000', '200/200', '100/100'] + for exres in expects: + our_file.seek(0) + if our_file.getvalue().count(exres) < 1: + our_file.seek(0) + raise AssertionError( + "\nExpected:\n{0}\nIn:\n {1}\n".format( + exres + " at least once.", our_file.read())) + + +@with_setup(pretest, posttest) +def test_pandas_groupby_apply(): + """Test pandas.DataFrame.groupby(...).progress_apply""" + try: + from numpy.random import randint, rand + import pandas as pd + except ImportError: + raise SkipTest + + with closing(StringIO()) as our_file: + tqdm.pandas(file=our_file, leave=False, ascii=True) + + df = pd.DataFrame(randint(0, 50, (500, 3))) + df.groupby(0).progress_apply(lambda x: None) + + dfs = pd.DataFrame(randint(0, 50, (500, 3)), columns=list('abc')) + dfs.groupby(['a']).progress_apply(lambda x: None) + + df2 = df = pd.DataFrame(dict(a=randint(1, 8, 10000), b=rand(10000))) + res1 = df2.groupby("a").apply(max) + res2 = df2.groupby("a").progress_apply(max) + assert res1.equals(res2) + + our_file.seek(0) + + # don't expect final output since no `leave` and + # high dynamic `miniters` + nexres = '100%|##########|' + if nexres in our_file.read(): + our_file.seek(0) + raise AssertionError("\nDid not expect:\n{0}\nIn:{1}\n".format( + nexres, our_file.read())) + + with closing(StringIO()) as our_file: + tqdm.pandas(file=our_file, leave=True, ascii=True) + + dfs = pd.DataFrame(randint(0, 50, (500, 3)), columns=list('abc')) + dfs.loc[0] = [2, 1, 1] + dfs['d'] = 100 + + expects = ['500/500', '1/1', '4/4', '2/2'] + dfs.groupby(dfs.index).progress_apply(lambda x: None) + dfs.groupby('d').progress_apply(lambda x: None) + dfs.groupby(dfs.columns, axis=1).progress_apply(lambda x: None) + dfs.groupby([2, 2, 1, 1], axis=1).progress_apply(lambda x: None) + + our_file.seek(0) + if our_file.read().count('100%') < 4: + our_file.seek(0) + raise AssertionError("\nExpected:\n{0}\nIn:\n{1}\n".format( + '100% at least four times', our_file.read())) + + for exres in expects: + our_file.seek(0) + if our_file.getvalue().count(exres) < 1: + our_file.seek(0) + raise AssertionError( + "\nExpected:\n{0}\nIn:\n {1}\n".format( + exres + " at least once.", our_file.read())) + + +@with_setup(pretest, posttest) +def test_pandas_leave(): + """Test pandas with `leave=True`""" + try: + from numpy.random import randint + import pandas as pd + except ImportError: + raise SkipTest + + with closing(StringIO()) as our_file: + df = pd.DataFrame(randint(0, 100, (1000, 6))) + tqdm.pandas(file=our_file, leave=True, ascii=True) + df.groupby(0).progress_apply(lambda x: None) + + our_file.seek(0) + + exres = '100%|##########| 100/100' + if exres not in our_file.read(): + our_file.seek(0) + raise AssertionError( + "\nExpected:\n{0}\nIn:{1}\n".format(exres, our_file.read())) + + +@with_setup(pretest, posttest) +def test_pandas_apply_args_deprecation(): + """Test warning info in + `pandas.Dataframe(Series).progress_apply(func, *args)`""" + try: + from numpy.random import randint + from tqdm import tqdm_pandas + import pandas as pd + except ImportError: + raise SkipTest + + with closing(StringIO()) as our_file: + tqdm_pandas(tqdm(file=our_file, leave=False, ascii=True, ncols=20)) + df = pd.DataFrame(randint(0, 50, (500, 3))) + df.progress_apply(lambda x: None, 1) # 1 shall cause a warning + # Check deprecation message + res = our_file.getvalue() + assert all([i in res for i in ( + "TqdmDeprecationWarning", "not supported", + "keyword arguments instead")]) + + +@with_setup(pretest, posttest) +def test_pandas_deprecation(): + """Test bar object instance as argument deprecation""" + try: + from numpy.random import randint + from tqdm import tqdm_pandas + import pandas as pd + except ImportError: + raise SkipTest + + with closing(StringIO()) as our_file: + tqdm_pandas(tqdm(file=our_file, leave=False, ascii=True, ncols=20)) + df = pd.DataFrame(randint(0, 50, (500, 3))) + df.groupby(0).progress_apply(lambda x: None) + # Check deprecation message + assert "TqdmDeprecationWarning" in our_file.getvalue() + assert "instead of `tqdm_pandas(tqdm(...))`" in our_file.getvalue() + + with closing(StringIO()) as our_file: + tqdm_pandas(tqdm, file=our_file, leave=False, ascii=True, ncols=20) + df = pd.DataFrame(randint(0, 50, (500, 3))) + df.groupby(0).progress_apply(lambda x: None) + # Check deprecation message + assert "TqdmDeprecationWarning" in our_file.getvalue() + assert "instead of `tqdm_pandas(tqdm, ...)`" in our_file.getvalue() diff --git a/tqdm/tests/tests_perf.py b/tqdm/tests/tests_perf.py new file mode 100644 index 0000000..6cb7a6e --- /dev/null +++ b/tqdm/tests/tests_perf.py @@ -0,0 +1,367 @@ +from __future__ import print_function, division + +from nose.plugins.skip import SkipTest + +from contextlib import contextmanager + +import sys +from time import sleep, time + +from tqdm import trange +from tqdm import tqdm + +from tests_tqdm import with_setup, pretest, posttest, StringIO, closing, _range + +# Use relative/cpu timer to have reliable timings when there is a sudden load +try: + from time import process_time +except ImportError: + from time import clock + process_time = clock + + +def get_relative_time(prevtime=0): + return process_time() - prevtime + + +def cpu_sleep(t): + """Sleep the given amount of cpu time""" + start = process_time() + while (process_time() - start) < t: + pass + + +def checkCpuTime(sleeptime=0.2): + """Check if cpu time works correctly""" + if checkCpuTime.passed: + return True + # First test that sleeping does not consume cputime + start1 = process_time() + sleep(sleeptime) + t1 = process_time() - start1 + + # secondly check by comparing to cpusleep (where we actually do something) + start2 = process_time() + cpu_sleep(sleeptime) + t2 = process_time() - start2 + + if abs(t1) < 0.0001 and (t1 < t2 / 10): + return True + raise SkipTest + + +checkCpuTime.passed = False + + +@contextmanager +def relative_timer(): + start = process_time() + + def elapser(): + return process_time() - start + + yield lambda: elapser() + spent = process_time() - start + + def elapser(): # NOQA + return spent + + +def retry_on_except(n=3): + def wrapper(fn): + def test_inner(): + for i in range(1, n + 1): + try: + checkCpuTime() + fn() + except SkipTest: + if i >= n: + raise + else: + return + + test_inner.__doc__ = fn.__doc__ + return test_inner + + return wrapper + + +class MockIO(StringIO): + """Wraps StringIO to mock a file with no I/O""" + + def write(self, data): + return + + +def simple_progress(iterable=None, total=None, file=sys.stdout, desc='', + leave=False, miniters=1, mininterval=0.1, width=60): + """Simple progress bar reproducing tqdm's major features""" + n = [0] # use a closure + start_t = [time()] + last_n = [0] + last_t = [0] + if iterable is not None: + total = len(iterable) + + def format_interval(t): + mins, s = divmod(int(t), 60) + h, m = divmod(mins, 60) + if h: + return '{0:d}:{1:02d}:{2:02d}'.format(h, m, s) + else: + return '{0:02d}:{1:02d}'.format(m, s) + + def update_and_print(i=1): + n[0] += i + if (n[0] - last_n[0]) >= miniters: + last_n[0] = n[0] + + if (time() - last_t[0]) >= mininterval: + last_t[0] = time() # last_t[0] == current time + + spent = last_t[0] - start_t[0] + spent_fmt = format_interval(spent) + rate = n[0] / spent if spent > 0 else 0 + if 0.0 < rate < 1.0: + rate_fmt = "%.2fs/it" % (1.0 / rate) + else: + rate_fmt = "%.2fit/s" % rate + + frac = n[0] / total + percentage = int(frac * 100) + eta = (total - n[0]) / rate if rate > 0 else 0 + eta_fmt = format_interval(eta) + + # bar = "#" * int(frac * width) + barfill = " " * int((1.0 - frac) * width) + bar_length, frac_bar_length = divmod(int(frac * width * 10), 10) + bar = '#' * bar_length + frac_bar = chr(48 + frac_bar_length) if frac_bar_length \ + else ' ' + + file.write("\r%s %i%%|%s%s%s| %i/%i [%s<%s, %s]" % + (desc, percentage, bar, frac_bar, barfill, n[0], + total, spent_fmt, eta_fmt, rate_fmt)) + + if n[0] == total and leave: + file.write("\n") + file.flush() + + def update_and_yield(): + for elt in iterable: + yield elt + update_and_print() + + update_and_print(0) + if iterable is not None: + return update_and_yield() + else: + return update_and_print + + +def assert_performance(thresh, name_left, time_left, name_right, time_right): + """raises if time_left > thresh * time_right""" + if time_left > thresh * time_right: + raise ValueError( + ('{name[0]}: {time[0]:f}, ' + '{name[1]}: {time[1]:f}, ' + 'ratio {ratio:f} > {thresh:f}').format( + name=(name_left, name_right), + time=(time_left, time_right), + ratio=time_left / time_right, thresh=thresh)) + + +@with_setup(pretest, posttest) +@retry_on_except() +def test_iter_overhead(): + """Test overhead of iteration based tqdm""" + + total = int(1e6) + + with closing(MockIO()) as our_file: + a = 0 + with trange(total, file=our_file) as t: + with relative_timer() as time_tqdm: + for i in t: + a += i + assert a == (total * total - total) / 2.0 + + a = 0 + with relative_timer() as time_bench: + for i in _range(total): + a += i + our_file.write(a) + + assert_performance(6, 'trange', time_tqdm(), 'range', time_bench()) + + +@with_setup(pretest, posttest) +@retry_on_except() +def test_manual_overhead(): + """Test overhead of manual tqdm""" + + total = int(1e6) + + with closing(MockIO()) as our_file: + with tqdm(total=total * 10, file=our_file, leave=True) as t: + a = 0 + with relative_timer() as time_tqdm: + for i in _range(total): + a += i + t.update(10) + + a = 0 + with relative_timer() as time_bench: + for i in _range(total): + a += i + our_file.write(a) + + assert_performance(6, 'tqdm', time_tqdm(), 'range', time_bench()) + + +def worker(total, blocking=True): + def incr_bar(x): + with closing(StringIO()) as our_file: + for _ in trange( + total, file=our_file, + lock_args=None if blocking else (False,), + miniters=1, mininterval=0, maxinterval=0): + pass + return x + 1 + return incr_bar + + +@with_setup(pretest, posttest) +@retry_on_except() +def test_lock_args(): + """Test overhead of nonblocking threads""" + try: + from concurrent.futures import ThreadPoolExecutor + from threading import RLock + except ImportError: + raise SkipTest + import sys + + total = 8 + subtotal = 1000 + + tqdm.set_lock(RLock()) + with ThreadPoolExecutor(total) as pool: + sys.stderr.write('block ... ') + sys.stderr.flush() + with relative_timer() as time_tqdm: + res = list(pool.map(worker(subtotal, True), range(total))) + assert sum(res) == sum(range(total)) + total + sys.stderr.write('noblock ... ') + sys.stderr.flush() + with relative_timer() as time_noblock: + res = list(pool.map(worker(subtotal, False), range(total))) + assert sum(res) == sum(range(total)) + total + + assert_performance(0.2, 'noblock', time_noblock(), 'tqdm', time_tqdm()) + + +@with_setup(pretest, posttest) +@retry_on_except() +def test_iter_overhead_hard(): + """Test overhead of iteration based tqdm (hard)""" + + total = int(1e5) + + with closing(MockIO()) as our_file: + a = 0 + with trange(total, file=our_file, leave=True, miniters=1, + mininterval=0, maxinterval=0) as t: + with relative_timer() as time_tqdm: + for i in t: + a += i + assert a == (total * total - total) / 2.0 + + a = 0 + with relative_timer() as time_bench: + for i in _range(total): + a += i + our_file.write(("%i" % a) * 40) + + assert_performance(85, 'trange', time_tqdm(), 'range', time_bench()) + + +@with_setup(pretest, posttest) +@retry_on_except() +def test_manual_overhead_hard(): + """Test overhead of manual tqdm (hard)""" + + total = int(1e5) + + with closing(MockIO()) as our_file: + t = tqdm(total=total * 10, file=our_file, leave=True, miniters=1, + mininterval=0, maxinterval=0) + a = 0 + with relative_timer() as time_tqdm: + for i in _range(total): + a += i + t.update(10) + + a = 0 + with relative_timer() as time_bench: + for i in _range(total): + a += i + our_file.write(("%i" % a) * 40) + + assert_performance(85, 'tqdm', time_tqdm(), 'range', time_bench()) + + +@with_setup(pretest, posttest) +@retry_on_except() +def test_iter_overhead_simplebar_hard(): + """Test overhead of iteration based tqdm vs simple progress bar (hard)""" + + total = int(1e4) + + with closing(MockIO()) as our_file: + a = 0 + with trange(total, file=our_file, leave=True, miniters=1, + mininterval=0, maxinterval=0) as t: + with relative_timer() as time_tqdm: + for i in t: + a += i + assert a == (total * total - total) / 2.0 + + a = 0 + s = simple_progress(_range(total), file=our_file, leave=True, + miniters=1, mininterval=0) + with relative_timer() as time_bench: + for i in s: + a += i + + assert_performance( + 5, 'trange', time_tqdm(), 'simple_progress', time_bench()) + + +@with_setup(pretest, posttest) +@retry_on_except() +def test_manual_overhead_simplebar_hard(): + """Test overhead of manual tqdm vs simple progress bar (hard)""" + + total = int(1e4) + + with closing(MockIO()) as our_file: + t = tqdm(total=total * 10, file=our_file, leave=True, miniters=1, + mininterval=0, maxinterval=0) + a = 0 + with relative_timer() as time_tqdm: + for i in _range(total): + a += i + t.update(10) + + simplebar_update = simple_progress( + total=total * 10, file=our_file, leave=True, miniters=1, + mininterval=0) + a = 0 + with relative_timer() as time_bench: + for i in _range(total): + a += i + simplebar_update(10) + + assert_performance( + 5, 'tqdm', time_tqdm(), 'simple_progress', time_bench()) diff --git a/tqdm/tests/tests_synchronisation.py b/tqdm/tests/tests_synchronisation.py new file mode 100644 index 0000000..34f682a --- /dev/null +++ b/tqdm/tests/tests_synchronisation.py @@ -0,0 +1,213 @@ +from __future__ import division +from tqdm import tqdm, trange, TMonitor +from tests_tqdm import with_setup, pretest, posttest, SkipTest, \ + StringIO, closing +from tests_tqdm import DiscreteTimer, cpu_timify +from tests_perf import retry_on_except + +import sys +from time import sleep +from threading import Event + + +class FakeSleep(object): + """Wait until the discrete timer reached the required time""" + def __init__(self, dtimer): + self.dtimer = dtimer + + def sleep(self, t): + end = t + self.dtimer.t + while self.dtimer.t < end: + sleep(0.0000001) # sleep a bit to interrupt (instead of pass) + + +class FakeTqdm(object): + _instances = [] + + +def make_create_fake_sleep_event(sleep): + def wait(self, timeout=None): + if timeout is not None: + sleep(timeout) + return self.is_set() + + def create_fake_sleep_event(): + event = Event() + event.wait = wait + return event + + return create_fake_sleep_event + + +def incr(x): + return x + 1 + + +def incr_bar(x): + with closing(StringIO()) as our_file: + for _ in trange(x, lock_args=(False,), file=our_file): + pass + return incr(x) + + +@with_setup(pretest, posttest) +def test_monitor_thread(): + """Test dummy monitoring thread""" + maxinterval = 10 + + # Setup a discrete timer + timer = DiscreteTimer() + TMonitor._time = timer.time + # And a fake sleeper + sleeper = FakeSleep(timer) + TMonitor._event = make_create_fake_sleep_event(sleeper.sleep) + + # Instanciate the monitor + monitor = TMonitor(FakeTqdm, maxinterval) + # Test if alive, then killed + assert monitor.report() + monitor.exit() + timer.sleep(maxinterval * 2) # need to go out of the sleep to die + assert not monitor.report() + # assert not monitor.is_alive() # not working dunno why, thread not killed + del monitor + + +@with_setup(pretest, posttest) +def test_monitoring_and_cleanup(): + """Test for stalled tqdm instance and monitor deletion""" + # Note: should fix miniters for these tests, else with dynamic_miniters + # it's too complicated to handle with monitoring update and maxinterval... + maxinterval = 2 + + total = 1000 + # Setup a discrete timer + timer = DiscreteTimer() + # And a fake sleeper + sleeper = FakeSleep(timer) + # Setup TMonitor to use the timer + TMonitor._time = timer.time + TMonitor._event = make_create_fake_sleep_event(sleeper.sleep) + # Set monitor interval + tqdm.monitor_interval = maxinterval + with closing(StringIO()) as our_file: + with tqdm(total=total, file=our_file, miniters=500, mininterval=0.1, + maxinterval=maxinterval) as t: + cpu_timify(t, timer) + # Do a lot of iterations in a small timeframe + # (smaller than monitor interval) + timer.sleep(maxinterval / 2) # monitor won't wake up + t.update(500) + # check that our fixed miniters is still there + assert t.miniters == 500 + # Then do 1 it after monitor interval, so that monitor kicks in + timer.sleep(maxinterval * 2) + t.update(1) + # Wait for the monitor to get out of sleep's loop and update tqdm.. + timeend = timer.time() + while not (t.monitor.woken >= timeend and t.miniters == 1): + timer.sleep(1) # Force monitor to wake up if it woken too soon + sleep(0.000001) # sleep to allow interrupt (instead of pass) + assert t.miniters == 1 # check that monitor corrected miniters + # Note: at this point, there may be a race condition: monitor saved + # current woken time but timer.sleep() happen just before monitor + # sleep. To fix that, either sleep here or increase time in a loop + # to ensure that monitor wakes up at some point. + + # Try again but already at miniters = 1 so nothing will be done + timer.sleep(maxinterval * 2) + t.update(2) + timeend = timer.time() + while t.monitor.woken < timeend: + timer.sleep(1) # Force monitor to wake up if it woken too soon + sleep(0.000001) + # Wait for the monitor to get out of sleep's loop and update tqdm.. + assert t.miniters == 1 # check that monitor corrected miniters + + # Check that class var monitor is deleted if no instance left + tqdm.monitor_interval = 10 + assert tqdm.monitor is None + + +@with_setup(pretest, posttest) +def test_monitoring_multi(): + """Test on multiple bars, one not needing miniters adjustment""" + # Note: should fix miniters for these tests, else with dynamic_miniters + # it's too complicated to handle with monitoring update and maxinterval... + maxinterval = 2 + + total = 1000 + # Setup a discrete timer + timer = DiscreteTimer() + # And a fake sleeper + sleeper = FakeSleep(timer) + # Setup TMonitor to use the timer + TMonitor._time = timer.time + TMonitor._event = make_create_fake_sleep_event(sleeper.sleep) + # Set monitor interval + tqdm.monitor_interval = maxinterval + with closing(StringIO()) as our_file: + with tqdm(total=total, file=our_file, miniters=500, mininterval=0.1, + maxinterval=maxinterval) as t1: + # Set high maxinterval for t2 so monitor does not need to adjust it + with tqdm(total=total, file=our_file, miniters=500, mininterval=0.1, + maxinterval=1E5) as t2: + cpu_timify(t1, timer) + cpu_timify(t2, timer) + # Do a lot of iterations in a small timeframe + timer.sleep(maxinterval / 2) + t1.update(500) + t2.update(500) + assert t1.miniters == 500 + assert t2.miniters == 500 + # Then do 1 it after monitor interval, so that monitor kicks in + timer.sleep(maxinterval * 2) + t1.update(1) + t2.update(1) + # Wait for the monitor to get out of sleep and update tqdm + timeend = timer.time() + while not (t1.monitor.woken >= timeend and t1.miniters == 1): + timer.sleep(1) + sleep(0.000001) + assert t1.miniters == 1 # check that monitor corrected miniters + assert t2.miniters == 500 # check that t2 was not adjusted + + # Check that class var monitor is deleted if no instance left + tqdm.monitor_interval = 10 + assert tqdm.monitor is None + + +@with_setup(pretest, posttest) +def test_imap(): + """Test multiprocessing.Pool""" + try: + from multiprocessing import Pool + except ImportError: + raise SkipTest + + pool = Pool() + res = list(tqdm(pool.imap(incr, range(100)), disable=True)) + assert res[-1] == 100 + + +# py2: locks won't propagate to incr_bar so may cause `AttributeError` +@retry_on_except(n=3 if sys.version_info < (3,) else 1) +@with_setup(pretest, posttest) +def test_threadpool(): + """Test concurrent.futures.ThreadPoolExecutor""" + try: + from concurrent.futures import ThreadPoolExecutor + from threading import RLock + except ImportError: + raise SkipTest + + tqdm.set_lock(RLock()) + with ThreadPoolExecutor(8) as pool: + try: + res = list(tqdm(pool.map(incr_bar, range(100)), disable=True)) + except AttributeError: + if sys.version_info < (3,): + raise SkipTest + else: + raise + assert sum(res) == sum(range(1, 101)) diff --git a/tqdm/tests/tests_tqdm.py b/tqdm/tests/tests_tqdm.py new file mode 100644 index 0000000..e76970b --- /dev/null +++ b/tqdm/tests/tests_tqdm.py @@ -0,0 +1,1892 @@ +# -*- coding: utf-8 -*- +# Advice: use repr(our_file.read()) to print the full output of tqdm +# (else '\r' will replace the previous lines and you'll see only the latest. + +import sys +import csv +import re +import os +from nose import with_setup +from nose.plugins.skip import SkipTest +from nose.tools import assert_raises +from nose.tools import eq_ +from contextlib import contextmanager +from warnings import catch_warnings, simplefilter + +from tqdm import tqdm +from tqdm import trange +from tqdm import TqdmDeprecationWarning +from tqdm.std import Bar +from tqdm.contrib import DummyTqdmFile + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +from io import BytesIO +from io import IOBase # to support unicode strings + + +class DeprecationError(Exception): + pass + + +# Ensure we can use `with closing(...) as ... :` syntax +if getattr(StringIO, '__exit__', False) and \ + getattr(StringIO, '__enter__', False): + def closing(arg): + return arg +else: + from contextlib import closing + +try: + _range = xrange +except NameError: + _range = range + +try: + _unicode = unicode +except NameError: + _unicode = str + +nt_and_no_colorama = False +if os.name == 'nt': + try: + import colorama # NOQA + except ImportError: + nt_and_no_colorama = True + +# Regex definitions +# List of control characters +CTRLCHR = [r'\r', r'\n', r'\x1b\[A'] # Need to escape [ for regex +# Regular expressions compilation +RE_rate = re.compile(r'(\d+\.\d+)it/s') +RE_ctrlchr = re.compile("(%s)" % '|'.join(CTRLCHR)) # Match control chars +RE_ctrlchr_excl = re.compile('|'.join(CTRLCHR)) # Match and exclude ctrl chars +RE_pos = re.compile( + r'([\r\n]+((pos\d+) bar:\s+\d+%|\s{3,6})?[^\r\n]*)') + + +def pos_line_diff(res_list, expected_list, raise_nonempty=True): + """ + Return differences between two bar output lists. + To be used with `RE_pos` + """ + res = [(r, e) for r, e in zip(res_list, expected_list) + for pos in [len(e) - len(e.lstrip('\n'))] # bar position + if r != e # simple comparison + if not r.startswith(e) # start matches + or not ( + # move up at end (maybe less due to closing bars) + any(r.endswith(end + i * '\x1b[A') for i in range(pos + 1) + for end in [ + ']', # bar + ' ']) # cleared + or '100%' in r # completed bar + or r == '\n') # final bar + or r[(-1 - pos) * len('\x1b[A'):] == '\x1b[A'] # too many moves up + if raise_nonempty and (res or len(res_list) != len(expected_list)): + if len(res_list) < len(expected_list): + res.extend([(None, e) for e in expected_list[len(res_list):]]) + elif len(res_list) > len(expected_list): + res.extend([(r, None) for r in res_list[len(expected_list):]]) + raise AssertionError( + "Got => Expected\n" + '\n'.join('%r => %r' % i for i in res)) + return res + + +class DiscreteTimer(object): + """Virtual discrete time manager, to precisely control time for tests""" + + def __init__(self): + self.t = 0.0 + + def sleep(self, t): + """Sleep = increment the time counter (almost no CPU used)""" + self.t += t + + def time(self): + """Get the current time""" + return self.t + + +def cpu_timify(t, timer=None): + """Force tqdm to use the specified timer instead of system-wide time()""" + if timer is None: + timer = DiscreteTimer() + t._time = timer.time + t._sleep = timer.sleep + t.start_t = t.last_print_t = t._time() + return timer + + +def pretest(): + # setcheckinterval is deprecated + getattr(sys, 'setswitchinterval', getattr(sys, 'setcheckinterval'))(100) + + if getattr(tqdm, "_instances", False): + n = len(tqdm._instances) + if n: + tqdm._instances.clear() + raise EnvironmentError( + "{0} `tqdm` instances still in existence PRE-test".format(n)) + + +def posttest(): + if getattr(tqdm, "_instances", False): + n = len(tqdm._instances) + if n: + tqdm._instances.clear() + raise EnvironmentError( + "{0} `tqdm` instances still in existence POST-test".format(n)) + + +class UnicodeIO(IOBase): + """Unicode version of StringIO""" + + def __init__(self, *args, **kwargs): + super(UnicodeIO, self).__init__(*args, **kwargs) + self.encoding = 'U8' # io.StringIO supports unicode, but no encoding + self.text = '' + self.cursor = 0 + + def __len__(self): + return len(self.text) + + def seek(self, offset): + self.cursor = offset + + def tell(self): + return self.cursor + + def write(self, s): + self.text = self.text[:self.cursor] + s + \ + self.text[self.cursor + len(s):] + self.cursor += len(s) + + def read(self, n=-1): + _cur = self.cursor + self.cursor = len(self) if n < 0 \ + else min(_cur + n, len(self)) + return self.text[_cur:self.cursor] + + def getvalue(self): + return self.text + + +def get_bar(all_bars, i): + """Get a specific update from a whole bar traceback""" + # Split according to any used control characters + bars_split = RE_ctrlchr_excl.split(all_bars) + bars_split = list(filter(None, bars_split)) # filter out empty splits + return bars_split[i] + + +def progressbar_rate(bar_str): + return float(RE_rate.search(bar_str).group(1)) + + +def squash_ctrlchars(s): + """Apply control characters in a string just like a terminal display""" + # Init variables + curline = 0 # current line in our fake terminal + lines = [''] # state of our fake terminal + + # Split input string by control codes + RE_ctrl = re.compile("(%s)" % ("|".join(CTRLCHR)), flags=re.DOTALL) + s_split = RE_ctrl.split(s) + s_split = filter(None, s_split) # filter out empty splits + + # For each control character or message + for nextctrl in s_split: + # If it's a control character, apply it + if nextctrl == '\r': + # Carriage return + # Go to the beginning of the line + # simplified here: we just empty the string + lines[curline] = '' + elif nextctrl == '\n': + # Newline + # Go to the next line + if curline < (len(lines) - 1): + # If already exists, just move cursor + curline += 1 + else: + # Else the new line is created + lines.append('') + curline += 1 + elif nextctrl == '\x1b[A': + # Move cursor up + if curline > 0: + curline -= 1 + else: + raise ValueError("Cannot go up, anymore!") + # Else, it is a message, we print it on current line + else: + lines[curline] += nextctrl + + return lines + + +def test_format_interval(): + """Test time interval format""" + format_interval = tqdm.format_interval + + assert format_interval(60) == '01:00' + assert format_interval(6160) == '1:42:40' + assert format_interval(238113) == '66:08:33' + + +def test_format_num(): + """Test number format""" + format_num = tqdm.format_num + + assert float(format_num(1337)) == 1337 + assert format_num(int(1e6)) == '1e+6' + assert format_num(1239876) == '1''239''876' + + +def test_format_meter(): + """Test statistics and progress bar formatting""" + try: + unich = unichr + except NameError: + unich = chr + + format_meter = tqdm.format_meter + + assert format_meter(0, 1000, 13) == \ + " 0%| | 0/1000 [00:13= (3,): + assert format_meter(0, 1000, 13, ncols=68, prefix='fullwidth: ') == \ + "fullwidth: 0%| | 0/1000 [00:13= (bigstep - 1) and \ + ((i - (bigstep - 1)) % smallstep) == 0: + timer.sleep(1e-2) + if i >= 3 * bigstep: + break + + assert "15%" in our_file.getvalue() + + # Test different behavior with and without mininterval + timer = DiscreteTimer() + total = 1000 + mininterval = 0.1 + maxinterval = 10 + with closing(StringIO()) as our_file: + with tqdm(total=total, file=our_file, miniters=None, smoothing=1, + mininterval=mininterval, maxinterval=maxinterval) as tm1: + with tqdm(total=total, file=our_file, miniters=None, smoothing=1, + mininterval=0, maxinterval=maxinterval) as tm2: + + cpu_timify(tm1, timer) + cpu_timify(tm2, timer) + + # Fast iterations, check if dynamic_miniters triggers + timer.sleep(mininterval) # to force update for t1 + tm1.update(total / 2) + tm2.update(total / 2) + assert int(tm1.miniters) == tm2.miniters == total / 2 + + # Slow iterations, check different miniters if mininterval + timer.sleep(maxinterval * 2) + tm1.update(total / 2) + tm2.update(total / 2) + res = [tm1.miniters, tm2.miniters] + assert res == [(total / 2) * mininterval / (maxinterval * 2), + (total / 2) * maxinterval / (maxinterval * 2)] + + # Same with iterable based tqdm + timer1 = DiscreteTimer() # need 2 timers for each bar because zip not work + timer2 = DiscreteTimer() + total = 100 + mininterval = 0.1 + maxinterval = 10 + with closing(StringIO()) as our_file: + t1 = tqdm(_range(total), file=our_file, miniters=None, smoothing=1, + mininterval=mininterval, maxinterval=maxinterval) + t2 = tqdm(_range(total), file=our_file, miniters=None, smoothing=1, + mininterval=0, maxinterval=maxinterval) + + cpu_timify(t1, timer1) + cpu_timify(t2, timer2) + + for i in t1: + if i == ((total / 2) - 2): + timer1.sleep(mininterval) + if i == (total - 1): + timer1.sleep(maxinterval * 2) + + for i in t2: + if i == ((total / 2) - 2): + timer2.sleep(mininterval) + if i == (total - 1): + timer2.sleep(maxinterval * 2) + + assert t1.miniters == 0.255 + assert t2.miniters == 0.5 + + t1.close() + t2.close() + + +@with_setup(pretest, posttest) +def test_min_iters(): + """Test miniters""" + with closing(StringIO()) as our_file: + for _ in tqdm(_range(3), file=our_file, leave=True, miniters=4): + our_file.write('blank\n') + assert '\nblank\nblank\n' in our_file.getvalue() + + with closing(StringIO()) as our_file: + for _ in tqdm(_range(3), file=our_file, leave=True, miniters=1): + our_file.write('blank\n') + # assume automatic mininterval = 0 means intermediate output + assert '| 3/3 ' in our_file.getvalue() + + +@with_setup(pretest, posttest) +def test_dynamic_min_iters(): + """Test purely dynamic miniters (and manual updates and __del__)""" + with closing(StringIO()) as our_file: + total = 10 + t = tqdm(total=total, file=our_file, miniters=None, mininterval=0, + smoothing=1) + + t.update() + # Increase 3 iterations + t.update(3) + # The next two iterations should be skipped because of dynamic_miniters + t.update() + t.update() + # The third iteration should be displayed + t.update() + + out = our_file.getvalue() + assert t.dynamic_miniters + t.__del__() # simulate immediate del gc + + assert ' 0%| | 0/10 [00:00<' in out + assert '40%' in out + assert '50%' not in out + assert '60%' not in out + assert '70%' in out + + # Check with smoothing=0, miniters should be set to max update seen so far + with closing(StringIO()) as our_file: + total = 10 + t = tqdm(total=total, file=our_file, miniters=None, mininterval=0, + smoothing=0) + + t.update() + t.update(2) + t.update(5) # this should be stored as miniters + t.update(1) + + out = our_file.getvalue() + assert all(i in out for i in ("0/10", "1/10", "3/10")) + assert "2/10" not in out + assert t.dynamic_miniters and not t.smoothing + assert t.miniters == 5 + t.close() + + # Check iterable based tqdm + with closing(StringIO()) as our_file: + t = tqdm(_range(10), file=our_file, miniters=None, mininterval=None, + smoothing=0.5) + for _ in t: + pass + assert t.dynamic_miniters + + # No smoothing + with closing(StringIO()) as our_file: + t = tqdm(_range(10), file=our_file, miniters=None, mininterval=None, + smoothing=0) + for _ in t: + pass + assert t.dynamic_miniters + + # No dynamic_miniters (miniters is fixed manually) + with closing(StringIO()) as our_file: + t = tqdm(_range(10), file=our_file, miniters=1, mininterval=None) + for _ in t: + pass + assert not t.dynamic_miniters + + +@with_setup(pretest, posttest) +def test_big_min_interval(): + """Test large mininterval""" + with closing(StringIO()) as our_file: + for _ in tqdm(_range(2), file=our_file, mininterval=1E10): + pass + assert '50%' not in our_file.getvalue() + + with closing(StringIO()) as our_file: + with tqdm(_range(2), file=our_file, mininterval=1E10) as t: + t.update() + t.update() + assert '50%' not in our_file.getvalue() + + +@with_setup(pretest, posttest) +def test_smoothed_dynamic_min_iters(): + """Test smoothed dynamic miniters""" + timer = DiscreteTimer() + + with closing(StringIO()) as our_file: + with tqdm(total=100, file=our_file, miniters=None, mininterval=0, + smoothing=0.5, maxinterval=0) as t: + cpu_timify(t, timer) + + # Increase 10 iterations at once + t.update(10) + # The next iterations should be partially skipped + for _ in _range(2): + t.update(4) + for _ in _range(20): + t.update() + + out = our_file.getvalue() + assert t.dynamic_miniters + assert ' 0%| | 0/100 [00:00<' in out + assert '10%' in out + assert '14%' not in out + assert '18%' in out + assert '20%' not in out + assert '25%' in out + assert '30%' not in out + assert '32%' in out + + +@with_setup(pretest, posttest) +def test_smoothed_dynamic_min_iters_with_min_interval(): + """Test smoothed dynamic miniters with mininterval""" + timer = DiscreteTimer() + + # In this test, `miniters` should gradually decline + total = 100 + + with closing(StringIO()) as our_file: + # Test manual updating tqdm + with tqdm(total=total, file=our_file, miniters=None, mininterval=1e-3, + smoothing=1, maxinterval=0) as t: + cpu_timify(t, timer) + + t.update(10) + timer.sleep(1e-2) + for _ in _range(4): + t.update() + timer.sleep(1e-2) + out = our_file.getvalue() + assert t.dynamic_miniters + + with closing(StringIO()) as our_file: + # Test iteration-based tqdm + with tqdm(_range(total), file=our_file, miniters=None, + mininterval=0.01, smoothing=1, maxinterval=0) as t2: + cpu_timify(t2, timer) + + for i in t2: + if i >= 10: + timer.sleep(0.1) + if i >= 14: + break + out2 = our_file.getvalue() + + assert t.dynamic_miniters + assert ' 0%| | 0/100 [00:00<' in out + assert '11%' in out and '11%' in out2 + # assert '12%' not in out and '12%' in out2 + assert '13%' in out and '13%' in out2 + assert '14%' in out and '14%' in out2 + + +@with_setup(pretest, posttest) +def test_rlock_creation(): + """Test that importing tqdm does not create multiprocessing objects.""" + import multiprocessing as mp + if sys.version_info < (3, 3): + # unittest.mock is a 3.3+ feature + raise SkipTest + + # Use 'spawn' instead of 'fork' so that the process does not inherit any + # globals that have been constructed by running other tests + ctx = mp.get_context('spawn') + with ctx.Pool(1) as pool: + # The pool will propagate the error if the target method fails + pool.apply(_rlock_creation_target) + + +def _rlock_creation_target(): + """Check that the RLock has not been constructed.""" + from unittest.mock import patch + import multiprocessing as mp + + # Patch the RLock class/method but use the original implementation + with patch('multiprocessing.RLock', wraps=mp.RLock) as rlock_mock: + # Importing the module should not create a lock + from tqdm import tqdm + assert rlock_mock.call_count == 0 + # Creating a progress bar should initialize the lock + with closing(StringIO()) as our_file: + with tqdm(file=our_file) as _: # NOQA + pass + assert rlock_mock.call_count == 1 + # Creating a progress bar again should reuse the lock + with closing(StringIO()) as our_file: + with tqdm(file=our_file) as _: # NOQA + pass + assert rlock_mock.call_count == 1 + + +@with_setup(pretest, posttest) +def test_disable(): + """Test disable""" + with closing(StringIO()) as our_file: + for _ in tqdm(_range(3), file=our_file, disable=True): + pass + assert our_file.getvalue() == '' + + with closing(StringIO()) as our_file: + progressbar = tqdm(total=3, file=our_file, miniters=1, disable=True) + progressbar.update(3) + progressbar.close() + assert our_file.getvalue() == '' + + +@with_setup(pretest, posttest) +def test_infinite_total(): + """Test treatment of infinite total""" + with closing(StringIO()) as our_file: + for _ in tqdm(_range(3), file=our_file, total=float("inf")): + pass + + +@with_setup(pretest, posttest) +def test_nototal(): + """Test unknown total length""" + with closing(StringIO()) as our_file: + for i in tqdm((i for i in range(10)), file=our_file, unit_scale=10): + pass + assert "100it" in our_file.getvalue() + + with closing(StringIO()) as our_file: + for i in tqdm((i for i in range(10)), file=our_file, + bar_format="{l_bar}{bar}{r_bar}"): + pass + assert "10/?" in our_file.getvalue() + + +@with_setup(pretest, posttest) +def test_unit(): + """Test SI unit prefix""" + with closing(StringIO()) as our_file: + for _ in tqdm(_range(3), file=our_file, miniters=1, unit="bytes"): + pass + assert 'bytes/s' in our_file.getvalue() + + +@with_setup(pretest, posttest) +def test_ascii(): + """Test ascii/unicode bar""" + # Test ascii autodetection + with closing(StringIO()) as our_file: + with tqdm(total=10, file=our_file, ascii=None) as t: + assert t.ascii # TODO: this may fail in the future + + # Test ascii bar + with closing(StringIO()) as our_file: + for _ in tqdm(_range(3), total=15, file=our_file, miniters=1, + mininterval=0, ascii=True): + pass + res = our_file.getvalue().strip("\r").split("\r") + assert '7%|6' in res[1] + assert '13%|#3' in res[2] + assert '20%|##' in res[3] + + # Test unicode bar + with closing(UnicodeIO()) as our_file: + with tqdm(total=15, file=our_file, ascii=False, mininterval=0) as t: + for _ in _range(3): + t.update() + res = our_file.getvalue().strip("\r").split("\r") + assert u"7%|\u258b" in res[1] + assert u"13%|\u2588\u258e" in res[2] + assert u"20%|\u2588\u2588" in res[3] + + # Test custom bar + for ascii in [" .oO0", " #"]: + with closing(StringIO()) as our_file: + for _ in tqdm(_range(len(ascii) - 1), file=our_file, miniters=1, + mininterval=0, ascii=ascii, ncols=27): + pass + res = our_file.getvalue().strip("\r").split("\r") + for bar, line in zip(ascii, res): + assert '|' + bar + '|' in line + + +@with_setup(pretest, posttest) +def test_update(): + """Test manual creation and updates""" + res = None + with closing(StringIO()) as our_file: + with tqdm(total=2, file=our_file, miniters=1, mininterval=0) \ + as progressbar: + assert len(progressbar) == 2 + progressbar.update(2) + assert '| 2/2' in our_file.getvalue() + progressbar.desc = 'dynamically notify of 4 increments in total' + progressbar.total = 4 + progressbar.update(-1) + progressbar.update(2) + res = our_file.getvalue() + assert '| 3/4 ' in res + assert 'dynamically notify of 4 increments in total' in res + + +@with_setup(pretest, posttest) +def test_close(): + """Test manual creation and closure and n_instances""" + + # With `leave` option + with closing(StringIO()) as our_file: + progressbar = tqdm(total=3, file=our_file, miniters=10) + progressbar.update(3) + assert '| 3/3 ' not in our_file.getvalue() # Should be blank + assert len(tqdm._instances) == 1 + progressbar.close() + assert len(tqdm._instances) == 0 + assert '| 3/3 ' in our_file.getvalue() + + # Without `leave` option + with closing(StringIO()) as our_file: + progressbar = tqdm(total=3, file=our_file, miniters=10, leave=False) + progressbar.update(3) + progressbar.close() + assert '| 3/3 ' not in our_file.getvalue() # Should be blank + + # With all updates + with closing(StringIO()) as our_file: + assert len(tqdm._instances) == 0 + with tqdm(total=3, file=our_file, miniters=0, mininterval=0, + leave=True) as progressbar: + assert len(tqdm._instances) == 1 + progressbar.update(3) + res = our_file.getvalue() + assert '| 3/3 ' in res # Should be blank + assert '\n' not in res + # close() called + assert len(tqdm._instances) == 0 + + exres = res.rsplit(', ', 1)[0] + res = our_file.getvalue() + assert res[-1] == '\n' + if not res.startswith(exres): + raise AssertionError( + "\n<<< Expected:\n{0}\n>>> Got:\n{1}\n===".format( + exres + ', ...it/s]\n', our_file.getvalue())) + + # Closing after the output stream has closed + with closing(StringIO()) as our_file: + t = tqdm(total=2, file=our_file) + t.update() + t.update() + t.close() + + +@with_setup(pretest, posttest) +def test_smoothing(): + """Test exponential weighted average smoothing""" + timer = DiscreteTimer() + + # -- Test disabling smoothing + with closing(StringIO()) as our_file: + with tqdm(_range(3), file=our_file, smoothing=None, leave=True) as t: + cpu_timify(t, timer) + + for _ in t: + pass + assert '| 3/3 ' in our_file.getvalue() + + # -- Test smoothing + # Compile the regex to find the rate + # 1st case: no smoothing (only use average) + with closing(StringIO()) as our_file2: + with closing(StringIO()) as our_file: + t = tqdm(_range(3), file=our_file2, smoothing=None, leave=True, + miniters=1, mininterval=0) + cpu_timify(t, timer) + + with tqdm(_range(3), file=our_file, smoothing=None, leave=True, + miniters=1, mininterval=0) as t2: + cpu_timify(t2, timer) + + for i in t2: + # Sleep more for first iteration and + # see how quickly rate is updated + if i == 0: + timer.sleep(0.01) + else: + # Need to sleep in all iterations + # to calculate smoothed rate + # (else delta_t is 0!) + timer.sleep(0.001) + t.update() + n_old = len(tqdm._instances) + t.close() + assert len(tqdm._instances) == n_old - 1 + # Get result for iter-based bar + a = progressbar_rate(get_bar(our_file.getvalue(), 3)) + # Get result for manually updated bar + a2 = progressbar_rate(get_bar(our_file2.getvalue(), 3)) + + # 2nd case: use max smoothing (= instant rate) + with closing(StringIO()) as our_file2: + with closing(StringIO()) as our_file: + t = tqdm(_range(3), file=our_file2, smoothing=1, leave=True, + miniters=1, mininterval=0) + cpu_timify(t, timer) + + with tqdm(_range(3), file=our_file, smoothing=1, leave=True, + miniters=1, mininterval=0) as t2: + cpu_timify(t2, timer) + + for i in t2: + if i == 0: + timer.sleep(0.01) + else: + timer.sleep(0.001) + t.update() + t.close() + # Get result for iter-based bar + b = progressbar_rate(get_bar(our_file.getvalue(), 3)) + # Get result for manually updated bar + b2 = progressbar_rate(get_bar(our_file2.getvalue(), 3)) + + # 3rd case: use medium smoothing + with closing(StringIO()) as our_file2: + with closing(StringIO()) as our_file: + t = tqdm(_range(3), file=our_file2, smoothing=0.5, leave=True, + miniters=1, mininterval=0) + cpu_timify(t, timer) + + t2 = tqdm(_range(3), file=our_file, smoothing=0.5, leave=True, + miniters=1, mininterval=0) + cpu_timify(t2, timer) + + for i in t2: + if i == 0: + timer.sleep(0.01) + else: + timer.sleep(0.001) + t.update() + t2.close() + t.close() + # Get result for iter-based bar + c = progressbar_rate(get_bar(our_file.getvalue(), 3)) + # Get result for manually updated bar + c2 = progressbar_rate(get_bar(our_file2.getvalue(), 3)) + + # Check that medium smoothing's rate is between no and max smoothing rates + assert a <= c <= b + assert a2 <= c2 <= b2 + + +@with_setup(pretest, posttest) +def test_deprecated_nested(): + """Test nested progress bars""" + if nt_and_no_colorama: + raise SkipTest + # TODO: test degradation on windows without colorama? + + # Artificially test nested loop printing + # Without leave + our_file = StringIO() + try: + tqdm(total=2, file=our_file, nested=True) + except TqdmDeprecationWarning: + if """`nested` is deprecated and automated. +Use `position` instead for manual control.""" not in our_file.getvalue(): + raise + else: + raise DeprecationError("Should not allow nested kwarg") + + +@with_setup(pretest, posttest) +def test_bar_format(): + """Test custom bar formatting""" + with closing(StringIO()) as our_file: + bar_format = r'{l_bar}{bar}|{n_fmt}/{total_fmt}-{n}/{total}{percentage}{rate}{rate_fmt}{elapsed}{remaining}' # NOQA + for _ in trange(2, file=our_file, leave=True, bar_format=bar_format): + pass + out = our_file.getvalue() + assert "\r 0%| |0/2-0/20.0None?it/s00:00?\r" in out + + # Test unicode string auto conversion + with closing(StringIO()) as our_file: + bar_format = r'hello world' + with tqdm(ascii=False, bar_format=bar_format, file=our_file) as t: + assert isinstance(t.bar_format, _unicode) + + +@with_setup(pretest, posttest) +def test_custom_format(): + """Test adding additional derived format arguments""" + class TqdmExtraFormat(tqdm): + """Provides a `total_time` format parameter""" + @property + def format_dict(self): + d = super(TqdmExtraFormat, self).format_dict + total_time = d["elapsed"] * (d["total"] or 0) / max(d["n"], 1) + d.update(total_time=self.format_interval(total_time) + " in total") + return d + + with closing(StringIO()) as our_file: + for i in TqdmExtraFormat( + range(10), file=our_file, + bar_format="{total_time}: {percentage:.0f}%|{bar}{r_bar}"): + pass + assert "00:00 in total" in our_file.getvalue() + + +@with_setup(pretest, posttest) +def test_unpause(): + """Test unpause""" + timer = DiscreteTimer() + with closing(StringIO()) as our_file: + t = trange(10, file=our_file, leave=True, mininterval=0) + cpu_timify(t, timer) + timer.sleep(0.01) + t.update() + timer.sleep(0.01) + t.update() + timer.sleep(0.1) # longer wait time + t.unpause() + timer.sleep(0.01) + t.update() + timer.sleep(0.01) + t.update() + t.close() + r_before = progressbar_rate(get_bar(our_file.getvalue(), 2)) + r_after = progressbar_rate(get_bar(our_file.getvalue(), 3)) + assert r_before == r_after + + +@with_setup(pretest, posttest) +def test_reset(): + """Test resetting a bar for re-use""" + with closing(StringIO()) as our_file: + with tqdm(total=10, file=our_file, + miniters=1, mininterval=0, maxinterval=0) as t: + t.update(9) + t.reset() + t.update() + t.reset(total=12) + t.update(10) + assert '| 1/10' in our_file.getvalue() + assert '| 10/12' in our_file.getvalue() + + +@with_setup(pretest, posttest) +def test_position(): + """Test positioned progress bars""" + if nt_and_no_colorama: + raise SkipTest + + # Artificially test nested loop printing + # Without leave + our_file = StringIO() + kwargs = dict(file=our_file, miniters=1, mininterval=0, maxinterval=0) + t = tqdm(total=2, desc='pos2 bar', leave=False, position=2, **kwargs) + t.update() + t.close() + out = our_file.getvalue() + res = [m[0] for m in RE_pos.findall(out)] + exres = ['\n\n\rpos2 bar: 0%', + '\n\n\rpos2 bar: 50%', + '\n\n\r '] + + pos_line_diff(res, exres) + + # Test iteration-based tqdm positioning + our_file = StringIO() + kwargs["file"] = our_file + for _ in trange(2, desc='pos0 bar', position=0, **kwargs): + for _ in trange(2, desc='pos1 bar', position=1, **kwargs): + for _ in trange(2, desc='pos2 bar', position=2, **kwargs): + pass + out = our_file.getvalue() + res = [m[0] for m in RE_pos.findall(out)] + exres = ['\rpos0 bar: 0%', + '\n\rpos1 bar: 0%', + '\n\n\rpos2 bar: 0%', + '\n\n\rpos2 bar: 50%', + '\n\n\rpos2 bar: 100%', + '\rpos2 bar: 100%', + '\n\n\rpos1 bar: 50%', + '\n\n\rpos2 bar: 0%', + '\n\n\rpos2 bar: 50%', + '\n\n\rpos2 bar: 100%', + '\rpos2 bar: 100%', + '\n\n\rpos1 bar: 100%', + '\rpos1 bar: 100%', + '\n\rpos0 bar: 50%', + '\n\rpos1 bar: 0%', + '\n\n\rpos2 bar: 0%', + '\n\n\rpos2 bar: 50%', + '\n\n\rpos2 bar: 100%', + '\rpos2 bar: 100%', + '\n\n\rpos1 bar: 50%', + '\n\n\rpos2 bar: 0%', + '\n\n\rpos2 bar: 50%', + '\n\n\rpos2 bar: 100%', + '\rpos2 bar: 100%', + '\n\n\rpos1 bar: 100%', + '\rpos1 bar: 100%', + '\n\rpos0 bar: 100%', + '\rpos0 bar: 100%', + '\n'] + pos_line_diff(res, exres) + + # Test manual tqdm positioning + our_file = StringIO() + kwargs["file"] = our_file + kwargs["total"] = 2 + t1 = tqdm(desc='pos0 bar', position=0, **kwargs) + t2 = tqdm(desc='pos1 bar', position=1, **kwargs) + t3 = tqdm(desc='pos2 bar', position=2, **kwargs) + for _ in _range(2): + t1.update() + t3.update() + t2.update() + out = our_file.getvalue() + res = [m[0] for m in RE_pos.findall(out)] + exres = ['\rpos0 bar: 0%', + '\n\rpos1 bar: 0%', + '\n\n\rpos2 bar: 0%', + '\rpos0 bar: 50%', + '\n\n\rpos2 bar: 50%', + '\n\rpos1 bar: 50%', + '\rpos0 bar: 100%', + '\n\n\rpos2 bar: 100%', + '\n\rpos1 bar: 100%'] + pos_line_diff(res, exres) + t1.close() + t2.close() + t3.close() + + # Test auto repositioning of bars when a bar is prematurely closed + # tqdm._instances.clear() # reset number of instances + with closing(StringIO()) as our_file: + t1 = tqdm(total=10, file=our_file, desc='pos0 bar', mininterval=0) + t2 = tqdm(total=10, file=our_file, desc='pos1 bar', mininterval=0) + t3 = tqdm(total=10, file=our_file, desc='pos2 bar', mininterval=0) + res = [m[0] for m in RE_pos.findall(our_file.getvalue())] + exres = ['\rpos0 bar: 0%', + '\n\rpos1 bar: 0%', + '\n\n\rpos2 bar: 0%'] + pos_line_diff(res, exres) + + t2.close() + t4 = tqdm(total=10, file=our_file, desc='pos3 bar', mininterval=0) + t1.update(1) + t3.update(1) + t4.update(1) + res = [m[0] for m in RE_pos.findall(our_file.getvalue())] + exres = ['\rpos0 bar: 0%', + '\n\rpos1 bar: 0%', + '\n\n\rpos2 bar: 0%', + '\n\n\r ', + '\r\x1b[A\x1b[A', + '\rpos1 bar: 0%', + '\n\n\n\rpos3 bar: 0%', + '\rpos0 bar: 10%', + '\n\rpos2 bar: 10%', + '\n\n\rpos3 bar: 10%'] + pos_line_diff(res, exres) + t4.close() + t3.close() + t1.close() + + +@with_setup(pretest, posttest) +def test_set_description(): + """Test set description""" + with closing(StringIO()) as our_file: + with tqdm(desc='Hello', file=our_file) as t: + assert t.desc == 'Hello' + t.set_description_str('World') + assert t.desc == 'World' + t.set_description() + assert t.desc == '' + t.set_description('Bye') + assert t.desc == 'Bye: ' + assert "World" in our_file.getvalue() + + # without refresh + with closing(StringIO()) as our_file: + with tqdm(desc='Hello', file=our_file) as t: + assert t.desc == 'Hello' + t.set_description_str('World', False) + assert t.desc == 'World' + t.set_description(None, False) + assert t.desc == '' + assert "World" not in our_file.getvalue() + + # unicode + with closing(StringIO()) as our_file: + with tqdm(total=10, file=our_file) as t: + t.set_description(u"\xe1\xe9\xed\xf3\xfa") + + +@with_setup(pretest, posttest) +def test_deprecated_gui(): + """Test internal GUI properties""" + # Check: StatusPrinter iff gui is disabled + with closing(StringIO()) as our_file: + t = tqdm(total=2, gui=True, file=our_file, miniters=1, mininterval=0) + assert not hasattr(t, "sp") + try: + t.update(1) + except TqdmDeprecationWarning as e: + if ('Please use `tqdm.gui.tqdm(...)` instead of' + ' `tqdm(..., gui=True)`') \ + not in our_file.getvalue(): + raise e + else: + raise DeprecationError('Should not allow manual gui=True without' + ' overriding __iter__() and update()') + finally: + t._instances.clear() + # t.close() + # len(tqdm._instances) += 1 # undo the close() decrement + + t = tqdm(_range(3), gui=True, file=our_file, miniters=1, mininterval=0) + try: + for _ in t: + pass + except TqdmDeprecationWarning as e: + if ('Please use `tqdm.gui.tqdm(...)` instead of' + ' `tqdm(..., gui=True)`') \ + not in our_file.getvalue(): + raise e + else: + raise DeprecationError('Should not allow manual gui=True without' + ' overriding __iter__() and update()') + finally: + t._instances.clear() + # t.close() + # len(tqdm._instances) += 1 # undo the close() decrement + + with tqdm(total=1, gui=False, file=our_file) as t: + assert hasattr(t, "sp") + + +@with_setup(pretest, posttest) +def test_cmp(): + """Test comparison functions""" + with closing(StringIO()) as our_file: + t0 = tqdm(total=10, file=our_file) + t1 = tqdm(total=10, file=our_file) + t2 = tqdm(total=10, file=our_file) + + assert t0 < t1 + assert t2 >= t0 + assert t0 <= t2 + + t3 = tqdm(total=10, file=our_file) + t4 = tqdm(total=10, file=our_file) + t5 = tqdm(total=10, file=our_file) + t5.close() + t6 = tqdm(total=10, file=our_file) + + assert t3 != t4 + assert t3 > t2 + assert t5 == t6 + t6.close() + t4.close() + t3.close() + t2.close() + t1.close() + t0.close() + + +@with_setup(pretest, posttest) +def test_repr(): + """Test representation""" + with closing(StringIO()) as our_file: + with tqdm(total=10, ascii=True, file=our_file) as t: + assert str(t) == ' 0%| | 0/10 [00:00 out3.count('\r') + assert out4.count(", ".join(expected_order)) == 2 + + # Test setting postfix string directly + with closing(StringIO()) as our_file: + with trange(10, file=our_file, desc='pos2 bar', bar_format='{r_bar}', + postfix=None) as t5: + t5.set_postfix_str("Hello", False) + t5.set_postfix_str("World") + out5 = our_file.getvalue() + + assert "Hello" not in out5 + out5 = out5[1:-1].split(', ')[3:] + assert out5 == ["World"] + + +def test_postfix_direct(): + """Test directly assigning non-str objects to postfix""" + with closing(StringIO()) as our_file: + with tqdm(total=10, file=our_file, miniters=1, mininterval=0, + bar_format="{postfix[0][name]} {postfix[1]:>5.2f}", + postfix=[dict(name="foo"), 42]) as t: + for i in range(10): + if i % 2: + t.postfix[0]["name"] = "abcdefghij"[i] + else: + t.postfix[1] = i + t.update() + res = our_file.getvalue() + assert "f 6.00" in res + assert "h 6.00" in res + assert "h 8.00" in res + assert "j 8.00" in res + + +@contextmanager +def std_out_err_redirect_tqdm(tqdm_file=sys.stderr): + orig_out_err = sys.stdout, sys.stderr + try: + sys.stdout = sys.stderr = DummyTqdmFile(tqdm_file) + yield orig_out_err[0] + # Relay exceptions + except Exception as exc: + raise exc + # Always restore sys.stdout/err if necessary + finally: + sys.stdout, sys.stderr = orig_out_err + + +@with_setup(pretest, posttest) +def test_file_redirection(): + """Test redirection of output""" + with closing(StringIO()) as our_file: + # Redirect stdout to tqdm.write() + with std_out_err_redirect_tqdm(tqdm_file=our_file): + for _ in trange(3): + print("Such fun") + res = our_file.getvalue() + assert res.count("Such fun\n") == 3 + assert "0/3" in res + assert "3/3" in res + + +@with_setup(pretest, posttest) +def test_external_write(): + """Test external write mode""" + with closing(StringIO()) as our_file: + # Redirect stdout to tqdm.write() + for _ in trange(3, file=our_file): + del tqdm._lock # classmethod should be able to recreate lock + with tqdm.external_write_mode(file=our_file): + our_file.write("Such fun\n") + res = our_file.getvalue() + assert res.count("Such fun\n") == 3 + assert "0/3" in res + assert "3/3" in res + + +@with_setup(pretest, posttest) +def test_unit_scale(): + """Test numeric `unit_scale`""" + with closing(StringIO()) as our_file: + for _ in tqdm(_range(9), unit_scale=9, file=our_file, + miniters=1, mininterval=0): + pass + out = our_file.getvalue() + assert '81/81' in out + + +@with_setup(pretest, posttest) +def test_threading(): + """Test multiprocess/thread-realted features""" + from multiprocessing import RLock + try: + mp_lock = RLock() + except OSError: + pass + else: + tqdm.set_lock(mp_lock) + # TODO: test interleaved output #445 + + +@with_setup(pretest, posttest) +def test_bool(): + """Test boolean cast""" + def internal(our_file, disable): + kwargs = dict(file=our_file, disable=disable) + with trange(10, **kwargs) as t: + assert t + with trange(0, **kwargs) as t: + assert not t + with tqdm(total=10, **kwargs) as t: + assert bool(t) + with tqdm(total=0, **kwargs) as t: + assert not bool(t) + with tqdm([], **kwargs) as t: + assert not t + with tqdm([0], **kwargs) as t: + assert t + with tqdm((x for x in []), **kwargs) as t: + assert t + with tqdm((x for x in [1, 2, 3]), **kwargs) as t: + assert t + with tqdm(**kwargs) as t: + try: + print(bool(t)) + except TypeError: + pass + else: + raise TypeError("Expected bool(tqdm()) to fail") + + # test with and without disable + with closing(StringIO()) as our_file: + internal(our_file, False) + internal(our_file, True) + + +def backendCheck(module): + """Test tqdm-like module fallback""" + tn = module.tqdm + tr = module.trange + + with closing(StringIO()) as our_file: + with tn(total=10, file=our_file) as t: + assert len(t) == 10 + with tr(1337) as t: + assert len(t) == 1337 + + +@with_setup(pretest, posttest) +def test_auto(): + """Test auto fallback""" + from tqdm import autonotebook, auto + backendCheck(autonotebook) + backendCheck(auto) + + +@with_setup(pretest, posttest) +def test_wrapattr(): + """Test wrapping file-like objects""" + data = "a twenty-char string" + + with closing(StringIO()) as our_file: + with closing(StringIO()) as writer: + with tqdm.wrapattr( + writer, "write", file=our_file, bytes=True) as wrap: + wrap.write(data) + res = writer.getvalue() + assert data == res + res = our_file.getvalue() + assert ('%.1fB [' % len(data)) in res + + with closing(StringIO()) as our_file: + with closing(StringIO()) as writer: + with tqdm.wrapattr( + writer, "write", file=our_file, bytes=False) as wrap: + wrap.write(data) + res = our_file.getvalue() + assert ('%dit [' % len(data)) in res + + +@with_setup(pretest, posttest) +def test_float_progress(): + """Test float totals""" + with closing(StringIO()) as our_file: + with trange(10, total=9.6, file=our_file) as t: + with catch_warnings(record=True) as w: + simplefilter("always") + for i in t: + if i < 9: + assert not w + assert w + assert "clamping frac" in str(w[-1].message) diff --git a/tqdm/tests/tests_version.py b/tqdm/tests/tests_version.py new file mode 100644 index 0000000..226b998 --- /dev/null +++ b/tqdm/tests/tests_version.py @@ -0,0 +1,12 @@ +import re + + +def test_version(): + """Test version string""" + from tqdm import __version__ + version_parts = re.split('[.-]', __version__) + assert 3 <= len(version_parts) # must have at least Major.minor.patch + try: + map(int, version_parts[:3]) + except ValueError: + raise TypeError('Version Major.minor.patch must be 3 integers') diff --git a/tqdm/tqdm.1 b/tqdm/tqdm.1 new file mode 100644 index 0000000..d2f8f1f --- /dev/null +++ b/tqdm/tqdm.1 @@ -0,0 +1,257 @@ +.\" Automatically generated by Pandoc 1.19.2 +.\" +.TH "TQDM" "1" "2015\-2020" "tqdm User Manuals" "" +.hy +.SH NAME +.PP +tqdm \- fast, extensible progress bar for Python and CLI +.SH SYNOPSIS +.PP +tqdm [\f[I]options\f[]] +.SH DESCRIPTION +.PP +See . +Can be used as a pipe: +.IP +.nf +\f[C] +$\ #\ count\ lines\ of\ code +$\ cat\ *.py\ |\ tqdm\ |\ wc\ \-l +327it\ [00:00,\ 981773.38it/s] +327 + +$\ #\ find\ all\ files +$\ find\ .\ \-name\ "*.py"\ |\ tqdm\ |\ wc\ \-l +432it\ [00:00,\ 833842.30it/s] +432 + +#\ ...\ and\ more\ info +$\ find\ .\ \-name\ \[aq]*.py\[aq]\ \-exec\ wc\ \-l\ \\{}\ \\;\ \\ +\ \ |\ tqdm\ \-\-total\ 432\ \-\-unit\ files\ \-\-desc\ counting\ \\ +\ \ |\ awk\ \[aq]{\ sum\ +=\ $1\ };\ END\ {\ print\ sum\ }\[aq] +counting:\ 100%|█████████|\ 432/432\ [00:00<00:00,\ 794361.83files/s] +131998 +\f[] +.fi +.SH OPTIONS +.TP +.B \-h, \-\-help +Print this help and exit +.RS +.RE +.TP +.B \-v, \-\-version +Print version and exit +.RS +.RE +.TP +.B \-\-desc=\f[I]desc\f[] +str, optional. +Prefix for the progressbar. +.RS +.RE +.TP +.B \-\-total=\f[I]total\f[] +int or float, optional. +The number of expected iterations. +If unspecified, len(iterable) is used if possible. +If float("inf") or as a last resort, only basic progress statistics are +displayed (no ETA, no progressbar). +If \f[C]gui\f[] is True and this parameter needs subsequent updating, +specify an initial arbitrary large positive number, e.g. +9e9. +.RS +.RE +.TP +.B \-\-leave=\f[I]leave\f[] +bool, optional. +If [default: True], keeps all traces of the progressbar upon termination +of iteration. +If \f[C]None\f[], will leave only if \f[C]position\f[] is \f[C]0\f[]. +.RS +.RE +.TP +.B \-\-ncols=\f[I]ncols\f[] +int, optional. +The width of the entire output message. +If specified, dynamically resizes the progressbar to stay within this +bound. +If unspecified, attempts to use environment width. +The fallback is a meter width of 10 and no limit for the counter and +statistics. +If 0, will not print any meter (only stats). +.RS +.RE +.TP +.B \-\-mininterval=\f[I]mininterval\f[] +float, optional. +Minimum progress display update interval [default: 0.1] seconds. +.RS +.RE +.TP +.B \-\-maxinterval=\f[I]maxinterval\f[] +float, optional. +Maximum progress display update interval [default: 10] seconds. +Automatically adjusts \f[C]miniters\f[] to correspond to +\f[C]mininterval\f[] after long display update lag. +Only works if \f[C]dynamic_miniters\f[] or monitor thread is enabled. +.RS +.RE +.TP +.B \-\-miniters=\f[I]miniters\f[] +int or float, optional. +Minimum progress display update interval, in iterations. +If 0 and \f[C]dynamic_miniters\f[], will automatically adjust to equal +\f[C]mininterval\f[] (more CPU efficient, good for tight loops). +If > 0, will skip display of specified number of iterations. +Tweak this and \f[C]mininterval\f[] to get very efficient loops. +If your progress is erratic with both fast and slow iterations (network, +skipping items, etc) you should set miniters=1. +.RS +.RE +.TP +.B \-\-ascii=\f[I]ascii\f[] +bool or str, optional. +If unspecified or False, use unicode (smooth blocks) to fill the meter. +The fallback is to use ASCII characters " 123456789#". +.RS +.RE +.TP +.B \-\-disable=\f[I]disable\f[] +bool, optional. +Whether to disable the entire progressbar wrapper [default: False]. +If set to None, disable on non\-TTY. +.RS +.RE +.TP +.B \-\-unit=\f[I]unit\f[] +str, optional. +String that will be used to define the unit of each iteration [default: +it]. +.RS +.RE +.TP +.B \-\-unit_scale=\f[I]unit_scale\f[] +bool or int or float, optional. +If 1 or True, the number of iterations will be reduced/scaled +automatically and a metric prefix following the International System of +Units standard will be added (kilo, mega, etc.) [default: False]. +If any other non\-zero number, will scale \f[C]total\f[] and \f[C]n\f[]. +.RS +.RE +.TP +.B \-\-dynamic_ncols=\f[I]dynamic_ncols\f[] +bool, optional. +If set, constantly alters \f[C]ncols\f[] to the environment (allowing +for window resizes) [default: False]. +.RS +.RE +.TP +.B \-\-smoothing=\f[I]smoothing\f[] +float, optional. +Exponential moving average smoothing factor for speed estimates (ignored +in GUI mode). +Ranges from 0 (average speed) to 1 (current/instantaneous speed) +[default: 0.3]. +.RS +.RE +.TP +.B \-\-bar_format=\f[I]bar_format\f[] +str, optional. +Specify a custom bar string formatting. +May impact performance. +[default: \[aq]{l_bar}{bar}{r_bar}\[aq]], where l_bar=\[aq]{desc}: +{percentage:3.0f}%|\[aq] and r_bar=\[aq]| {n_fmt}/{total_fmt} +[{elapsed}<{remaining}, \[aq] \[aq]{rate_fmt}{postfix}]\[aq] Possible +vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, percentage, +elapsed, elapsed_s, ncols, desc, unit, rate, rate_fmt, rate_noinv, +rate_noinv_fmt, rate_inv, rate_inv_fmt, postfix, unit_divisor, +remaining, remaining_s. +Note that a trailing ": " is automatically removed after {desc} if the +latter is empty. +.RS +.RE +.TP +.B \-\-initial=\f[I]initial\f[] +int or float, optional. +The initial counter value. +Useful when restarting a progress bar [default: 0]. +If using float, consider specifying \f[C]{n:.3f}\f[] or similar in +\f[C]bar_format\f[], or specifying \f[C]unit_scale\f[]. +.RS +.RE +.TP +.B \-\-position=\f[I]position\f[] +int, optional. +Specify the line offset to print this bar (starting from 0) Automatic if +unspecified. +Useful to manage multiple bars at once (eg, from threads). +.RS +.RE +.TP +.B \-\-postfix=\f[I]postfix\f[] +dict or *, optional. +Specify additional stats to display at the end of the bar. +Calls \f[C]set_postfix(**postfix)\f[] if possible (dict). +.RS +.RE +.TP +.B \-\-unit_divisor=\f[I]unit_divisor\f[] +float, optional. +[default: 1000], ignored unless \f[C]unit_scale\f[] is True. +.RS +.RE +.TP +.B \-\-write_bytes=\f[I]write_bytes\f[] +bool, optional. +If (default: None) and \f[C]file\f[] is unspecified, bytes will be +written in Python 2. +If \f[C]True\f[] will also write bytes. +In all other cases will default to unicode. +.RS +.RE +.TP +.B \-\-lock_args=\f[I]lock_args\f[] +tuple, optional. +Passed to \f[C]refresh\f[] for intermediate output (initialisation, +iterating, and updating). +.RS +.RE +.TP +.B \-\-delim=\f[I]delim\f[] +chr, optional. +Delimiting character [default: \[aq]\\n\[aq]]. +Use \[aq]\\0\[aq] for null. +N.B.: on Windows systems, Python converts \[aq]\\n\[aq] to +\[aq]\\r\\n\[aq]. +.RS +.RE +.TP +.B \-\-buf_size=\f[I]buf_size\f[] +int, optional. +String buffer size in bytes [default: 256] used when \f[C]delim\f[] is +specified. +.RS +.RE +.TP +.B \-\-bytes=\f[I]bytes\f[] +bool, optional. +If true, will count bytes, ignore \f[C]delim\f[], and default +\f[C]unit_scale\f[] to True, \f[C]unit_divisor\f[] to 1024, and +\f[C]unit\f[] to \[aq]B\[aq]. +.RS +.RE +.TP +.B \-\-manpath=\f[I]manpath\f[] +str, optional. +Directory in which to install tqdm man pages. +.RS +.RE +.TP +.B \-\-log=\f[I]log\f[] +str, optional. +CRITICAL|FATAL|ERROR|WARN(ING)|[default: \[aq]INFO\[aq]]|DEBUG|NOTSET. +.RS +.RE +.SH AUTHORS +tqdm developers . diff --git a/tqdm/utils.py b/tqdm/utils.py new file mode 100644 index 0000000..a9a42be --- /dev/null +++ b/tqdm/utils.py @@ -0,0 +1,369 @@ +from functools import wraps +import os +from platform import system as _curos +import re +import subprocess +CUR_OS = _curos() +IS_WIN = CUR_OS in ['Windows', 'cli'] +IS_NIX = (not IS_WIN) and any( + CUR_OS.startswith(i) for i in + ['CYGWIN', 'MSYS', 'Linux', 'Darwin', 'SunOS', + 'FreeBSD', 'NetBSD', 'OpenBSD']) +RE_ANSI = re.compile(r"\x1b\[[;\d]*[A-Za-z]") + + +# Py2/3 compat. Empty conditional to avoid coverage +if True: # pragma: no cover + try: + _range = xrange + except NameError: + _range = range + + try: + _unich = unichr + except NameError: + _unich = chr + + try: + _unicode = unicode + except NameError: + _unicode = str + + try: + if IS_WIN: + import colorama + else: + raise ImportError + except ImportError: + colorama = None + else: + try: + colorama.init(strip=False) + except TypeError: + colorama.init() + + try: + from weakref import WeakSet + except ImportError: + WeakSet = set + + try: + _basestring = basestring + except NameError: + _basestring = str + + try: # py>=2.7,>=3.1 + from collections import OrderedDict as _OrderedDict + except ImportError: + try: # older Python versions with backported ordereddict lib + from ordereddict import OrderedDict as _OrderedDict + except ImportError: # older Python versions without ordereddict lib + # Py2.6,3.0 compat, from PEP 372 + from collections import MutableMapping + + class _OrderedDict(dict, MutableMapping): + # Methods with direct access to underlying attributes + def __init__(self, *args, **kwds): + if len(args) > 1: + raise TypeError('expected at 1 argument, got %d', + len(args)) + if not hasattr(self, '_keys'): + self._keys = [] + self.update(*args, **kwds) + + def clear(self): + del self._keys[:] + dict.clear(self) + + def __setitem__(self, key, value): + if key not in self: + self._keys.append(key) + dict.__setitem__(self, key, value) + + def __delitem__(self, key): + dict.__delitem__(self, key) + self._keys.remove(key) + + def __iter__(self): + return iter(self._keys) + + def __reversed__(self): + return reversed(self._keys) + + def popitem(self): + if not self: + raise KeyError + key = self._keys.pop() + value = dict.pop(self, key) + return key, value + + def __reduce__(self): + items = [[k, self[k]] for k in self] + inst_dict = vars(self).copy() + inst_dict.pop('_keys', None) + return self.__class__, (items,), inst_dict + + # Methods with indirect access via the above methods + setdefault = MutableMapping.setdefault + update = MutableMapping.update + pop = MutableMapping.pop + keys = MutableMapping.keys + values = MutableMapping.values + items = MutableMapping.items + + def __repr__(self): + pairs = ', '.join(map('%r: %r'.__mod__, self.items())) + return '%s({%s})' % (self.__class__.__name__, pairs) + + def copy(self): + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + +class FormatReplace(object): + """ + >>> a = FormatReplace('something') + >>> "{:5d}".format(a) + 'something' + """ + def __init__(self, replace=''): + self.replace = replace + self.format_called = 0 + + def __format__(self, _): + self.format_called += 1 + return self.replace + + +class Comparable(object): + """Assumes child has self._comparable attr/@property""" + def __lt__(self, other): + return self._comparable < other._comparable + + def __le__(self, other): + return (self < other) or (self == other) + + def __eq__(self, other): + return self._comparable == other._comparable + + def __ne__(self, other): + return not self == other + + def __gt__(self, other): + return not self <= other + + def __ge__(self, other): + return not self < other + + +class ObjectWrapper(object): + def __getattr__(self, name): + return getattr(self._wrapped, name) + + def __setattr__(self, name, value): + return setattr(self._wrapped, name, value) + + def wrapper_getattr(self, name): + """Actual `self.getattr` rather than self._wrapped.getattr""" + try: + return object.__getattr__(self, name) + except AttributeError: # py2 + return getattr(self, name) + + def wrapper_setattr(self, name, value): + """Actual `self.setattr` rather than self._wrapped.setattr""" + return object.__setattr__(self, name, value) + + def __init__(self, wrapped): + """ + Thin wrapper around a given object + """ + self.wrapper_setattr('_wrapped', wrapped) + + +class SimpleTextIOWrapper(ObjectWrapper): + """ + Change only `.write()` of the wrapped object by encoding the passed + value and passing the result to the wrapped object's `.write()` method. + """ + # pylint: disable=too-few-public-methods + def __init__(self, wrapped, encoding): + super(SimpleTextIOWrapper, self).__init__(wrapped) + self.wrapper_setattr('encoding', encoding) + + def write(self, s): + """ + Encode `s` and pass to the wrapped object's `.write()` method. + """ + return self._wrapped.write(s.encode(self.wrapper_getattr('encoding'))) + + +class CallbackIOWrapper(ObjectWrapper): + def __init__(self, callback, stream, method="read"): + """ + Wrap a given `file`-like object's `read()` or `write()` to report + lengths to the given `callback` + """ + super(CallbackIOWrapper, self).__init__(stream) + func = getattr(stream, method) + if method == "write": + @wraps(func) + def write(data, *args, **kwargs): + res = func(data, *args, **kwargs) + callback(len(data)) + return res + self.wrapper_setattr('write', write) + elif method == "read": + @wraps(func) + def read(*args, **kwargs): + data = func(*args, **kwargs) + callback(len(data)) + return data + self.wrapper_setattr('read', read) + else: + raise KeyError("Can only wrap read/write methods") + + +def _is_utf(encoding): + try: + u'\u2588\u2589'.encode(encoding) + except UnicodeEncodeError: # pragma: no cover + return False + except Exception: # pragma: no cover + try: + return encoding.lower().startswith('utf-') or ('U8' == encoding) + except: + return False + else: + return True + + +def _supports_unicode(fp): + try: + return _is_utf(fp.encoding) + except AttributeError: + return False + + +def _is_ascii(s): + if isinstance(s, str): + for c in s: + if ord(c) > 255: + return False + return True + return _supports_unicode(s) + + +def _environ_cols_wrapper(): # pragma: no cover + """ + Return a function which gets width and height of console + (linux,osx,windows,cygwin). + """ + _environ_cols = None + if IS_WIN: + _environ_cols = _environ_cols_windows + if _environ_cols is None: + _environ_cols = _environ_cols_tput + if IS_NIX: + _environ_cols = _environ_cols_linux + return _environ_cols + + +def _environ_cols_windows(fp): # pragma: no cover + try: + from ctypes import windll, create_string_buffer + import struct + from sys import stdin, stdout + + io_handle = -12 # assume stderr + if fp == stdin: + io_handle = -10 + elif fp == stdout: + io_handle = -11 + + h = windll.kernel32.GetStdHandle(io_handle) + csbi = create_string_buffer(22) + res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) + if res: + (_bufx, _bufy, _curx, _cury, _wattr, left, _top, right, _bottom, + _maxx, _maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) + # nlines = bottom - top + 1 + return right - left # +1 + except: + pass + return None + + +def _environ_cols_tput(*_): # pragma: no cover + """cygwin xterm (windows)""" + try: + import shlex + cols = int(subprocess.check_call(shlex.split('tput cols'))) + # rows = int(subprocess.check_call(shlex.split('tput lines'))) + return cols + except: + pass + return None + + +def _environ_cols_linux(fp): # pragma: no cover + + try: + from termios import TIOCGWINSZ + from fcntl import ioctl + from array import array + except ImportError: + return None + else: + try: + return array('h', ioctl(fp, TIOCGWINSZ, '\0' * 8))[1] + except: + try: + return int(os.environ["COLUMNS"]) - 1 + except KeyError: + return None + + +def _term_move_up(): # pragma: no cover + return '' if (os.name == 'nt') and (colorama is None) else '\x1b[A' + + +try: + # TODO consider using wcswidth third-party package for 0-width characters + from unicodedata import east_asian_width +except ImportError: + _text_width = len +else: + def _text_width(s): + return sum( + 2 if east_asian_width(ch) in 'FW' else 1 for ch in _unicode(s)) + + +def disp_len(data): + """ + Returns the real on-screen length of a string which may contain + ANSI control codes and wide chars. + """ + return _text_width(RE_ANSI.sub('', data)) + + +def disp_trim(data, length): + """ + Trim a string which may contain ANSI control characters. + """ + if len(data) == disp_len(data): + return data[:length] + + ansi_present = bool(RE_ANSI.search(data)) + while disp_len(data) > length: # carefully delete one char at a time + data = data[:-1] + if ansi_present and bool(RE_ANSI.search(data)): + # assume ANSI reset is required + return data if data.endswith("\033[0m") else data + "\033[0m" + return data diff --git a/tqdm/utils.pyc b/tqdm/utils.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9dca48242870b02e6ded45a6e5c73bbe6d2b9900 GIT binary patch literal 15087 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 literal 0 HcmV?d00001 From 2a6624649ecc847dd44e61a3da46d4fa7435be8b Mon Sep 17 00:00:00 2001 From: "manh6264@gmail.com" Date: Wed, 15 Apr 2020 14:51:21 +0700 Subject: [PATCH 2/4] Delete exam file --- classes.txt | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 classes.txt diff --git a/classes.txt b/classes.txt deleted file mode 100644 index 370b4b5..0000000 --- a/classes.txt +++ /dev/null @@ -1,3 +0,0 @@ -Apple -Orange -Light switch From 0486f21c37053b6cb25def9acb4a69f510f48d89 Mon Sep 17 00:00:00 2001 From: "manh6264@gmail.com" Date: Wed, 22 Apr 2020 17:00:16 +0700 Subject: [PATCH 3/4] Fix requirements --- requirements.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index c222e63..a86925d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,5 @@ pandas numpy awscli - urllib3 - -tqdm - opencv-python \ No newline at end of file From 005fed4e3f6c26d19518cdf09614796c6f519664 Mon Sep 17 00:00:00 2001 From: "manh6264@gmail.com" Date: Mon, 27 Apr 2020 15:22:00 +0700 Subject: [PATCH 4/4] Add file to change label to id in label file --- label2yolo.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 label2yolo.py diff --git a/label2yolo.py b/label2yolo.py new file mode 100644 index 0000000..3024b4d --- /dev/null +++ b/label2yolo.py @@ -0,0 +1,45 @@ +import os + +def labelChange(path_link): + label = 'Label' + + #Customỉze class_id + class_id = 0 + + for v in os.listdir(path_link): + #Load link + file = os.path.join(path_link, v) + file = file + '/' #On ubuntu dont need this + file = os.path.join(file, label) + file = file + '/' + + for i in os.listdir(file): + #Link labelfile + labelfile = os.path.join(file, i) + + #Get lineString + f = open(labelfile, 'r') + i = f.readlines() + f.close() + + #Replace string to class_id + f = open(labelfile, 'w') + for line in i: + #Load line and split + listString = line.split() + listString[0] = class_id #Replace string + + list2String = ' '.join([str(elem) for elem in listString]) + f.write(list2String) + f.write('\n') + f.close() + class_id += 1 + +#Link to pathfile, can customize it +train_link = 'OID/Dataset/train/' +test_link = 'OID/Dataset/test/' +val_link = 'OID/Dataset/validation/' + +labelChange(train_link) +labelChange(test_link) +labelChange(val_link) \ No newline at end of file