Skip to content

Commit

Permalink
feature: add support for non-tqdm arbitrary progress bars and documen…
Browse files Browse the repository at this point in the history
…tation
  • Loading branch information
neutrinoceros committed Mar 9, 2021
1 parent 7424c0d commit 2825da0
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 9 deletions.
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ order by last name) and are considered "The Pooch Developers":
* [Kacper Kowalik](https://github.com/Xarthisius) - National Center for Supercomputing Applications, Univeristy of Illinois at Urbana-Champaign, USA (ORCID: [0000-0003-1709-3744](https://www.orcid.org/0000-0003-1709-3744))
* [John Leeman](https://github.com/jrleeman)
* [Rémi Rampin](https://github.com/remram44) - New York University, USA (ORCID: [0000-0002-0524-2282](https://www.orcid.org/0000-0002-0524-2282))
* [Clément Robert](https://github.com/neutrinoceros) - Institut de Planétologie et d'Astrophysique de Grenoble, France (ORCID: [0000-0001-8629-7068](https://orcid.org/0000-0001-8629-7068))
* [Daniel Shapero](https://github.com/danshapero) - Polar Science Center, University of Washington Applied Physics Lab, USA (ORCID: [0000-0002-3651-0649](https://www.orcid.org/0000-0002-3651-0649))
* [Santiago Soler](https://github.com/santisoler) - CONICET, Argentina; Instituto Geofísico Sismológico Volponi, Universidad Nacional de San Juan, Argentina (ORCID: [0000-0001-9202-5317](https://www.orcid.org/0000-0001-9202-5317))
* [Matthew Turk](https://github.com/matthewturk) - University of Illinois at Urbana-Champaign, USA (ORCID: [0000-0002-5294-0198](https://www.orcid.org/0000-0002-5294-0198))
Expand Down
2 changes: 1 addition & 1 deletion doc/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Required:

Optional:

* `tqdm <https://github.com/tqdm/tqdm>`__: Required to print a download
* `tqdm <https://github.com/tqdm/tqdm>`__: print a simple download
progress bar (see :class:`pooch.HTTPDownloader`).
* `paramiko <https://github.com/paramiko/paramiko>`__: Required for SFTP
downloads (see :class:`pooch.SFTPDownloader`).
Expand Down
33 changes: 33 additions & 0 deletions doc/intermediate.rst
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,36 @@ like this:

``tqdm`` is not installed by default with Pooch. You will have to install
it separately in order to use this feature.

Alternatively, you can pass an arbitrary object that behaves like a progress
that implements the ``update``, ``reset`, and ``close`` methods. ``update``
should accept a single integer positional argument representing the current
completion (in bytes), while ``reset`` and ``update`` do not take any argument
beside ``self``. The object must also have a `total` attribute that can be set
from outside the class.
Here's a minimal working example of such a custom "progress display" class

.. code:: python
import sys
class MinimalProgressDisplay:
def __init__(self, total):
self.count = 0
self.total = total
def __repr__(self):
return str(self.count) + "/" + str(self.total)
def render(self):
print(f"\r{self}", file=sys.stderr, end="")
def update(self, i):
self.count = i
self.render()
def reset(self):
self.count = 0
def close(self):
print("", file=sys.stderr)
29 changes: 21 additions & 8 deletions pooch/downloaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,13 @@ class HTTPDownloader: # pylint: disable=too-few-public-methods
Parameters
----------
progressbar : bool
progressbar : bool or an arbitrary progress bar object
If True, will print a progress bar of the download to standard error
(stderr). Requires `tqdm <https://github.com/tqdm/tqdm>`__ to be
installed.
Alternatively, an arbitrary object with the following methods
can be passed instead reset, update(int), close()
chunk_size : int
Files are streamed *chunk_size* bytes at a time instead of loading
everything into memory at one. Usually doesn't need to be changed.
Expand Down Expand Up @@ -151,7 +154,7 @@ def __init__(self, progressbar=False, chunk_size=1024, **kwargs):
self.kwargs = kwargs
self.progressbar = progressbar
self.chunk_size = chunk_size
if self.progressbar and tqdm is None:
if self.progressbar is True and tqdm is None:
raise ValueError("Missing package 'tqdm' required for progress bars.")

def __call__(self, url, output_file, pooch):
Expand Down Expand Up @@ -179,8 +182,8 @@ def __call__(self, url, output_file, pooch):
response = requests.get(url, **kwargs)
response.raise_for_status()
content = response.iter_content(chunk_size=self.chunk_size)
if self.progressbar:
total = int(response.headers.get("content-length", 0))
total = int(response.headers.get("content-length", 0))
if self.progressbar is True:
# Need to use ascii characters on Windows because there isn't
# always full unicode support
# (see https://github.com/tqdm/tqdm/issues/454)
Expand All @@ -193,6 +196,9 @@ def __call__(self, url, output_file, pooch):
unit_scale=True,
leave=True,
)
elif self.progressbar:
progress = self.progressbar
progress.total = total
for chunk in content:
if chunk:
output_file.write(chunk)
Expand Down Expand Up @@ -242,10 +248,12 @@ class FTPDownloader: # pylint: disable=too-few-public-methods
timeout : int
Timeout in seconds for ftp socket operations, use None to mean no
timeout.
progressbar : bool
progressbar : bool or an arbitrary progress bar object
If True, will print a progress bar of the download to standard error
(stderr). Requires `tqdm <https://github.com/tqdm/tqdm>`__ to be
installed.
Alternatively, an arbitrary object with the following methods
can be passed instead reset, update(int), close()
chunk_size : int
Files are streamed *chunk_size* bytes at a time instead of loading
everything into memory at one. Usually doesn't need to be changed.
Expand Down Expand Up @@ -352,10 +360,12 @@ class SFTPDownloader: # pylint: disable=too-few-public-methods
timeout : int
Timeout in seconds for sftp socket operations, use None to mean no
timeout.
progressbar : bool
progressbar : bool or an arbitrary progress bar object
If True, will print a progress bar of the download to standard
error (stderr). Requires `tqdm <https://github.com/tqdm/tqdm>`__ to
be installed.
Alternatively, an arbitrary object with the following methods
can be passed instead reset, update(int), close()
"""

Expand All @@ -378,7 +388,7 @@ def __init__(
# captured. Otherwise, the user is only warned of one of them at a
# time (and we can't test properly when they are both missing).
errors = []
if self.progressbar and tqdm is None:
if self.progressbar is True and tqdm is None:
errors.append("Missing package 'tqdm' required for progress bars.")
if paramiko is None:
errors.append("Missing package 'paramiko' required for SFTP downloads.")
Expand Down Expand Up @@ -409,7 +419,7 @@ def __call__(self, url, output_file, pooch):
connection.connect(username=self.username, password=self.password)
sftp = paramiko.SFTPClient.from_transport(connection)
sftp.get_channel().settimeout = self.timeout
if self.progressbar:
if self.progressbar is True:
size = int(sftp.stat(parsed_url["path"]).st_size)
use_ascii = bool(sys.platform == "win32")
progress = tqdm(
Expand All @@ -420,6 +430,9 @@ def __call__(self, url, output_file, pooch):
unit_scale=True,
leave=True,
)
elif self.progressbar:
progress = self.progressbar
if self.progressbar:
with progress:

def callback(current, total):
Expand Down
51 changes: 51 additions & 0 deletions pooch/tests/test_downloaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,54 @@ def test_downloader_progressbar_sftp(capsys):
assert printed[:25] == progress
# Check that the file was actually downloaded
assert os.path.exists(outfile)


def test_downloader_arbitrary_progressbar(capsys):
"Setup a downloader function with an arbitrary progress bar class."

class MinimalProgressDisplay:
"""A minimalist replacement for tqdm.tqdm"""

def __init__(self, total):
self.count = 0
self.total = total

def __repr__(self):
"""represent current completion"""
return str(self.count) + "/" + str(self.total)

def render(self):
"""print self.__repr__ to stderr"""
print(f"\r{self}", file=sys.stderr, end="")

def update(self, i):
"""modify completion and render"""
self.count = i
self.render()

def reset(self):
"""set counter to 0"""
self.count = 0

@staticmethod
def close():
"""print a new empty line"""
print("", file=sys.stderr)

pbar = MinimalProgressDisplay(total=None)
download = HTTPDownloader(progressbar=pbar)
with TemporaryDirectory() as local_store:
fname = "large-data.txt"
url = BASEURL + fname
outfile = os.path.join(local_store, "large-data.txt")
download(url, outfile, None)
# Read stderr and make sure the progress bar is printed only when told
captured = capsys.readouterr()
printed = captured.err.split("\r")[-1].strip()

progress = "336/336"
# Bar size is not always the same so can't reliably test the whole bar.
assert printed == progress

# Check that the downloaded file has the right content
check_large_data(outfile)

0 comments on commit 2825da0

Please sign in to comment.