Skip to content

Commit

Permalink
Merge pull request #1759 from althonos/progress-reader
Browse files Browse the repository at this point in the history
Add API to support tracking progress while reading from a file
  • Loading branch information
willmcgugan committed Mar 30, 2022
2 parents 9f43ccc + 5717d8d commit 3f61975
Show file tree
Hide file tree
Showing 6 changed files with 659 additions and 5 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Expand Up @@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## 12.1.0

### Added

- Progress.open and Progress.wrap_file method to track the progress while reading from a file or file-like object https://github.com/willmcgugan/rich/pull/1759

### Added

Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Expand Up @@ -18,6 +18,7 @@ The following people have contributed to the development of Rich:
- [Finn Hughes](https://github.com/finnhughes)
- [Josh Karpel](https://github.com/JoshKarpel)
- [Andrew Kettmann](https://github.com/akettmann)
- [Martin Larralde](https://github.com/althonos)
- [Hedy Li](https://github.com/hedythedev)
- [Luka Mamukashvili](https://github.com/UltraStudioLTD)
- [Alexander Mancevice](https://github.com/amancevice)
Expand Down
53 changes: 49 additions & 4 deletions docs/source/progress.rst
Expand Up @@ -26,6 +26,16 @@ For basic usage call the :func:`~rich.progress.track` function, which accepts a
for n in track(range(n), description="Processing..."):
do_work(n)


To get a progress bar while reading from a file, you may consider using the :func:`~rich.progress.read` function, which accepts a path, or a *file-like* object. It will return a *file-like* object in *binary mode* that will update the progress information as it's being read from. Here's an example, tracking the progresses made by :func:`json.load` to load a file::

import json
from rich.progress import read

with read("data.json", description="Loading data...") as f:
data = json.load(f)


Advanced usage
--------------

Expand All @@ -34,9 +44,9 @@ If you require multiple tasks in the display, or wish to configure the columns i
The Progress class is designed to be used as a *context manager* which will start and stop the progress display automatically.

Here's a simple example::

import time

from rich.progress import Progress

with Progress() as progress:
Expand Down Expand Up @@ -179,7 +189,7 @@ If you have another Console object you want to use, pass it in to the :class:`~r
with Progress(console=my_console) as progress:
my_console.print("[bold blue]Starting work!")
do_work(progress)


Redirecting stdout / stderr
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -199,6 +209,42 @@ If the :class:`~rich.progress.Progress` class doesn't offer exactly what you nee
def get_renderables(self):
yield Panel(self.make_tasks_table(self.tasks))

Reading from a file
~~~~~~~~~~~~~~~~~~~

You can obtain a progress-tracking reader using the :meth:`~rich.progress.Progress.open` method by giving it a path. You can specify the number of bytes to be read, but by default :meth:`~rich.progress.Progress.open` will query the size of the file with :func:`os.stat`. You are responsible for closing the file, and you should consider using a *context* to make sure it is closed ::

import json
from rich.progress import Progress

with Progress() as progress:
with progress.open("data.json", "rb") as file:
json.load(file)


Note that in the above snippet we use the `"rb"` mode, because we needed the file to be opened in binary mode to pass it to :func:`json.load`. If the API consuming the file is expecting an object in *text mode* (for instance, :func:`csv.reader`), you can open the file with the `"r"` mode, which happens to be the default ::

from rich.progress import Progress

with Progress() as progress:
with progress.open("README.md") as file:
for line in file:
print(line)


Reading from a file-like object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can obtain a progress-tracking reader wrapping a file-like object using the :meth:`~rich.progress.Progress.wrap_file` method. The file-like object must be in *binary mode*, and a total must be provided, unless it was provided to a :class:`~rich.progress.Task` created beforehand. The returned reader may be used in a context, but will not take care of closing the wrapped file ::

import json
from rich.progress import Progress

with Progress() as progress:
with open("data.json", "rb") as file:
json.load(progress.wrap_file(file, total=2048))


Multiple Progress
-----------------

Expand All @@ -208,4 +254,3 @@ Example
-------

See `downloader.py <https://github.com/willmcgugan/rich/blob/master/examples/downloader.py>`_ for a realistic application of a progress display. This script can download multiple concurrent files with a progress bar, transfer speed and file size.

39 changes: 39 additions & 0 deletions examples/cp_progress.py
@@ -0,0 +1,39 @@
"""
A very minimal `cp` clone that displays a progress bar.
"""
import os
import shutil
import sys

from rich.progress import (
BarColumn,
DownloadColumn,
Progress,
TaskID,
TextColumn,
TimeRemainingColumn,
TransferSpeedColumn,
)

progress = Progress(
TextColumn("[bold blue]{task.description}", justify="right"),
BarColumn(bar_width=None),
"[progress.percentage]{task.percentage:>3.1f}%",
"•",
DownloadColumn(),
"•",
TransferSpeedColumn(),
"•",
TimeRemainingColumn(),
)

if __name__ == "__main__":
if len(sys.argv) == 3:

with progress:
desc = os.path.basename(sys.argv[1])
with progress.read(sys.argv[1], description=desc) as src:
with open(sys.argv[2], "wb") as dst:
shutil.copyfileobj(src, dst)
else:
print("Usage:\n\tpython cp_progress.py SRC DST")

0 comments on commit 3f61975

Please sign in to comment.