diff --git a/CHANGELOG.md b/CHANGELOG.md index 5adc3993e..8c45b9898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,14 +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). -## 12.1.0 +## [12.1.0] - 2022-04-03 ### 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 - - SVG export functionality https://github.com/Textualize/rich/pull/2101 ### Fixed diff --git a/docs/source/progress.rst b/docs/source/progress.rst index f905b255e..59813a473 100644 --- a/docs/source/progress.rst +++ b/docs/source/progress.rst @@ -212,37 +212,38 @@ If the :class:`~rich.progress.Progress` class doesn't offer exactly what you nee 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 :: +Rich provides an easy way to generate a progress bar for reading a file. If you call :func:`~rich.progress.open` it will return a context manager which displays a progress bar while you read. This is particularly useful when you can't easily modify the code that does the reading. + +The following example shows how we might show progress for reading a JSON file:: import json - from rich.progress import Progress + import rich.progress - with Progress() as progress: - with progress.open("data.json", "rb") as file: - json.load(file) + with rich.progress.open("data.json", "rb") as file: + data = json.load(file) + print(data) +If you already have a file object, you can call :func:`~rich.progress.wrap_file` which returns a context manager that wraps your file so that it displays a progress bar. If you use this function you will need to set the number of bytes or characters you expect to read. -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 :: +Here's an example that reads a url from the internet:: - from rich.progress import Progress + from time import sleep + from urllib.request import urlopen - with Progress() as progress: - with progress.open("README.md") as file: - for line in file: - print(line) + from rich.progress import wrap_file + response = urlopen("https://www.textualize.io") + size = int(response.headers["Content-Length"]) -Reading from a file-like object -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + with wrap_file(response, size) as file: + for line in file: + print(line.decode("utf-8"), end="") + sleep(0.1) -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 +If you expect to be reading from multiple files, you can use :meth:`~rich.progress.Progress.open` or :meth:`~rich.progress.Progress.wrap_file` to add a file progress to an existing Progress instance. - with Progress() as progress: - with open("data.json", "rb") as file: - json.load(progress.wrap_file(file, total=2048)) +See `cp_progress.py ` for a minimal clone of the ``cp`` command which shows a progress bar as the file is copied. Multiple Progress diff --git a/examples/cp_progress.py b/examples/cp_progress.py index 0f4059d02..b5cc4c818 100644 --- a/examples/cp_progress.py +++ b/examples/cp_progress.py @@ -5,35 +5,15 @@ 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(), -) +from rich.progress import Progress if __name__ == "__main__": if len(sys.argv) == 3: - - with progress: + with Progress() as progress: desc = os.path.basename(sys.argv[1]) - with progress.read(sys.argv[1], description=desc) as src: + with progress.open(sys.argv[1], "rb", description=desc) as src: with open(sys.argv[2], "wb") as dst: shutil.copyfileobj(src, dst) else: + print("Copy a file with a progress bar.") print("Usage:\n\tpython cp_progress.py SRC DST") diff --git a/examples/file_progress.py b/examples/file_progress.py new file mode 100644 index 000000000..fd18d7537 --- /dev/null +++ b/examples/file_progress.py @@ -0,0 +1,16 @@ +from time import sleep +from urllib.request import urlopen + +from rich.progress import wrap_file + +# Read a URL with urlopen +response = urlopen("https://www.textualize.io") +# Get the size from the headers +size = int(response.headers["Content-Length"]) + +# Wrap the response so that it update progress + +with wrap_file(response, size) as file: + for line in file: + print(line.decode("utf-8"), end="") + sleep(0.1) diff --git a/examples/save_table_svg.py b/examples/save_table_svg.py new file mode 100644 index 000000000..e22aee22b --- /dev/null +++ b/examples/save_table_svg.py @@ -0,0 +1,26 @@ +""" +Demonstrates how to export a SVG +""" + +from rich.console import Console +from rich.table import Table + +table = Table(title="Star Wars Movies") + +table.add_column("Released", style="cyan", no_wrap=True) +table.add_column("Title", style="magenta") +table.add_column("Box Office", justify="right", style="green") + +table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690") +table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") +table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889") +table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889") + +console = Console(record=True) +console.print(table, justify="center") +console.save_svg("table.svg", title="save_table_svg.py") + +import os +import webbrowser + +webbrowser.open(f"file://{os.path.abspath('table.svg')}") diff --git a/rich/console.py b/rich/console.py index bcacbc738..2cdab4280 100644 --- a/rich/console.py +++ b/rich/console.py @@ -2350,7 +2350,7 @@ def export_svg( ) ) - fragments = [] + fragments: List[str] = [] theme_foreground_color = _theme.foreground_color.hex theme_background_color = _theme.background_color.hex