diff --git a/.mailmap b/.mailmap index 93e47c0e6..9a5dfb335 100644 --- a/.mailmap +++ b/.mailmap @@ -1,3 +1,9 @@ -Noam Yorav-Raphael -Casper da Costa-Luis casperdcl -Casper da Costa-Luis casperdcl +Casper da Costa-Luis casperdcl +Stephen Larroque +Guangshuo Chen Guangshuo CHEN +Noam Yorav-Raphael +James Lu +Julien Chaumont +Riccardo Coccioli +Albert Kottke +Pablo Zivic diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f942cb79..0007d434f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -237,6 +237,10 @@ Finally, upload everything to pypi. This can be done easily using the Also, the new release can (should) be added to `github` by creating a new release from the web interface; uploading packages from the `dist/` folder created by `[python setup.py] make build`. +The [wiki] can be automatically updated with github release notes by +running `make` within the wiki repository. + +[wiki]: https://github.com/tqdm/tqdm/wiki Notes ~~~~~ @@ -267,7 +271,7 @@ following: Additionally (less maintained), there exists: -- A [wiki](https://github.com/tqdm/tqdm/wiki) which is publicly editable. +- A [wiki] which is publicly editable. - The [gh-pages project](https://tqdm.github.io/tqdm/) which is built from the [gh-pages branch](https://github.com/tqdm/tqdm/tree/gh-pages), which is built using [asv](https://github.com/spacetelescope/asv/). diff --git a/README.rst b/README.rst index dc5b71436..187b7af72 100644 --- a/README.rst +++ b/README.rst @@ -103,7 +103,8 @@ Changelog --------- The list of all changes is available either on GitHub's Releases: -|GitHub-Status| or on crawlers such as +|GitHub-Status|, on the +`wiki `__ or on crawlers such as `allmychanges.com `_. @@ -798,8 +799,9 @@ The main developers, ranked by surviving lines of code, are: - Casper da Costa-Luis (`casperdcl `__, ~2/3, |Gift-Casper|) - Stephen Larroque (`lrq3000 `__, ~1/3) -- Noam Yorav-Raphael (`noamraph `__, ~1%, original author) +- Guangshuo Chen (`chengs `__, ~1%) - Hadrien Mary (`hadim `__, ~1%) +- Noam Yorav-Raphael (`noamraph `__, ~1%, original author) - Mikhail Korobov (`kmike `__, ~1%) There are also many |GitHub-Contributions| which we are grateful for. diff --git a/examples/7zx.py b/examples/7zx.py index bc9f08193..78f97d189 100644 --- a/examples/7zx.py +++ b/examples/7zx.py @@ -19,8 +19,8 @@ -d, --debug-trace Print lots of debugging information (-D NOTSET) """ from __future__ import print_function -from docopt import docopt -import logging as log +from argopt import argopt +import logging import subprocess import re from tqdm import tqdm @@ -29,33 +29,37 @@ import io __author__ = "Casper da Costa-Luis " __licence__ = "MPLv2.0" -__version__ = "0.2.0" +__version__ = "0.2.1" __license__ = __licence__ RE_SCN = re.compile("([0-9]+)\s+([0-9]+)\s+(.*)$", flags=re.M) def main(): - args = docopt(__doc__, version=__version__) - if args.pop('--debug-trace', False): - args['--debug'] = "NOTSET" - log.basicConfig(level=getattr(log, args['--debug'], log.INFO), - format='%(levelname)s: %(message)s') + args = argopt(__doc__, version=__version__).parse_args() + if args.debug_trace: + args.debug = "NOTSET" + logging.basicConfig(level=getattr(logging, args.debug, logging.INFO), + format='%(levelname)s:%(message)s') + log = logging.getLogger(__name__) log.debug(args) # Get compressed sizes zips = {} - for fn in args['']: + for fn in args.zipfiles: info = subprocess.check_output(["7z", "l", fn]).strip() - finfo = RE_SCN.findall(info) + finfo = RE_SCN.findall(info) # size|compressed|name # builtin test: last line should be total sizes log.debug(finfo) totals = map(int, finfo[-1][:2]) # log.debug(totals) - for s in range(2): - assert (sum(map(int, (inf[s] for inf in finfo[:-1]))) == totals[s]) - fcomp = dict((n, int(c if args['--compressed'] else u)) + for s in range(2): # size|compressed totals + totals_s = sum(map(int, (inf[s] for inf in finfo[:-1]))) + if totals_s != totals[s]: + log.warn("%s: individual total %d != 7z total %d" % ( + fn, totals_s, totals[s])) + fcomp = dict((n, int(c if args.compressed else u)) for (u, c, n) in finfo[:-1]) # log.debug(fcomp) # zips : {'zipname' : {'filename' : int(size)}} @@ -63,7 +67,7 @@ def main(): # Extract cmd7zx = ["7z", "x", "-bd"] - if args['--yes']: + if args.yes: cmd7zx += ["-y"] log.info("Extracting from {:d} file(s)".format(len(zips))) with tqdm(total=sum(sum(fcomp.values()) for fcomp in zips.values()), @@ -79,6 +83,8 @@ def main(): with io.open(md, mode="rU", buffering=1) as m: with tqdm(total=sum(fcomp.values()), disable=len(zips) < 2, leave=False, unit="B", unit_scale=True) as t: + if not hasattr(t, "start_t"): # disabled + t.start_t = tall._time() while True: try: l_raw = m.readline() @@ -98,11 +104,10 @@ def main(): "Files: ", "Size: ", "Compressed: ")): if l.startswith("Processing archive: "): - if not args['--silent']: - t.write( - t.format_interval( - t.start_t - tall.start_t) + ' ' - + l.lstrip("Processing archive: ")) + if not args.silent: + t.write(t.format_interval( + t.start_t - tall.start_t) + ' ' + + l.lstrip("Processing archive: ")) else: t.write(l) ex.wait() diff --git a/tqdm/__init__.py b/tqdm/__init__.py index e83f92866..d6b251fe7 100644 --- a/tqdm/__init__.py +++ b/tqdm/__init__.py @@ -6,12 +6,15 @@ from ._main import main from ._monitor import TMonitor, TqdmSynchronisationWarning from ._version import __version__ # NOQA -from ._tqdm import TqdmTypeError, TqdmKeyError, TqdmDeprecationWarning, \ +from ._tqdm import TqdmTypeError, TqdmKeyError, TqdmWarning, \ + TqdmDeprecationWarning, TqdmExperimentalWarning, \ TqdmMonitorWarning __all__ = ['tqdm', 'tqdm_gui', 'trange', 'tgrange', 'tqdm_pandas', 'tqdm_notebook', 'tnrange', 'main', 'TMonitor', - 'TqdmTypeError', 'TqdmKeyError', 'TqdmDeprecationWarning', + 'TqdmTypeError', 'TqdmKeyError', + 'TqdmWarning', 'TqdmDeprecationWarning', + 'TqdmExperimentalWarning', 'TqdmMonitorWarning', 'TqdmSynchronisationWarning', '__version__'] diff --git a/tqdm/_main.py b/tqdm/_main.py index b2fd1d3c2..923324bde 100644 --- a/tqdm/_main.py +++ b/tqdm/_main.py @@ -128,7 +128,8 @@ def main(fp=sys.stderr): # sys.argv.pop(log) # logLevel = sys.argv.pop(log) logLevel = sys.argv[log + 1] - logging.basicConfig(level=getattr(logging, logLevel)) + logging.basicConfig(level=getattr(logging, logLevel), + format="%(levelname)s:%(module)s:%(lineno)d:%(message)s") log = logging.getLogger(__name__) d = tqdm.__init__.__doc__ + CLI_EXTRA_DOC diff --git a/tqdm/_tqdm.py b/tqdm/_tqdm.py index 9bdca9f01..0eafbf0a8 100755 --- a/tqdm/_tqdm.py +++ b/tqdm/_tqdm.py @@ -22,12 +22,13 @@ # For parallelism safety import multiprocessing as mp import threading as th - +from warnings import warn __author__ = {"github.com/": ["noamraph", "obiwanus", "kmike", "hadim", "casperdcl", "lrq3000"]} __all__ = ['tqdm', 'trange', - 'TqdmTypeError', 'TqdmKeyError', 'TqdmDeprecationWarning', + 'TqdmTypeError', 'TqdmKeyError', 'TqdmWarning', + 'TqdmExperimentalWarning', 'TqdmDeprecationWarning', 'TqdmMonitorWarning'] @@ -39,16 +40,27 @@ class TqdmKeyError(KeyError): pass -class TqdmDeprecationWarning(DeprecationWarning): - # not suppressed if raised +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("\nTqdmDeprecationWarning: " + str(msg).rstrip() + '\n') + fp_write("\n" + self.__class__.__name__ + ": " + str(msg).rstrip() + '\n') else: - super(TqdmDeprecationWarning, self).__init__(msg, *a, **k) + 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(RuntimeWarning): + +class TqdmMonitorWarning(TqdmWarning, RuntimeWarning): """tqdm monitor errors which do not affect external functionality""" pass @@ -393,7 +405,6 @@ def __new__(cls, *args, **kwargs): try: cls.monitor = TMonitor(cls, cls.monitor_interval) except Exception as e: # pragma: nocover - from warnings import warn warn("tqdm:disabling monitor support" " (monitor_interval = 0) due to:\n" + str(e), TqdmMonitorWarning) @@ -403,14 +414,10 @@ def __new__(cls, *args, **kwargs): @classmethod def _get_free_pos(cls, instance=None): - """ Skips specified instance """ - try: - return max(inst.pos for inst in cls._instances - if inst is not instance) + 1 - except ValueError as e: - if "arg is an empty sequence" in str(e): - return 0 - raise # pragma: no cover + """Skips specified instance""" + positions = set(abs(inst.pos) for inst in cls._instances + if inst is not instance) + return min(set(range(len(positions) + 1)).difference(positions)) @classmethod def _decr_instances(cls, instance): @@ -418,23 +425,27 @@ def _decr_instances(cls, instance): Remove from list and reposition other bars so that newer bars won't overlap previous bars """ - try: # in case instance was explicitly positioned, it won't be in set - with cls._lock: + with cls._lock: + try: cls._instances.remove(instance) + except KeyError: + if not instance.gui: # pragma: no cover + raise + else: for inst in cls._instances: - if inst.pos > instance.pos: + # negative `pos` means fixed + if inst.pos > abs(instance.pos): inst.pos -= 1 - # 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 - except KeyError: - pass + # 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): @@ -476,7 +487,6 @@ def external_write_mode(cls, file=None, nolock=False): inst.refresh(nolock=True) if not nolock: cls._lock.release() - # TODO: make list of all instances incl. absolutely positioned ones? @classmethod def set_lock(cls, lock): @@ -812,19 +822,18 @@ def __init__(self, iterable=None, desc=None, total=None, leave=True, # not overwrite the outer progress bar if position is None: self.pos = self._get_free_pos(self) - else: - self.pos = position - self._instances.remove(self) + else: # mark fixed positions as negative + self.pos = -position if not gui: # Initialize the screen printer self.sp = self.status_printer(self.fp) with self._lock: if self.pos: - self.moveto(self.pos) + self.moveto(abs(self.pos)) self.sp(self.__repr__(elapsed=0)) if self.pos: - self.moveto(-self.pos) + self.moveto(-abs(self.pos)) # Init the time counter self.last_print_t = self._time() @@ -857,13 +866,13 @@ def __repr__(self, elapsed=None): self.bar_format, self.postfix, self.unit_divisor) def __lt__(self, other): - return self.pos < other.pos + return abs(self.pos) < abs(other.pos) def __le__(self, other): return (self < other) or (self == other) def __eq__(self, other): - return self.pos == other.pos + return abs(self.pos) == abs(other.pos) def __ne__(self, other): return not (self == other) @@ -929,11 +938,11 @@ def __iter__(self): self.n = n with self._lock: if self.pos: - self.moveto(self.pos) + self.moveto(abs(self.pos)) # Print bar update sp(self.__repr__()) if self.pos: - self.moveto(-self.pos) + self.moveto(-abs(self.pos)) # If no `miniters` was specified, adjust automatically # to the max iteration rate seen so far between 2 prints @@ -1018,13 +1027,13 @@ def update(self, n=1): with self._lock: if self.pos: - self.moveto(self.pos) + self.moveto(abs(self.pos)) # Print bar update self.sp(self.__repr__()) if self.pos: - self.moveto(-self.pos) + self.moveto(-abs(self.pos)) # If no `miniters` was specified, adjust automatically to the # maximum iteration rate seen so far between two prints. @@ -1063,7 +1072,7 @@ def close(self): self.disable = True # decrement instance pos and remove from internal set - pos = self.pos + pos = abs(self.pos) self._decr_instances(self) # GUI mode @@ -1183,10 +1192,10 @@ def clear(self, nolock=False): if not nolock: self._lock.acquire() - self.moveto(self.pos) + self.moveto(abs(self.pos)) self.sp('') self.fp.write('\r') # place cursor back at the beginning of line - self.moveto(-self.pos) + self.moveto(-abs(self.pos)) if not nolock: self._lock.release() @@ -1199,9 +1208,9 @@ def refresh(self, nolock=False): if not nolock: self._lock.acquire() - self.moveto(self.pos) + self.moveto(abs(self.pos)) self.sp(self.__repr__()) - self.moveto(-self.pos) + self.moveto(-abs(self.pos)) if not nolock: self._lock.release() diff --git a/tqdm/_tqdm_gui.py b/tqdm/_tqdm_gui.py index 65645dd50..13420ef7c 100644 --- a/tqdm/_tqdm_gui.py +++ b/tqdm/_tqdm_gui.py @@ -15,7 +15,8 @@ from time import time from ._utils import _range # to inherit from the tqdm class -from ._tqdm import tqdm +from ._tqdm import tqdm, TqdmExperimentalWarning +from warnings import warn __author__ = {"github.com/": ["casperdcl", "lrq3000"]} @@ -41,7 +42,7 @@ def __init__(self, *args, **kwargs): if self.disable or not kwargs['gui']: return - self.fp.write('Warning: GUI is experimental/alpha\n') + warn('GUI is experimental/alpha', TqdmExperimentalWarning) self.mpl = mpl self.plt = plt self.sp = None diff --git a/tqdm/_version.py b/tqdm/_version.py index c2523cfa7..45bef8d7e 100644 --- a/tqdm/_version.py +++ b/tqdm/_version.py @@ -5,7 +5,7 @@ __all__ = ["__version__"] # major, minor, patch, -extra -version_info = 4, 20, 0 +version_info = 4, 21, 0 # Nice string for the version __version__ = '.'.join(map(str, version_info)) diff --git a/tqdm/tests/tests_tqdm.py b/tqdm/tests/tests_tqdm.py index 8bc7bec41..89640d7f2 100644 --- a/tqdm/tests/tests_tqdm.py +++ b/tqdm/tests/tests_tqdm.py @@ -989,8 +989,8 @@ def test_position(): # Artificially test nested loop printing # Without leave our_file = StringIO() - t = tqdm(total=2, file=our_file, miniters=1, mininterval=0, maxinterval=0, - desc='pos2 bar', leave=False, position=2) + 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() our_file.seek(0) @@ -1006,12 +1006,10 @@ def test_position(): # Test iteration-based tqdm positioning our_file = StringIO() - for _ in trange(2, file=our_file, miniters=1, mininterval=0, maxinterval=0, - desc='pos0 bar', position=0): - for _ in trange(2, file=our_file, miniters=1, mininterval=0, - maxinterval=0, desc='pos1 bar', position=1): - for _ in trange(2, file=our_file, miniters=1, mininterval=0, - maxinterval=0, desc='pos2 bar', position=2): + 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 our_file.seek(0) out = our_file.read() @@ -1044,12 +1042,11 @@ def test_position(): # Test manual tqdm positioning our_file = StringIO() - t1 = tqdm(total=2, file=our_file, miniters=1, mininterval=0, maxinterval=0, - desc='pos0 bar', position=0) - t2 = tqdm(total=2, file=our_file, miniters=1, mininterval=0, maxinterval=0, - desc='pos1 bar', position=1) - t3 = tqdm(total=2, file=our_file, miniters=1, mininterval=0, maxinterval=0, - desc='pos2 bar', position=2) + 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()