summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPradyun Gedam <pgedam@bloomberg.net>2022-04-22 15:46:11 +0100
committerPradyun Gedam <pgedam@bloomberg.net>2022-04-22 15:50:29 +0100
commit377d642384befe47e96e87550d9a12cbe8f13413 (patch)
treed474fe20cb825df21cb0988dc973facc73ab02d0
parentdfcac4add2801ea078f976b8fba3b24fa246c1ce (diff)
downloadpip-377d642384befe47e96e87550d9a12cbe8f13413.tar.gz
Upgrade rich to 12.2.0
-rw-r--r--news/rich.vendor.rst1
-rw-r--r--src/pip/_vendor/rich/__init__.py6
-rw-r--r--src/pip/_vendor/rich/__main__.py20
-rw-r--r--src/pip/_vendor/rich/_inspect.py22
-rw-r--r--src/pip/_vendor/rich/_lru_cache.py24
-rw-r--r--src/pip/_vendor/rich/_spinners.py474
-rw-r--r--src/pip/_vendor/rich/_win32_console.py630
-rw-r--r--src/pip/_vendor/rich/_windows.py34
-rw-r--r--src/pip/_vendor/rich/_windows_renderer.py53
-rw-r--r--src/pip/_vendor/rich/align.py1
-rw-r--r--src/pip/_vendor/rich/ansi.py51
-rw-r--r--src/pip/_vendor/rich/cells.py31
-rw-r--r--src/pip/_vendor/rich/color.py40
-rw-r--r--src/pip/_vendor/rich/console.py422
-rw-r--r--src/pip/_vendor/rich/control.py2
-rw-r--r--src/pip/_vendor/rich/default_styles.py2
-rw-r--r--src/pip/_vendor/rich/diagnose.py35
-rw-r--r--src/pip/_vendor/rich/filesize.py6
-rw-r--r--src/pip/_vendor/rich/highlighter.py50
-rw-r--r--src/pip/_vendor/rich/jupyter.py14
-rw-r--r--src/pip/_vendor/rich/layout.py3
-rw-r--r--src/pip/_vendor/rich/logging.py18
-rw-r--r--src/pip/_vendor/rich/markup.py15
-rw-r--r--src/pip/_vendor/rich/measure.py6
-rw-r--r--src/pip/_vendor/rich/pager.py2
-rw-r--r--src/pip/_vendor/rich/panel.py15
-rw-r--r--src/pip/_vendor/rich/pretty.py140
-rw-r--r--src/pip/_vendor/rich/progress.py604
-rw-r--r--src/pip/_vendor/rich/prompt.py4
-rw-r--r--src/pip/_vendor/rich/protocol.py2
-rw-r--r--src/pip/_vendor/rich/repr.py37
-rw-r--r--src/pip/_vendor/rich/segment.py87
-rw-r--r--src/pip/_vendor/rich/syntax.py96
-rw-r--r--src/pip/_vendor/rich/table.py30
-rw-r--r--src/pip/_vendor/rich/tabulate.py51
-rw-r--r--src/pip/_vendor/rich/terminal_theme.py98
-rw-r--r--src/pip/_vendor/rich/text.py3
-rw-r--r--src/pip/_vendor/rich/traceback.py25
-rw-r--r--src/pip/_vendor/rich/tree.py10
-rw-r--r--src/pip/_vendor/urllib3/_version.py2
-rw-r--r--src/pip/_vendor/urllib3/connection.py6
-rw-r--r--src/pip/_vendor/urllib3/poolmanager.py1
-rw-r--r--src/pip/_vendor/urllib3/util/ssl_match_hostname.py10
-rw-r--r--src/pip/_vendor/vendor.txt4
-rw-r--r--tools/vendoring/patches/urllib3-disable-brotli.patch39
-rw-r--r--tools/vendoring/patches/urllib3.patch34
46 files changed, 2400 insertions, 860 deletions
diff --git a/news/rich.vendor.rst b/news/rich.vendor.rst
new file mode 100644
index 000000000..cc778ecef
--- /dev/null
+++ b/news/rich.vendor.rst
@@ -0,0 +1 @@
+Upgrade rich to 12.2.0
diff --git a/src/pip/_vendor/rich/__init__.py b/src/pip/_vendor/rich/__init__.py
index 50f381576..657811b5e 100644
--- a/src/pip/_vendor/rich/__init__.py
+++ b/src/pip/_vendor/rich/__init__.py
@@ -1,9 +1,9 @@
"""Rich text and beautiful formatting in the terminal."""
import os
-from typing import Callable, IO, TYPE_CHECKING, Any, Optional
+from typing import Callable, IO, TYPE_CHECKING, Any, Optional, Union
-from ._extension import load_ipython_extension
+from ._extension import load_ipython_extension # noqa: F401
__all__ = ["get_console", "reconfigure", "print", "inspect"]
@@ -73,7 +73,7 @@ def print_json(
json: Optional[str] = None,
*,
data: Any = None,
- indent: int = 2,
+ indent: Union[None, int, str] = 2,
highlight: bool = True,
skip_keys: bool = False,
ensure_ascii: bool = True,
diff --git a/src/pip/_vendor/rich/__main__.py b/src/pip/_vendor/rich/__main__.py
index 8692d37e0..54e6d5e8a 100644
--- a/src/pip/_vendor/rich/__main__.py
+++ b/src/pip/_vendor/rich/__main__.py
@@ -51,7 +51,6 @@ def make_test_card() -> Table:
pad_edge=False,
)
color_table.add_row(
- # "[bold yellow]256[/] colors or [bold green]16.7 million[/] colors [blue](if supported by your terminal)[/].",
(
"✓ [bold green]4-bit color[/]\n"
"✓ [bold blue]8-bit color[/]\n"
@@ -226,10 +225,12 @@ if __name__ == "__main__": # pragma: no cover
console.print(test_card)
taken = round((process_time() - start) * 1000.0, 1)
- text = console.file.getvalue()
- # https://bugs.python.org/issue37871
- for line in text.splitlines(True):
- print(line, end="")
+ c = Console(record=True)
+ c.print(test_card)
+ # c.save_svg(
+ # path="/Users/darrenburns/Library/Application Support/JetBrains/PyCharm2021.3/scratches/svg_export.svg",
+ # title="Rich can export to SVG",
+ # )
print(f"rendered in {pre_cache_taken}ms (cold cache)")
print(f"rendered in {taken}ms (warm cache)")
@@ -243,6 +244,10 @@ if __name__ == "__main__": # pragma: no cover
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(
"Buy devs a :coffee:",
"[u blue link=https://ko-fi.com/textualize]https://ko-fi.com/textualize",
)
@@ -250,15 +255,12 @@ if __name__ == "__main__": # pragma: no cover
"Twitter",
"[u blue link=https://twitter.com/willmcgugan]https://twitter.com/willmcgugan",
)
- sponsor_message.add_row(
- "Blog", "[u blue link=https://www.willmcgugan.com]https://www.willmcgugan.com"
- )
intro_message = Text.from_markup(
"""\
We hope you enjoy using Rich!
-Rich is maintained with :heart: by [link=https://www.textualize.io]Textualize.io[/]
+Rich is maintained with [red]:heart:[/] by [link=https://www.textualize.io]Textualize.io[/]
- Will McGugan"""
)
diff --git a/src/pip/_vendor/rich/_inspect.py b/src/pip/_vendor/rich/_inspect.py
index 262695b1c..01713e576 100644
--- a/src/pip/_vendor/rich/_inspect.py
+++ b/src/pip/_vendor/rich/_inspect.py
@@ -1,9 +1,10 @@
from __future__ import absolute_import
+import inspect
from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature
from typing import Any, Iterable, Optional, Tuple
-from .console import RenderableType, Group
+from .console import Group, RenderableType
from .highlighter import ReprHighlighter
from .jupyter import JupyterMixin
from .panel import Panel
@@ -97,7 +98,8 @@ class Inspect(JupyterMixin):
source_filename: Optional[str] = None
try:
source_filename = getfile(obj)
- except TypeError:
+ except (OSError, TypeError):
+ # OSError is raised if obj has no source file, e.g. when defined in REPL.
pass
callable_name = Text(name, style="inspect.callable")
@@ -106,8 +108,17 @@ class Inspect(JupyterMixin):
signature_text = self.highlighter(_signature)
qualname = name or getattr(obj, "__qualname__", name)
+
+ # If obj is a module, there may be classes (which are callable) to display
+ if inspect.isclass(obj):
+ prefix = "class"
+ else:
+ prefix = "def"
+
qual_signature = Text.assemble(
- ("def ", "inspect.def"), (qualname, "inspect.callable"), signature_text
+ (f"{prefix} ", f"inspect.{prefix}"),
+ (qualname, "inspect.callable"),
+ signature_text,
)
return qual_signature
@@ -204,7 +215,8 @@ class Inspect(JupyterMixin):
add_row(key_text, Pretty(value, highlighter=highlighter))
if items_table.row_count:
yield items_table
- else:
+ elif not_shown_count:
yield Text.from_markup(
- f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options."
+ f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] "
+ f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options."
)
diff --git a/src/pip/_vendor/rich/_lru_cache.py b/src/pip/_vendor/rich/_lru_cache.py
index b7bf2ce1a..10c818743 100644
--- a/src/pip/_vendor/rich/_lru_cache.py
+++ b/src/pip/_vendor/rich/_lru_cache.py
@@ -1,12 +1,16 @@
-from collections import OrderedDict
-from typing import Dict, Generic, TypeVar
-
+from typing import Dict, Generic, TypeVar, TYPE_CHECKING
+import sys
CacheKey = TypeVar("CacheKey")
CacheValue = TypeVar("CacheValue")
+if sys.version_info < (3, 9):
+ from pip._vendor.typing_extensions import OrderedDict
+else:
+ from collections import OrderedDict
+
-class LRUCache(Generic[CacheKey, CacheValue], OrderedDict): # type: ignore # https://github.com/python/mypy/issues/6904
+class LRUCache(OrderedDict[CacheKey, CacheValue]):
"""
A dictionary-like container that stores a given maximum items.
@@ -17,18 +21,18 @@ class LRUCache(Generic[CacheKey, CacheValue], OrderedDict): # type: ignore # ht
def __init__(self, cache_size: int) -> None:
self.cache_size = cache_size
- super(LRUCache, self).__init__()
+ super().__init__()
def __setitem__(self, key: CacheKey, value: CacheValue) -> None:
"""Store a new views, potentially discarding an old value."""
if key not in self:
if len(self) >= self.cache_size:
self.popitem(last=False)
- OrderedDict.__setitem__(self, key, value)
+ super().__setitem__(key, value)
- def __getitem__(self: Dict[CacheKey, CacheValue], key: CacheKey) -> CacheValue:
+ def __getitem__(self, key: CacheKey) -> CacheValue:
"""Gets the item, but also makes it most recent."""
- value: CacheValue = OrderedDict.__getitem__(self, key)
- OrderedDict.__delitem__(self, key)
- OrderedDict.__setitem__(self, key, value)
+ value: CacheValue = super().__getitem__(key)
+ super().__delitem__(key)
+ super().__setitem__(key, value)
return value
diff --git a/src/pip/_vendor/rich/_spinners.py b/src/pip/_vendor/rich/_spinners.py
index dc1db0777..d0bb1fe75 100644
--- a/src/pip/_vendor/rich/_spinners.py
+++ b/src/pip/_vendor/rich/_spinners.py
@@ -22,149 +22,36 @@ Spinners are from:
SPINNERS = {
"dots": {
"interval": 80,
- "frames": ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
+ "frames": "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏",
},
- "dots2": {"interval": 80, "frames": ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]},
+ "dots2": {"interval": 80, "frames": "⣾⣽⣻⢿⡿⣟⣯⣷"},
"dots3": {
"interval": 80,
- "frames": ["⠋", "⠙", "⠚", "⠞", "⠖", "⠦", "⠴", "⠲", "⠳", "⠓"],
+ "frames": "⠋⠙⠚⠞⠖⠦⠴⠲⠳⠓",
},
"dots4": {
"interval": 80,
- "frames": [
- "⠄",
- "⠆",
- "⠇",
- "⠋",
- "⠙",
- "⠸",
- "⠰",
- "⠠",
- "⠰",
- "⠸",
- "⠙",
- "⠋",
- "⠇",
- "⠆",
- ],
+ "frames": "⠄⠆⠇⠋⠙⠸⠰⠠⠰⠸⠙⠋⠇⠆",
},
"dots5": {
"interval": 80,
- "frames": [
- "⠋",
- "⠙",
- "⠚",
- "⠒",
- "⠂",
- "⠂",
- "⠒",
- "⠲",
- "⠴",
- "⠦",
- "⠖",
- "⠒",
- "⠐",
- "⠐",
- "⠒",
- "⠓",
- "⠋",
- ],
+ "frames": "⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋",
},
"dots6": {
"interval": 80,
- "frames": [
- "⠁",
- "⠉",
- "⠙",
- "⠚",
- "⠒",
- "⠂",
- "⠂",
- "⠒",
- "⠲",
- "⠴",
- "⠤",
- "⠄",
- "⠄",
- "⠤",
- "⠴",
- "⠲",
- "⠒",
- "⠂",
- "⠂",
- "⠒",
- "⠚",
- "⠙",
- "⠉",
- "⠁",
- ],
+ "frames": "⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁",
},
"dots7": {
"interval": 80,
- "frames": [
- "⠈",
- "⠉",
- "⠋",
- "⠓",
- "⠒",
- "⠐",
- "⠐",
- "⠒",
- "⠖",
- "⠦",
- "⠤",
- "⠠",
- "⠠",
- "⠤",
- "⠦",
- "⠖",
- "⠒",
- "⠐",
- "⠐",
- "⠒",
- "⠓",
- "⠋",
- "⠉",
- "⠈",
- ],
+ "frames": "⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈",
},
"dots8": {
"interval": 80,
- "frames": [
- "⠁",
- "⠁",
- "⠉",
- "⠙",
- "⠚",
- "⠒",
- "⠂",
- "⠂",
- "⠒",
- "⠲",
- "⠴",
- "⠤",
- "⠄",
- "⠄",
- "⠤",
- "⠠",
- "⠠",
- "⠤",
- "⠦",
- "⠖",
- "⠒",
- "⠐",
- "⠐",
- "⠒",
- "⠓",
- "⠋",
- "⠉",
- "⠈",
- "⠈",
- ],
+ "frames": "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈",
},
- "dots9": {"interval": 80, "frames": ["⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"]},
- "dots10": {"interval": 80, "frames": ["⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"]},
- "dots11": {"interval": 100, "frames": ["⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"]},
+ "dots9": {"interval": 80, "frames": "⢹⢺⢼⣸⣇⡧⡗⡏"},
+ "dots10": {"interval": 80, "frames": "⢄⢂⢁⡁⡈⡐⡠"},
+ "dots11": {"interval": 100, "frames": "⠁⠂⠄⡀⢀⠠⠐⠈"},
"dots12": {
"interval": 80,
"frames": [
@@ -228,315 +115,62 @@ SPINNERS = {
},
"dots8Bit": {
"interval": 80,
- "frames": [
- "⠀",
- "⠁",
- "⠂",
- "⠃",
- "⠄",
- "⠅",
- "⠆",
- "⠇",
- "⡀",
- "⡁",
- "⡂",
- "⡃",
- "⡄",
- "⡅",
- "⡆",
- "⡇",
- "⠈",
- "⠉",
- "⠊",
- "⠋",
- "⠌",
- "⠍",
- "⠎",
- "⠏",
- "⡈",
- "⡉",
- "⡊",
- "⡋",
- "⡌",
- "⡍",
- "⡎",
- "⡏",
- "⠐",
- "⠑",
- "⠒",
- "⠓",
- "⠔",
- "⠕",
- "⠖",
- "⠗",
- "⡐",
- "⡑",
- "⡒",
- "⡓",
- "⡔",
- "⡕",
- "⡖",
- "⡗",
- "⠘",
- "⠙",
- "⠚",
- "⠛",
- "⠜",
- "⠝",
- "⠞",
- "⠟",
- "⡘",
- "⡙",
- "⡚",
- "⡛",
- "⡜",
- "⡝",
- "⡞",
- "⡟",
- "⠠",
- "⠡",
- "⠢",
- "⠣",
- "⠤",
- "⠥",
- "⠦",
- "⠧",
- "⡠",
- "⡡",
- "⡢",
- "⡣",
- "⡤",
- "⡥",
- "⡦",
- "⡧",
- "⠨",
- "⠩",
- "⠪",
- "⠫",
- "⠬",
- "⠭",
- "⠮",
- "⠯",
- "⡨",
- "⡩",
- "⡪",
- "⡫",
- "⡬",
- "⡭",
- "⡮",
- "⡯",
- "⠰",
- "⠱",
- "⠲",
- "⠳",
- "⠴",
- "⠵",
- "⠶",
- "⠷",
- "⡰",
- "⡱",
- "⡲",
- "⡳",
- "⡴",
- "⡵",
- "⡶",
- "⡷",
- "⠸",
- "⠹",
- "⠺",
- "⠻",
- "⠼",
- "⠽",
- "⠾",
- "⠿",
- "⡸",
- "⡹",
- "⡺",
- "⡻",
- "⡼",
- "⡽",
- "⡾",
- "⡿",
- "⢀",
- "⢁",
- "⢂",
- "⢃",
- "⢄",
- "⢅",
- "⢆",
- "⢇",
- "⣀",
- "⣁",
- "⣂",
- "⣃",
- "⣄",
- "⣅",
- "⣆",
- "⣇",
- "⢈",
- "⢉",
- "⢊",
- "⢋",
- "⢌",
- "⢍",
- "⢎",
- "⢏",
- "⣈",
- "⣉",
- "⣊",
- "⣋",
- "⣌",
- "⣍",
- "⣎",
- "⣏",
- "⢐",
- "⢑",
- "⢒",
- "⢓",
- "⢔",
- "⢕",
- "⢖",
- "⢗",
- "⣐",
- "⣑",
- "⣒",
- "⣓",
- "⣔",
- "⣕",
- "⣖",
- "⣗",
- "⢘",
- "⢙",
- "⢚",
- "⢛",
- "⢜",
- "⢝",
- "⢞",
- "⢟",
- "⣘",
- "⣙",
- "⣚",
- "⣛",
- "⣜",
- "⣝",
- "⣞",
- "⣟",
- "⢠",
- "⢡",
- "⢢",
- "⢣",
- "⢤",
- "⢥",
- "⢦",
- "⢧",
- "⣠",
- "⣡",
- "⣢",
- "⣣",
- "⣤",
- "⣥",
- "⣦",
- "⣧",
- "⢨",
- "⢩",
- "⢪",
- "⢫",
- "⢬",
- "⢭",
- "⢮",
- "⢯",
- "⣨",
- "⣩",
- "⣪",
- "⣫",
- "⣬",
- "⣭",
- "⣮",
- "⣯",
- "⢰",
- "⢱",
- "⢲",
- "⢳",
- "⢴",
- "⢵",
- "⢶",
- "⢷",
- "⣰",
- "⣱",
- "⣲",
- "⣳",
- "⣴",
- "⣵",
- "⣶",
- "⣷",
- "⢸",
- "⢹",
- "⢺",
- "⢻",
- "⢼",
- "⢽",
- "⢾",
- "⢿",
- "⣸",
- "⣹",
- "⣺",
- "⣻",
- "⣼",
- "⣽",
- "⣾",
- "⣿",
- ],
+ "frames": "⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙"
+ "⡚⡛⡜⡝⡞⡟⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻"
+ "⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕"
+ "⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷"
+ "⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿",
},
"line": {"interval": 130, "frames": ["-", "\\", "|", "/"]},
- "line2": {"interval": 100, "frames": ["⠂", "-", "–", "—", "–", "-"]},
- "pipe": {"interval": 100, "frames": ["┤", "┘", "┴", "└", "├", "┌", "┬", "┐"]},
+ "line2": {"interval": 100, "frames": "⠂-–—–-"},
+ "pipe": {"interval": 100, "frames": "┤┘┴└├┌┬┐"},
"simpleDots": {"interval": 400, "frames": [". ", ".. ", "...", " "]},
"simpleDotsScrolling": {
"interval": 200,
"frames": [". ", ".. ", "...", " ..", " .", " "],
},
- "star": {"interval": 70, "frames": ["✶", "✸", "✹", "✺", "✹", "✷"]},
- "star2": {"interval": 80, "frames": ["+", "x", "*"]},
+ "star": {"interval": 70, "frames": "✶✸✹✺✹✷"},
+ "star2": {"interval": 80, "frames": "+x*"},
"flip": {
"interval": 70,
- "frames": ["_", "_", "_", "-", "`", "`", "'", "´", "-", "_", "_", "_"],
+ "frames": "___-``'´-___",
},
- "hamburger": {"interval": 100, "frames": ["☱", "☲", "☴"]},
+ "hamburger": {"interval": 100, "frames": "☱☲☴"},
"growVertical": {
"interval": 120,
- "frames": ["▁", "▃", "▄", "▅", "▆", "▇", "▆", "▅", "▄", "▃"],
+ "frames": "▁▃▄▅▆▇▆▅▄▃",
},
"growHorizontal": {
"interval": 120,
- "frames": ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "▊", "▋", "▌", "▍", "▎"],
+ "frames": "▏▎▍▌▋▊▉▊▋▌▍▎",
},
- "balloon": {"interval": 140, "frames": [" ", ".", "o", "O", "@", "*", " "]},
- "balloon2": {"interval": 120, "frames": [".", "o", "O", "°", "O", "o", "."]},
- "noise": {"interval": 100, "frames": ["▓", "▒", "░"]},
- "bounce": {"interval": 120, "frames": ["⠁", "⠂", "⠄", "⠂"]},
- "boxBounce": {"interval": 120, "frames": ["▖", "▘", "▝", "▗"]},
- "boxBounce2": {"interval": 100, "frames": ["▌", "▀", "▐", "▄"]},
- "triangle": {"interval": 50, "frames": ["◢", "◣", "◤", "◥"]},
- "arc": {"interval": 100, "frames": ["◜", "◠", "◝", "◞", "◡", "◟"]},
- "circle": {"interval": 120, "frames": ["◡", "⊙", "◠"]},
- "squareCorners": {"interval": 180, "frames": ["◰", "◳", "◲", "◱"]},
- "circleQuarters": {"interval": 120, "frames": ["◴", "◷", "◶", "◵"]},
- "circleHalves": {"interval": 50, "frames": ["◐", "◓", "◑", "◒"]},
- "squish": {"interval": 100, "frames": ["╫", "╪"]},
- "toggle": {"interval": 250, "frames": ["⊶", "⊷"]},
- "toggle2": {"interval": 80, "frames": ["▫", "▪"]},
- "toggle3": {"interval": 120, "frames": ["□", "■"]},
- "toggle4": {"interval": 100, "frames": ["■", "□", "▪", "▫"]},
- "toggle5": {"interval": 100, "frames": ["▮", "▯"]},
- "toggle6": {"interval": 300, "frames": ["ဝ", "၀"]},
- "toggle7": {"interval": 80, "frames": ["⦾", "⦿"]},
- "toggle8": {"interval": 100, "frames": ["◍", "◌"]},
- "toggle9": {"interval": 100, "frames": ["◉", "◎"]},
- "toggle10": {"interval": 100, "frames": ["㊂", "㊀", "㊁"]},
- "toggle11": {"interval": 50, "frames": ["⧇", "⧆"]},
- "toggle12": {"interval": 120, "frames": ["☗", "☖"]},
- "toggle13": {"interval": 80, "frames": ["=", "*", "-"]},
- "arrow": {"interval": 100, "frames": ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"]},
+ "balloon": {"interval": 140, "frames": " .oO@* "},
+ "balloon2": {"interval": 120, "frames": ".oO°Oo."},
+ "noise": {"interval": 100, "frames": "▓▒░"},
+ "bounce": {"interval": 120, "frames": "⠁⠂⠄⠂"},
+ "boxBounce": {"interval": 120, "frames": "▖▘▝▗"},
+ "boxBounce2": {"interval": 100, "frames": "▌▀▐▄"},
+ "triangle": {"interval": 50, "frames": "◢◣◤◥"},
+ "arc": {"interval": 100, "frames": "◜◠◝◞◡◟"},
+ "circle": {"interval": 120, "frames": "◡⊙◠"},
+ "squareCorners": {"interval": 180, "frames": "◰◳◲◱"},
+ "circleQuarters": {"interval": 120, "frames": "◴◷◶◵"},
+ "circleHalves": {"interval": 50, "frames": "◐◓◑◒"},
+ "squish": {"interval": 100, "frames": "╫╪"},
+ "toggle": {"interval": 250, "frames": "⊶⊷"},
+ "toggle2": {"interval": 80, "frames": "▫▪"},
+ "toggle3": {"interval": 120, "frames": "□■"},
+ "toggle4": {"interval": 100, "frames": "■□▪▫"},
+ "toggle5": {"interval": 100, "frames": "▮▯"},
+ "toggle6": {"interval": 300, "frames": "ဝ၀"},
+ "toggle7": {"interval": 80, "frames": "⦾⦿"},
+ "toggle8": {"interval": 100, "frames": "◍◌"},
+ "toggle9": {"interval": 100, "frames": "◉◎"},
+ "toggle10": {"interval": 100, "frames": "㊂㊀㊁"},
+ "toggle11": {"interval": 50, "frames": "⧇⧆"},
+ "toggle12": {"interval": 120, "frames": "☗☖"},
+ "toggle13": {"interval": 80, "frames": "=*-"},
+ "arrow": {"interval": 100, "frames": "←↖↑↗→↘↓↙"},
"arrow2": {
"interval": 80,
"frames": ["⬆️ ", "↗️ ", "➡️ ", "↘️ ", "⬇️ ", "↙️ ", "⬅️ ", "↖️ "],
@@ -769,7 +403,7 @@ SPINNERS = {
"▐/|____________▌",
],
},
- "dqpb": {"interval": 100, "frames": ["d", "q", "p", "b"]},
+ "dqpb": {"interval": 100, "frames": "dqpb"},
"weather": {
"interval": 100,
"frames": [
@@ -798,7 +432,7 @@ SPINNERS = {
"☀️ ",
],
},
- "christmas": {"interval": 400, "frames": ["🌲", "🎄"]},
+ "christmas": {"interval": 400, "frames": "🌲🎄"},
"grenade": {
"interval": 80,
"frames": [
@@ -819,7 +453,7 @@ SPINNERS = {
],
},
"point": {"interval": 125, "frames": ["∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"]},
- "layer": {"interval": 150, "frames": ["-", "=", "≡"]},
+ "layer": {"interval": 150, "frames": "-=≡"},
"betaWave": {
"interval": 80,
"frames": [
diff --git a/src/pip/_vendor/rich/_win32_console.py b/src/pip/_vendor/rich/_win32_console.py
new file mode 100644
index 000000000..d42cd7916
--- /dev/null
+++ b/src/pip/_vendor/rich/_win32_console.py
@@ -0,0 +1,630 @@
+"""Light wrapper around the Win32 Console API - this module should only be imported on Windows
+
+The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions
+"""
+import ctypes
+import sys
+from typing import Any
+
+windll: Any = None
+if sys.platform == "win32":
+ windll = ctypes.LibraryLoader(ctypes.WinDLL)
+else:
+ raise ImportError(f"{__name__} can only be imported on Windows")
+
+import time
+from ctypes import Structure, byref, wintypes
+from typing import IO, NamedTuple, Type, cast
+
+from pip._vendor.rich.color import ColorSystem
+from pip._vendor.rich.style import Style
+
+STDOUT = -11
+ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
+
+COORD = wintypes._COORD
+
+
+class LegacyWindowsError(Exception):
+ pass
+
+
+class WindowsCoordinates(NamedTuple):
+ """Coordinates in the Windows Console API are (y, x), not (x, y).
+ This class is intended to prevent that confusion.
+ Rows and columns are indexed from 0.
+ This class can be used in place of wintypes._COORD in arguments and argtypes.
+ """
+
+ row: int
+ col: int
+
+ @classmethod
+ def from_param(cls, value: "WindowsCoordinates") -> COORD:
+ """Converts a WindowsCoordinates into a wintypes _COORD structure.
+ This classmethod is internally called by ctypes to perform the conversion.
+
+ Args:
+ value (WindowsCoordinates): The input coordinates to convert.
+
+ Returns:
+ wintypes._COORD: The converted coordinates struct.
+ """
+ return COORD(value.col, value.row)
+
+
+class CONSOLE_SCREEN_BUFFER_INFO(Structure):
+ _fields_ = [
+ ("dwSize", COORD),
+ ("dwCursorPosition", COORD),
+ ("wAttributes", wintypes.WORD),
+ ("srWindow", wintypes.SMALL_RECT),
+ ("dwMaximumWindowSize", COORD),
+ ]
+
+
+class CONSOLE_CURSOR_INFO(ctypes.Structure):
+ _fields_ = [("dwSize", wintypes.DWORD), ("bVisible", wintypes.BOOL)]
+
+
+_GetStdHandle = windll.kernel32.GetStdHandle
+_GetStdHandle.argtypes = [
+ wintypes.DWORD,
+]
+_GetStdHandle.restype = wintypes.HANDLE
+
+
+def GetStdHandle(handle: int = STDOUT) -> wintypes.HANDLE:
+ """Retrieves a handle to the specified standard device (standard input, standard output, or standard error).
+
+ Args:
+ handle (int): Integer identifier for the handle. Defaults to -11 (stdout).
+
+ Returns:
+ wintypes.HANDLE: The handle
+ """
+ return cast(wintypes.HANDLE, _GetStdHandle(handle))
+
+
+_GetConsoleMode = windll.kernel32.GetConsoleMode
+_GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]
+_GetConsoleMode.restype = wintypes.BOOL
+
+
+def GetConsoleMode(std_handle: wintypes.HANDLE) -> int:
+ """Retrieves the current input mode of a console's input buffer
+ or the current output mode of a console screen buffer.
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+
+ Raises:
+ LegacyWindowsError: If any error occurs while calling the Windows console API.
+
+ Returns:
+ int: Value representing the current console mode as documented at
+ https://docs.microsoft.com/en-us/windows/console/getconsolemode#parameters
+ """
+
+ console_mode = wintypes.DWORD()
+ success = bool(_GetConsoleMode(std_handle, console_mode))
+ if not success:
+ raise LegacyWindowsError("Unable to get legacy Windows Console Mode")
+ return console_mode.value
+
+
+_FillConsoleOutputCharacterW = windll.kernel32.FillConsoleOutputCharacterW
+_FillConsoleOutputCharacterW.argtypes = [
+ wintypes.HANDLE,
+ ctypes.c_char,
+ wintypes.DWORD,
+ cast(Type[COORD], WindowsCoordinates),
+ ctypes.POINTER(wintypes.DWORD),
+]
+_FillConsoleOutputCharacterW.restype = wintypes.BOOL
+
+
+def FillConsoleOutputCharacter(
+ std_handle: wintypes.HANDLE,
+ char: str,
+ length: int,
+ start: WindowsCoordinates,
+) -> int:
+ """Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates.
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+ char (str): The character to write. Must be a string of length 1.
+ length (int): The number of times to write the character.
+ start (WindowsCoordinates): The coordinates to start writing at.
+
+ Returns:
+ int: The number of characters written.
+ """
+ character = ctypes.c_char(char.encode())
+ num_characters = wintypes.DWORD(length)
+ num_written = wintypes.DWORD(0)
+ _FillConsoleOutputCharacterW(
+ std_handle,
+ character,
+ num_characters,
+ start,
+ byref(num_written),
+ )
+ return num_written.value
+
+
+_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
+_FillConsoleOutputAttribute.argtypes = [
+ wintypes.HANDLE,
+ wintypes.WORD,
+ wintypes.DWORD,
+ cast(Type[COORD], WindowsCoordinates),
+ ctypes.POINTER(wintypes.DWORD),
+]
+_FillConsoleOutputAttribute.restype = wintypes.BOOL
+
+
+def FillConsoleOutputAttribute(
+ std_handle: wintypes.HANDLE,
+ attributes: int,
+ length: int,
+ start: WindowsCoordinates,
+) -> int:
+ """Sets the character attributes for a specified number of character cells,
+ beginning at the specified coordinates in a screen buffer.
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+ attributes (int): Integer value representing the foreground and background colours of the cells.
+ length (int): The number of cells to set the output attribute of.
+ start (WindowsCoordinates): The coordinates of the first cell whose attributes are to be set.
+
+ Returns:
+ int: The number of cells whose attributes were actually set.
+ """
+ num_cells = wintypes.DWORD(length)
+ style_attrs = wintypes.WORD(attributes)
+ num_written = wintypes.DWORD(0)
+ _FillConsoleOutputAttribute(
+ std_handle, style_attrs, num_cells, start, byref(num_written)
+ )
+ return num_written.value
+
+
+_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
+_SetConsoleTextAttribute.argtypes = [
+ wintypes.HANDLE,
+ wintypes.WORD,
+]
+_SetConsoleTextAttribute.restype = wintypes.BOOL
+
+
+def SetConsoleTextAttribute(
+ std_handle: wintypes.HANDLE, attributes: wintypes.WORD
+) -> bool:
+ """Set the colour attributes for all text written after this function is called.
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+ attributes (int): Integer value representing the foreground and background colours.
+
+
+ Returns:
+ bool: True if the attribute was set successfully, otherwise False.
+ """
+ return bool(_SetConsoleTextAttribute(std_handle, attributes))
+
+
+_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
+_GetConsoleScreenBufferInfo.argtypes = [
+ wintypes.HANDLE,
+ ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO),
+]
+_GetConsoleScreenBufferInfo.restype = wintypes.BOOL
+
+
+def GetConsoleScreenBufferInfo(
+ std_handle: wintypes.HANDLE,
+) -> CONSOLE_SCREEN_BUFFER_INFO:
+ """Retrieves information about the specified console screen buffer.
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+
+ Returns:
+ CONSOLE_SCREEN_BUFFER_INFO: A CONSOLE_SCREEN_BUFFER_INFO ctype struct contain information about
+ screen size, cursor position, colour attributes, and more."""
+ console_screen_buffer_info = CONSOLE_SCREEN_BUFFER_INFO()
+ _GetConsoleScreenBufferInfo(std_handle, byref(console_screen_buffer_info))
+ return console_screen_buffer_info
+
+
+_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
+_SetConsoleCursorPosition.argtypes = [
+ wintypes.HANDLE,
+ cast(Type[COORD], WindowsCoordinates),
+]
+_SetConsoleCursorPosition.restype = wintypes.BOOL
+
+
+def SetConsoleCursorPosition(
+ std_handle: wintypes.HANDLE, coords: WindowsCoordinates
+) -> bool:
+ """Set the position of the cursor in the console screen
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+ coords (WindowsCoordinates): The coordinates to move the cursor to.
+
+ Returns:
+ bool: True if the function succeeds, otherwise False.
+ """
+ return bool(_SetConsoleCursorPosition(std_handle, coords))
+
+
+_SetConsoleCursorInfo = windll.kernel32.SetConsoleCursorInfo
+_SetConsoleCursorInfo.argtypes = [
+ wintypes.HANDLE,
+ ctypes.POINTER(CONSOLE_CURSOR_INFO),
+]
+_SetConsoleCursorInfo.restype = wintypes.BOOL
+
+
+def SetConsoleCursorInfo(
+ std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO
+) -> bool:
+ """Set the cursor info - used for adjusting cursor visibility and width
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+ cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct containing the new cursor info.
+
+ Returns:
+ bool: True if the function succeeds, otherwise False.
+ """
+ return bool(_SetConsoleCursorInfo(std_handle, byref(cursor_info)))
+
+
+_SetConsoleTitle = windll.kernel32.SetConsoleTitleW
+_SetConsoleTitle.argtypes = [wintypes.LPCWSTR]
+_SetConsoleTitle.restype = wintypes.BOOL
+
+
+def SetConsoleTitle(title: str) -> bool:
+ """Sets the title of the current console window
+
+ Args:
+ title (str): The new title of the console window.
+
+ Returns:
+ bool: True if the function succeeds, otherwise False.
+ """
+ return bool(_SetConsoleTitle(title))
+
+
+class LegacyWindowsTerm:
+ """This class allows interaction with the legacy Windows Console API. It should only be used in the context
+ of environments where virtual terminal processing is not available. However, if it is used in a Windows environment,
+ the entire API should work.
+
+ Args:
+ file (IO[str]): The file which the Windows Console API HANDLE is retrieved from, defaults to sys.stdout.
+ """
+
+ BRIGHT_BIT = 8
+
+ # Indices are ANSI color numbers, values are the corresponding Windows Console API color numbers
+ ANSI_TO_WINDOWS = [
+ 0, # black The Windows colours are defined in wincon.h as follows:
+ 4, # red define FOREGROUND_BLUE 0x0001 -- 0000 0001
+ 2, # green define FOREGROUND_GREEN 0x0002 -- 0000 0010
+ 6, # yellow define FOREGROUND_RED 0x0004 -- 0000 0100
+ 1, # blue define FOREGROUND_INTENSITY 0x0008 -- 0000 1000
+ 5, # magenta define BACKGROUND_BLUE 0x0010 -- 0001 0000
+ 3, # cyan define BACKGROUND_GREEN 0x0020 -- 0010 0000
+ 7, # white define BACKGROUND_RED 0x0040 -- 0100 0000
+ 8, # bright black (grey) define BACKGROUND_INTENSITY 0x0080 -- 1000 0000
+ 12, # bright red
+ 10, # bright green
+ 14, # bright yellow
+ 9, # bright blue
+ 13, # bright magenta
+ 11, # bright cyan
+ 15, # bright white
+ ]
+
+ def __init__(self, file: "IO[str]") -> None:
+ handle = GetStdHandle(STDOUT)
+ self._handle = handle
+ default_text = GetConsoleScreenBufferInfo(handle).wAttributes
+ self._default_text = default_text
+
+ self._default_fore = default_text & 7
+ self._default_back = (default_text >> 4) & 7
+ self._default_attrs = self._default_fore | (self._default_back << 4)
+
+ self._file = file
+ self.write = file.write
+ self.flush = file.flush
+
+ @property
+ def cursor_position(self) -> WindowsCoordinates:
+ """Returns the current position of the cursor (0-based)
+
+ Returns:
+ WindowsCoordinates: The current cursor position.
+ """
+ coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition
+ return WindowsCoordinates(row=cast(int, coord.Y), col=cast(int, coord.X))
+
+ @property
+ def screen_size(self) -> WindowsCoordinates:
+ """Returns the current size of the console screen buffer, in character columns and rows
+
+ Returns:
+ WindowsCoordinates: The width and height of the screen as WindowsCoordinates.
+ """
+ screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize
+ return WindowsCoordinates(
+ row=cast(int, screen_size.Y), col=cast(int, screen_size.X)
+ )
+
+ def write_text(self, text: str) -> None:
+ """Write text directly to the terminal without any modification of styles
+
+ Args:
+ text (str): The text to write to the console
+ """
+ self.write(text)
+ self.flush()
+
+ def write_styled(self, text: str, style: Style) -> None:
+ """Write styled text to the terminal.
+
+ Args:
+ text (str): The text to write
+ style (Style): The style of the text
+ """
+ color = style.color
+ bgcolor = style.bgcolor
+ if style.reverse:
+ color, bgcolor = bgcolor, color
+
+ if color:
+ fore = color.downgrade(ColorSystem.WINDOWS).number
+ fore = fore if fore is not None else 7 # Default to ANSI 7: White
+ if style.bold:
+ fore = fore | self.BRIGHT_BIT
+ if style.dim:
+ fore = fore & ~self.BRIGHT_BIT
+ fore = self.ANSI_TO_WINDOWS[fore]
+ else:
+ fore = self._default_fore
+
+ if bgcolor:
+ back = bgcolor.downgrade(ColorSystem.WINDOWS).number
+ back = back if back is not None else 0 # Default to ANSI 0: Black
+ back = self.ANSI_TO_WINDOWS[back]
+ else:
+ back = self._default_back
+
+ assert fore is not None
+ assert back is not None
+
+ SetConsoleTextAttribute(
+ self._handle, attributes=ctypes.c_ushort(fore | (back << 4))
+ )
+ self.write_text(text)
+ SetConsoleTextAttribute(self._handle, attributes=self._default_text)
+
+ def move_cursor_to(self, new_position: WindowsCoordinates) -> None:
+ """Set the position of the cursor
+
+ Args:
+ new_position (WindowsCoordinates): The WindowsCoordinates representing the new position of the cursor.
+ """
+ if new_position.col < 0 or new_position.row < 0:
+ return
+ SetConsoleCursorPosition(self._handle, coords=new_position)
+
+ def erase_line(self) -> None:
+ """Erase all content on the line the cursor is currently located at"""
+ screen_size = self.screen_size
+ cursor_position = self.cursor_position
+ cells_to_erase = screen_size.col
+ start_coordinates = WindowsCoordinates(row=cursor_position.row, col=0)
+ FillConsoleOutputCharacter(
+ self._handle, " ", length=cells_to_erase, start=start_coordinates
+ )
+ FillConsoleOutputAttribute(
+ self._handle,
+ self._default_attrs,
+ length=cells_to_erase,
+ start=start_coordinates,
+ )
+
+ def erase_end_of_line(self) -> None:
+ """Erase all content from the cursor position to the end of that line"""
+ cursor_position = self.cursor_position
+ cells_to_erase = self.screen_size.col - cursor_position.col
+ FillConsoleOutputCharacter(
+ self._handle, " ", length=cells_to_erase, start=cursor_position
+ )
+ FillConsoleOutputAttribute(
+ self._handle,
+ self._default_attrs,
+ length=cells_to_erase,
+ start=cursor_position,
+ )
+
+ def erase_start_of_line(self) -> None:
+ """Erase all content from the cursor position to the start of that line"""
+ row, col = self.cursor_position
+ start = WindowsCoordinates(row, 0)
+ FillConsoleOutputCharacter(self._handle, " ", length=col, start=start)
+ FillConsoleOutputAttribute(
+ self._handle, self._default_attrs, length=col, start=start
+ )
+
+ def move_cursor_up(self) -> None:
+ """Move the cursor up a single cell"""
+ cursor_position = self.cursor_position
+ SetConsoleCursorPosition(
+ self._handle,
+ coords=WindowsCoordinates(
+ row=cursor_position.row - 1, col=cursor_position.col
+ ),
+ )
+
+ def move_cursor_down(self) -> None:
+ """Move the cursor down a single cell"""
+ cursor_position = self.cursor_position
+ SetConsoleCursorPosition(
+ self._handle,
+ coords=WindowsCoordinates(
+ row=cursor_position.row + 1,
+ col=cursor_position.col,
+ ),
+ )
+
+ def move_cursor_forward(self) -> None:
+ """Move the cursor forward a single cell. Wrap to the next line if required."""
+ row, col = self.cursor_position
+ if col == self.screen_size.col - 1:
+ row += 1
+ col = 0
+ else:
+ col += 1
+ SetConsoleCursorPosition(
+ self._handle, coords=WindowsCoordinates(row=row, col=col)
+ )
+
+ def move_cursor_to_column(self, column: int) -> None:
+ """Move cursor to the column specified by the zero-based column index, staying on the same row
+
+ Args:
+ column (int): The zero-based column index to move the cursor to.
+ """
+ row, _ = self.cursor_position
+ SetConsoleCursorPosition(self._handle, coords=WindowsCoordinates(row, column))
+
+ def move_cursor_backward(self) -> None:
+ """Move the cursor backward a single cell. Wrap to the previous line if required."""
+ row, col = self.cursor_position
+ if col == 0:
+ row -= 1
+ col = self.screen_size.col - 1
+ else:
+ col -= 1
+ SetConsoleCursorPosition(
+ self._handle, coords=WindowsCoordinates(row=row, col=col)
+ )
+
+ def hide_cursor(self) -> None:
+ """Hide the cursor"""
+ invisible_cursor = CONSOLE_CURSOR_INFO(dwSize=100, bVisible=0)
+ SetConsoleCursorInfo(self._handle, cursor_info=invisible_cursor)
+
+ def show_cursor(self) -> None:
+ """Show the cursor"""
+ visible_cursor = CONSOLE_CURSOR_INFO(dwSize=100, bVisible=1)
+ SetConsoleCursorInfo(self._handle, cursor_info=visible_cursor)
+
+ def set_title(self, title: str) -> None:
+ """Set the title of the terminal window
+
+ Args:
+ title (str): The new title of the console window
+ """
+ assert len(title) < 255, "Console title must be less than 255 characters"
+ SetConsoleTitle(title)
+
+
+if __name__ == "__main__":
+ handle = GetStdHandle()
+
+ from pip._vendor.rich.console import Console
+
+ console = Console()
+
+ term = LegacyWindowsTerm(sys.stdout)
+ term.set_title("Win32 Console Examples")
+
+ style = Style(color="black", bgcolor="red")
+
+ heading = Style.parse("black on green")
+
+ # Check colour output
+ console.rule("Checking colour output")
+ console.print("[on red]on red!")
+ console.print("[blue]blue!")
+ console.print("[yellow]yellow!")
+ console.print("[bold yellow]bold yellow!")
+ console.print("[bright_yellow]bright_yellow!")
+ console.print("[dim bright_yellow]dim bright_yellow!")
+ console.print("[italic cyan]italic cyan!")
+ console.print("[bold white on blue]bold white on blue!")
+ console.print("[reverse bold white on blue]reverse bold white on blue!")
+ console.print("[bold black on cyan]bold black on cyan!")
+ console.print("[black on green]black on green!")
+ console.print("[blue on green]blue on green!")
+ console.print("[white on black]white on black!")
+ console.print("[black on white]black on white!")
+ console.print("[#1BB152 on #DA812D]#1BB152 on #DA812D!")
+
+ # Check cursor movement
+ console.rule("Checking cursor movement")
+ console.print()
+ term.move_cursor_backward()
+ term.move_cursor_backward()
+ term.write_text("went back and wrapped to prev line")
+ time.sleep(1)
+ term.move_cursor_up()
+ term.write_text("we go up")
+ time.sleep(1)
+ term.move_cursor_down()
+ term.write_text("and down")
+ time.sleep(1)
+ term.move_cursor_up()
+ term.move_cursor_backward()
+ term.move_cursor_backward()
+ term.write_text("we went up and back 2")
+ time.sleep(1)
+ term.move_cursor_down()
+ term.move_cursor_backward()
+ term.move_cursor_backward()
+ term.write_text("we went down and back 2")
+ time.sleep(1)
+
+ # Check erasing of lines
+ term.hide_cursor()
+ console.print()
+ console.rule("Checking line erasing")
+ console.print("\n...Deleting to the start of the line...")
+ term.write_text("The red arrow shows the cursor location, and direction of erase")
+ time.sleep(1)
+ term.move_cursor_to_column(16)
+ term.write_styled("<", Style.parse("black on red"))
+ term.move_cursor_backward()
+ time.sleep(1)
+ term.erase_start_of_line()
+ time.sleep(1)
+
+ console.print("\n\n...And to the end of the line...")
+ term.write_text("The red arrow shows the cursor location, and direction of erase")
+ time.sleep(1)
+
+ term.move_cursor_to_column(16)
+ term.write_styled(">", Style.parse("black on red"))
+ time.sleep(1)
+ term.erase_end_of_line()
+ time.sleep(1)
+
+ console.print("\n\n...Now the whole line will be erased...")
+ term.write_styled("I'm going to disappear!", style=Style.parse("black on cyan"))
+ time.sleep(1)
+ term.erase_line()
+
+ term.show_cursor()
+ print("\n")
diff --git a/src/pip/_vendor/rich/_windows.py b/src/pip/_vendor/rich/_windows.py
index ca3a680d3..10fc0d7e9 100644
--- a/src/pip/_vendor/rich/_windows.py
+++ b/src/pip/_vendor/rich/_windows.py
@@ -14,13 +14,21 @@ class WindowsConsoleFeatures:
try:
import ctypes
- from ctypes import LibraryLoader, wintypes
+ from ctypes import LibraryLoader
if sys.platform == "win32":
windll = LibraryLoader(ctypes.WinDLL)
else:
windll = None
raise ImportError("Not windows")
+
+ from pip._vendor.rich._win32_console import (
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING,
+ GetConsoleMode,
+ GetStdHandle,
+ LegacyWindowsError,
+ )
+
except (AttributeError, ImportError, ValueError):
# Fallback if we can't load the Windows DLL
@@ -30,28 +38,20 @@ except (AttributeError, ImportError, ValueError):
else:
- STDOUT = -11
- ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
- _GetConsoleMode = windll.kernel32.GetConsoleMode
- _GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]
- _GetConsoleMode.restype = wintypes.BOOL
-
- _GetStdHandle = windll.kernel32.GetStdHandle
- _GetStdHandle.argtypes = [
- wintypes.DWORD,
- ]
- _GetStdHandle.restype = wintypes.HANDLE
-
def get_windows_console_features() -> WindowsConsoleFeatures:
"""Get windows console features.
Returns:
WindowsConsoleFeatures: An instance of WindowsConsoleFeatures.
"""
- handle = _GetStdHandle(STDOUT)
- console_mode = wintypes.DWORD()
- result = _GetConsoleMode(handle, console_mode)
- vt = bool(result and console_mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+ handle = GetStdHandle()
+ try:
+ console_mode = GetConsoleMode(handle)
+ success = True
+ except LegacyWindowsError:
+ console_mode = 0
+ success = False
+ vt = bool(success and console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
truecolor = False
if vt:
win_version = sys.getwindowsversion()
diff --git a/src/pip/_vendor/rich/_windows_renderer.py b/src/pip/_vendor/rich/_windows_renderer.py
new file mode 100644
index 000000000..066e585dd
--- /dev/null
+++ b/src/pip/_vendor/rich/_windows_renderer.py
@@ -0,0 +1,53 @@
+from typing import Iterable, Sequence, Tuple, cast
+
+from pip._vendor.rich._win32_console import LegacyWindowsTerm, WindowsCoordinates
+from pip._vendor.rich.segment import ControlCode, ControlType, Segment
+
+
+def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) -> None:
+ """Makes appropriate Windows Console API calls based on the segments in the buffer.
+
+ Args:
+ buffer (Iterable[Segment]): Iterable of Segments to convert to Win32 API calls.
+ term (LegacyWindowsTerm): Used to call the Windows Console API.
+ """
+ for text, style, control in buffer:
+ if not control:
+ if style:
+ term.write_styled(text, style)
+ else:
+ term.write_text(text)
+ else:
+ control_codes: Sequence[ControlCode] = control
+ for control_code in control_codes:
+ control_type = control_code[0]
+ if control_type == ControlType.CURSOR_MOVE_TO:
+ _, x, y = cast(Tuple[ControlType, int, int], control_code)
+ term.move_cursor_to(WindowsCoordinates(row=y - 1, col=x - 1))
+ elif control_type == ControlType.CARRIAGE_RETURN:
+ term.write_text("\r")
+ elif control_type == ControlType.HOME:
+ term.move_cursor_to(WindowsCoordinates(0, 0))
+ elif control_type == ControlType.CURSOR_UP:
+ term.move_cursor_up()
+ elif control_type == ControlType.CURSOR_DOWN:
+ term.move_cursor_down()
+ elif control_type == ControlType.CURSOR_FORWARD:
+ term.move_cursor_forward()
+ elif control_type == ControlType.CURSOR_BACKWARD:
+ term.move_cursor_backward()
+ elif control_type == ControlType.CURSOR_MOVE_TO_COLUMN:
+ _, column = cast(Tuple[ControlType, int], control_code)
+ term.move_cursor_to_column(column - 1)
+ elif control_type == ControlType.HIDE_CURSOR:
+ term.hide_cursor()
+ elif control_type == ControlType.SHOW_CURSOR:
+ term.show_cursor()
+ elif control_type == ControlType.ERASE_IN_LINE:
+ _, mode = cast(Tuple[ControlType, int], control_code)
+ if mode == 0:
+ term.erase_end_of_line()
+ elif mode == 1:
+ term.erase_start_of_line()
+ elif mode == 2:
+ term.erase_line()
diff --git a/src/pip/_vendor/rich/align.py b/src/pip/_vendor/rich/align.py
index 4344ae141..d5abb5947 100644
--- a/src/pip/_vendor/rich/align.py
+++ b/src/pip/_vendor/rich/align.py
@@ -18,7 +18,6 @@ if TYPE_CHECKING:
AlignMethod = Literal["left", "center", "right"]
VerticalAlignMethod = Literal["top", "middle", "bottom"]
-AlignValues = AlignMethod # TODO: deprecate AlignValues
class Align(JupyterMixin):
diff --git a/src/pip/_vendor/rich/ansi.py b/src/pip/_vendor/rich/ansi.py
index 92e4772ed..d4c32cef1 100644
--- a/src/pip/_vendor/rich/ansi.py
+++ b/src/pip/_vendor/rich/ansi.py
@@ -1,21 +1,27 @@
-from contextlib import suppress
import re
-from typing import Iterable, NamedTuple
+import sys
+from contextlib import suppress
+from typing import Iterable, NamedTuple, Optional
from .color import Color
from .style import Style
from .text import Text
-re_ansi = re.compile(r"(?:\x1b\[(.*?)m)|(?:\x1b\](.*?)\x1b\\)")
-re_csi = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
+re_ansi = re.compile(
+ r"""
+(?:\x1b\](.*?)\x1b\\)|
+(?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))
+""",
+ re.VERBOSE,
+)
class _AnsiToken(NamedTuple):
"""Result of ansi tokenized string."""
plain: str = ""
- sgr: str = ""
- osc: str = ""
+ sgr: Optional[str] = ""
+ osc: Optional[str] = ""
def _ansi_tokenize(ansi_text: str) -> Iterable[_AnsiToken]:
@@ -28,20 +34,22 @@ def _ansi_tokenize(ansi_text: str) -> Iterable[_AnsiToken]:
AnsiToken: A named tuple of (plain, sgr, osc)
"""
- def remove_csi(ansi_text: str) -> str:
- """Remove unknown CSI sequences."""
- return re_csi.sub("", ansi_text)
-
position = 0
+ sgr: Optional[str]
+ osc: Optional[str]
for match in re_ansi.finditer(ansi_text):
start, end = match.span(0)
- sgr, osc = match.groups()
+ osc, sgr = match.groups()
if start > position:
- yield _AnsiToken(remove_csi(ansi_text[position:start]))
- yield _AnsiToken("", sgr, osc)
+ yield _AnsiToken(ansi_text[position:start])
+ if sgr:
+ if sgr.endswith("m"):
+ yield _AnsiToken("", sgr[1:-1], osc)
+ else:
+ yield _AnsiToken("", sgr, osc)
position = end
if position < len(ansi_text):
- yield _AnsiToken(remove_csi(ansi_text[position:]))
+ yield _AnsiToken(ansi_text[position:])
SGR_STYLE_MAP = {
@@ -138,20 +146,21 @@ class AnsiDecoder:
text = Text()
append = text.append
line = line.rsplit("\r", 1)[-1]
- for token in _ansi_tokenize(line):
- plain_text, sgr, osc = token
+ for plain_text, sgr, osc in _ansi_tokenize(line):
if plain_text:
append(plain_text, self.style or None)
- elif osc:
+ elif osc is not None:
if osc.startswith("8;"):
_params, semicolon, link = osc[2:].partition(";")
if semicolon:
self.style = self.style.update_link(link or None)
- elif sgr:
+ elif sgr is not None:
# Translate in to semi-colon separated codes
# Ignore invalid codes, because we want to be lenient
codes = [
- min(255, int(_code)) for _code in sgr.split(";") if _code.isdigit()
+ min(255, int(_code) if _code else 0)
+ for _code in sgr.split(";")
+ if _code.isdigit() or _code == ""
]
iter_codes = iter(codes)
for code in iter_codes:
@@ -198,10 +207,10 @@ class AnsiDecoder:
return text
-if __name__ == "__main__": # pragma: no cover
- import pty
+if sys.platform != "win32" and __name__ == "__main__": # pragma: no cover
import io
import os
+ import pty
import sys
decoder = AnsiDecoder()
diff --git a/src/pip/_vendor/rich/cells.py b/src/pip/_vendor/rich/cells.py
index e824ea2a6..d7adf5a04 100644
--- a/src/pip/_vendor/rich/cells.py
+++ b/src/pip/_vendor/rich/cells.py
@@ -1,5 +1,5 @@
-from functools import lru_cache
import re
+from functools import lru_cache
from typing import Dict, List
from ._cell_widths import CELL_WIDTHS
@@ -18,17 +18,14 @@ def cell_len(text: str, _cache: Dict[str, int] = LRUCache(1024 * 4)) -> int:
Returns:
int: Get the number of cells required to display text.
"""
-
- if _is_single_cell_widths(text):
- return len(text)
- else:
- cached_result = _cache.get(text, None)
- if cached_result is not None:
- return cached_result
- _get_size = get_character_cell_size
- total_size = sum(_get_size(character) for character in text)
- if len(text) <= 64:
- _cache[text] = total_size
+ cached_result = _cache.get(text, None)
+ if cached_result is not None:
+ return cached_result
+
+ _get_size = get_character_cell_size
+ total_size = sum(_get_size(character) for character in text)
+ if len(text) <= 512:
+ _cache[text] = total_size
return total_size
@@ -42,9 +39,6 @@ def get_character_cell_size(character: str) -> int:
Returns:
int: Number of cells (0, 1 or 2) occupied by that character.
"""
- if _is_single_cell_widths(character):
- return 1
-
return _get_codepoint_cell_size(ord(character))
@@ -119,14 +113,12 @@ def chop_cells(text: str, max_size: int, position: int = 0) -> List[str]:
_get_character_cell_size = get_character_cell_size
characters = [
(character, _get_character_cell_size(character)) for character in text
- ][::-1]
+ ]
total_size = position
lines: List[List[str]] = [[]]
append = lines[-1].append
- pop = characters.pop
- while characters:
- character, size = pop()
+ for character, size in reversed(characters):
if total_size + size > max_size:
lines.append([character])
append = lines[-1].append
@@ -134,6 +126,7 @@ def chop_cells(text: str, max_size: int, position: int = 0) -> List[str]:
else:
total_size += size
append(character)
+
return ["".join(line) for line in lines]
diff --git a/src/pip/_vendor/rich/color.py b/src/pip/_vendor/rich/color.py
index f0fa026d6..6bca2da92 100644
--- a/src/pip/_vendor/rich/color.py
+++ b/src/pip/_vendor/rich/color.py
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, NamedTuple, Optional, Tuple
from ._palettes import EIGHT_BIT_PALETTE, STANDARD_PALETTE, WINDOWS_PALETTE
from .color_triplet import ColorTriplet
-from .repr import rich_repr, Result
+from .repr import Result, rich_repr
from .terminal_theme import DEFAULT_TERMINAL_THEME
if TYPE_CHECKING: # pragma: no cover
@@ -61,6 +61,7 @@ ANSI_COLOR_NAMES = {
"bright_cyan": 14,
"bright_white": 15,
"grey0": 16,
+ "gray0": 16,
"navy_blue": 17,
"dark_blue": 18,
"blue3": 20,
@@ -96,6 +97,7 @@ ANSI_COLOR_NAMES = {
"blue_violet": 57,
"orange4": 94,
"grey37": 59,
+ "gray37": 59,
"medium_purple4": 60,
"slate_blue3": 62,
"royal_blue1": 63,
@@ -128,7 +130,9 @@ ANSI_COLOR_NAMES = {
"yellow4": 106,
"wheat4": 101,
"grey53": 102,
+ "gray53": 102,
"light_slate_grey": 103,
+ "light_slate_gray": 103,
"medium_purple": 104,
"light_slate_blue": 105,
"dark_olive_green3": 149,
@@ -155,11 +159,13 @@ ANSI_COLOR_NAMES = {
"light_salmon3": 173,
"rosy_brown": 138,
"grey63": 139,
+ "gray63": 139,
"medium_purple1": 141,
"gold3": 178,
"dark_khaki": 143,
"navajo_white3": 144,
"grey69": 145,
+ "gray69": 145,
"light_steel_blue3": 146,
"light_steel_blue": 147,
"yellow3": 184,
@@ -189,6 +195,7 @@ ANSI_COLOR_NAMES = {
"light_goldenrod2": 222,
"light_yellow3": 187,
"grey84": 188,
+ "gray84": 188,
"light_steel_blue1": 189,
"yellow2": 190,
"dark_olive_green1": 192,
@@ -223,30 +230,55 @@ ANSI_COLOR_NAMES = {
"wheat1": 229,
"cornsilk1": 230,
"grey100": 231,
+ "gray100": 231,
"grey3": 232,
+ "gray3": 232,
"grey7": 233,
+ "gray7": 233,
"grey11": 234,
+ "gray11": 234,
"grey15": 235,
+ "gray15": 235,
"grey19": 236,
+ "gray19": 236,
"grey23": 237,
+ "gray23": 237,
"grey27": 238,
+ "gray27": 238,
"grey30": 239,
+ "gray30": 239,
"grey35": 240,
+ "gray35": 240,
"grey39": 241,
+ "gray39": 241,
"grey42": 242,
+ "gray42": 242,
"grey46": 243,
+ "gray46": 243,
"grey50": 244,
+ "gray50": 244,
"grey54": 245,
+ "gray54": 245,
"grey58": 246,
+ "gray58": 246,
"grey62": 247,
+ "gray62": 247,
"grey66": 248,
+ "gray66": 248,
"grey70": 249,
+ "gray70": 249,
"grey74": 250,
+ "gray74": 250,
"grey78": 251,
+ "gray78": 251,
"grey82": 252,
+ "gray82": 252,
"grey85": 253,
+ "gray85": 253,
"grey89": 254,
+ "gray89": 254,
"grey93": 255,
+ "gray93": 255,
}
@@ -279,8 +311,8 @@ class Color(NamedTuple):
def __rich__(self) -> "Text":
"""Dispays the actual color if Rich printed."""
- from .text import Text
from .style import Style
+ from .text import Text
return Text.assemble(
f"<color {self.name!r} ({self.type.name.lower()})",
@@ -569,11 +601,13 @@ if __name__ == "__main__": # pragma: no cover
colors = sorted((v, k) for k, v in ANSI_COLOR_NAMES.items())
for color_number, name in colors:
+ if "grey" in name:
+ continue
color_cell = Text(" " * 10, style=f"on {name}")
if color_number < 16:
table.add_row(color_cell, f"{color_number}", Text(f'"{name}"'))
else:
- color = EIGHT_BIT_PALETTE[color_number] # type: ignore
+ color = EIGHT_BIT_PALETTE[color_number] # type: ignore[has-type]
table.add_row(
color_cell, str(color_number), Text(f'"{name}"'), color.hex, color.rgb
)
diff --git a/src/pip/_vendor/rich/console.py b/src/pip/_vendor/rich/console.py
index 27e722760..8c305712d 100644
--- a/src/pip/_vendor/rich/console.py
+++ b/src/pip/_vendor/rich/console.py
@@ -1,4 +1,5 @@
import inspect
+import io
import os
import platform
import sys
@@ -11,7 +12,6 @@ from getpass import getpass
from html import escape
from inspect import isclass
from itertools import islice
-from threading import RLock
from time import monotonic
from types import FrameType, ModuleType, TracebackType
from typing import (
@@ -60,7 +60,7 @@ from .screen import Screen
from .segment import Segment
from .style import Style, StyleType
from .styled import Styled
-from .terminal_theme import DEFAULT_TERMINAL_THEME, TerminalTheme
+from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme
from .text import Text, TextType
from .theme import Theme, ThemeStack
@@ -82,6 +82,17 @@ class NoChange:
NO_CHANGE = NoChange()
+try:
+ _STDOUT_FILENO = sys.__stdout__.fileno()
+except Exception:
+ _STDOUT_FILENO = 1
+
+try:
+ _STDERR_FILENO = sys.__stderr__.fileno()
+except Exception:
+ _STDERR_FILENO = 2
+
+_STD_STREAMS = (_STDOUT_FILENO, _STDERR_FILENO)
CONSOLE_HTML_FORMAT = """\
<!DOCTYPE html>
@@ -104,6 +115,127 @@ body {{
</html>
"""
+CONSOLE_SVG_FORMAT = """\
+<svg width="{total_width}" height="{total_height}" viewBox="0 0 {total_width} {total_height}"
+ xmlns="http://www.w3.org/2000/svg">
+ <style>
+ @font-face {{
+ font-family: "Fira Code";
+ src: local("FiraCode-Regular"),
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
+ font-style: normal;
+ font-weight: 400;
+ }}
+ @font-face {{
+ font-family: "Fira Code";
+ src: local("FiraCode-Bold"),
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
+ font-style: bold;
+ font-weight: 700;
+ }}
+ span {{
+ display: inline-block;
+ white-space: pre;
+ vertical-align: top;
+ font-size: {font_size}px;
+ font-family:'Fira Code','Cascadia Code',Monaco,Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace;
+ }}
+ a {{
+ text-decoration: none;
+ color: inherit;
+ }}
+ .blink {{
+ animation: blinker 1s infinite;
+ }}
+ @keyframes blinker {{
+ from {{ opacity: 1.0; }}
+ 50% {{ opacity: 0.3; }}
+ to {{ opacity: 1.0; }}
+ }}
+ #wrapper {{
+ padding: {margin}px;
+ padding-top: 100px;
+ }}
+ #terminal {{
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background-color: {theme_background_color};
+ border-radius: 14px;
+ outline: 1px solid #484848;
+ }}
+ #terminal:after {{
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ content: '';
+ border-radius: 14px;
+ background: rgb(71,77,102);
+ background: linear-gradient(90deg, #804D69 0%, #4E4B89 100%);
+ transform: rotate(-4.5deg);
+ z-index: -1;
+ }}
+ #terminal-header {{
+ position: relative;
+ width: 100%;
+ background-color: #2e2e2e;
+ margin-bottom: 12px;
+ font-weight: bold;
+ border-radius: 14px 14px 0 0;
+ color: {theme_foreground_color};
+ font-size: 18px;
+ box-shadow: inset 0px -1px 0px 0px #4e4e4e,
+ inset 0px -4px 8px 0px #1a1a1a;
+ }}
+ #terminal-title-tab {{
+ display: inline-block;
+ margin-top: 14px;
+ margin-left: 124px;
+ font-family: sans-serif;
+ padding: 14px 28px;
+ border-radius: 6px 6px 0 0;
+ background-color: {theme_background_color};
+ box-shadow: inset 0px 1px 0px 0px #4e4e4e,
+ 0px -4px 4px 0px #1e1e1e,
+ inset 1px 0px 0px 0px #4e4e4e,
+ inset -1px 0px 0px 0px #4e4e4e;
+ }}
+ #terminal-traffic-lights {{
+ position: absolute;
+ top: 24px;
+ left: 20px;
+ }}
+ #terminal-body {{
+ line-height: {line_height}px;
+ padding: 14px;
+ }}
+ {stylesheet}
+ </style>
+ <foreignObject x="0" y="0" width="100%" height="100%">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <div id="wrapper">
+ <div id="terminal">
+ <div id='terminal-header'>
+ <svg id="terminal-traffic-lights" width="90" height="21" viewBox="0 0 90 21" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="14" cy="8" r="8" fill="#ff6159"/>
+ <circle cx="38" cy="8" r="8" fill="#ffbd2e"/>
+ <circle cx="62" cy="8" r="8" fill="#28c941"/>
+ </svg>
+ <div id="terminal-title-tab">{title}</div>
+ </div>
+ <div id='terminal-body'>
+ {code}
+ </div>
+ </div>
+ </div>
+ </body>
+ </foreignObject>
+</svg>
+"""
+
_TERM_COLORS = {"256color": ColorSystem.EIGHT_BIT, "16color": ColorSystem.STANDARD}
@@ -224,6 +356,16 @@ class ConsoleOptions:
options.max_height = options.height = height
return options
+ def reset_height(self) -> "ConsoleOptions":
+ """Return a copy of the options with height set to ``None``.
+
+ Returns:
+ ~ConsoleOptions: New console options instance.
+ """
+ options = self.copy()
+ options.height = None
+ return options
+
def update_dimensions(self, width: int, height: int) -> "ConsoleOptions":
"""Update the width and height, and return a copy.
@@ -244,7 +386,9 @@ class ConsoleOptions:
class RichCast(Protocol):
"""An object that may be 'cast' to a console renderable."""
- def __rich__(self) -> Union["ConsoleRenderable", str]: # pragma: no cover
+ def __rich__(
+ self,
+ ) -> Union["ConsoleRenderable", "RichCast", str]: # pragma: no cover
...
@@ -261,11 +405,9 @@ class ConsoleRenderable(Protocol):
# A type that may be rendered by Console.
RenderableType = Union[ConsoleRenderable, RichCast, str]
-
# The result of calling a __rich_console__ method.
RenderResult = Iterable[Union[RenderableType, Segment]]
-
_null_highlighter = NullHighlighter()
@@ -501,10 +643,10 @@ def group(fit: bool = True) -> Callable[..., Callable[..., Group]]:
def _is_jupyter() -> bool: # pragma: no cover
"""Check if we're running in a Jupyter notebook."""
try:
- get_ipython # type: ignore
+ get_ipython # type: ignore[name-defined]
except NameError:
return False
- ipython = get_ipython() # type: ignore
+ ipython = get_ipython() # type: ignore[name-defined]
shell = ipython.__class__.__name__
if "google.colab" in str(ipython.__class__) or shell == "ZMQInteractiveShell":
return True # Jupyter notebook or qtconsole
@@ -521,7 +663,6 @@ COLOR_SYSTEMS = {
"windows": ColorSystem.WINDOWS,
}
-
_COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()}
@@ -571,12 +712,6 @@ def detect_legacy_windows() -> bool:
return WINDOWS and not get_windows_console_features().vt
-if detect_legacy_windows(): # pragma: no cover
- from pip._vendor.colorama import init
-
- init(strip=False)
-
-
class Console:
"""A high level console interface.
@@ -597,7 +732,7 @@ class Console:
no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None.
tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8.
record (bool, optional): Boolean to enable recording of terminal output,
- required to call :meth:`export_html` and :meth:`export_text`. Defaults to False.
+ required to call :meth:`export_html`, :meth:`export_svg`, and :meth:`export_text`. Defaults to False.
markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True.
emoji (bool, optional): Enable emoji code. Defaults to True.
emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None.
@@ -1141,7 +1276,7 @@ class Console:
Args:
show (bool, optional): Set visibility of the cursor.
"""
- if self.is_terminal and not self.legacy_windows:
+ if self.is_terminal:
self.control(Control.show_cursor(show))
return True
return False
@@ -1232,7 +1367,7 @@ class Console:
renderable = rich_cast(renderable)
if hasattr(renderable, "__rich_console__") and not isclass(renderable):
- render_iterable = renderable.__rich_console__(self, _options) # type: ignore
+ render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr]
elif isinstance(renderable, str):
text_renderable = self.render_str(
renderable, highlight=_options.highlight, markup=_options.markup
@@ -1251,6 +1386,7 @@ class Console:
f"object {render_iterable!r} is not renderable"
)
_Segment = Segment
+ _options = _options.reset_height()
for render_output in iter_render:
if isinstance(render_output, _Segment):
yield render_output
@@ -1322,7 +1458,7 @@ class Console:
highlight: Optional[bool] = None,
highlighter: Optional[HighlighterType] = None,
) -> "Text":
- """Convert a string to a Text instance. This is is called automatically if
+ """Convert a string to a Text instance. This is called automatically if
you print or log a string.
Args:
@@ -1372,7 +1508,7 @@ class Console:
def get_style(
self, name: Union[str, Style], *, default: Optional[Union[Style, str]] = None
) -> Style:
- """Get a Style instance by it's theme name or parse a definition.
+ """Get a Style instance by its theme name or parse a definition.
Args:
name (str): The name of a style or a style definition.
@@ -1904,43 +2040,72 @@ class Console:
buffer_extend(line)
def _check_buffer(self) -> None:
- """Check if the buffer may be rendered."""
+ """Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False)
+ Rendering is supported on Windows, Unix and Jupyter environments. For
+ legacy Windows consoles, the win32 API is called directly.
+ This method will also record what it renders if recording is enabled via Console.record.
+ """
if self.quiet:
del self._buffer[:]
return
with self._lock:
if self._buffer_index == 0:
+
+ if self.record:
+ with self._record_buffer_lock:
+ self._record_buffer.extend(self._buffer[:])
+
if self.is_jupyter: # pragma: no cover
from .jupyter import display
display(self._buffer, self._render_buffer(self._buffer[:]))
del self._buffer[:]
else:
- text = self._render_buffer(self._buffer[:])
- del self._buffer[:]
- if text:
- try:
- if WINDOWS: # pragma: no cover
- # https://bugs.python.org/issue37871
- write = self.file.write
- for line in text.splitlines(True):
+ if WINDOWS:
+ use_legacy_windows_render = False
+ if self.legacy_windows:
+ try:
+ use_legacy_windows_render = (
+ self.file.fileno() in _STD_STREAMS
+ )
+ except (ValueError, io.UnsupportedOperation):
+ pass
+
+ if use_legacy_windows_render:
+ from pip._vendor.rich._win32_console import LegacyWindowsTerm
+ from pip._vendor.rich._windows_renderer import legacy_windows_render
+
+ legacy_windows_render(
+ self._buffer[:], LegacyWindowsTerm(self.file)
+ )
+ else:
+ # Either a non-std stream on legacy Windows, or modern Windows.
+ text = self._render_buffer(self._buffer[:])
+ # https://bugs.python.org/issue37871
+ write = self.file.write
+ for line in text.splitlines(True):
+ try:
write(line)
- else:
- self.file.write(text)
- self.file.flush()
+ except UnicodeEncodeError as error:
+ error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***"
+ raise
+ else:
+ text = self._render_buffer(self._buffer[:])
+ try:
+ self.file.write(text)
except UnicodeEncodeError as error:
error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***"
raise
+ self.file.flush()
+ del self._buffer[:]
+
def _render_buffer(self, buffer: Iterable[Segment]) -> str:
"""Render buffered output, and clear buffer."""
output: List[str] = []
append = output.append
color_system = self._color_system
legacy_windows = self.legacy_windows
- if self.record:
- with self._record_buffer_lock:
- self._record_buffer.extend(buffer)
not_terminal = not self.is_terminal
if self.no_color and color_system:
buffer = Segment.remove_color(buffer)
@@ -1982,23 +2147,15 @@ class Console:
Returns:
str: Text read from stdin.
"""
- prompt_str = ""
if prompt:
- with self.capture() as capture:
- self.print(prompt, markup=markup, emoji=emoji, end="")
- prompt_str = capture.get()
- if self.legacy_windows:
- # Legacy windows doesn't like ANSI codes in getpass or input (colorama bug)?
- self.file.write(prompt_str)
- prompt_str = ""
+ self.print(prompt, markup=markup, emoji=emoji, end="")
if password:
- result = getpass(prompt_str, stream=stream)
+ result = getpass("", stream=stream)
else:
if stream:
- self.file.write(prompt_str)
result = stream.readline()
else:
- result = input(prompt_str)
+ result = input()
return result
def export_text(self, *, clear: bool = True, styles: bool = False) -> str:
@@ -2060,8 +2217,8 @@ class Console:
Args:
theme (TerminalTheme, optional): TerminalTheme object containing console colors.
clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
- code_format (str, optional): Format string to render HTML, should contain {foreground}
- {background} and {code}.
+ code_format (str, optional): Format string to render HTML. In addition to '{foreground}',
+ '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``.
inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files
larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag.
Defaults to False.
@@ -2137,8 +2294,8 @@ class Console:
path (str): Path to write html file.
theme (TerminalTheme, optional): TerminalTheme object containing console colors.
clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
- code_format (str, optional): Format string to render HTML, should contain {foreground}
- {background} and {code}.
+ code_format (str, optional): Format string to render HTML. In addition to '{foreground}',
+ '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``.
inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files
larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag.
Defaults to False.
@@ -2153,9 +2310,173 @@ class Console:
with open(path, "wt", encoding="utf-8") as write_file:
write_file.write(html)
+ def export_svg(
+ self,
+ *,
+ title: str = "Rich",
+ theme: Optional[TerminalTheme] = None,
+ clear: bool = True,
+ code_format: str = CONSOLE_SVG_FORMAT,
+ ) -> str:
+ """Generate an SVG string from the console contents (requires record=True in Console constructor)
+
+ Args:
+ title (str): The title of the tab in the output image
+ theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
+ clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
+ code_format (str): Format string used to generate the SVG. Rich will inject a number of variables
+ into the string in order to form the final SVG output. The default template used and the variables
+ injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
+
+ Returns:
+ str: The string representation of the SVG. That is, the ``code_format`` template with content injected.
+ """
+ assert (
+ self.record
+ ), "To export console contents set record=True in the constructor or instance"
+
+ _theme = theme or SVG_EXPORT_THEME
+
+ with self._record_buffer_lock:
+ segments = Segment.simplify(self._record_buffer)
+ segments = Segment.filter_control(segments)
+ parts = [(text, style or Style.null()) for text, style, _ in segments]
+ terminal_text = Text.assemble(*parts)
+ lines = terminal_text.wrap(self, width=self.width, overflow="fold")
+ segments = self.render(lines, options=self.options)
+ segment_lines = list(
+ Segment.split_and_crop_lines(
+ segments, length=self.width, include_new_lines=False
+ )
+ )
+
+ fragments: List[str] = []
+ theme_foreground_color = _theme.foreground_color.hex
+ theme_background_color = _theme.background_color.hex
+
+ theme_foreground_css = f"color: {theme_foreground_color}; text-decoration-color: {theme_foreground_color};"
+ theme_background_css = f"background-color: {theme_background_color};"
+
+ theme_css = theme_foreground_css + theme_background_css
+
+ styles: Dict[str, int] = {}
+ styles[theme_css] = 1
+
+ for line in segment_lines:
+ line_spans = []
+ for segment in line:
+ text, style, _ = segment
+ text = escape(text)
+ if style:
+ rules = style.get_html_style(_theme)
+ if style.link:
+ text = f'<a href="{style.link}">{text}</a>'
+
+ if style.blink or style.blink2:
+ text = f'<span class="blink">{text}</span>'
+
+ # If the style doesn't contain a color, we still
+ # need to make sure we output the default foreground color
+ # from the TerminalTheme.
+ if not style.reverse:
+ foreground_css = theme_foreground_css
+ background_css = theme_background_css
+ else:
+ foreground_css = f"color: {theme_background_color}; text-decoration-color: {theme_background_color};"
+ background_css = (
+ f"background-color: {theme_foreground_color};"
+ )
+
+ if style.color is None:
+ rules += f";{foreground_css}"
+ if style.bgcolor is None:
+ rules += f";{background_css}"
+
+ style_number = styles.setdefault(rules, len(styles) + 1)
+ text = f'<span class="r{style_number}">{text}</span>'
+ else:
+ text = f'<span class="r1">{text}</span>'
+ line_spans.append(text)
+
+ fragments.append(f"<div>{''.join(line_spans)}</div>")
+
+ stylesheet_rules = []
+ for style_rule, style_number in styles.items():
+ if style_rule:
+ stylesheet_rules.append(f".r{style_number} {{{ style_rule }}}")
+ stylesheet = "\n".join(stylesheet_rules)
+
+ if clear:
+ self._record_buffer.clear()
+
+ # These values are the ones that I found to work well after experimentation.
+ # Many of them can be tweaked, but too much variation from these values could
+ # result in visually broken output/clipping issues.
+ terminal_padding = 12
+ font_size = 18
+ line_height = font_size + 4
+ code_start_y = 60
+ required_code_height = line_height * len(lines)
+ margin = 140
+
+ # Monospace fonts are generally around 0.5-0.55 width/height ratio, but I've
+ # added extra width to ensure that the output SVG is big enough.
+ monospace_font_width_scale = 0.60
+
+ # This works out as a good heuristic for the final size of the drawn terminal.
+ terminal_height = required_code_height + code_start_y
+ terminal_width = (
+ self.width * monospace_font_width_scale * font_size
+ + 2 * terminal_padding
+ + self.width
+ )
+ total_height = terminal_height + 2 * margin
+ total_width = terminal_width + 2 * margin
+
+ rendered_code = code_format.format(
+ code="\n".join(fragments),
+ total_height=total_height,
+ total_width=total_width,
+ theme_foreground_color=theme_foreground_color,
+ theme_background_color=theme_background_color,
+ margin=margin,
+ font_size=font_size,
+ line_height=line_height,
+ title=title,
+ stylesheet=stylesheet,
+ )
+
+ return rendered_code
+
+ def save_svg(
+ self,
+ path: str,
+ *,
+ title: str = "Rich",
+ theme: Optional[TerminalTheme] = None,
+ clear: bool = True,
+ code_format: str = CONSOLE_SVG_FORMAT,
+ ) -> None:
+ """Generate an SVG file from the console contents (requires record=True in Console constructor).
+
+ Args:
+ path (str): The path to write the SVG to.
+ title (str): The title of the tab in the output image
+ theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
+ clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
+ code_format (str): Format string used to generate the SVG. Rich will inject a number of variables
+ into the string in order to form the final SVG output. The default template used and the variables
+ injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
+ """
+ svg = self.export_svg(
+ title=title, theme=theme, clear=clear, code_format=code_format
+ )
+ with open(path, "wt", encoding="utf-8") as write_file:
+ write_file.write(svg)
+
if __name__ == "__main__": # pragma: no cover
- console = Console()
+ console = Console(record=True)
console.log(
"JSONRPC [i]request[/i]",
@@ -2208,4 +2529,3 @@ if __name__ == "__main__": # pragma: no cover
},
}
)
- console.log("foo")
diff --git a/src/pip/_vendor/rich/control.py b/src/pip/_vendor/rich/control.py
index c98d0d7d9..e17b2c634 100644
--- a/src/pip/_vendor/rich/control.py
+++ b/src/pip/_vendor/rich/control.py
@@ -1,4 +1,4 @@
-from typing import Any, Callable, Dict, Iterable, List, TYPE_CHECKING, Union
+from typing import Callable, Dict, Iterable, List, TYPE_CHECKING, Union
from .segment import ControlCode, ControlType, Segment
diff --git a/src/pip/_vendor/rich/default_styles.py b/src/pip/_vendor/rich/default_styles.py
index 91ab232d3..cb7bfc192 100644
--- a/src/pip/_vendor/rich/default_styles.py
+++ b/src/pip/_vendor/rich/default_styles.py
@@ -2,7 +2,6 @@ from typing import Dict
from .style import Style
-
DEFAULT_STYLES: Dict[str, Style] = {
"none": Style.null(),
"reset": Style(
@@ -41,6 +40,7 @@ DEFAULT_STYLES: Dict[str, Style] = {
"inspect.attr.dunder": Style(color="yellow", italic=True, dim=True),
"inspect.callable": Style(bold=True, color="red"),
"inspect.def": Style(italic=True, color="bright_cyan"),
+ "inspect.class": Style(italic=True, color="bright_cyan"),
"inspect.error": Style(bold=True, color="red"),
"inspect.equals": Style(),
"inspect.help": Style(color="cyan"),
diff --git a/src/pip/_vendor/rich/diagnose.py b/src/pip/_vendor/rich/diagnose.py
index 38728da2a..518586ea8 100644
--- a/src/pip/_vendor/rich/diagnose.py
+++ b/src/pip/_vendor/rich/diagnose.py
@@ -1,6 +1,35 @@
-if __name__ == "__main__": # pragma: no cover
- from pip._vendor.rich.console import Console
- from pip._vendor.rich import inspect
+import os
+import platform
+
+from pip._vendor.rich import inspect
+from pip._vendor.rich.console import Console, get_windows_console_features
+from pip._vendor.rich.panel import Panel
+from pip._vendor.rich.pretty import Pretty
+
+def report() -> None: # pragma: no cover
+ """Print a report to the terminal with debugging information"""
console = Console()
inspect(console)
+ features = get_windows_console_features()
+ inspect(features)
+
+ env_names = (
+ "TERM",
+ "COLORTERM",
+ "CLICOLOR",
+ "NO_COLOR",
+ "TERM_PROGRAM",
+ "COLUMNS",
+ "LINES",
+ "JPY_PARENT_PID",
+ "VSCODE_VERBOSE_LOGGING",
+ )
+ env = {name: os.getenv(name) for name in env_names}
+ console.print(Panel.fit((Pretty(env)), title="[b]Environment Variables"))
+
+ console.print(f'platform="{platform.system()}"')
+
+
+if __name__ == "__main__": # pragma: no cover
+ report()
diff --git a/src/pip/_vendor/rich/filesize.py b/src/pip/_vendor/rich/filesize.py
index b3a0996b0..61be47510 100644
--- a/src/pip/_vendor/rich/filesize.py
+++ b/src/pip/_vendor/rich/filesize.py
@@ -13,7 +13,7 @@ See Also:
__all__ = ["decimal"]
-from typing import Iterable, List, Tuple, Optional
+from typing import Iterable, List, Optional, Tuple
def _to_str(
@@ -30,7 +30,7 @@ def _to_str(
return "{:,} bytes".format(size)
for i, suffix in enumerate(suffixes, 2): # noqa: B007
- unit = base ** i
+ unit = base**i
if size < unit:
break
return "{:,.{precision}f}{separator}{}".format(
@@ -44,7 +44,7 @@ def _to_str(
def pick_unit_and_suffix(size: int, suffixes: List[str], base: int) -> Tuple[int, str]:
"""Pick a suffix and base for the given size."""
for i, suffix in enumerate(suffixes):
- unit = base ** i
+ unit = base**i
if size < unit * base:
break
return unit, suffix
diff --git a/src/pip/_vendor/rich/highlighter.py b/src/pip/_vendor/rich/highlighter.py
index 8afdd017b..7bee4167e 100644
--- a/src/pip/_vendor/rich/highlighter.py
+++ b/src/pip/_vendor/rich/highlighter.py
@@ -1,7 +1,8 @@
+import re
from abc import ABC, abstractmethod
from typing import List, Union
-from .text import Text
+from .text import Span, Text
def _combine_regex(*regexes: str) -> str:
@@ -81,22 +82,22 @@ class ReprHighlighter(RegexHighlighter):
base_style = "repr."
highlights = [
- r"(?P<tag_start>\<)(?P<tag_name>[\w\-\.\:]*)(?P<tag_contents>[\w\W]*?)(?P<tag_end>\>)",
- r"(?P<attrib_name>[\w_]{1,50})=(?P<attrib_value>\"?[\w_]+\"?)?",
- r"(?P<brace>[\{\[\(\)\]\}])",
+ r"(?P<tag_start><)(?P<tag_name>[-\w.:|]*)(?P<tag_contents>[\w\W]*?)(?P<tag_end>>)",
+ r'(?P<attrib_name>[\w_]{1,50})=(?P<attrib_value>"?[\w_]+"?)?',
+ r"(?P<brace>[][{}()])",
_combine_regex(
r"(?P<ipv4>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})",
r"(?P<ipv6>([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})",
r"(?P<eui64>(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){3}[0-9A-Fa-f]{4})",
r"(?P<eui48>(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4})",
- r"(?P<call>[\w\.]*?)\(",
+ r"(?P<uuid>[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})",
+ r"(?P<call>[\w.]*?)\(",
r"\b(?P<bool_true>True)\b|\b(?P<bool_false>False)\b|\b(?P<none>None)\b",
r"(?P<ellipsis>\.\.\.)",
- r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
- r"(?P<path>\B(\/[\w\.\-\_\+]+)*\/)(?P<filename>[\w\.\-\_\+]*)?",
- r"(?<![\\\w])(?P<str>b?\'\'\'.*?(?<!\\)\'\'\'|b?\'.*?(?<!\\)\'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
- r"(?P<uuid>[a-fA-F0-9]{8}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{12})",
- r"(?P<url>(file|https|http|ws|wss):\/\/[0-9a-zA-Z\$\-\_\+\!`\(\)\,\.\?\/\;\:\&\=\%\#]*)",
+ r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[-+]?\d+?)?\b|0x[0-9a-fA-F]*)",
+ r"(?P<path>\B(/[-\w._+]+)*\/)(?P<filename>[-\w._+]*)?",
+ r"(?<![\\\w])(?P<str>b?'''.*?(?<!\\)'''|b?'.*?(?<!\\)'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
+ r"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#]*)",
),
]
@@ -104,17 +105,39 @@ class ReprHighlighter(RegexHighlighter):
class JSONHighlighter(RegexHighlighter):
"""Highlights JSON"""
+ # Captures the start and end of JSON strings, handling escaped quotes
+ JSON_STR = r"(?<![\\\w])(?P<str>b?\".*?(?<!\\)\")"
+ JSON_WHITESPACE = {" ", "\n", "\r", "\t"}
+
base_style = "json."
highlights = [
_combine_regex(
r"(?P<brace>[\{\[\(\)\]\}])",
r"\b(?P<bool_true>true)\b|\b(?P<bool_false>false)\b|\b(?P<null>null)\b",
r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
- r"(?<![\\\w])(?P<str>b?\".*?(?<!\\)\")",
+ JSON_STR,
),
- r"(?<![\\\w])(?P<key>b?\".*?(?<!\\)\")\:",
]
+ def highlight(self, text: Text) -> None:
+ super().highlight(text)
+
+ # Additional work to handle highlighting JSON keys
+ plain = text.plain
+ append = text.spans.append
+ whitespace = self.JSON_WHITESPACE
+ for match in re.finditer(self.JSON_STR, plain):
+ start, end = match.span()
+ cursor = end
+ while cursor < len(plain):
+ char = plain[cursor]
+ cursor += 1
+ if char == ":":
+ append(Span(start, end, "json.key"))
+ elif char in whitespace:
+ continue
+ break
+
if __name__ == "__main__": # pragma: no cover
from .console import Console
@@ -145,3 +168,6 @@ if __name__ == "__main__": # pragma: no cover
console.print(
"127.0.1.1 bar 192.168.1.4 2001:0db8:85a3:0000:0000:8a2e:0370:7334 foo"
)
+ import json
+
+ console.print_json(json.dumps(obj={"name": "apple", "count": 1}), indent=None)
diff --git a/src/pip/_vendor/rich/jupyter.py b/src/pip/_vendor/rich/jupyter.py
index bedf5cb19..2ff4acca1 100644
--- a/src/pip/_vendor/rich/jupyter.py
+++ b/src/pip/_vendor/rich/jupyter.py
@@ -1,9 +1,12 @@
-from typing import Any, Dict, Iterable, List
+from typing import TYPE_CHECKING, Any, Dict, Iterable, List
from . import get_console
from .segment import Segment
from .terminal_theme import DEFAULT_TERMINAL_THEME
+if TYPE_CHECKING:
+ from pip._vendor.rich.console import ConsoleRenderable
+
JUPYTER_HTML_FORMAT = """\
<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace">{code}</pre>
"""
@@ -33,10 +36,13 @@ class JupyterMixin:
__slots__ = ()
def _repr_mimebundle_(
- self, include: Iterable[str], exclude: Iterable[str], **kwargs: Any
+ self: "ConsoleRenderable",
+ include: Iterable[str],
+ exclude: Iterable[str],
+ **kwargs: Any,
) -> Dict[str, str]:
console = get_console()
- segments = list(console.render(self, console.options)) # type: ignore
+ segments = list(console.render(self, console.options))
html = _render_segments(segments)
text = console._render_buffer(segments)
data = {"text/plain": text, "text/html": html}
@@ -63,7 +69,7 @@ def _render_segments(segments: Iterable[Segment]) -> str:
rule = style.get_html_style(theme)
text = f'<span style="{rule}">{text}</span>' if rule else text
if style.link:
- text = f'<a href="{style.link}">{text}</a>'
+ text = f'<a href="{style.link}" target="_blank">{text}</a>'
append_fragment(text)
code = "".join(fragments)
diff --git a/src/pip/_vendor/rich/layout.py b/src/pip/_vendor/rich/layout.py
index 22a4c5478..1d704652e 100644
--- a/src/pip/_vendor/rich/layout.py
+++ b/src/pip/_vendor/rich/layout.py
@@ -73,6 +73,7 @@ class _Placeholder:
style=self.style,
title=self.highlighter(title),
border_style="blue",
+ height=height,
)
@@ -299,7 +300,7 @@ class Layout:
self._children.extend(_layouts)
def split_row(self, *layouts: Union["Layout", RenderableType]) -> None:
- """Split the layout in tow a row (Layouts side by side).
+ """Split the layout in to a row (layouts side by side).
Args:
*layouts (Layout): Positional arguments should be (sub) Layout instances.
diff --git a/src/pip/_vendor/rich/logging.py b/src/pip/_vendor/rich/logging.py
index 002f1f7bf..58188fd8a 100644
--- a/src/pip/_vendor/rich/logging.py
+++ b/src/pip/_vendor/rich/logging.py
@@ -2,7 +2,8 @@ import logging
from datetime import datetime
from logging import Handler, LogRecord
from pathlib import Path
-from typing import ClassVar, List, Optional, Type, Union
+from types import ModuleType
+from typing import ClassVar, List, Optional, Iterable, Type, Union
from . import get_console
from ._log_render import LogRender, FormatTimeCallable
@@ -37,10 +38,12 @@ class RichHandler(Handler):
tracebacks_theme (str, optional): Override pygments theme used in traceback.
tracebacks_word_wrap (bool, optional): Enable word wrapping of long tracebacks lines. Defaults to True.
tracebacks_show_locals (bool, optional): Enable display of locals in tracebacks. Defaults to False.
+ tracebacks_suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to 10.
locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%x %X] ".
+ keywords (List[str], optional): List of words to highlight instead of ``RichHandler.KEYWORDS``.
"""
KEYWORDS: ClassVar[Optional[List[str]]] = [
@@ -73,9 +76,11 @@ class RichHandler(Handler):
tracebacks_theme: Optional[str] = None,
tracebacks_word_wrap: bool = True,
tracebacks_show_locals: bool = False,
+ tracebacks_suppress: Iterable[Union[str, ModuleType]] = (),
locals_max_length: int = 10,
locals_max_string: int = 80,
log_time_format: Union[str, FormatTimeCallable] = "[%x %X]",
+ keywords: Optional[List[str]] = None,
) -> None:
super().__init__(level=level)
self.console = console or get_console()
@@ -96,8 +101,10 @@ class RichHandler(Handler):
self.tracebacks_theme = tracebacks_theme
self.tracebacks_word_wrap = tracebacks_word_wrap
self.tracebacks_show_locals = tracebacks_show_locals
+ self.tracebacks_suppress = tracebacks_suppress
self.locals_max_length = locals_max_length
self.locals_max_string = locals_max_string
+ self.keywords = keywords
def get_level_text(self, record: LogRecord) -> Text:
"""Get the level name from the record.
@@ -137,6 +144,7 @@ class RichHandler(Handler):
show_locals=self.tracebacks_show_locals,
locals_max_length=self.locals_max_length,
locals_max_string=self.locals_max_string,
+ suppress=self.tracebacks_suppress,
)
message = record.getMessage()
if self.formatter:
@@ -171,8 +179,12 @@ class RichHandler(Handler):
if highlighter:
message_text = highlighter(message_text)
- if self.KEYWORDS:
- message_text.highlight_words(self.KEYWORDS, "logging.keyword")
+ if self.keywords is None:
+ self.keywords = self.KEYWORDS
+
+ if self.keywords:
+ message_text.highlight_words(self.keywords, "logging.keyword")
+
return message_text
def render(
diff --git a/src/pip/_vendor/rich/markup.py b/src/pip/_vendor/rich/markup.py
index 619540202..b7150c1c5 100644
--- a/src/pip/_vendor/rich/markup.py
+++ b/src/pip/_vendor/rich/markup.py
@@ -1,21 +1,20 @@
+import re
from ast import literal_eval
from operator import attrgetter
-import re
from typing import Callable, Iterable, List, Match, NamedTuple, Optional, Tuple, Union
+from ._emoji_replace import _emoji_replace
+from .emoji import EmojiVariant
from .errors import MarkupError
from .style import Style
from .text import Span, Text
-from .emoji import EmojiVariant
-from ._emoji_replace import _emoji_replace
-
RE_TAGS = re.compile(
- r"""((\\*)\[([a-z#\/@].*?)\])""",
+ r"""((\\*)\[([a-z#/@][^[]*?)])""",
re.VERBOSE,
)
-RE_HANDLER = re.compile(r"^([\w\.]*?)(\(.*?\))?$")
+RE_HANDLER = re.compile(r"^([\w.]*?)(\(.*?\))?$")
class Tag(NamedTuple):
@@ -146,6 +145,8 @@ def render(
for position, plain_text, tag in _parse(markup):
if plain_text is not None:
+ # Handle open brace escapes, where the brace is not part of a tag.
+ plain_text = plain_text.replace("\\[", "[")
append(emoji_replace(plain_text) if emoji else plain_text)
elif tag is not None:
if tag.name.startswith("/"): # Closing tag
@@ -233,8 +234,8 @@ if __name__ == "__main__": # pragma: no cover
":warning-emoji: [bold red blink] DANGER![/]",
]
- from pip._vendor.rich.table import Table
from pip._vendor.rich import print
+ from pip._vendor.rich.table import Table
grid = Table("Markup", "Result", padding=(0, 1))
diff --git a/src/pip/_vendor/rich/measure.py b/src/pip/_vendor/rich/measure.py
index aea238df9..e12787c8b 100644
--- a/src/pip/_vendor/rich/measure.py
+++ b/src/pip/_vendor/rich/measure.py
@@ -1,5 +1,5 @@
from operator import itemgetter
-from typing import Callable, Iterable, NamedTuple, Optional, TYPE_CHECKING
+from typing import TYPE_CHECKING, Callable, Iterable, NamedTuple, Optional
from . import errors
from .protocol import is_renderable, rich_cast
@@ -96,7 +96,9 @@ class Measurement(NamedTuple):
if _max_width < 1:
return Measurement(0, 0)
if isinstance(renderable, str):
- renderable = console.render_str(renderable, markup=options.markup)
+ renderable = console.render_str(
+ renderable, markup=options.markup, highlight=False
+ )
renderable = rich_cast(renderable)
if is_renderable(renderable):
get_console_width: Optional[
diff --git a/src/pip/_vendor/rich/pager.py b/src/pip/_vendor/rich/pager.py
index dbfb973e3..a3f7aa62a 100644
--- a/src/pip/_vendor/rich/pager.py
+++ b/src/pip/_vendor/rich/pager.py
@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
-from typing import Any, Callable
+from typing import Any
class Pager(ABC):
diff --git a/src/pip/_vendor/rich/panel.py b/src/pip/_vendor/rich/panel.py
index 151fe5f01..fc2807c31 100644
--- a/src/pip/_vendor/rich/panel.py
+++ b/src/pip/_vendor/rich/panel.py
@@ -1,14 +1,13 @@
-from typing import Optional, TYPE_CHECKING
-
-from .box import Box, ROUNDED
+from typing import TYPE_CHECKING, Optional
from .align import AlignMethod
+from .box import ROUNDED, Box
from .jupyter import JupyterMixin
from .measure import Measurement, measure_renderables
from .padding import Padding, PaddingDimensions
+from .segment import Segment
from .style import StyleType
from .text import Text, TextType
-from .segment import Segment
if TYPE_CHECKING:
from .console import Console, ConsoleOptions, RenderableType, RenderResult
@@ -183,7 +182,7 @@ class Panel(JupyterMixin):
else:
title_text.align(self.title_align, width - 4, character=box.top)
yield Segment(box.top_left + box.top, border_style)
- yield from console.render(title_text)
+ yield from console.render(title_text, child_options.update_width(width - 4))
yield Segment(box.top + box.top_right, border_style)
yield new_line
@@ -202,7 +201,9 @@ class Panel(JupyterMixin):
else:
subtitle_text.align(self.subtitle_align, width - 4, character=box.bottom)
yield Segment(box.bottom_left + box.bottom, border_style)
- yield from console.render(subtitle_text)
+ yield from console.render(
+ subtitle_text, child_options.update_width(width - 4)
+ )
yield Segment(box.bottom + box.bottom_right, border_style)
yield new_line
@@ -235,8 +236,8 @@ if __name__ == "__main__": # pragma: no cover
c = Console()
+ from .box import DOUBLE, ROUNDED
from .padding import Padding
- from .box import ROUNDED, DOUBLE
p = Panel(
"Hello, World!",
diff --git a/src/pip/_vendor/rich/pretty.py b/src/pip/_vendor/rich/pretty.py
index 606ee3382..95f3d34f9 100644
--- a/src/pip/_vendor/rich/pretty.py
+++ b/src/pip/_vendor/rich/pretty.py
@@ -1,8 +1,8 @@
import builtins
+import collections
import dataclasses
import inspect
import os
-import re
import sys
from array import array
from collections import Counter, UserDict, UserList, defaultdict, deque
@@ -29,8 +29,7 @@ from pip._vendor.rich.repr import RichReprResult
try:
import attr as _attr_module
except ImportError: # pragma: no cover
- _attr_module = None # type: ignore
-
+ _attr_module = None # type: ignore[assignment]
from . import get_console
from ._loop import loop_last
@@ -80,6 +79,29 @@ def _is_dataclass_repr(obj: object) -> bool:
return False
+_dummy_namedtuple = collections.namedtuple("_dummy_namedtuple", [])
+
+
+def _has_default_namedtuple_repr(obj: object) -> bool:
+ """Check if an instance of namedtuple contains the default repr
+
+ Args:
+ obj (object): A namedtuple
+
+ Returns:
+ bool: True if the default repr is used, False if there's a custom repr.
+ """
+ obj_file = None
+ try:
+ obj_file = inspect.getfile(obj.__repr__)
+ except (OSError, TypeError):
+ # OSError handles case where object is defined in __main__ scope, e.g. REPL - no filename available.
+ # TypeError trapped defensively, in case of object without filename slips through.
+ pass
+ default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__)
+ return obj_file == default_repr_file
+
+
def _ipy_display_hook(
value: Any,
console: Optional["Console"] = None,
@@ -93,7 +115,7 @@ def _ipy_display_hook(
from .console import ConsoleRenderable # needed here to prevent circular import
# always skip rich generated jupyter renderables or None values
- if isinstance(value, JupyterRenderable) or value is None:
+ if _safe_isinstance(value, JupyterRenderable) or value is None:
return
console = console or get_console()
@@ -124,12 +146,12 @@ def _ipy_display_hook(
return # Delegate rendering to IPython
# certain renderables should start on a new line
- if isinstance(value, ConsoleRenderable):
+ if _safe_isinstance(value, ConsoleRenderable):
console.line()
console.print(
value
- if isinstance(value, RichRenderable)
+ if _safe_isinstance(value, RichRenderable)
else Pretty(
value,
overflow=overflow,
@@ -144,6 +166,16 @@ def _ipy_display_hook(
)
+def _safe_isinstance(
+ obj: object, class_or_tuple: Union[type, Tuple[type, ...]]
+) -> bool:
+ """isinstance can fail in rare cases, for example types with no __class__"""
+ try:
+ return isinstance(obj, class_or_tuple)
+ except Exception:
+ return False
+
+
def install(
console: Optional["Console"] = None,
overflow: "OverflowMethod" = "ignore",
@@ -175,10 +207,10 @@ def install(
"""Replacement sys.displayhook which prettifies objects with Rich."""
if value is not None:
assert console is not None
- builtins._ = None # type: ignore
+ builtins._ = None # type: ignore[attr-defined]
console.print(
value
- if isinstance(value, RichRenderable)
+ if _safe_isinstance(value, RichRenderable)
else Pretty(
value,
overflow=overflow,
@@ -189,13 +221,13 @@ def install(
),
crop=crop,
)
- builtins._ = value # type: ignore
+ builtins._ = value # type: ignore[attr-defined]
try: # pragma: no cover
- ip = get_ipython() # type: ignore
+ ip = get_ipython() # type: ignore[name-defined]
from IPython.core.formatters import BaseFormatter
- class RichFormatter(BaseFormatter): # type: ignore
+ class RichFormatter(BaseFormatter): # type: ignore[misc]
pprint: bool = True
def __call__(self, value: Any) -> Any:
@@ -314,6 +346,7 @@ class Pretty(JupyterMixin):
indent_size=self.indent_size,
max_length=self.max_length,
max_string=self.max_string,
+ expand_all=self.expand_all,
)
text_width = (
max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0
@@ -355,7 +388,7 @@ _MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict)
def is_expandable(obj: Any) -> bool:
"""Check if an object may be expanded by pretty print."""
return (
- isinstance(obj, _CONTAINERS)
+ _safe_isinstance(obj, _CONTAINERS)
or (is_dataclass(obj))
or (hasattr(obj, "__rich_repr__"))
or _is_attr_object(obj)
@@ -373,6 +406,7 @@ class Node:
empty: str = ""
last: bool = False
is_tuple: bool = False
+ is_namedtuple: bool = False
children: Optional[List["Node"]] = None
key_separator = ": "
separator: str = ", "
@@ -387,7 +421,7 @@ class Node:
elif self.children is not None:
if self.children:
yield self.open_brace
- if self.is_tuple and len(self.children) == 1:
+ if self.is_tuple and not self.is_namedtuple and len(self.children) == 1:
yield from self.children[0].iter_tokens()
yield ","
else:
@@ -514,6 +548,25 @@ class _Line:
)
+def _is_namedtuple(obj: Any) -> bool:
+ """Checks if an object is most likely a namedtuple. It is possible
+ to craft an object that passes this check and isn't a namedtuple, but
+ there is only a minuscule chance of this happening unintentionally.
+
+ Args:
+ obj (Any): The object to test
+
+ Returns:
+ bool: True if the object is a namedtuple. False otherwise.
+ """
+ try:
+ fields = getattr(obj, "_fields", None)
+ except Exception:
+ # Being very defensive - if we cannot get the attr then its not a namedtuple
+ return False
+ return isinstance(obj, tuple) and isinstance(fields, tuple)
+
+
def traverse(
_object: Any,
max_length: Optional[int] = None,
@@ -539,7 +592,7 @@ def traverse(
"""Get repr string for an object, but catch errors."""
if (
max_string is not None
- and isinstance(obj, (bytes, str))
+ and _safe_isinstance(obj, (bytes, str))
and len(obj) > max_string
):
truncated = len(obj) - max_string
@@ -565,7 +618,7 @@ def traverse(
def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
for arg in rich_args:
- if isinstance(arg, tuple):
+ if _safe_isinstance(arg, tuple):
if len(arg) == 3:
key, child, default = arg
if default == child:
@@ -622,7 +675,7 @@ def traverse(
last=root,
)
for last, arg in loop_last(args):
- if isinstance(arg, tuple):
+ if _safe_isinstance(arg, tuple):
key, child = arg
child_node = _traverse(child, depth=depth + 1)
child_node.last = last
@@ -689,7 +742,7 @@ def traverse(
elif (
is_dataclass(obj)
- and not isinstance(obj, type)
+ and not _safe_isinstance(obj, type)
and not fake_attributes
and (_is_dataclass_repr(obj) or py_version == (3, 6))
):
@@ -721,10 +774,28 @@ def traverse(
append(child_node)
pop_visited(obj_id)
-
- elif isinstance(obj, _CONTAINERS):
+ elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj):
+ if reached_max_depth:
+ node = Node(value_repr="...")
+ else:
+ children = []
+ class_name = obj.__class__.__name__
+ node = Node(
+ open_brace=f"{class_name}(",
+ close_brace=")",
+ children=children,
+ empty=f"{class_name}()",
+ )
+ append = children.append
+ for last, (key, value) in loop_last(obj._asdict().items()):
+ child_node = _traverse(value, depth=depth + 1)
+ child_node.key_repr = key
+ child_node.last = last
+ child_node.key_separator = "="
+ append(child_node)
+ elif _safe_isinstance(obj, _CONTAINERS):
for container_type in _CONTAINERS:
- if isinstance(obj, container_type):
+ if _safe_isinstance(obj, container_type):
obj_type = container_type
break
@@ -752,7 +823,7 @@ def traverse(
num_items = len(obj)
last_item_index = num_items - 1
- if isinstance(obj, _MAPPING_CONTAINERS):
+ if _safe_isinstance(obj, _MAPPING_CONTAINERS):
iter_items = iter(obj.items())
if max_length is not None:
iter_items = islice(iter_items, max_length)
@@ -770,14 +841,15 @@ def traverse(
child_node.last = index == last_item_index
append(child_node)
if max_length is not None and num_items > max_length:
- append(Node(value_repr=f"... +{num_items-max_length}", last=True))
+ append(Node(value_repr=f"... +{num_items - max_length}", last=True))
else:
node = Node(empty=empty, children=[], last=root)
pop_visited(obj_id)
else:
node = Node(value_repr=to_repr(obj), last=root)
- node.is_tuple = isinstance(obj, tuple)
+ node.is_tuple = _safe_isinstance(obj, tuple)
+ node.is_namedtuple = _is_namedtuple(obj)
return node
node = _traverse(_object, root=True)
@@ -812,13 +884,13 @@ def pretty_repr(
str: A possibly multi-line representation of the object.
"""
- if isinstance(_object, Node):
+ if _safe_isinstance(_object, Node):
node = _object
else:
node = traverse(
_object, max_length=max_length, max_string=max_string, max_depth=max_depth
)
- repr_str = node.render(
+ repr_str: str = node.render(
max_width=max_width, indent_size=indent_size, expand_all=expand_all
)
return repr_str
@@ -868,6 +940,15 @@ if __name__ == "__main__": # pragma: no cover
1 / 0
return "this will fail"
+ from typing import NamedTuple
+
+ class StockKeepingUnit(NamedTuple):
+ name: str
+ description: str
+ price: float
+ category: str
+ reviews: List[str]
+
d = defaultdict(int)
d["foo"] = 5
data = {
@@ -894,9 +975,16 @@ if __name__ == "__main__": # pragma: no cover
]
),
"atomic": (False, True, None),
+ "namedtuple": StockKeepingUnit(
+ "Sparkling British Spring Water",
+ "Carbonated spring water",
+ 0.9,
+ "water",
+ ["its amazing!", "its terrible!"],
+ ),
"Broken": BrokenRepr(),
}
- data["foo"].append(data) # type: ignore
+ data["foo"].append(data) # type: ignore[attr-defined]
from pip._vendor.rich import print
diff --git a/src/pip/_vendor/rich/progress.py b/src/pip/_vendor/rich/progress.py
index 1f670db43..5140eda63 100644
--- a/src/pip/_vendor/rich/progress.py
+++ b/src/pip/_vendor/rich/progress.py
@@ -1,28 +1,44 @@
+import io
+import sys
+import typing
+import warnings
from abc import ABC, abstractmethod
from collections import deque
from collections.abc import Sized
from dataclasses import dataclass, field
from datetime import timedelta
+from io import RawIOBase, UnsupportedOperation
from math import ceil
+from mmap import mmap
+from os import PathLike, stat
from threading import Event, RLock, Thread
from types import TracebackType
from typing import (
Any,
+ BinaryIO,
Callable,
+ ContextManager,
Deque,
Dict,
+ Generic,
Iterable,
List,
NamedTuple,
NewType,
Optional,
Sequence,
+ TextIO,
Tuple,
Type,
TypeVar,
Union,
)
+if sys.version_info >= (3, 8):
+ from typing import Literal
+else:
+ from pip._vendor.typing_extensions import Literal # pragma: no cover
+
from . import filesize, get_console
from .console import Console, JustifyMethod, RenderableType, Group
from .highlighter import Highlighter
@@ -41,6 +57,9 @@ ProgressType = TypeVar("ProgressType")
GetTimeCallable = Callable[[], float]
+_I = typing.TypeVar("_I", TextIO, BinaryIO)
+
+
class _TrackThread(Thread):
"""A thread to periodically update progress."""
@@ -149,6 +168,320 @@ def track(
)
+class _Reader(RawIOBase, BinaryIO):
+ """A reader that tracks progress while it's being read from."""
+
+ def __init__(
+ self,
+ handle: BinaryIO,
+ progress: "Progress",
+ task: TaskID,
+ close_handle: bool = True,
+ ) -> None:
+ self.handle = handle
+ self.progress = progress
+ self.task = task
+ self.close_handle = close_handle
+ self._closed = False
+
+ def __enter__(self) -> "_Reader":
+ self.handle.__enter__()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self.close()
+
+ def __iter__(self) -> BinaryIO:
+ return self
+
+ def __next__(self) -> bytes:
+ line = next(self.handle)
+ self.progress.advance(self.task, advance=len(line))
+ return line
+
+ @property
+ def closed(self) -> bool:
+ return self._closed
+
+ def fileno(self) -> int:
+ return self.handle.fileno()
+
+ def isatty(self) -> bool:
+ return self.handle.isatty()
+
+ def readable(self) -> bool:
+ return self.handle.readable()
+
+ def seekable(self) -> bool:
+ return self.handle.seekable()
+
+ def writable(self) -> bool:
+ return False
+
+ def read(self, size: int = -1) -> bytes:
+ block = self.handle.read(size)
+ self.progress.advance(self.task, advance=len(block))
+ return block
+
+ def readinto(self, b: Union[bytearray, memoryview, mmap]): # type: ignore[no-untyped-def, override]
+ n = self.handle.readinto(b) # type: ignore[attr-defined]
+ self.progress.advance(self.task, advance=n)
+ return n
+
+ def readline(self, size: int = -1) -> bytes: # type: ignore[override]
+ line = self.handle.readline(size)
+ self.progress.advance(self.task, advance=len(line))
+ return line
+
+ def readlines(self, hint: int = -1) -> List[bytes]:
+ lines = self.handle.readlines(hint)
+ self.progress.advance(self.task, advance=sum(map(len, lines)))
+ return lines
+
+ def close(self) -> None:
+ if self.close_handle:
+ self.handle.close()
+ self._closed = True
+
+ def seek(self, offset: int, whence: int = 0) -> int:
+ pos = self.handle.seek(offset, whence)
+ self.progress.update(self.task, completed=pos)
+ return pos
+
+ def tell(self) -> int:
+ return self.handle.tell()
+
+ def write(self, s: Any) -> int:
+ raise UnsupportedOperation("write")
+
+
+class _ReadContext(ContextManager[_I], Generic[_I]):
+ """A utility class to handle a context for both a reader and a progress."""
+
+ def __init__(self, progress: "Progress", reader: _I) -> None:
+ self.progress = progress
+ self.reader: _I = reader
+
+ def __enter__(self) -> _I:
+ self.progress.start()
+ return self.reader.__enter__()
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self.progress.stop()
+ self.reader.__exit__(exc_type, exc_val, exc_tb)
+
+
+def wrap_file(
+ file: BinaryIO,
+ total: int,
+ *,
+ description: str = "Reading...",
+ auto_refresh: bool = True,
+ console: Optional[Console] = None,
+ transient: bool = False,
+ get_time: Optional[Callable[[], float]] = None,
+ refresh_per_second: float = 10,
+ style: StyleType = "bar.back",
+ complete_style: StyleType = "bar.complete",
+ finished_style: StyleType = "bar.finished",
+ pulse_style: StyleType = "bar.pulse",
+ disable: bool = False,
+) -> ContextManager[BinaryIO]:
+ """Read bytes from a file while tracking progress.
+
+ Args:
+ file (Union[str, PathLike[str], BinaryIO]): The path to the file to read, or a file-like object in binary mode.
+ total (int): Total number of bytes to read.
+ description (str, optional): Description of task show next to progress bar. Defaults to "Reading".
+ auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
+ transient: (bool, optional): Clear the progress on exit. Defaults to False.
+ console (Console, optional): Console to write to. Default creates internal Console instance.
+ refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10.
+ style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
+ complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
+ finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done".
+ pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
+ disable (bool, optional): Disable display of progress.
+ Returns:
+ ContextManager[BinaryIO]: A context manager yielding a progress reader.
+
+ """
+
+ columns: List["ProgressColumn"] = (
+ [TextColumn("[progress.description]{task.description}")] if description else []
+ )
+ columns.extend(
+ (
+ BarColumn(
+ style=style,
+ complete_style=complete_style,
+ finished_style=finished_style,
+ pulse_style=pulse_style,
+ ),
+ DownloadColumn(),
+ TimeRemainingColumn(),
+ )
+ )
+ progress = Progress(
+ *columns,
+ auto_refresh=auto_refresh,
+ console=console,
+ transient=transient,
+ get_time=get_time,
+ refresh_per_second=refresh_per_second or 10,
+ disable=disable,
+ )
+
+ reader = progress.wrap_file(file, total=total, description=description)
+ return _ReadContext(progress, reader)
+
+
+@typing.overload
+def open(
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Union[Literal["rt"], Literal["r"]],
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ description: str = "Reading...",
+ auto_refresh: bool = True,
+ console: Optional[Console] = None,
+ transient: bool = False,
+ get_time: Optional[Callable[[], float]] = None,
+ refresh_per_second: float = 10,
+ style: StyleType = "bar.back",
+ complete_style: StyleType = "bar.complete",
+ finished_style: StyleType = "bar.finished",
+ pulse_style: StyleType = "bar.pulse",
+ disable: bool = False,
+) -> ContextManager[TextIO]:
+ pass
+
+
+@typing.overload
+def open(
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Literal["rb"],
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ description: str = "Reading...",
+ auto_refresh: bool = True,
+ console: Optional[Console] = None,
+ transient: bool = False,
+ get_time: Optional[Callable[[], float]] = None,
+ refresh_per_second: float = 10,
+ style: StyleType = "bar.back",
+ complete_style: StyleType = "bar.complete",
+ finished_style: StyleType = "bar.finished",
+ pulse_style: StyleType = "bar.pulse",
+ disable: bool = False,
+) -> ContextManager[BinaryIO]:
+ pass
+
+
+def open(
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Union[Literal["rb"], Literal["rt"], Literal["r"]] = "r",
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ description: str = "Reading...",
+ auto_refresh: bool = True,
+ console: Optional[Console] = None,
+ transient: bool = False,
+ get_time: Optional[Callable[[], float]] = None,
+ refresh_per_second: float = 10,
+ style: StyleType = "bar.back",
+ complete_style: StyleType = "bar.complete",
+ finished_style: StyleType = "bar.finished",
+ pulse_style: StyleType = "bar.pulse",
+ disable: bool = False,
+) -> Union[ContextManager[BinaryIO], ContextManager[TextIO]]:
+ """Read bytes from a file while tracking progress.
+
+ Args:
+ path (Union[str, PathLike[str], BinaryIO]): The path to the file to read, or a file-like object in binary mode.
+ mode (str): The mode to use to open the file. Only supports "r", "rb" or "rt".
+ buffering (int): The buffering strategy to use, see :func:`io.open`.
+ encoding (str, optional): The encoding to use when reading in text mode, see :func:`io.open`.
+ errors (str, optional): The error handling strategy for decoding errors, see :func:`io.open`.
+ newline (str, optional): The strategy for handling newlines in text mode, see :func:`io.open`
+ total: (int, optional): Total number of bytes to read. Must be provided if reading from a file handle. Default for a path is os.stat(file).st_size.
+ description (str, optional): Description of task show next to progress bar. Defaults to "Reading".
+ auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
+ transient: (bool, optional): Clear the progress on exit. Defaults to False.
+ console (Console, optional): Console to write to. Default creates internal Console instance.
+ refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10.
+ style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
+ complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
+ finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done".
+ pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
+ disable (bool, optional): Disable display of progress.
+ encoding (str, optional): The encoding to use when reading in text mode.
+
+ Returns:
+ ContextManager[BinaryIO]: A context manager yielding a progress reader.
+
+ """
+
+ columns: List["ProgressColumn"] = (
+ [TextColumn("[progress.description]{task.description}")] if description else []
+ )
+ columns.extend(
+ (
+ BarColumn(
+ style=style,
+ complete_style=complete_style,
+ finished_style=finished_style,
+ pulse_style=pulse_style,
+ ),
+ DownloadColumn(),
+ TimeRemainingColumn(),
+ )
+ )
+ progress = Progress(
+ *columns,
+ auto_refresh=auto_refresh,
+ console=console,
+ transient=transient,
+ get_time=get_time,
+ refresh_per_second=refresh_per_second or 10,
+ disable=disable,
+ )
+
+ reader = progress.open(
+ file,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ errors=errors,
+ newline=newline,
+ total=total,
+ description=description,
+ )
+ return _ReadContext(progress, reader) # type: ignore[return-value, type-var]
+
+
class ProgressColumn(ABC):
"""Base class for a widget to use in progress display."""
@@ -343,18 +676,48 @@ class TimeElapsedColumn(ProgressColumn):
class TimeRemainingColumn(ProgressColumn):
- """Renders estimated time remaining."""
+ """Renders estimated time remaining.
+
+ Args:
+ compact (bool, optional): Render MM:SS when time remaining is less than an hour. Defaults to False.
+ elapsed_when_finished (bool, optional): Render time elapsed when the task is finished. Defaults to False.
+ """
# Only refresh twice a second to prevent jitter
max_refresh = 0.5
+ def __init__(
+ self,
+ compact: bool = False,
+ elapsed_when_finished: bool = False,
+ table_column: Optional[Column] = None,
+ ):
+ self.compact = compact
+ self.elapsed_when_finished = elapsed_when_finished
+ super().__init__(table_column=table_column)
+
def render(self, task: "Task") -> Text:
"""Show time remaining."""
- remaining = task.time_remaining
- if remaining is None:
- return Text("-:--:--", style="progress.remaining")
- remaining_delta = timedelta(seconds=int(remaining))
- return Text(str(remaining_delta), style="progress.remaining")
+ if self.elapsed_when_finished and task.finished:
+ task_time = task.finished_time
+ style = "progress.elapsed"
+ else:
+ task_time = task.time_remaining
+ style = "progress.remaining"
+
+ if task_time is None:
+ return Text("--:--" if self.compact else "-:--:--", style=style)
+
+ # Based on https://github.com/tqdm/tqdm/blob/master/tqdm/std.py
+ minutes, seconds = divmod(int(task_time), 60)
+ hours, minutes = divmod(minutes, 60)
+
+ if self.compact and not hours:
+ formatted = f"{minutes:02d}:{seconds:02d}"
+ else:
+ formatted = f"{hours:d}:{minutes:02d}:{seconds:02d}"
+
+ return Text(formatted, style=style)
class FileSizeColumn(ProgressColumn):
@@ -375,6 +738,33 @@ class TotalFileSizeColumn(ProgressColumn):
return Text(data_size, style="progress.filesize.total")
+class MofNCompleteColumn(ProgressColumn):
+ """Renders completed count/total, e.g. ' 10/1000'.
+
+ Best for bounded tasks with int quantities.
+
+ Space pads the completed count so that progress length does not change as task progresses
+ past powers of 10.
+
+ Args:
+ separator (str, optional): Text to separate completed and total values. Defaults to "/".
+ """
+
+ def __init__(self, separator: str = "/", table_column: Optional[Column] = None):
+ self.separator = separator
+ super().__init__(table_column=table_column)
+
+ def render(self, task: "Task") -> Text:
+ """Show completed/total."""
+ completed = int(task.completed)
+ total = int(task.total)
+ total_width = len(str(total))
+ return Text(
+ f"{completed:{total_width}d}{self.separator}{total}",
+ style="progress.download",
+ )
+
+
class DownloadColumn(ProgressColumn):
"""Renders file size downloaded and total, e.g. '0.5/2.3 GB'.
@@ -400,7 +790,9 @@ class DownloadColumn(ProgressColumn):
)
else:
unit, suffix = filesize.pick_unit_and_suffix(
- total, ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], 1000
+ total,
+ ["bytes", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
+ 1000,
)
completed_ratio = completed / unit
total_ratio = total / unit
@@ -475,7 +867,7 @@ class Task:
"""Optional[float]: The last speed for a finished task."""
_progress: Deque[ProgressSample] = field(
- default_factory=deque, init=False, repr=False
+ default_factory=lambda: deque(maxlen=1000), init=False, repr=False
)
_lock: RLock = field(repr=False, default_factory=RLock)
@@ -588,12 +980,7 @@ class Progress(JupyterMixin):
refresh_per_second is None or refresh_per_second > 0
), "refresh_per_second must be > 0"
self._lock = RLock()
- self.columns = columns or (
- TextColumn("[progress.description]{task.description}"),
- BarColumn(),
- TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
- TimeRemainingColumn(),
- )
+ self.columns = columns or self.get_default_columns()
self.speed_estimate_period = speed_estimate_period
self.disable = disable
@@ -613,6 +1000,37 @@ class Progress(JupyterMixin):
self.print = self.console.print
self.log = self.console.log
+ @classmethod
+ def get_default_columns(cls) -> Tuple[ProgressColumn, ...]:
+ """Get the default columns used for a new Progress instance:
+ - a text column for the description (TextColumn)
+ - the bar itself (BarColumn)
+ - a text column showing completion percentage (TextColumn)
+ - an estimated-time-remaining column (TimeRemainingColumn)
+ If the Progress instance is created without passing a columns argument,
+ the default columns defined here will be used.
+
+ You can also create a Progress instance using custom columns before
+ and/or after the defaults, as in this example:
+
+ progress = Progress(
+ SpinnerColumn(),
+ *Progress.default_columns(),
+ "Elapsed:",
+ TimeElapsedColumn(),
+ )
+
+ This code shows the creation of a Progress display, containing
+ a spinner to the left, the default columns, and a labeled elapsed
+ time column.
+ """
+ return (
+ TextColumn("[progress.description]{task.description}"),
+ BarColumn(),
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
+ TimeRemainingColumn(),
+ )
+
@property
def console(self) -> Console:
return self.live.console
@@ -709,6 +1127,157 @@ class Progress(JupyterMixin):
advance(task_id, 1)
refresh()
+ def wrap_file(
+ self,
+ file: BinaryIO,
+ total: Optional[int] = None,
+ *,
+ task_id: Optional[TaskID] = None,
+ description: str = "Reading...",
+ ) -> BinaryIO:
+ """Track progress file reading from a binary file.
+
+ Args:
+ file (BinaryIO): A file-like object opened in binary mode.
+ total (int, optional): Total number of bytes to read. This must be provided unless a task with a total is also given.
+ task_id (TaskID): Task to track. Default is new task.
+ description (str, optional): Description of task, if new task is created.
+
+ Returns:
+ BinaryIO: A readable file-like object in binary mode.
+
+ Raises:
+ ValueError: When no total value can be extracted from the arguments or the task.
+ """
+ # attempt to recover the total from the task
+ total_bytes: Optional[float] = None
+ if total is not None:
+ total_bytes = total
+ elif task_id is not None:
+ with self._lock:
+ total_bytes = self._tasks[task_id].total
+ if total_bytes is None:
+ raise ValueError(
+ f"unable to get the total number of bytes, please specify 'total'"
+ )
+
+ # update total of task or create new task
+ if task_id is None:
+ task_id = self.add_task(description, total=total_bytes)
+ else:
+ self.update(task_id, total=total_bytes)
+
+ return _Reader(file, self, task_id, close_handle=False)
+
+ @typing.overload
+ def open(
+ self,
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Literal["rb"],
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ task_id: Optional[TaskID] = None,
+ description: str = "Reading...",
+ ) -> BinaryIO:
+ pass
+
+ @typing.overload
+ def open(
+ self,
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Union[Literal["r"], Literal["rt"]],
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ task_id: Optional[TaskID] = None,
+ description: str = "Reading...",
+ ) -> TextIO:
+ pass
+
+ def open(
+ self,
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Union[Literal["rb"], Literal["rt"], Literal["r"]] = "r",
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ task_id: Optional[TaskID] = None,
+ description: str = "Reading...",
+ ) -> Union[BinaryIO, TextIO]:
+ """Track progress while reading from a binary file.
+
+ Args:
+ path (Union[str, PathLike[str]]): The path to the file to read.
+ mode (str): The mode to use to open the file. Only supports "r", "rb" or "rt".
+ buffering (int): The buffering strategy to use, see :func:`io.open`.
+ encoding (str, optional): The encoding to use when reading in text mode, see :func:`io.open`.
+ errors (str, optional): The error handling strategy for decoding errors, see :func:`io.open`.
+ newline (str, optional): The strategy for handling newlines in text mode, see :func:`io.open`.
+ total (int, optional): Total number of bytes to read. If none given, os.stat(path).st_size is used.
+ task_id (TaskID): Task to track. Default is new task.
+ description (str, optional): Description of task, if new task is created.
+
+ Returns:
+ BinaryIO: A readable file-like object in binary mode.
+
+ Raises:
+ ValueError: When an invalid mode is given.
+ """
+ # normalize the mode (always rb, rt)
+ _mode = "".join(sorted(mode, reverse=False))
+ if _mode not in ("br", "rt", "r"):
+ raise ValueError("invalid mode {!r}".format(mode))
+
+ # patch buffering to provide the same behaviour as the builtin `open`
+ line_buffering = buffering == 1
+ if _mode == "br" and buffering == 1:
+ warnings.warn(
+ "line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used",
+ RuntimeWarning,
+ )
+ buffering = -1
+ elif _mode == "rt" or _mode == "r":
+ if buffering == 0:
+ raise ValueError("can't have unbuffered text I/O")
+ elif buffering == 1:
+ buffering = -1
+
+ # attempt to get the total with `os.stat`
+ if total is None:
+ total = stat(file).st_size
+
+ # update total of task or create new task
+ if task_id is None:
+ task_id = self.add_task(description, total=total)
+ else:
+ self.update(task_id, total=total)
+
+ # open the file in binary mode,
+ handle = io.open(file, "rb", buffering=buffering)
+ reader = _Reader(handle, self, task_id, close_handle=True)
+
+ # wrap the reader in a `TextIOWrapper` if text mode
+ if mode == "r" or mode == "rt":
+ return io.TextIOWrapper(
+ reader,
+ encoding=encoding,
+ errors=errors,
+ newline=newline,
+ line_buffering=line_buffering,
+ )
+
+ return reader
+
def start_task(self, task_id: TaskID) -> None:
"""Start a task.
@@ -787,8 +1356,6 @@ class Progress(JupyterMixin):
popleft = _progress.popleft
while _progress and _progress[0].timestamp < old_sample_time:
popleft()
- while len(_progress) > 1000:
- popleft()
if update_completed > 0:
_progress.append(ProgressSample(current_time, update_completed))
if task.completed >= task.total and task.finished_time is None:
@@ -1015,10 +1582,7 @@ if __name__ == "__main__": # pragma: no coverage
with Progress(
SpinnerColumn(),
- TextColumn("[progress.description]{task.description}"),
- BarColumn(),
- TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
- TimeRemainingColumn(),
+ *Progress.get_default_columns(),
TimeElapsedColumn(),
console=console,
transient=True,
diff --git a/src/pip/_vendor/rich/prompt.py b/src/pip/_vendor/rich/prompt.py
index b2cea2b52..2bd0a7724 100644
--- a/src/pip/_vendor/rich/prompt.py
+++ b/src/pip/_vendor/rich/prompt.py
@@ -228,14 +228,14 @@ class PromptBase(Generic[PromptType]):
"""
value = value.strip()
try:
- return_value = self.response_type(value)
+ return_value: PromptType = self.response_type(value)
except ValueError:
raise InvalidResponse(self.validate_error_message)
if self.choices is not None and not self.check_choice(value):
raise InvalidResponse(self.illegal_choice_message)
- return return_value # type: ignore
+ return return_value
def on_validate_error(self, value: str, error: InvalidResponse) -> None:
"""Called to handle validation error.
diff --git a/src/pip/_vendor/rich/protocol.py b/src/pip/_vendor/rich/protocol.py
index 624805211..12ab23713 100644
--- a/src/pip/_vendor/rich/protocol.py
+++ b/src/pip/_vendor/rich/protocol.py
@@ -1,4 +1,4 @@
-from typing import Any, Callable, cast, Set, TYPE_CHECKING
+from typing import Any, cast, Set, TYPE_CHECKING
from inspect import isclass
if TYPE_CHECKING:
diff --git a/src/pip/_vendor/rich/repr.py b/src/pip/_vendor/rich/repr.py
index 17147fd4b..36966e70f 100644
--- a/src/pip/_vendor/rich/repr.py
+++ b/src/pip/_vendor/rich/repr.py
@@ -1,5 +1,6 @@
from functools import partial
import inspect
+import sys
from typing import (
Any,
@@ -27,28 +28,28 @@ class ReprError(Exception):
@overload
-def auto(cls: Optional[T]) -> T:
+def auto(cls: Optional[Type[T]]) -> Type[T]:
...
@overload
-def auto(*, angular: bool = False) -> Callable[[T], T]:
+def auto(*, angular: bool = False) -> Callable[[Type[T]], Type[T]]:
...
def auto(
- cls: Optional[T] = None, *, angular: Optional[bool] = None
-) -> Union[T, Callable[[T], T]]:
+ cls: Optional[Type[T]] = None, *, angular: Optional[bool] = None
+) -> Union[Type[T], Callable[[Type[T]], Type[T]]]:
"""Class decorator to create __repr__ from __rich_repr__"""
def do_replace(cls: Type[T], angular: Optional[bool] = None) -> Type[T]:
- def auto_repr(self: Type[T]) -> str:
+ def auto_repr(self: T) -> str:
"""Create repr string from __rich_repr__"""
repr_str: List[str] = []
append = repr_str.append
- angular = getattr(self.__rich_repr__, "angular", False) # type: ignore
- for arg in self.__rich_repr__(): # type: ignore
+ angular: bool = getattr(self.__rich_repr__, "angular", False) # type: ignore[attr-defined]
+ for arg in self.__rich_repr__(): # type: ignore[attr-defined]
if isinstance(arg, tuple):
if len(arg) == 1:
append(repr(arg[0]))
@@ -70,7 +71,7 @@ def auto(
def auto_rich_repr(self: Type[T]) -> Result:
"""Auto generate __rich_rep__ from signature of __init__"""
try:
- signature = inspect.signature(self.__init__) ## type: ignore
+ signature = inspect.signature(self.__init__)
for name, param in signature.parameters.items():
if param.kind == param.POSITIONAL_ONLY:
yield getattr(self, name)
@@ -89,33 +90,33 @@ def auto(
if not hasattr(cls, "__rich_repr__"):
auto_rich_repr.__doc__ = "Build a rich repr"
- cls.__rich_repr__ = auto_rich_repr # type: ignore
+ cls.__rich_repr__ = auto_rich_repr # type: ignore[attr-defined]
auto_repr.__doc__ = "Return repr(self)"
- cls.__repr__ = auto_repr # type: ignore
+ cls.__repr__ = auto_repr # type: ignore[assignment]
if angular is not None:
- cls.__rich_repr__.angular = angular # type: ignore
+ cls.__rich_repr__.angular = angular # type: ignore[attr-defined]
return cls
if cls is None:
- return partial(do_replace, angular=angular) # type: ignore
+ return partial(do_replace, angular=angular)
else:
- return do_replace(cls, angular=angular) # type: ignore
+ return do_replace(cls, angular=angular)
@overload
-def rich_repr(cls: Optional[T]) -> T:
+def rich_repr(cls: Optional[Type[T]]) -> Type[T]:
...
@overload
-def rich_repr(*, angular: bool = False) -> Callable[[T], T]:
+def rich_repr(*, angular: bool = False) -> Callable[[Type[T]], Type[T]]:
...
def rich_repr(
- cls: Optional[T] = None, *, angular: bool = False
-) -> Union[T, Callable[[T], T]]:
+ cls: Optional[Type[T]] = None, *, angular: bool = False
+) -> Union[Type[T], Callable[[Type[T]], Type[T]]]:
if cls is None:
return auto(angular=angular)
else:
@@ -143,7 +144,7 @@ if __name__ == "__main__":
console.print(foo, width=30)
console.rule("Angular repr")
- Foo.__rich_repr__.angular = True # type: ignore
+ Foo.__rich_repr__.angular = True # type: ignore[attr-defined]
console.print(foo)
diff --git a/src/pip/_vendor/rich/segment.py b/src/pip/_vendor/rich/segment.py
index 94ca73076..adb3dd3a9 100644
--- a/src/pip/_vendor/rich/segment.py
+++ b/src/pip/_vendor/rich/segment.py
@@ -64,15 +64,25 @@ class Segment(NamedTuple):
Args:
text (str): A piece of text.
style (:class:`~rich.style.Style`, optional): An optional style to apply to the text.
- control (Tuple[ControlCode..], optional): Optional sequence of control codes.
+ control (Tuple[ControlCode], optional): Optional sequence of control codes.
+
+ Attributes:
+ cell_length (int): The cell length of this Segment.
"""
- text: str = ""
- """Raw text."""
+ text: str
style: Optional[Style] = None
- """An optional style."""
control: Optional[Sequence[ControlCode]] = None
- """Optional sequence of control codes."""
+
+ @property
+ def cell_length(self) -> int:
+ """The number of terminal cells required to display self.text.
+
+ Returns:
+ int: A number of cells.
+ """
+ text, _style, control = self
+ return 0 if control else cell_len(text)
def __rich_repr__(self) -> Result:
yield self.text
@@ -88,18 +98,13 @@ class Segment(NamedTuple):
return bool(self.text)
@property
- def cell_length(self) -> int:
- """Get cell length of segment."""
- return 0 if self.control else cell_len(self.text)
-
- @property
def is_control(self) -> bool:
"""Check if the segment contains control codes."""
return self.control is not None
@classmethod
@lru_cache(1024 * 16)
- def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment"]: # type: ignore
+ def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment"]:
text, style, control = segment
_Segment = Segment
@@ -135,6 +140,8 @@ class Segment(NamedTuple):
_Segment(" " + text[pos:], style, control),
)
+ raise AssertionError("Will never reach here")
+
def split_cells(self, cut: int) -> Tuple["Segment", "Segment"]:
"""Split segment in to two segments at the specified column.
@@ -682,39 +689,35 @@ class SegmentLines:
yield from line
-if __name__ == "__main__":
+if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich.console import Console
+ from pip._vendor.rich.syntax import Syntax
+ from pip._vendor.rich.text import Text
- if __name__ == "__main__": # pragma: no cover
- from pip._vendor.rich.console import Console
- from pip._vendor.rich.syntax import Syntax
- from pip._vendor.rich.text import Text
+ code = """from rich.console import Console
+console = Console()
+text = Text.from_markup("Hello, [bold magenta]World[/]!")
+console.print(text)"""
- code = """from rich.console import Console
- console = Console()
text = Text.from_markup("Hello, [bold magenta]World[/]!")
- console.print(text)"""
- text = Text.from_markup("Hello, [bold magenta]World[/]!")
-
- console = Console()
+ console = Console()
- console.rule("rich.Segment")
- console.print(
- "A Segment is the last step in the Rich render process before generating text with ANSI codes."
- )
- console.print("\nConsider the following code:\n")
- console.print(Syntax(code, "python", line_numbers=True))
- console.print()
- console.print(
- "When you call [b]print()[/b], Rich [i]renders[/i] the object in to the the following:\n"
- )
- fragments = list(console.render(text))
- console.print(fragments)
- console.print()
- console.print(
- "The Segments are then processed to produce the following output:\n"
- )
- console.print(text)
- console.print(
- "\nYou will only need to know this if you are implementing your own Rich renderables."
- )
+ console.rule("rich.Segment")
+ console.print(
+ "A Segment is the last step in the Rich render process before generating text with ANSI codes."
+ )
+ console.print("\nConsider the following code:\n")
+ console.print(Syntax(code, "python", line_numbers=True))
+ console.print()
+ console.print(
+ "When you call [b]print()[/b], Rich [i]renders[/i] the object in to the the following:\n"
+ )
+ fragments = list(console.render(text))
+ console.print(fragments)
+ console.print()
+ console.print("The Segments are then processed to produce the following output:\n")
+ console.print(text)
+ console.print(
+ "\nYou will only need to know this if you are implementing your own Rich renderables."
+ )
diff --git a/src/pip/_vendor/rich/syntax.py b/src/pip/_vendor/rich/syntax.py
index 58cc1037f..089ebfcd1 100644
--- a/src/pip/_vendor/rich/syntax.py
+++ b/src/pip/_vendor/rich/syntax.py
@@ -1,6 +1,5 @@
import os.path
import platform
-from pip._vendor.rich.containers import Lines
import textwrap
from abc import ABC, abstractmethod
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, Union
@@ -23,6 +22,8 @@ from pip._vendor.pygments.token import (
)
from pip._vendor.pygments.util import ClassNotFound
+from pip._vendor.rich.containers import Lines
+
from ._loop import loop_first
from .color import Color, blend_rgb
from .console import Console, ConsoleOptions, JustifyMethod, RenderResult
@@ -200,7 +201,8 @@ class Syntax(JupyterMixin):
dedent (bool, optional): Enable stripping of initial whitespace. Defaults to False.
line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
start_line (int, optional): Starting number for line numbers. Defaults to 1.
- line_range (Tuple[int, int], optional): If given should be a tuple of the start and end line to render.
+ line_range (Tuple[int | None, int | None], optional): If given should be a tuple of the start and end line to render.
+ A value of None in the tuple indicates the range is open in that direction.
highlight_lines (Set[int]): A set of line numbers to highlight.
code_width: Width of code to render (not including line numbers), or ``None`` to use all available width.
tab_size (int, optional): Size of tabs. Defaults to 4.
@@ -233,7 +235,7 @@ class Syntax(JupyterMixin):
dedent: bool = False,
line_numbers: bool = False,
start_line: int = 1,
- line_range: Optional[Tuple[int, int]] = None,
+ line_range: Optional[Tuple[Optional[int], Optional[int]]] = None,
highlight_lines: Optional[Set[int]] = None,
code_width: Optional[int] = None,
tab_size: int = 4,
@@ -264,6 +266,7 @@ class Syntax(JupyterMixin):
cls,
path: str,
encoding: str = "utf-8",
+ lexer: Optional[Union[Lexer, str]] = None,
theme: Union[str, SyntaxTheme] = DEFAULT_THEME,
dedent: bool = False,
line_numbers: bool = False,
@@ -281,6 +284,7 @@ class Syntax(JupyterMixin):
Args:
path (str): Path to file to highlight.
encoding (str): Encoding of file.
+ lexer (str | Lexer, optional): Lexer to use. If None, lexer will be auto-detected from path/file content.
theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "emacs".
dedent (bool, optional): Enable stripping of initial whitespace. Defaults to True.
line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
@@ -299,26 +303,12 @@ class Syntax(JupyterMixin):
with open(path, "rt", encoding=encoding) as code_file:
code = code_file.read()
- lexer = None
- lexer_name = "default"
- try:
- _, ext = os.path.splitext(path)
- if ext:
- extension = ext.lstrip(".").lower()
- lexer = get_lexer_by_name(extension)
- lexer_name = lexer.name
- except ClassNotFound:
- pass
-
- if lexer is None:
- try:
- lexer_name = guess_lexer_for_filename(path, code).name
- except ClassNotFound:
- pass
+ if not lexer:
+ lexer = cls.guess_lexer(path, code=code)
return cls(
code,
- lexer_name,
+ lexer,
theme=theme,
dedent=dedent,
line_numbers=line_numbers,
@@ -332,6 +322,48 @@ class Syntax(JupyterMixin):
indent_guides=indent_guides,
)
+ @classmethod
+ def guess_lexer(cls, path: str, code: Optional[str] = None) -> str:
+ """Guess the alias of the Pygments lexer to use based on a path and an optional string of code.
+ If code is supplied, it will use a combination of the code and the filename to determine the
+ best lexer to use. For example, if the file is ``index.html`` and the file contains Django
+ templating syntax, then "html+django" will be returned. If the file is ``index.html``, and no
+ templating language is used, the "html" lexer will be used. If no string of code
+ 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
+ is found for the supplied path.
+
+ Returns:
+ str: The name of the Pygments lexer that best matches the supplied path/code.
+ """
+ lexer: Optional[Lexer] = None
+ lexer_name = "default"
+ if code:
+ try:
+ lexer = guess_lexer_for_filename(path, code)
+ except ClassNotFound:
+ pass
+
+ if not lexer:
+ try:
+ _, ext = os.path.splitext(path)
+ if ext:
+ extension = ext.lstrip(".").lower()
+ lexer = get_lexer_by_name(extension)
+ except ClassNotFound:
+ pass
+
+ if lexer:
+ if lexer.aliases:
+ lexer_name = lexer.aliases[0]
+ else:
+ lexer_name = lexer.name
+
+ return lexer_name
+
def _get_base_style(self) -> Style:
"""Get the base style."""
default_style = self._theme.get_background_style() + self.background_style
@@ -369,7 +401,9 @@ class Syntax(JupyterMixin):
return None
def highlight(
- self, code: str, line_range: Optional[Tuple[int, int]] = None
+ self,
+ code: str,
+ line_range: Optional[Tuple[Optional[int], Optional[int]]] = None,
) -> Text:
"""Highlight code and return a Text instance.
@@ -417,7 +451,7 @@ class Syntax(JupyterMixin):
"""Convert tokens to spans."""
tokens = iter(line_tokenize())
line_no = 0
- _line_start = line_start - 1
+ _line_start = line_start - 1 if line_start else 0
# Skip over tokens until line start
while line_no < _line_start:
@@ -430,7 +464,7 @@ class Syntax(JupyterMixin):
yield (token, _get_theme_style(token_type))
if token.endswith("\n"):
line_no += 1
- if line_no >= line_end:
+ if line_end and line_no >= line_end:
break
text.append_tokens(tokens_to_spans())
@@ -513,11 +547,6 @@ class Syntax(JupyterMixin):
else self.code_width
)
- line_offset = 0
- if self.line_range:
- start_line, end_line = self.line_range
- line_offset = max(0, start_line - 1)
-
ends_on_nl = self.code.endswith("\n")
code = self.code if ends_on_nl else self.code + "\n"
code = textwrap.dedent(code) if self.dedent else code
@@ -550,7 +579,7 @@ class Syntax(JupyterMixin):
else:
syntax_lines = console.render_lines(
text,
- options.update(width=code_width, height=None),
+ options.update(width=code_width, height=None, justify="left"),
style=self.background_style,
pad=True,
new_lines=True,
@@ -559,6 +588,10 @@ class Syntax(JupyterMixin):
yield from syntax_line
return
+ start_line, end_line = self.line_range or (None, None)
+ line_offset = 0
+ if start_line:
+ line_offset = max(0, start_line - 1)
lines: Union[List[Text], Lines] = text.split("\n", allow_blank=ends_on_nl)
if self.line_range:
lines = lines[line_offset:end_line]
@@ -591,7 +624,7 @@ class Syntax(JupyterMixin):
if self.word_wrap:
wrapped_lines = console.render_lines(
line,
- render_options.update(height=None),
+ render_options.update(height=None, justify="left"),
style=background_style,
pad=not transparent_background,
)
@@ -702,7 +735,7 @@ if __name__ == "__main__": # pragma: no cover
parser.add_argument(
"-x",
"--lexer",
- default="default",
+ default=None,
dest="lexer_name",
help="Lexer name",
)
@@ -726,6 +759,7 @@ if __name__ == "__main__": # pragma: no cover
else:
syntax = Syntax.from_path(
args.path,
+ lexer=args.lexer_name,
line_numbers=args.line_numbers,
word_wrap=args.word_wrap,
theme=args.theme,
diff --git a/src/pip/_vendor/rich/table.py b/src/pip/_vendor/rich/table.py
index da4386085..bafb86a13 100644
--- a/src/pip/_vendor/rich/table.py
+++ b/src/pip/_vendor/rich/table.py
@@ -37,7 +37,35 @@ if TYPE_CHECKING:
@dataclass
class Column:
- """Defines a column in a table."""
+ """Defines a column within a ~Table.
+
+ Args:
+ title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.
+ caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.
+ width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.
+ min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.
+ box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD.
+ safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
+ padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).
+ collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.
+ pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.
+ expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
+ show_header (bool, optional): Show a header row. Defaults to True.
+ show_footer (bool, optional): Show a footer row. Defaults to False.
+ show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
+ show_lines (bool, optional): Draw lines between every row. Defaults to False.
+ leading (bool, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.
+ style (Union[str, Style], optional): Default style for the table. Defaults to "none".
+ row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None.
+ header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".
+ footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".
+ border_style (Union[str, Style], optional): Style of the border. Defaults to None.
+ title_style (Union[str, Style], optional): Style of the title. Defaults to None.
+ caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.
+ title_justify (str, optional): Justify method for title. Defaults to "center".
+ caption_justify (str, optional): Justify method for caption. Defaults to "center".
+ highlight (bool, optional): Highlight cell contents (if str). Defaults to False.
+ """
header: "RenderableType" = ""
"""RenderableType: Renderable for the header (typically a string)"""
diff --git a/src/pip/_vendor/rich/tabulate.py b/src/pip/_vendor/rich/tabulate.py
deleted file mode 100644
index 6889f2d33..000000000
--- a/src/pip/_vendor/rich/tabulate.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from collections.abc import Mapping
-from typing import Any, Optional
-import warnings
-
-from pip._vendor.rich.console import JustifyMethod
-
-from . import box
-from .highlighter import ReprHighlighter
-from .pretty import Pretty
-from .table import Table
-
-
-def tabulate_mapping(
- mapping: "Mapping[Any, Any]",
- title: Optional[str] = None,
- caption: Optional[str] = None,
- title_justify: Optional[JustifyMethod] = None,
- caption_justify: Optional[JustifyMethod] = None,
-) -> Table:
- """Generate a simple table from a mapping.
-
- Args:
- mapping (Mapping): A mapping object (e.g. a dict);
- title (str, optional): Optional title to be displayed over the table.
- caption (str, optional): Optional caption to be displayed below the table.
- title_justify (str, optional): Justify method for title. Defaults to None.
- caption_justify (str, optional): Justify method for caption. Defaults to None.
-
- Returns:
- Table: A table instance which may be rendered by the Console.
- """
- warnings.warn("tabulate_mapping will be deprecated in Rich v11", DeprecationWarning)
- table = Table(
- show_header=False,
- title=title,
- caption=caption,
- box=box.ROUNDED,
- border_style="blue",
- )
- table.title = title
- table.caption = caption
- if title_justify is not None:
- table.title_justify = title_justify
- if caption_justify is not None:
- table.caption_justify = caption_justify
- highlighter = ReprHighlighter()
- for key, value in mapping.items():
- table.add_row(
- Pretty(key, highlighter=highlighter), Pretty(value, highlighter=highlighter)
- )
- return table
diff --git a/src/pip/_vendor/rich/terminal_theme.py b/src/pip/_vendor/rich/terminal_theme.py
index 801ac0b7b..ace8e93de 100644
--- a/src/pip/_vendor/rich/terminal_theme.py
+++ b/src/pip/_vendor/rich/terminal_theme.py
@@ -53,3 +53,101 @@ DEFAULT_TERMINAL_THEME = TerminalTheme(
(255, 255, 255),
],
)
+
+SVG_EXPORT_THEME = TerminalTheme(
+ (12, 12, 12),
+ (242, 242, 242),
+ [
+ (12, 12, 12),
+ (205, 49, 49),
+ (13, 188, 121),
+ (229, 229, 16),
+ (36, 114, 200),
+ (188, 63, 188),
+ (17, 168, 205),
+ (229, 229, 229),
+ ],
+ [
+ (102, 102, 102),
+ (241, 76, 76),
+ (35, 209, 139),
+ (245, 245, 67),
+ (59, 142, 234),
+ (214, 112, 214),
+ (41, 184, 219),
+ (229, 229, 229),
+ ],
+)
+
+MONOKAI = TerminalTheme(
+ (12, 12, 12),
+ (217, 217, 217),
+ [
+ (26, 26, 26),
+ (244, 0, 95),
+ (152, 224, 36),
+ (253, 151, 31),
+ (157, 101, 255),
+ (244, 0, 95),
+ (88, 209, 235),
+ (196, 197, 181),
+ (98, 94, 76),
+ ],
+ [
+ (244, 0, 95),
+ (152, 224, 36),
+ (224, 213, 97),
+ (157, 101, 255),
+ (244, 0, 95),
+ (88, 209, 235),
+ (246, 246, 239),
+ ],
+)
+DIMMED_MONOKAI = TerminalTheme(
+ (25, 25, 25),
+ (185, 188, 186),
+ [
+ (58, 61, 67),
+ (190, 63, 72),
+ (135, 154, 59),
+ (197, 166, 53),
+ (79, 118, 161),
+ (133, 92, 141),
+ (87, 143, 164),
+ (185, 188, 186),
+ (136, 137, 135),
+ ],
+ [
+ (251, 0, 31),
+ (15, 114, 47),
+ (196, 112, 51),
+ (24, 109, 227),
+ (251, 0, 103),
+ (46, 112, 109),
+ (253, 255, 185),
+ ],
+)
+NIGHT_OWLISH = TerminalTheme(
+ (255, 255, 255),
+ (64, 63, 83),
+ [
+ (1, 22, 39),
+ (211, 66, 62),
+ (42, 162, 152),
+ (218, 170, 1),
+ (72, 118, 214),
+ (64, 63, 83),
+ (8, 145, 106),
+ (122, 129, 129),
+ (122, 129, 129),
+ ],
+ [
+ (247, 110, 110),
+ (73, 208, 197),
+ (218, 194, 107),
+ (92, 167, 228),
+ (105, 112, 152),
+ (0, 201, 144),
+ (152, 159, 177),
+ ],
+)
diff --git a/src/pip/_vendor/rich/text.py b/src/pip/_vendor/rich/text.py
index ea12c09d7..9bddbb26d 100644
--- a/src/pip/_vendor/rich/text.py
+++ b/src/pip/_vendor/rich/text.py
@@ -253,6 +253,7 @@ class Text(JupyterMixin):
emoji_variant: Optional[EmojiVariant] = None,
justify: Optional["JustifyMethod"] = None,
overflow: Optional["OverflowMethod"] = None,
+ end: str = "\n",
) -> "Text":
"""Create Text instance from markup.
@@ -261,6 +262,7 @@ class Text(JupyterMixin):
emoji (bool, optional): Also render emoji code. Defaults to True.
justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
+ end (str, optional): Character to end text with. Defaults to "\\\\n".
Returns:
Text: A Text instance with markup rendered.
@@ -270,6 +272,7 @@ class Text(JupyterMixin):
rendered_text = render(text, style, emoji=emoji, emoji_variant=emoji_variant)
rendered_text.justify = justify
rendered_text.overflow = overflow
+ rendered_text.end = end
return rendered_text
@classmethod
diff --git a/src/pip/_vendor/rich/traceback.py b/src/pip/_vendor/rich/traceback.py
index 66a39ebab..b14cb71a5 100644
--- a/src/pip/_vendor/rich/traceback.py
+++ b/src/pip/_vendor/rich/traceback.py
@@ -12,9 +12,10 @@ from pip._vendor.pygments.lexers import guess_lexer_for_filename
from pip._vendor.pygments.token import Comment, Keyword, Name, Number, Operator, String
from pip._vendor.pygments.token import Text as TextToken
from pip._vendor.pygments.token import Token
+from pip._vendor.pygments.util import ClassNotFound
from . import pretty
-from ._loop import loop_first, loop_last
+from ._loop import loop_last
from .columns import Columns
from .console import Console, ConsoleOptions, ConsoleRenderable, RenderResult, group
from .constrain import Constrain
@@ -130,7 +131,7 @@ def install(
try: # pragma: no cover
# if within ipython, use customized traceback
- ip = get_ipython() # type: ignore
+ ip = get_ipython() # type: ignore[name-defined]
ipy_excepthook_closure(ip)
return sys.excepthook
except Exception:
@@ -390,9 +391,8 @@ class Traceback:
exc_type = cause.__class__
exc_value = cause
traceback = cause.__traceback__
- if traceback:
- is_cause = True
- continue
+ is_cause = True
+ continue
cause = exc_value.__context__
if (
@@ -403,9 +403,8 @@ class Traceback:
exc_type = cause.__class__
exc_value = cause
traceback = cause.__traceback__
- if traceback:
- is_cause = False
- continue
+ is_cause = False
+ continue
# No cover, code is reached but coverage doesn't recognize it.
break # pragma: no cover
@@ -523,10 +522,10 @@ class Traceback:
first_line = code[:new_line_index] if new_line_index != -1 else code
if first_line.startswith("#!") and "python" in first_line.lower():
return "python"
- lexer_name = (
- cls.LEXERS.get(ext) or guess_lexer_for_filename(filename, code).name
- )
- return lexer_name
+ try:
+ return cls.LEXERS.get(ext) or guess_lexer_for_filename(filename, code).name
+ except ClassNotFound:
+ return "text"
@group()
def _render_stack(self, stack: Stack) -> RenderResult:
@@ -671,7 +670,7 @@ if __name__ == "__main__": # pragma: no cover
try:
foo(0)
except:
- slfkjsldkfj # type: ignore
+ slfkjsldkfj # type: ignore[name-defined]
except:
console.print_exception(show_locals=True)
diff --git a/src/pip/_vendor/rich/tree.py b/src/pip/_vendor/rich/tree.py
index c5ec27da9..92561a5b6 100644
--- a/src/pip/_vendor/rich/tree.py
+++ b/src/pip/_vendor/rich/tree.py
@@ -136,6 +136,7 @@ class Tree(JupyterMixin):
highlight=self.highlight,
height=None,
),
+ pad=options.justify is not None,
)
if not (depth == 0 and self.hide_root):
@@ -214,9 +215,9 @@ if __name__ == "__main__": # pragma: no cover
code = """\
class Segment(NamedTuple):
- text: str = ""
- style: Optional[Style] = None
- is_control: bool = False
+ text: str = ""
+ style: Optional[Style] = None
+ is_control: bool = False
"""
syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
@@ -224,7 +225,7 @@ class Segment(NamedTuple):
"""\
### example.md
> Hello, World!
->
+>
> Markdown _all_ the things
"""
)
@@ -246,4 +247,5 @@ class Segment(NamedTuple):
containers_node.add(Group("📄 [b magenta]Table", table))
console = Console()
+
console.print(root)
diff --git a/src/pip/_vendor/urllib3/_version.py b/src/pip/_vendor/urllib3/_version.py
index fa8979d73..d905b6975 100644
--- a/src/pip/_vendor/urllib3/_version.py
+++ b/src/pip/_vendor/urllib3/_version.py
@@ -1,2 +1,2 @@
# This file is protected via CODEOWNERS
-__version__ = "1.26.8"
+__version__ = "1.26.9"
diff --git a/src/pip/_vendor/urllib3/connection.py b/src/pip/_vendor/urllib3/connection.py
index 4d92ac6d2..7bf395bda 100644
--- a/src/pip/_vendor/urllib3/connection.py
+++ b/src/pip/_vendor/urllib3/connection.py
@@ -355,17 +355,15 @@ class HTTPSConnection(HTTPConnection):
def connect(self):
# Add certificate verification
- conn = self._new_conn()
+ self.sock = conn = self._new_conn()
hostname = self.host
tls_in_tls = False
if self._is_using_tunnel():
if self.tls_in_tls_required:
- conn = self._connect_tls_proxy(hostname, conn)
+ self.sock = conn = self._connect_tls_proxy(hostname, conn)
tls_in_tls = True
- self.sock = conn
-
# Calls self._set_hostport(), so self.host is
# self._tunnel_host below.
self._tunnel()
diff --git a/src/pip/_vendor/urllib3/poolmanager.py b/src/pip/_vendor/urllib3/poolmanager.py
index 3a31a285b..ca4ec3411 100644
--- a/src/pip/_vendor/urllib3/poolmanager.py
+++ b/src/pip/_vendor/urllib3/poolmanager.py
@@ -34,6 +34,7 @@ SSL_KEYWORDS = (
"ca_cert_dir",
"ssl_context",
"key_password",
+ "server_hostname",
)
# All known keyword arguments that could be provided to the pool manager, its
diff --git a/src/pip/_vendor/urllib3/util/ssl_match_hostname.py b/src/pip/_vendor/urllib3/util/ssl_match_hostname.py
index a4b4a569c..1dd950c48 100644
--- a/src/pip/_vendor/urllib3/util/ssl_match_hostname.py
+++ b/src/pip/_vendor/urllib3/util/ssl_match_hostname.py
@@ -112,11 +112,9 @@ def match_hostname(cert, hostname):
try:
# Divergence from upstream: ipaddress can't handle byte str
host_ip = ipaddress.ip_address(_to_unicode(hostname))
- except ValueError:
- # Not an IP address (common case)
- host_ip = None
- except UnicodeError:
- # Divergence from upstream: Have to deal with ipaddress not taking
+ except (UnicodeError, ValueError):
+ # ValueError: Not an IP address (common case)
+ # UnicodeError: Divergence from upstream: Have to deal with ipaddress not taking
# byte strings. addresses should be all ascii, so we consider it not
# an ipaddress in this case
host_ip = None
@@ -124,7 +122,7 @@ def match_hostname(cert, hostname):
# Divergence from upstream: Make ipaddress library optional
if ipaddress is None:
host_ip = None
- else:
+ else: # Defensive
raise
dnsnames = []
san = cert.get("subjectAltName", ())
diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt
index 525f6d80e..7134851f6 100644
--- a/src/pip/_vendor/vendor.txt
+++ b/src/pip/_vendor/vendor.txt
@@ -12,8 +12,8 @@ requests==2.27.1
certifi==2021.10.08
chardet==4.0.0
idna==3.3
- urllib3==1.26.8
-rich==11.0.0
+ urllib3==1.26.9
+rich==12.2.0
pygments==2.11.2
typing_extensions==4.0.1
resolvelib==0.8.1
diff --git a/tools/vendoring/patches/urllib3-disable-brotli.patch b/tools/vendoring/patches/urllib3-disable-brotli.patch
new file mode 100644
index 000000000..1058ac479
--- /dev/null
+++ b/tools/vendoring/patches/urllib3-disable-brotli.patch
@@ -0,0 +1,39 @@
+diff --git a/src/pip/_vendor/urllib3/response.py b/src/pip/_vendor/urllib3/response.py
+index fdb50ddb2..db259d6ce 100644
+--- a/src/pip/_vendor/urllib3/response.py
++++ b/src/pip/_vendor/urllib3/response.py
+@@ -7,13 +7,7 @@
+ from socket import error as SocketError
+ from socket import timeout as SocketTimeout
+
+-try:
+- try:
+- import brotlicffi as brotli
+- except ImportError:
+- import brotli
+-except ImportError:
+- brotli = None
++brotli = None
+
+ from ._collections import HTTPHeaderDict
+ from .connection import BaseSSLError, HTTPException
+diff --git a/src/pip/_vendor/urllib3/util/request.py b/src/pip/_vendor/urllib3/util/request.py
+index b574b081e..330766ef4 100644
+--- a/src/pip/_vendor/urllib3/util/request.py
++++ b/src/pip/_vendor/urllib3/util/request.py
+@@ -13,15 +13,6 @@
+ SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"])
+
+ ACCEPT_ENCODING = "gzip,deflate"
+-try:
+- try:
+- import brotlicffi as _unused_module_brotli # noqa: F401
+- except ImportError:
+- import brotli as _unused_module_brotli # noqa: F401
+-except ImportError:
+- pass
+-else:
+- ACCEPT_ENCODING += ",br"
+
+ _FAILEDTELL = object()
+
diff --git a/tools/vendoring/patches/urllib3.patch b/tools/vendoring/patches/urllib3.patch
index 747b81e1d..3ca7226fa 100644
--- a/tools/vendoring/patches/urllib3.patch
+++ b/tools/vendoring/patches/urllib3.patch
@@ -27,37 +27,3 @@ index c43146279..4cded53f6 100644
+ pyopenssl.inject_into_urllib3()
except ImportError:
pass
-
-diff --git a/src/pip/_vendor/urllib3/response.py b/src/pip/_vendor/urllib3/response.py
-index 38693f4fc..776e49dd2 100644
---- a/src/pip/_vendor/urllib3/response.py
-+++ b/src/pip/_vendor/urllib3/response.py
-@@ -7,10 +7,7 @@ from contextlib import contextmanager
- from socket import error as SocketError
- from socket import timeout as SocketTimeout
-
--try:
-- import brotli
--except ImportError:
-- brotli = None
-+brotli = None
-
- from ._collections import HTTPHeaderDict
- from .connection import BaseSSLError, HTTPException
-diff --git a/src/pip/_vendor/urllib3/util/request.py b/src/pip/_vendor/urllib3/util/request.py
-index 25103383e..330766ef4 100644
---- a/src/pip/_vendor/urllib3/util/request.py
-+++ b/src/pip/_vendor/urllib3/util/request.py
-@@ -13,12 +13,6 @@ SKIP_HEADER = "@@@SKIP_HEADER@@@"
- SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"])
-
- ACCEPT_ENCODING = "gzip,deflate"
--try:
-- import brotli as _unused_module_brotli # noqa: F401
--except ImportError:
-- pass
--else:
-- ACCEPT_ENCODING += ",br"
-
- _FAILEDTELL = object()
-