Merge branch 'master' into fix-panel-fix-title-missing-panel-background

This commit is contained in:
Will McGugan 2025-06-24 11:38:28 +01:00 committed by GitHub
commit 6b8b3fa281
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 527 additions and 384 deletions

View file

@ -17,4 +17,6 @@ Thank you for your issue. Give us a little time to review it.
PS. You might want to check the [FAQ]({{ faq_url }}) if you haven't done so already.
{%- endif %}
Rich was created by Will McGugan. Consider [sponsoring Will's work on Rich](https://github.com/sponsors/willmcgugan).
This is an automated reply, generated by [FAQtory](https://github.com/willmcgugan/faqtory)

1
.github/FUNDING.yml vendored
View file

@ -1,2 +1,3 @@
# These are supported funding model platforms
github: willmcgugan

View file

@ -1,3 +1,18 @@
<!--
Please note that Rich isn't accepting any new features at this point.
If a feature can be implemented without modifying the core library, then
they should be released as a third-party module. I can accept updates to the
core library that make it easier to extend (think hooks).
Bugfixes are always welcome of course.
Sometimes it is not clear what is a feature and what is a bug fix.
If there is any doubt, please open a discussion first.
-->
## Type of changes
- [ ] Bug fix

View file

@ -13,7 +13,11 @@ jobs:
with:
issue-number: ${{ github.event.issue.number }}
body: |
I hope we solved your problem.
I hope I helped!
If you like using Rich, you might also enjoy [Textual](https://textual.textualize.io)
Consider [sponsoring](https://github.com/sponsors/willmcgugan) my work on Rich. I give tech support for free, in addition to maintaining Rich and Textual.
If you like using Rich, you might also enjoy [Textual](https://textual.textualize.io).
Will McGugan

View file

@ -5,12 +5,25 @@ 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
## [Unreleased]
### Changed
- Removed `typing_extensions` from runtime dependencies https://github.com/Textualize/rich/pull/3763
- Live objects (including Progress) may now be nested https://github.com/Textualize/rich/pull/3768
- Added padding property to Syntax which returns a tuple of four integers https://github.com/Textualize/rich/pull/3782
### Fixed
- Fixed extraction of recursive exceptions https://github.com/Textualize/rich/pull/3772
- Fixed padding applied to Syntax https://github.com/Textualize/rich/pull/3782
- Fixed `Panel` title missing the panel background style https://github.com/Textualize/rich/issues/3569
### Added
- Added `TTY_INTERACTIVE` environment variable to force interactive mode off or on https://github.com/Textualize/rich/pull/3777
## [14.0.0] - 2025-03-30
### Added

View file

@ -67,6 +67,7 @@ The following people have contributed to the development of Rich:
- [Louis Sautier](https://github.com/sbraz)
- [Tim Savage](https://github.com/timsavage)
- [Anthony Shaw](https://github.com/tonybaloney)
- [Damian Shaw](https://github.com/notatallshaw)
- [Nicolas Simonds](https://github.com/0xDEC0DE)
- [Aaron Stephens](https://github.com/aaronst)
- [Karolina Surma](https://github.com/befeleme)
@ -92,3 +93,4 @@ The following people have contributed to the development of Rich:
- [chthollyphile](https://github.com/chthollyphile)
- [Jonathan Helmus](https://github.com/jjhelmus)
- [Brandon Capener](https://github.com/bcapener)
- [Alex Zheng](https://github.com/alexzheng111)

View file

@ -1,6 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,6 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,6 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,6 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,7 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich/13.2.0)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,6 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,6 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,7 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich/13.2.0)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,5 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,6 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,6 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,4 +1,4 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich/13.2.0)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
@ -436,4 +436,4 @@ See also [Rich CLI](https://github.com/textualize/rich-cli) for a command line a
See also Rich's sister project, [Textual](https://github.com/Textualize/textual), which you can use to build sophisticated User Interfaces in the terminal.
![Textual screenshot](https://raw.githubusercontent.com/Textualize/textual/main/imgs/textual.png)
![textual-splash](https://github.com/user-attachments/assets/4caeb77e-48c0-4cf7-b14d-c53ded855ffd)

View file

@ -1,4 +1,4 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich/13.2.0)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
@ -82,7 +82,7 @@ Rich może zostać zainstalowany w REPL, żeby wszystkie struktury danych były
## Używanie konsoli
Dla większej kontroli nad bogatą zawartością terminala, zaimportuj i skonstruuj objekt [Console](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console).
Dla większej kontroli nad bogatą zawartością terminala, zaimportuj i skonstruuj obiekt [Console](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console).
```python
from rich.console import Console
@ -90,7 +90,7 @@ from rich.console import Console
console = Console()
```
Objekt Console ma metodę `print`, mającą celowo podobny interfejs do wbudowanej funkcji `print`. Tu jest przykład użycia:
Obiekt Console ma metodę `print`, mającą celowo podobny interfejs do wbudowanej funkcji `print`. Tu jest przykład użycia:
```python
console.print("Hello", "World!")
@ -116,11 +116,11 @@ console.print("Where there is a [bold cyan]Will[/bold cyan] there [u]is[/u] a [i
![Console Markup](https://github.com/textualize/rich/raw/master/imgs/where_there_is_a_will.png)
Możesz użyć objektu Console, aby wygenerować skomplikowane wyjście bez problemu. Więcej informacji odnośnie Console API w [dokumentacji](https://rich.readthedocs.io/en/latest/console.html).
Możesz użyć obiektu Console, aby wygenerować skomplikowane wyjście bez problemu. Więcej informacji odnośnie Console API w [dokumentacji](https://rich.readthedocs.io/en/latest/console.html).
## Rich Inspect
Rich ma funkcję [inspect](https://rich.readthedocs.io/en/latest/reference/init.html?highlight=inspect#rich.inspect), która może produkować raporty na jakimkolwiek objekcie Python, jak np. klasa, instancja, lub wbudowana funkcja.
Rich ma funkcję [inspect](https://rich.readthedocs.io/en/latest/reference/init.html?highlight=inspect#rich.inspect), która może produkować raporty na jakimkolwiek obiekcie Python, jak np. klasa, instancja, lub wbudowana funkcja.
```python
>>> my_list = ["foo", "bar"]
@ -141,7 +141,7 @@ Kliknij poniższe nagłówki, żeby poznać detale:
<details>
<summary>Log</summary>
Objekt Console ma metodę `log()`, mającą podobny interfejs do `print()`, ale wyświetla również kolumnę zawierającą aktualny czas oraz plik i linijkę, która wywołała powyższą metodę. Domyślnie Rich podświetla składnię dla struktur Pythona i ciągów repr. Jeśli zlogujesz kolekcję (czyli listę `list` lub słownik `dict`), Rich ją ładnie wypisze tak, żeby zmieściła się w dostępnym miejscu. Poniżej znajduje się przykład tych funkcji.
Obiekt Console ma metodę `log()`, mającą podobny interfejs do `print()`, ale wyświetla również kolumnę zawierającą aktualny czas oraz plik i linijkę, która wywołała powyższą metodę. Domyślnie Rich podświetla składnię dla struktur Pythona i ciągów repr. Jeśli zlogujesz kolekcję (czyli listę `list` lub słownik `dict`), Rich ją ładnie wypisze tak, żeby zmieściła się w dostępnym miejscu. Poniżej znajduje się przykład tych funkcji.
```python
from rich.console import Console
@ -170,7 +170,7 @@ Powyższy kod wyświetla poniższy tekst:
![Log](https://github.com/textualize/rich/raw/master/imgs/log.png)
Istnieje argument `log_locals`, który wyświetla tabelę zawierającą zmienne lokalne z kąd wywołano metodę log.
Istnieje argument `log_locals`, który wyświetla tabelę zawierającą zmienne lokalne skąd wywołano metodę log.
Metoda log może być używana do logowania do terminala dla długo działających aplikacji takich jak serwery, ale jest również bardzo dobrą pomocą w debugowaniu.
@ -178,7 +178,7 @@ Metoda log może być używana do logowania do terminala dla długo działający
<details>
<summary>Handler Logów</summary>
Możesz także użyć wbudowanej [klasy Handler](https://rich.readthedocs.io/en/latest/logging.html), aby zformatować i pokolorować wyjście z modułu logging Pythona. Przykład poniżej:
Możesz także użyć wbudowanej [klasy Handler](https://rich.readthedocs.io/en/latest/logging.html), aby sformatować i pokolorować wyjście z modułu logging Pythona. Przykład poniżej:
![Logging](https://github.com/textualize/rich/raw/master/imgs/logging.png)
@ -379,7 +379,7 @@ Ten kod wyświetli tekst w stylu:
<details>
<summary>Podświetlanie kodu źródłowego</summary>
Rich używa biblioteki [pygments](https://pygments.org/), żeby zaimplementować [podświetlanie kodu źródłowego](https://rich.readthedocs.io/en/latest/syntax.html). Użycie jest podobne do renderowania markdownu; skonstruuj objekt `Syntax` i wydrukuj go do konsoli. Przykład poniżej:
Rich używa biblioteki [pygments](https://pygments.org/), żeby zaimplementować [podświetlanie kodu źródłowego](https://rich.readthedocs.io/en/latest/syntax.html). Użycie jest podobne do renderowania markdownu; skonstruuj obiekt `Syntax` i wydrukuj go do konsoli. Przykład poniżej:
```python
from rich.console import Console

View file

@ -1,6 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,7 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich/13.2.0)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,6 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -1,4 +1,4 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich/13.2.0)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)

View file

@ -1,6 +1,7 @@
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich)
[![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich)
[![codecov](https://codecov.io/gh/Textualize/rich/branch/master/graph/badge.svg)](https://codecov.io/gh/Textualize/rich)
[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich)
[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/)
[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan)

View file

@ -17,14 +17,8 @@
# -- Project information -----------------------------------------------------
import sys
import sphinx_rtd_theme
if sys.version_info >= (3, 8):
from importlib.metadata import Distribution
else:
from importlib_metadata import Distribution
from importlib.metadata import Distribution
html_theme = "sphinx_rtd_theme"
@ -50,6 +44,7 @@ extensions = [
"sphinx.ext.intersphinx",
"sphinx.ext.autosectionlabel",
"sphinx_copybutton",
"sphinx_rtd_theme",
]
# Add any paths that contain templates here, relative to this directory.

View file

@ -405,13 +405,13 @@ If Rich detects that it is not writing to a terminal it will strip control codes
Letting Rich auto-detect terminals is useful as it will write plain text when you pipe output to a file or other application.
.. _interactive_mode:
Interactive mode
----------------
Rich will remove animations such as progress bars and status indicators when not writing to a terminal as you probably don't want to write these out to a text file (for example). You can override this behavior by setting the ``force_interactive`` argument on the constructor. Set it to True to enable animations or False to disable them.
Rich will remove animations such as progress bars and status indicators when not writing to a terminal as you probably don't want to write these out to a text file (for example). You can override this behavior by setting the ``force_interactive`` argument on the constructor. Set it to ``True`` to enable animations or ``False`` to disable them.
.. note::
Some CI systems support ANSI color and style but not anything that moves the cursor or selectively refreshes parts of the terminal. For these you might want to set ``force_terminal`` to ``True`` and ``force_interactive`` to ``False``.
Environment variables
---------------------
@ -429,8 +429,11 @@ If the environment variable ``NO_COLOR`` is set, Rich will disable all color in
The environment variable ``TTY_COMPATIBLE`` is used to override Rich's auto-detection of terminal support. If ``TTY_COMPATIBLE`` is set to ``1`` then Rich will assume it is writing to a device which can handle escape sequences like a terminal. If ``TTY_COMPATIBLE`` is set to ``"0"``, then Rich will assume that it is writing to a device that is *not* capable of displaying escape sequences (such as a regular file). If the variable is not set, or set to a value other than "0" or "1", then Rich will attempt to auto-detect terminal support.
The environment variable ``TTY_INTERACTIVE`` is used to override Rich's auto-detection of :ref:`interactive_mode`. If you set this to ``"0"``, it will disable interactive mode even if Rich thinks it is writing to a terminal. Set this to ``"1"`` to force interactive mode on. If this environment variable is not set, or set to any other value, then interactive mode will be auto-detected as normal.
.. note::
If you want Rich output in CI or Github Actions, then you should set ``TTY_COMPATIBLE=1``.
If you want Rich output in CI or Github Actions, then you should set ``TTY_COMPATIBLE=1`` and ``TTY_INTERACTIVE=0``. The combination of both these variables tells rich that it can output escape sequences,
and also that there is no user interacting with the terminal -- so it won't bother animating progress bars.
If ``width`` / ``height`` arguments are not explicitly provided as arguments to ``Console`` then the environment variables ``COLUMNS`` / ``LINES`` can be used to set the console width / height. ``JUPYTER_COLUMNS`` / ``JUPYTER_LINES`` behave similarly and are used in Jupyter.

View file

@ -12,7 +12,7 @@ Rich works with macOS, Linux and Windows.
On Windows both the (ancient) cmd.exe terminal is supported and the new `Windows Terminal <https://github.com/microsoft/terminal/releases>`_. The latter has much improved support for color and style.
Rich requires Python 3.7.0 and above.
Rich requires Python 3.8.0 and above.
.. note::
PyCharm users will need to enable "emulate terminal" in output console option in run/debug configuration to see styled output.
@ -30,6 +30,12 @@ If you intend to use Rich with Jupyter then there are some additional dependenci
pip install "rich[jupyter]"
Demo
----
To check if Rich was installed correctly, and to see a little of what Rich can do, run the following from the command line::
python -m rich
Quick Start
-----------

View file

@ -3,7 +3,7 @@
Live Display
============
Progress bars and status indicators use a *live* display to animate parts of the terminal. You can build custom live displays with the :class:`~rich.live.Live` class.
Progress bars and status indicators use a *live* display to animate parts of the terminal. You can build custom live displays with the :class:`~rich.live.Live` class.
For a demonstration of a live display, run the following command::
@ -72,7 +72,7 @@ You can also change the renderable on-the-fly by calling the :meth:`~rich.live.L
Alternate screen
~~~~~~~~~~~~~~~~
You can opt to show a Live display in the "alternate screen" by setting ``screen=True`` on the constructor. This will allow your live display to go full screen and restore the command prompt on exit.
You can opt to show a Live display in the "alternate screen" by setting ``screen=True`` on the constructor. This will allow your live display to go full screen and restore the command prompt on exit.
You can use this feature in combination with :ref:`Layout` to display sophisticated terminal "applications".
@ -80,7 +80,7 @@ Transient display
~~~~~~~~~~~~~~~~~
Normally when you exit live context manager (or call :meth:`~rich.live.Live.stop`) the last refreshed item remains in the terminal with the cursor on the following line.
You can also make the live display disappear on exit by setting ``transient=True`` on the Live constructor.
You can also make the live display disappear on exit by setting ``transient=True`` on the Live constructor.
Auto refresh
~~~~~~~~~~~~
@ -149,13 +149,10 @@ This feature is enabled by default, but you can disable by setting ``redirect_st
Nesting Lives
-------------
Note that only a single live context may be active at any one time. The following will raise a :class:`~rich.errors.LiveError` because status also uses Live::
If you create a Live instance within the context of an existing Live instance, then the content of the inner Live will be displayed below the outer Live.
with Live(table, console=console):
with console.status("working"): # Will not work
do_work()
Prior to version 14.0.0 this would have resulted in a :class:`~rich.errors.LiveError` exception.
In practice this is rarely a problem because you can display any combination of renderables in a Live context.
Examples
--------

View file

@ -163,7 +163,6 @@ The following column objects are available:
- :class:`~rich.progress.TransferSpeedColumn` Displays transfer speed (assumes the steps are bytes).
- :class:`~rich.progress.SpinnerColumn` Displays a "spinner" animation.
- :class:`~rich.progress.RenderableColumn` Displays an arbitrary Rich renderable in the column.
- :class:`~rich.progress.IterationSpeedColumn` Displays iteration speed in it/s (iterations per second).
To implement your own columns, extend the :class:`~rich.progress.ProgressColumn` class and use it as you would the other columns.
@ -266,6 +265,28 @@ If you expect to be reading from multiple files, you can use :meth:`~rich.progre
See `cp_progress.py <https://github.com/willmcgugan/rich/blob/master/examples/cp_progress.py>`_ for a minimal clone of the ``cp`` command which shows a progress bar as the file is copied.
Nesting Progress bars
---------------------
If you create a new progress bar within the context of an existing progress bar (with the context manager or `track` function), then Rich will display the inner progress bar(s) under the initial bar.
Here's an example that nests progress bars::
from rich.progress import track
from time import sleep
for count in track(range(10)):
for letter in track("ABCDEF", transient=True):
print(f"Stage {count}{letter}")
sleep(0.1)
sleep(0.1)
The inner loop creates a new progress bar below the first, but both can update.
Note that if you nest progress bars like this, then the nested bars will updating according to the `refresh_per_second` attribute of the outer bar.
Multiple Progress
-----------------

View file

@ -0,0 +1,5 @@
rich.control
============
.. automodule:: rich.control
:members:

View file

@ -1,7 +1,6 @@
"""Lite simulation of the top linux command."""
import datetime
import random
import sys
import time
from dataclasses import dataclass
@ -9,11 +8,7 @@ from rich import box
from rich.console import Console
from rich.live import Live
from rich.table import Table
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
from typing import Literal
@dataclass

252
poetry.lock generated
View file

@ -13,21 +13,18 @@ files = [
[[package]]
name = "asttokens"
version = "2.4.1"
version = "3.0.0"
description = "Annotate AST trees with source code positions"
optional = true
python-versions = "*"
python-versions = ">=3.8"
files = [
{file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
{file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
{file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"},
{file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"},
]
[package.dependencies]
six = ">=1.12.0"
[package.extras]
astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"]
test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"]
astroid = ["astroid (>=2,<4)"]
test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"]
[[package]]
name = "asv"
@ -122,13 +119,13 @@ files = [
[[package]]
name = "click"
version = "8.1.7"
version = "8.1.8"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
]
[package.dependencies]
@ -251,49 +248,52 @@ toml = ["tomli"]
[[package]]
name = "decorator"
version = "5.1.1"
version = "5.2.1"
description = "Decorators for Humans"
optional = true
python-versions = ">=3.5"
python-versions = ">=3.8"
files = [
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
{file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"},
{file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"},
]
[[package]]
name = "distlib"
version = "0.3.8"
version = "0.3.9"
description = "Distribution utilities"
optional = false
python-versions = "*"
files = [
{file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
{file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
{file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
]
[[package]]
name = "exceptiongroup"
version = "1.2.2"
version = "1.3.0"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
{file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
{file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
]
[package.dependencies]
typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "executing"
version = "2.1.0"
version = "2.2.0"
description = "Get the currently executing AST node of a frame, and other information"
optional = true
python-versions = ">=3.8"
files = [
{file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"},
{file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"},
{file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"},
{file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"},
]
[package.extras]
@ -331,13 +331,13 @@ license = ["ukkonen"]
[[package]]
name = "iniconfig"
version = "2.0.0"
version = "2.1.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
{file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
{file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
]
[[package]]
@ -381,53 +381,53 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pa
[[package]]
name = "ipywidgets"
version = "8.1.5"
version = "8.1.7"
description = "Jupyter interactive widgets"
optional = true
python-versions = ">=3.7"
files = [
{file = "ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245"},
{file = "ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17"},
{file = "ipywidgets-8.1.7-py3-none-any.whl", hash = "sha256:764f2602d25471c213919b8a1997df04bef869251db4ca8efba1b76b1bd9f7bb"},
{file = "ipywidgets-8.1.7.tar.gz", hash = "sha256:15f1ac050b9ccbefd45dccfbb2ef6bed0029d8278682d569d71b8dd96bee0376"},
]
[package.dependencies]
comm = ">=0.1.3"
ipython = ">=6.1.0"
jupyterlab-widgets = ">=3.0.12,<3.1.0"
jupyterlab_widgets = ">=3.0.15,<3.1.0"
traitlets = ">=4.3.1"
widgetsnbextension = ">=4.0.12,<4.1.0"
widgetsnbextension = ">=4.0.14,<4.1.0"
[package.extras]
test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"]
[[package]]
name = "jedi"
version = "0.19.1"
version = "0.19.2"
description = "An autocompletion tool for Python that can be used for text editors."
optional = true
python-versions = ">=3.6"
files = [
{file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"},
{file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"},
{file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"},
{file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"},
]
[package.dependencies]
parso = ">=0.8.3,<0.9.0"
parso = ">=0.8.4,<0.9.0"
[package.extras]
docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"]
[[package]]
name = "jupyterlab-widgets"
version = "3.0.13"
version = "3.0.15"
description = "Jupyter interactive widgets for JupyterLab"
optional = true
python-versions = ">=3.7"
files = [
{file = "jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54"},
{file = "jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed"},
{file = "jupyterlab_widgets-3.0.15-py3-none-any.whl", hash = "sha256:d59023d7d7ef71400d51e6fee9a88867f6e65e10a4201605d2d7f3e8f012a31c"},
{file = "jupyterlab_widgets-3.0.15.tar.gz", hash = "sha256:2920888a0c2922351a9202817957a68c07d99673504d6cd37345299e971bb08b"},
]
[[package]]
@ -481,60 +481,72 @@ files = [
[[package]]
name = "mypy"
version = "1.11.2"
version = "1.14.1"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"},
{file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"},
{file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"},
{file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"},
{file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"},
{file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"},
{file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"},
{file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"},
{file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"},
{file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"},
{file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"},
{file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"},
{file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"},
{file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"},
{file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"},
{file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"},
{file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"},
{file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"},
{file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"},
{file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"},
{file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"},
{file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"},
{file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"},
{file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"},
{file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"},
{file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"},
{file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"},
{file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"},
{file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"},
{file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"},
{file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"},
{file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"},
{file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"},
{file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"},
{file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"},
{file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"},
{file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"},
{file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"},
{file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"},
{file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"},
{file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"},
{file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"},
{file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"},
{file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"},
{file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"},
{file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"},
{file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"},
{file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"},
{file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"},
{file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"},
{file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"},
{file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"},
{file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"},
{file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"},
{file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"},
{file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"},
{file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"},
{file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"},
{file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"},
{file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"},
{file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"},
{file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"},
{file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"},
{file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"},
{file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"},
]
[package.dependencies]
mypy-extensions = ">=1.0.0"
mypy_extensions = ">=1.0.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = ">=4.6.0"
typing_extensions = ">=4.6.0"
[package.extras]
dmypy = ["psutil (>=4.0)"]
faster-cache = ["orjson"]
install-types = ["pip"]
mypyc = ["setuptools (>=50)"]
reports = ["lxml"]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
version = "1.1.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
python-versions = ">=3.8"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
{file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
{file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
]
[[package]]
@ -550,13 +562,13 @@ files = [
[[package]]
name = "packaging"
version = "24.1"
version = "25.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
{file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
]
[[package]]
@ -661,13 +673,13 @@ virtualenv = ">=20.10.0"
[[package]]
name = "prompt-toolkit"
version = "3.0.48"
version = "3.0.51"
description = "Library for building powerful interactive command lines in Python"
optional = true
python-versions = ">=3.7.0"
python-versions = ">=3.8"
files = [
{file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"},
{file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"},
{file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"},
{file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"},
]
[package.dependencies]
@ -700,13 +712,13 @@ tests = ["pytest"]
[[package]]
name = "pygments"
version = "2.18.0"
version = "2.19.1"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
files = [
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
{file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
{file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
]
[package.extras]
@ -816,13 +828,13 @@ files = [
[[package]]
name = "six"
version = "1.16.0"
version = "1.17.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
]
[[package]]
@ -846,13 +858,43 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
[[package]]
name = "tomli"
version = "2.0.1"
version = "2.2.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
]
[[package]]
@ -872,24 +914,24 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,
[[package]]
name = "typing-extensions"
version = "4.12.2"
version = "4.13.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
{file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"},
{file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"},
]
[[package]]
name = "virtualenv"
version = "20.26.6"
version = "20.31.2"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"},
{file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"},
{file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"},
{file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"},
]
[package.dependencies]
@ -914,13 +956,13 @@ files = [
[[package]]
name = "widgetsnbextension"
version = "4.0.13"
version = "4.0.14"
description = "Jupyter interactive widgets for Jupyter Notebook"
optional = true
python-versions = ">=3.7"
files = [
{file = "widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71"},
{file = "widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6"},
{file = "widgetsnbextension-4.0.14-py3-none-any.whl", hash = "sha256:4875a9eaf72fbf5079dc372a51a9f268fc38d46f767cbf85c43a36da5cb9b575"},
{file = "widgetsnbextension-4.0.14.tar.gz", hash = "sha256:a3629b04e3edb893212df862038c7232f62973373869db5084aed739b437b5af"},
]
[extras]
@ -929,4 +971,4 @@ jupyter = ["ipywidgets"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.8.0"
content-hash = "804a37e7036779a41ab3dfe0cab6324de5095f81684e7db724ba803b26159b60"
content-hash = "2e87c73a127b5e6456de92c6cab8e8be8f746f2c4d792ae0563ae74e19593e4c"

View file

@ -28,7 +28,6 @@ include = ["rich/py.typed"]
[tool.poetry.dependencies]
python = ">=3.8.0"
typing-extensions = { version = ">=4.0.0, <5.0", python = "<3.11" }
pygments = "^2.13.0"
ipywidgets = { version = ">=7.5.1,<9", optional = true }
markdown-it-py = ">=2.2.0"
@ -44,6 +43,7 @@ pytest-cov = "^3.0.0"
attrs = "^21.4.0"
pre-commit = "^2.17.0"
asv = "^0.5.1"
typing-extensions = ">=4.0.0, <5.0"
[build-system]
requires = ["poetry-core>=1.0.0"]

View file

@ -207,6 +207,8 @@ Supports much of the *markdown* __syntax__!
if __name__ == "__main__": # pragma: no cover
from rich.panel import Panel
console = Console(
file=io.StringIO(),
force_terminal=True,
@ -227,47 +229,17 @@ if __name__ == "__main__": # pragma: no cover
c = Console(record=True)
c.print(test_card)
print(f"rendered in {pre_cache_taken}ms (cold cache)")
print(f"rendered in {taken}ms (warm cache)")
from rich.panel import Panel
console = Console()
sponsor_message = Table.grid(padding=1)
sponsor_message.add_column(style="green", justify="right")
sponsor_message.add_column(no_wrap=True)
sponsor_message.add_row(
"Textualize",
"[u blue link=https://github.com/textualize]https://github.com/textualize",
)
sponsor_message.add_row(
"Twitter",
"[u blue link=https://twitter.com/willmcgugan]https://twitter.com/willmcgugan",
)
intro_message = Text.from_markup(
"""\
We hope you enjoy using Rich!
Rich is maintained with [red]:heart:[/] by [link=https://www.textualize.io]Textualize.io[/]
- Will McGugan"""
)
message = Table.grid(padding=2)
message.add_column()
message.add_column(no_wrap=True)
message.add_row(intro_message, sponsor_message)
console.print(f"[dim]rendered in [not dim]{pre_cache_taken}ms[/] (cold cache)")
console.print(f"[dim]rendered in [not dim]{taken}ms[/] (warm cache)")
console.print()
console.print(
Panel.fit(
message,
box=box.ROUNDED,
padding=(1, 2),
title="[b red]Thanks for trying out Rich!",
border_style="bright_blue",
),
justify="center",
"[b magenta]Hope you enjoy using Rich![/]\n\n"
"Please consider sponsoring me if you get value from my work.\n\n"
"Even the price of a ☕ can brighten my day!\n\n"
"https://github.com/sponsors/willmcgugan",
border_style="red",
title="Help ensure Rich is maintained",
)
)

View file

@ -214,7 +214,7 @@ class Inspect(JupyterMixin):
def _get_formatted_doc(self, object_: Any) -> Optional[str]:
"""
Extract the docstring of an object, process it and returns it.
The processing consists in cleaning up the doctring's indentation,
The processing consists in cleaning up the docstring's indentation,
taking only its 1st paragraph if `self.help` is not True,
and escape its control codes.

View file

@ -1,12 +1,6 @@
import sys
from fractions import Fraction
from math import ceil
from typing import cast, List, Optional, Sequence
if sys.version_info >= (3, 8):
from typing import Protocol
else:
from typing_extensions import Protocol # pragma: no cover
from typing import cast, List, Optional, Sequence, Protocol
class Edge(Protocol):

View file

@ -1,11 +1,5 @@
import sys
from itertools import chain
from typing import TYPE_CHECKING, Iterable, Optional
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal # pragma: no cover
from typing import TYPE_CHECKING, Iterable, Optional, Literal
from .constrain import Constrain
from .jupyter import JupyterMixin

View file

@ -1,10 +1,4 @@
import sys
from typing import TYPE_CHECKING, Iterable, List
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal # pragma: no cover
from typing import TYPE_CHECKING, Iterable, List, Literal
from ._loop import loop_last

View file

@ -22,27 +22,21 @@ from typing import (
Dict,
Iterable,
List,
Literal,
Mapping,
NamedTuple,
Optional,
Protocol,
TextIO,
Tuple,
Type,
Union,
cast,
runtime_checkable,
)
from rich._null_file import NULL_FILE
if sys.version_info >= (3, 8):
from typing import Literal, Protocol, runtime_checkable
else:
from typing_extensions import (
Literal,
Protocol,
runtime_checkable,
) # pragma: no cover
from . import errors, themes
from ._emoji_replace import _emoji_replace
from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT
@ -739,6 +733,14 @@ class Console:
if no_color is not None
else self._environ.get("NO_COLOR", "") != ""
)
if force_interactive is None:
tty_interactive = self._environ.get("TTY_INTERACTIVE", None)
if tty_interactive is not None:
if tty_interactive == "0":
force_interactive = False
elif tty_interactive == "1":
force_interactive = True
self.is_interactive = (
(self.is_terminal and not self.is_dumb_terminal)
if force_interactive is None
@ -751,7 +753,7 @@ class Console:
)
self._record_buffer: List[Segment] = []
self._render_hooks: List[RenderHook] = []
self._live: Optional["Live"] = None
self._live_stack: List[Live] = []
self._is_alt_screen = False
def __repr__(self) -> str:
@ -823,24 +825,26 @@ class Console:
self._buffer_index -= 1
self._check_buffer()
def set_live(self, live: "Live") -> None:
"""Set Live instance. Used by Live context manager.
def set_live(self, live: "Live") -> bool:
"""Set Live instance. Used by Live context manager (no need to call directly).
Args:
live (Live): Live instance using this Console.
Returns:
Boolean that indicates if the live is the topmost of the stack.
Raises:
errors.LiveError: If this Console has a Live context currently active.
"""
with self._lock:
if self._live is not None:
raise errors.LiveError("Only one live display may be active at once")
self._live = live
self._live_stack.append(live)
return len(self._live_stack) == 1
def clear_live(self) -> None:
"""Clear the Live instance."""
"""Clear the Live instance. Used by the Live context manager (no need to call directly)."""
with self._lock:
self._live = None
self._live_stack.pop()
def push_render_hook(self, hook: RenderHook) -> None:
"""Add a new render hook to the stack.

View file

@ -1,11 +1,5 @@
import sys
import time
from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union
if sys.version_info >= (3, 8):
from typing import Final
else:
from typing_extensions import Final # pragma: no cover
from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union, Final
from .segment import ControlCode, ControlType, Segment

View file

@ -26,6 +26,7 @@ def report() -> None: # pragma: no cover
"TERM_PROGRAM",
"TERM",
"TTY_COMPATIBLE",
"TTY_INTERACTIVE",
"VSCODE_VERBOSE_LOGGING",
)
env = {name: os.getenv(name) for name in env_names}

View file

@ -1,5 +1,5 @@
import sys
from typing import TYPE_CHECKING, Optional, Union
from typing import TYPE_CHECKING, Optional, Union, Literal
from .jupyter import JupyterMixin
from .segment import Segment
@ -7,11 +7,6 @@ from .style import Style
from ._emoji_codes import EMOJI
from ._emoji_replace import _emoji_replace
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal # pragma: no cover
if TYPE_CHECKING:
from .console import Console, ConsoleOptions, RenderResult

View file

@ -4,7 +4,7 @@ from types import TracebackType
from typing import IO, Any, Callable, List, Optional, TextIO, Type, cast
from . import get_console
from .console import Console, ConsoleRenderable, RenderableType, RenderHook
from .console import Console, ConsoleRenderable, Group, RenderableType, RenderHook
from .control import Control
from .file_proxy import FileProxy
from .jupyter import JupyterMixin
@ -87,6 +87,7 @@ class Live(JupyterMixin, RenderHook):
self._live_render = LiveRender(
self.get_renderable(), vertical_overflow=vertical_overflow
)
self._nested = False
@property
def is_started(self) -> bool:
@ -110,8 +111,12 @@ class Live(JupyterMixin, RenderHook):
with self._lock:
if self._started:
return
self.console.set_live(self)
self._started = True
if not self.console.set_live(self):
self._nested = True
return
if self._screen:
self._alt_screen = self.console.set_alt_screen(True)
self.console.show_cursor(False)
@ -136,8 +141,12 @@ class Live(JupyterMixin, RenderHook):
with self._lock:
if not self._started:
return
self.console.clear_live()
self._started = False
self.console.clear_live()
if self._nested:
if not self.transient:
self.console.print(self.renderable)
return
if self.auto_refresh and self._refresh_thread is not None:
self._refresh_thread.stop()
@ -156,7 +165,6 @@ class Live(JupyterMixin, RenderHook):
self.console.show_cursor(True)
if self._alt_screen:
self.console.set_alt_screen(False)
if self.transient and not self._alt_screen:
self.console.control(self._live_render.restore_cursor())
if self.ipy_widget is not None and self.transient:
@ -200,7 +208,13 @@ class Live(JupyterMixin, RenderHook):
Returns:
RenderableType: Displayed renderable.
"""
renderable = self.get_renderable()
live_stack = self.console._live_stack
renderable: RenderableType
if live_stack and self is live_stack[0]:
# The first Live instance will render everything in the Live stack
renderable = Group(*[live.get_renderable() for live in live_stack])
else:
renderable = self.get_renderable()
return Screen(renderable) if self._alt_screen else renderable
def update(self, renderable: RenderableType, *, refresh: bool = False) -> None:
@ -221,6 +235,11 @@ class Live(JupyterMixin, RenderHook):
"""Update the display of the Live Render."""
with self._lock:
self._live_render.set_renderable(self.renderable)
if self._nested:
if self.console._live_stack:
self.console._live_stack[0].refresh()
return
if self.console.is_jupyter: # pragma: no cover
try:
from IPython.display import display

View file

@ -1,10 +1,4 @@
import sys
from typing import Optional, Tuple
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal # pragma: no cover
from typing import Optional, Tuple, Literal
from ._loop import loop_last

View file

@ -76,7 +76,7 @@ class RichHandler(Handler):
markup: bool = False,
rich_tracebacks: bool = False,
tracebacks_width: Optional[int] = None,
tracebacks_code_width: int = 88,
tracebacks_code_width: Optional[int] = 88,
tracebacks_extra_lines: int = 3,
tracebacks_theme: Optional[str] = None,
tracebacks_word_wrap: bool = True,

View file

@ -1,16 +1,11 @@
from __future__ import annotations
import sys
from typing import ClassVar, Iterable
from typing import ClassVar, Iterable, get_args
from markdown_it import MarkdownIt
from markdown_it.token import Token
if sys.version_info >= (3, 8):
from typing import get_args
else:
from typing_extensions import get_args # pragma: no cover
from rich.table import Table
from . import box

View file

@ -1,5 +1,6 @@
from __future__ import annotations
import io
import sys
import typing
import warnings
from abc import ABC, abstractmethod
@ -14,6 +15,7 @@ from os import PathLike, stat
from threading import Event, RLock, Thread
from types import TracebackType
from typing import (
TYPE_CHECKING,
Any,
BinaryIO,
Callable,
@ -23,10 +25,10 @@ from typing import (
Generic,
Iterable,
List,
Literal,
NamedTuple,
NewType,
Optional,
Sequence,
TextIO,
Tuple,
Type,
@ -34,14 +36,8 @@ from typing import (
Union,
)
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal # pragma: no cover
if sys.version_info >= (3, 11):
from typing import Self
else:
if TYPE_CHECKING:
# Can be replaced with `from typing import Self` in Python 3.11+
from typing_extensions import Self # pragma: no cover
from . import filesize, get_console
@ -106,7 +102,7 @@ class _TrackThread(Thread):
def track(
sequence: Union[Sequence[ProgressType], Iterable[ProgressType]],
sequence: Iterable[ProgressType],
description: str = "Working...",
total: Optional[float] = None,
completed: int = 0,
@ -125,8 +121,10 @@ def track(
) -> Iterable[ProgressType]:
"""Track progress by iterating over a sequence.
You can also track progress of an iterable, which might require that you additionally specify ``total``.
Args:
sequence (Iterable[ProgressType]): A sequence (must support "len") you wish to iterate over.
sequence (Iterable[ProgressType]): Values you wish to iterate over and track progress.
description (str, optional): Description of task show next to progress bar. Defaults to "Working".
total: (float, optional): Total number of steps. Default is len(sequence).
completed (int, optional): Number of steps completed so far. Defaults to 0.
@ -1192,7 +1190,7 @@ class Progress(JupyterMixin):
def track(
self,
sequence: Union[Iterable[ProgressType], Sequence[ProgressType]],
sequence: Iterable[ProgressType],
total: Optional[float] = None,
completed: int = 0,
task_id: Optional[TaskID] = None,
@ -1201,8 +1199,10 @@ class Progress(JupyterMixin):
) -> Iterable[ProgressType]:
"""Track progress by iterating over a sequence.
You can also track progress of an iterable, which might require that you additionally specify ``total``.
Args:
sequence (Sequence[ProgressType]): A sequence of values you want to iterate over and track progress.
sequence (Iterable[ProgressType]): Values you want to iterate over and track progress.
total: (float, optional): Total number of steps. Default is len(sequence).
completed (int, optional): Number of steps completed so far. Defaults to 0.
task_id: (TaskID): Task to track. Default is new task.

View file

@ -1,4 +1,4 @@
from typing import cast, List, Optional, TYPE_CHECKING, Union
from typing import TYPE_CHECKING, List, Optional, Union, cast
from ._spinners import SPINNERS
from .measure import Measurement
@ -6,7 +6,7 @@ from .table import Table
from .text import Text
if TYPE_CHECKING:
from .console import Console, ConsoleOptions, RenderResult, RenderableType
from .console import Console, ConsoleOptions, RenderableType, RenderResult
from .style import StyleType
@ -117,22 +117,16 @@ class Spinner:
if __name__ == "__main__": # pragma: no cover
from time import sleep
from .columns import Columns
from .panel import Panel
from .console import Group
from .live import Live
all_spinners = Columns(
[
all_spinners = Group(
*[
Spinner(spinner_name, text=Text(repr(spinner_name), style="green"))
for spinner_name in sorted(SPINNERS.keys())
],
column_first=True,
expand=True,
]
)
with Live(
Panel(all_spinners, title="Spinners", border_style="blue"),
refresh_per_second=20,
) as live:
with Live(all_spinners, refresh_per_second=20) as live:
while True:
sleep(0.1)

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path
import re
import sys
@ -224,6 +226,17 @@ class _SyntaxHighlightRange(NamedTuple):
style_before: bool = False
class PaddingProperty:
"""Descriptor to get and set padding."""
def __get__(self, obj: Syntax, objtype: Type[Syntax]) -> Tuple[int, int, int, int]:
"""Space around the Syntax."""
return obj._padding
def __set__(self, obj: Syntax, padding: PaddingDimensions) -> None:
obj._padding = Padding.unpack(padding)
class Syntax(JupyterMixin):
"""Construct a Syntax object to render syntax highlighted code.
@ -293,11 +306,13 @@ class Syntax(JupyterMixin):
Style(bgcolor=background_color) if background_color else Style()
)
self.indent_guides = indent_guides
self.padding = padding
self._padding = Padding.unpack(padding)
self._theme = self.get_theme(theme)
self._stylized_ranges: List[_SyntaxHighlightRange] = []
padding = PaddingProperty()
@classmethod
def from_path(
cls,
@ -371,8 +386,8 @@ class Syntax(JupyterMixin):
is supplied, the lexer will be chosen based on the file extension..
Args:
path (AnyStr): The path to the file containing the code you wish to know the lexer for.
code (str, optional): Optional string of code that will be used as a fallback if no lexer
path (AnyStr): The path to the file containing the code you wish to know the lexer for.
code (str, optional): Optional string of code that will be used as a fallback if no lexer
is found for the supplied path.
Returns:
@ -607,7 +622,7 @@ class Syntax(JupyterMixin):
def __rich_measure__(
self, console: "Console", options: "ConsoleOptions"
) -> "Measurement":
_, right, _, left = Padding.unpack(self.padding)
_, right, _, left = self.padding
padding = left + right
if self.code_width is not None:
width = self.code_width + self._numbers_column_width + padding + 1
@ -626,7 +641,7 @@ class Syntax(JupyterMixin):
self, console: Console, options: ConsoleOptions
) -> RenderResult:
segments = Segments(self._get_syntax(console, options))
if self.padding:
if any(self.padding):
yield Padding(segments, style=self._get_base_style(), pad=self.padding)
else:
yield segments
@ -640,15 +655,19 @@ class Syntax(JupyterMixin):
Get the Segments for the Syntax object, excluding any vertical/horizontal padding
"""
transparent_background = self._get_base_style().transparent_background
_pad_top, pad_right, _pad_bottom, pad_left = self.padding
horizontal_padding = pad_left + pad_right
code_width = (
(
(options.max_width - self._numbers_column_width - 1)
if self.line_numbers
else options.max_width
)
- horizontal_padding
if self.code_width is None
else self.code_width
)
code_width = max(0, code_width)
ends_on_nl, processed_code = self._process_code(self.code)
text = self.highlight(processed_code, self.line_range)

View file

@ -14,6 +14,7 @@ from typing import (
List,
Optional,
Sequence,
Set,
Tuple,
Type,
Union,
@ -418,6 +419,7 @@ class Traceback:
locals_max_string: int = LOCALS_MAX_STRING,
locals_hide_dunder: bool = True,
locals_hide_sunder: bool = False,
_visited_exceptions: Optional[Set[BaseException]] = None,
) -> Trace:
"""Extract traceback information.
@ -443,6 +445,10 @@ class Traceback:
notes: List[str] = getattr(exc_value, "__notes__", None) or []
grouped_exceptions: Set[BaseException] = (
set() if _visited_exceptions is None else _visited_exceptions
)
def safe_str(_object: Any) -> str:
"""Don't allow exceptions from __str__ to propagate."""
try:
@ -462,6 +468,9 @@ class Traceback:
if isinstance(exc_value, (BaseExceptionGroup, ExceptionGroup)):
stack.is_group = True
for exception in exc_value.exceptions:
if exception in grouped_exceptions:
continue
grouped_exceptions.add(exception)
stack.exceptions.append(
Traceback.extract(
type(exception),
@ -471,6 +480,7 @@ class Traceback:
locals_max_length=locals_max_length,
locals_hide_dunder=locals_hide_dunder,
locals_hide_sunder=locals_hide_sunder,
_visited_exceptions=grouped_exceptions,
)
)
@ -561,23 +571,26 @@ class Traceback:
if frame_summary.f_locals.get("_rich_traceback_guard", False):
del stack.frames[:]
cause = getattr(exc_value, "__cause__", None)
if cause:
exc_type = cause.__class__
exc_value = cause
# __traceback__ can be None, e.g. for exceptions raised by the
# 'multiprocessing' module
traceback = cause.__traceback__
is_cause = True
continue
if not grouped_exceptions:
cause = getattr(exc_value, "__cause__", None)
if cause is not None and cause is not exc_value:
exc_type = cause.__class__
exc_value = cause
# __traceback__ can be None, e.g. for exceptions raised by the
# 'multiprocessing' module
traceback = cause.__traceback__
is_cause = True
continue
cause = exc_value.__context__
if cause and not getattr(exc_value, "__suppress_context__", False):
exc_type = cause.__class__
exc_value = cause
traceback = cause.__traceback__
is_cause = False
continue
cause = exc_value.__context__
if cause is not None and not getattr(
exc_value, "__suppress_context__", False
):
exc_type = cause.__class__
exc_value = cause
traceback = cause.__traceback__
is_cause = False
continue
# No cover, code is reached but coverage doesn't recognize it.
break # pragma: no cover

View file

@ -713,14 +713,6 @@ def test_quiet() -> None:
assert console.file.getvalue() == ""
def test_no_nested_live() -> None:
console = Console()
with pytest.raises(errors.LiveError):
with console.status("foo"):
with console.status("bar"):
pass
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
def test_screen() -> None:
console = Console(
@ -1046,6 +1038,31 @@ def test_capture_and_record() -> None:
assert recorded_content == "Print 0\n"
def test_tty_interactive() -> None:
"""Check TTY_INTERACTIVE environment var."""
# Bytes file, not interactive
console = Console(file=io.BytesIO())
assert not console.is_interactive
# Bytes file, force interactive
console = Console(file=io.BytesIO(), _environ={"TTY_INTERACTIVE": "1"})
assert console.is_interactive
# Force tty compatible, should be interactive
console = Console(file=io.BytesIO(), _environ={"TTY_COMPATIBLE": "1"})
assert console.is_interactive
# Force tty compatible, force not interactive
console = Console(
file=io.BytesIO(), _environ={"TTY_COMPATIBLE": "1", "TTY_INTERACTIVE": "0"}
)
# Bytes file, Unknown value of TTY_INTERACTIVE should still auto-detect
console = Console(file=io.BytesIO(), _environ={"TTY_INTERACTIVE": "foo"})
assert not console.is_interactive
def test_tty_compatible() -> None:
"""Check TTY_COMPATIBLE environment var."""
@ -1087,7 +1104,7 @@ def test_tty_compatible() -> None:
console = Console(file=FakeTTY())
# Should report True
assert console.is_terminal
# SHould have auto-detected
# Should have auto-detected
assert console.file.called_isatty
# File is a fake TTY

View file

@ -26,7 +26,7 @@ def test_live_state() -> None:
assert live._started
live.start()
assert live.renderable == ""
assert live.get_renderable() == ""
assert live._started
live.stop()

View file

@ -160,9 +160,3 @@ def test_markup_and_highlight():
render_plain = handler.console.file.getvalue()
assert "FORMATTER" in render_plain
assert log_message in render_plain
if __name__ == "__main__":
render = make_log()
print(render)
print(repr(render))

View file

@ -110,7 +110,7 @@ def test_inline_code():
inline_code_theme="emacs",
)
result = render(markdown)
expected = "inline \x1b[1;38;2;170;34;255;48;2;248;248;248mimport\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;0;255;48;2;248;248;248mthis\x1b[0m code \n"
expected = "inline \x1b[1;38;2;170;34;255;48;2;248;248;248mimport\x1b[0m\x1b[38;2;187;187;187;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;0;255;48;2;248;248;248mthis\x1b[0m code \n"
print(result)
print(repr(result))
assert result == expected

View file

@ -2,6 +2,7 @@ import io
import os
import sys
import tempfile
from importlib.metadata import Distribution
import pytest
from pygments.lexers import PythonLexer
@ -20,11 +21,6 @@ from rich.syntax import (
from .render import render
if sys.version_info >= (3, 8):
from importlib.metadata import Distribution
else:
from importlib_metadata import Distribution
PYGMENTS_VERSION = Distribution.from_name("pygments").version
OLD_PYGMENTS = PYGMENTS_VERSION == "2.13.0"
@ -44,7 +40,7 @@ def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]:
yield first, True, previous_value'''
def test_blank_lines():
def test_blank_lines() -> None:
code = "\n\nimport this\n\n"
syntax = Syntax(
code, lexer="python", theme="ascii_light", code_width=30, line_numbers=True
@ -53,11 +49,11 @@ def test_blank_lines():
print(repr(result))
assert (
result
== "\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m1 \x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m2 \x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m3 \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mimport\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;0;255;48;2;248;248;248mthis\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m4 \x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m5 \x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n"
== "\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m1 \x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m2 \x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m3 \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mimport\x1b[0m\x1b[38;2;187;187;187;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;0;255;48;2;248;248;248mthis\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m4 \x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[1;38;2;24;24;24;48;2;248;248;248m \x1b[0m\x1b[38;2;173;173;173;48;2;248;248;248m5 \x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n"
)
def test_python_render():
def test_python_render() -> None:
syntax = Panel.fit(
Syntax(
CODE,
@ -76,7 +72,7 @@ def test_python_render():
assert rendered_syntax == expected
def test_python_render_simple():
def test_python_render_simple() -> None:
syntax = Syntax(
CODE,
lexer="python",
@ -91,7 +87,7 @@ def test_python_render_simple():
assert rendered_syntax == expected
def test_python_render_simple_passing_lexer_instance():
def test_python_render_simple_passing_lexer_instance() -> None:
syntax = Syntax(
CODE,
lexer=PythonLexer(),
@ -107,7 +103,7 @@ def test_python_render_simple_passing_lexer_instance():
@pytest.mark.skipif(OLD_PYGMENTS, reason="Pygments changed their tokenizer")
def test_python_render_simple_indent_guides():
def test_python_render_simple_indent_guides() -> None:
syntax = Syntax(
CODE,
lexer="python",
@ -119,12 +115,12 @@ def test_python_render_simple_indent_guides():
)
rendered_syntax = render(syntax)
print(repr(rendered_syntax))
expected = '\x1b[34mdef\x1b[0m \x1b[32mloop_first_last\x1b[0m(values: Iterable[T]) -> Iterable[Tuple[\x1b[36mb\x1b[0m\n\x1b[2;37m│ \x1b[0m\x1b[33m"""Iterate and generate a tuple with a flag for first an\x1b[0m\n\x1b[2m│ \x1b[0miter_values = \x1b[36miter\x1b[0m(values)\n\x1b[2m│ \x1b[0m\x1b[34mtry\x1b[0m:\n\x1b[2m│ │ \x1b[0mprevious_value = \x1b[36mnext\x1b[0m(iter_values)\n\x1b[2m│ \x1b[0m\x1b[34mexcept\x1b[0m \x1b[36mStopIteration\x1b[0m:\n\x1b[2m│ │ \x1b[0m\x1b[34mreturn\x1b[0m\n\x1b[2m│ \x1b[0mfirst = \x1b[34mTrue\x1b[0m\n\x1b[2m│ \x1b[0m\x1b[34mfor\x1b[0m value \x1b[35min\x1b[0m iter_values:\n\x1b[2m│ │ \x1b[0m\x1b[34myield\x1b[0m first, \x1b[34mFalse\x1b[0m, previous_value\n\x1b[2m│ │ \x1b[0mfirst = \x1b[34mFalse\x1b[0m\n\x1b[2m│ │ \x1b[0mprevious_value = value\n\x1b[2m│ \x1b[0m\x1b[34myield\x1b[0m first, \x1b[34mTrue\x1b[0m, previous_value\n'
expected = '\x1b[34mdef\x1b[0m\x1b[37m \x1b[0m\x1b[32mloop_first_last\x1b[0m(values: Iterable[T]) -> Iterable[Tuple[\x1b[36mb\x1b[0m\n\x1b[2;37m│ \x1b[0m\x1b[33m"""Iterate and generate a tuple with a flag for first an\x1b[0m\n\x1b[2m│ \x1b[0miter_values = \x1b[36miter\x1b[0m(values)\n\x1b[2m│ \x1b[0m\x1b[34mtry\x1b[0m:\n\x1b[2m│ │ \x1b[0mprevious_value = \x1b[36mnext\x1b[0m(iter_values)\n\x1b[2m│ \x1b[0m\x1b[34mexcept\x1b[0m \x1b[36mStopIteration\x1b[0m:\n\x1b[2m│ │ \x1b[0m\x1b[34mreturn\x1b[0m\n\x1b[2m│ \x1b[0mfirst = \x1b[34mTrue\x1b[0m\n\x1b[2m│ \x1b[0m\x1b[34mfor\x1b[0m value \x1b[35min\x1b[0m iter_values:\n\x1b[2m│ │ \x1b[0m\x1b[34myield\x1b[0m first, \x1b[34mFalse\x1b[0m, previous_value\n\x1b[2m│ │ \x1b[0mfirst = \x1b[34mFalse\x1b[0m\n\x1b[2m│ │ \x1b[0mprevious_value = value\n\x1b[2m│ \x1b[0m\x1b[34myield\x1b[0m first, \x1b[34mTrue\x1b[0m, previous_value\n'
assert rendered_syntax == expected
@pytest.mark.skipif(OLD_PYGMENTS, reason="Pygments changed their tokenizer")
def test_python_render_line_range_indent_guides():
def test_python_render_line_range_indent_guides() -> None:
syntax = Syntax(
CODE,
lexer="python",
@ -141,7 +137,7 @@ def test_python_render_line_range_indent_guides():
assert rendered_syntax == expected
def test_python_render_indent_guides():
def test_python_render_indent_guides() -> None:
syntax = Panel.fit(
Syntax(
CODE,
@ -161,19 +157,19 @@ def test_python_render_indent_guides():
assert rendered_syntax == expected
def test_pygments_syntax_theme_non_str():
def test_pygments_syntax_theme_non_str() -> None:
from pygments.style import Style as PygmentsStyle
style = PygmentsSyntaxTheme(PygmentsStyle())
assert style.get_background_style().bgcolor == Color.parse("#ffffff")
def test_pygments_syntax_theme():
def test_pygments_syntax_theme() -> None:
style = PygmentsSyntaxTheme("default")
assert style.get_style_for_token("abc") == Style.parse("none")
def test_get_line_color_none():
def test_get_line_color_none() -> None:
style = PygmentsSyntaxTheme("default")
style._background_style = Style(bgcolor=None)
syntax = Syntax(
@ -189,7 +185,7 @@ def test_get_line_color_none():
assert syntax._get_line_numbers_color() == Color.default()
def test_highlight_background_color():
def test_highlight_background_color() -> None:
syntax = Syntax(
CODE,
lexer="python",
@ -203,7 +199,7 @@ def test_highlight_background_color():
assert syntax.highlight(CODE).style == Style.parse("on red")
def test_get_number_styles():
def test_get_number_styles() -> None:
syntax = Syntax(CODE, "python", theme="monokai", line_numbers=True)
console = Console(color_system="windows")
assert syntax._get_number_styles(console=console) == (
@ -213,7 +209,7 @@ def test_get_number_styles():
)
def test_get_style_for_token():
def test_get_style_for_token() -> None:
# from pygments.style import Style as PygmentsStyle
# pygments_style = PygmentsStyle()
from pygments.style import Token
@ -234,7 +230,7 @@ def test_get_style_for_token():
assert syntax._get_line_numbers_color() == Color.default()
def test_option_no_wrap():
def test_option_no_wrap() -> None:
syntax = Syntax(
CODE,
lexer="python",
@ -251,7 +247,7 @@ def test_option_no_wrap():
assert rendered_syntax == expected
def test_syntax_highlight_ranges():
def test_syntax_highlight_ranges() -> None:
syntax = Syntax(
CODE,
lexer="python",
@ -306,7 +302,7 @@ def test_syntax_highlight_ranges():
assert rendered_syntax == expected
def test_ansi_theme():
def test_ansi_theme() -> None:
style = Style(color="red")
theme = ANSISyntaxTheme({("foo", "bar"): style})
assert theme.get_style_for_token(("foo", "bar", "baz")) == style
@ -319,7 +315,7 @@ skip_windows_permission_error = pytest.mark.skipif(
@skip_windows_permission_error
def test_from_path():
def test_from_path() -> None:
fh, path = tempfile.mkstemp("example.py")
try:
os.write(fh, b"import this\n")
@ -332,7 +328,7 @@ def test_from_path():
@skip_windows_permission_error
def test_from_path_unknown_lexer():
def test_from_path_unknown_lexer() -> None:
fh, path = tempfile.mkstemp("example.nosuchtype")
try:
os.write(fh, b"import this\n")
@ -344,7 +340,7 @@ def test_from_path_unknown_lexer():
@skip_windows_permission_error
def test_from_path_lexer_override():
def test_from_path_lexer_override() -> None:
fh, path = tempfile.mkstemp("example.nosuchtype")
try:
os.write(fh, b"import this\n")
@ -356,7 +352,7 @@ def test_from_path_lexer_override():
@skip_windows_permission_error
def test_from_path_lexer_override_invalid_lexer():
def test_from_path_lexer_override_invalid_lexer() -> None:
fh, path = tempfile.mkstemp("example.nosuchtype")
try:
os.write(fh, b"import this\n")
@ -367,7 +363,7 @@ def test_from_path_lexer_override_invalid_lexer():
os.remove(path)
def test_syntax_guess_lexer():
def test_syntax_guess_lexer() -> None:
assert Syntax.guess_lexer("banana.py") == "python"
assert Syntax.guess_lexer("banana.py", "import this") == "python"
assert Syntax.guess_lexer("banana.html", "<a href='#'>hello</a>") == "html"
@ -375,7 +371,7 @@ def test_syntax_guess_lexer():
assert Syntax.guess_lexer("banana.html", "{{something|filter:3}}") == "html+django"
def test_syntax_padding():
def test_syntax_padding() -> None:
syntax = Syntax("x = 1", lexer="python", padding=(1, 3))
console = Console(
width=20,
@ -391,7 +387,7 @@ def test_syntax_padding():
)
def test_syntax_measure():
def test_syntax_measure() -> None:
console = Console()
code = Syntax("Hello, World", "python")
assert code.__rich_measure__(console, console.options) == Measurement(0, 12)
@ -406,7 +402,7 @@ def test_syntax_measure():
assert code.__rich_measure__(console, console.options) == Measurement(3, 24)
def test_background_color_override_includes_padding():
def test_background_color_override_includes_padding() -> None:
"""Regression test for https://github.com/Textualize/rich/issues/3295"""
syntax = Syntax(
@ -423,6 +419,22 @@ def test_background_color_override_includes_padding():
)
def test_padding_plus_wrap() -> None:
"""Regression test for https://github.com/Textualize/rich/issues/3727"""
console = Console(width=24, file=io.StringIO(), legacy_windows=False)
syntax = Syntax(
"'Hello, World. This should wrap.'",
lexer="python",
padding=(0, 3),
word_wrap=True,
)
console.print(syntax)
output = console.file.getvalue()
print(repr(output))
expected = " 'Hello, World. \n This should wrap.' \n"
assert output == expected
if __name__ == "__main__":
syntax = Panel.fit(
Syntax(

View file

@ -373,3 +373,27 @@ def test_notes() -> None:
traceback = Traceback()
assert traceback.trace.stacks[0].notes == ["Hello", "World"]
def test_recursive_exception() -> None:
"""Regression test for https://github.com/Textualize/rich/issues/3708
Test this doesn't create an infinite loop.
"""
console = Console()
def foo() -> None:
try:
raise RuntimeError("Hello")
except Exception as e:
raise e from e
def bar() -> None:
try:
foo()
except Exception as e:
assert e is e.__cause__
console.print_exception(show_locals=True)
bar()