From 5bd617d79b362cb811ba0a733c21b079b9812063 Mon Sep 17 00:00:00 2001
From: Paul Moore
Date: Tue, 1 Apr 2025 11:52:12 +0100
Subject: [PATCH 1/5] Upgrade platformdirs to 4.3.7
---
news/platformdirs.vendor.rst | 1 +
src/pip/_vendor/platformdirs/__init__.py | 40 ++++++++++++------------
src/pip/_vendor/platformdirs/android.py | 6 ++--
src/pip/_vendor/platformdirs/api.py | 5 +--
src/pip/_vendor/platformdirs/unix.py | 5 ++-
src/pip/_vendor/platformdirs/version.py | 13 +++++---
src/pip/_vendor/vendor.txt | 2 +-
7 files changed, 41 insertions(+), 31 deletions(-)
create mode 100644 news/platformdirs.vendor.rst
diff --git a/news/platformdirs.vendor.rst b/news/platformdirs.vendor.rst
new file mode 100644
index 00000000000..d1f397d2308
--- /dev/null
+++ b/news/platformdirs.vendor.rst
@@ -0,0 +1 @@
+Upgrade platformdirs to 4.3.7
diff --git a/src/pip/_vendor/platformdirs/__init__.py b/src/pip/_vendor/platformdirs/__init__.py
index edc21fad2e9..2325ec2eb6f 100644
--- a/src/pip/_vendor/platformdirs/__init__.py
+++ b/src/pip/_vendor/platformdirs/__init__.py
@@ -52,7 +52,7 @@ def _set_platform_dir_class() -> type[PlatformDirsABC]:
def user_data_dir(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -76,7 +76,7 @@ def user_data_dir(
def site_data_dir(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
multipath: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -100,7 +100,7 @@ def site_data_dir(
def user_config_dir(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -124,7 +124,7 @@ def user_config_dir(
def site_config_dir(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
multipath: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -148,7 +148,7 @@ def site_config_dir(
def user_cache_dir(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -172,7 +172,7 @@ def user_cache_dir(
def site_cache_dir(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -196,7 +196,7 @@ def site_cache_dir(
def user_state_dir(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -220,7 +220,7 @@ def user_state_dir(
def user_log_dir(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -274,7 +274,7 @@ def user_desktop_dir() -> str:
def user_runtime_dir(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -298,7 +298,7 @@ def user_runtime_dir(
def site_runtime_dir(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -322,7 +322,7 @@ def site_runtime_dir(
def user_data_path(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -346,7 +346,7 @@ def user_data_path(
def site_data_path(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
multipath: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -370,7 +370,7 @@ def site_data_path(
def user_config_path(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -394,7 +394,7 @@ def user_config_path(
def site_config_path(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
multipath: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -418,7 +418,7 @@ def site_config_path(
def site_cache_path(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -442,7 +442,7 @@ def site_cache_path(
def user_cache_path(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -466,7 +466,7 @@ def user_cache_path(
def user_state_path(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -490,7 +490,7 @@ def user_state_path(
def user_log_path(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -544,7 +544,7 @@ def user_desktop_path() -> Path:
def user_runtime_path(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
@@ -568,7 +568,7 @@ def user_runtime_path(
def site_runtime_path(
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
diff --git a/src/pip/_vendor/platformdirs/android.py b/src/pip/_vendor/platformdirs/android.py
index 7004a852422..92efc852d38 100644
--- a/src/pip/_vendor/platformdirs/android.py
+++ b/src/pip/_vendor/platformdirs/android.py
@@ -23,7 +23,7 @@ class Android(PlatformDirsABC):
@property
def user_data_dir(self) -> str:
""":return: data directory tied to the user, e.g. ``/data/user///files/``"""
- return self._append_app_name_and_version(cast(str, _android_folder()), "files")
+ return self._append_app_name_and_version(cast("str", _android_folder()), "files")
@property
def site_data_dir(self) -> str:
@@ -36,7 +36,7 @@ def user_config_dir(self) -> str:
:return: config directory tied to the user, e.g. \
``/data/user///shared_prefs/``
"""
- return self._append_app_name_and_version(cast(str, _android_folder()), "shared_prefs")
+ return self._append_app_name_and_version(cast("str", _android_folder()), "shared_prefs")
@property
def site_config_dir(self) -> str:
@@ -46,7 +46,7 @@ def site_config_dir(self) -> str:
@property
def user_cache_dir(self) -> str:
""":return: cache directory tied to the user, e.g.,``/data/user///cache/``"""
- return self._append_app_name_and_version(cast(str, _android_folder()), "cache")
+ return self._append_app_name_and_version(cast("str", _android_folder()), "cache")
@property
def site_cache_dir(self) -> str:
diff --git a/src/pip/_vendor/platformdirs/api.py b/src/pip/_vendor/platformdirs/api.py
index 18d660e4f8c..a352035ec69 100644
--- a/src/pip/_vendor/platformdirs/api.py
+++ b/src/pip/_vendor/platformdirs/api.py
@@ -8,7 +8,8 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
- from typing import Iterator, Literal
+ from collections.abc import Iterator
+ from typing import Literal
class PlatformDirsABC(ABC): # noqa: PLR0904
@@ -17,7 +18,7 @@ class PlatformDirsABC(ABC): # noqa: PLR0904
def __init__( # noqa: PLR0913, PLR0917
self,
appname: str | None = None,
- appauthor: str | None | Literal[False] = None,
+ appauthor: str | Literal[False] | None = None,
version: str | None = None,
roaming: bool = False, # noqa: FBT001, FBT002
multipath: bool = False, # noqa: FBT001, FBT002
diff --git a/src/pip/_vendor/platformdirs/unix.py b/src/pip/_vendor/platformdirs/unix.py
index f1942e92ef4..fc75d8d0747 100644
--- a/src/pip/_vendor/platformdirs/unix.py
+++ b/src/pip/_vendor/platformdirs/unix.py
@@ -6,10 +6,13 @@
import sys
from configparser import ConfigParser
from pathlib import Path
-from typing import Iterator, NoReturn
+from typing import TYPE_CHECKING, NoReturn
from .api import PlatformDirsABC
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
if sys.platform == "win32":
def getuid() -> NoReturn:
diff --git a/src/pip/_vendor/platformdirs/version.py b/src/pip/_vendor/platformdirs/version.py
index afb49243e3d..ed85187adca 100644
--- a/src/pip/_vendor/platformdirs/version.py
+++ b/src/pip/_vendor/platformdirs/version.py
@@ -1,8 +1,13 @@
-# file generated by setuptools_scm
+# file generated by setuptools-scm
# don't change, don't track in version control
+
+__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
+
TYPE_CHECKING = False
if TYPE_CHECKING:
- from typing import Tuple, Union
+ from typing import Tuple
+ from typing import Union
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
else:
VERSION_TUPLE = object
@@ -12,5 +17,5 @@
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
-__version__ = version = '4.3.6'
-__version_tuple__ = version_tuple = (4, 3, 6)
+__version__ = version = '4.3.7'
+__version_tuple__ = version_tuple = (4, 3, 7)
diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt
index 0c7733ff8a5..78b7ed2dd8e 100644
--- a/src/pip/_vendor/vendor.txt
+++ b/src/pip/_vendor/vendor.txt
@@ -3,7 +3,7 @@ distlib==0.3.9
distro==1.9.0
msgpack==1.1.0
packaging==24.2
-platformdirs==4.3.6
+platformdirs==4.3.7
pyproject-hooks==1.2.0
requests==2.32.3
certifi==2025.1.31
From 9490bdf055ec7a086e7ba0ed8d250096d4861a2a Mon Sep 17 00:00:00 2001
From: Paul Moore
Date: Tue, 1 Apr 2025 11:52:51 +0100
Subject: [PATCH 2/5] Upgrade rich to 14.0.0
---
news/rich.vendor.rst | 1 +
src/pip/_vendor/rich/console.py | 28 +++-
src/pip/_vendor/rich/default_styles.py | 4 +-
src/pip/_vendor/rich/diagnose.py | 13 +-
src/pip/_vendor/rich/panel.py | 2 +-
src/pip/_vendor/rich/style.py | 2 +-
src/pip/_vendor/rich/table.py | 1 -
src/pip/_vendor/rich/traceback.py | 171 +++++++++++++++++++------
src/pip/_vendor/vendor.txt | 2 +-
9 files changed, 164 insertions(+), 60 deletions(-)
create mode 100644 news/rich.vendor.rst
diff --git a/news/rich.vendor.rst b/news/rich.vendor.rst
new file mode 100644
index 00000000000..a2f369fa5aa
--- /dev/null
+++ b/news/rich.vendor.rst
@@ -0,0 +1 @@
+Upgrade rich to 14.0.0
diff --git a/src/pip/_vendor/rich/console.py b/src/pip/_vendor/rich/console.py
index 572884542a1..57474835f92 100644
--- a/src/pip/_vendor/rich/console.py
+++ b/src/pip/_vendor/rich/console.py
@@ -500,7 +500,7 @@ def group(fit: bool = True) -> Callable[..., Callable[..., Group]]:
"""
def decorator(
- method: Callable[..., Iterable[RenderableType]]
+ method: Callable[..., Iterable[RenderableType]],
) -> Callable[..., Group]:
"""Convert a method that returns an iterable of renderables in to a Group."""
@@ -735,7 +735,9 @@ def __init__(
self.get_time = get_time or monotonic
self.style = style
self.no_color = (
- no_color if no_color is not None else "NO_COLOR" in self._environ
+ no_color
+ if no_color is not None
+ else self._environ.get("NO_COLOR", "") != ""
)
self.is_interactive = (
(self.is_terminal and not self.is_dumb_terminal)
@@ -933,11 +935,13 @@ def is_terminal(self) -> bool:
Returns:
bool: True if the console writing to a device capable of
- understanding terminal codes, otherwise False.
+ understanding escape sequences, otherwise False.
"""
+ # If dev has explicitly set this value, return it
if self._force_terminal is not None:
return self._force_terminal
+ # Fudge for Idle
if hasattr(sys.stdin, "__module__") and sys.stdin.__module__.startswith(
"idlelib"
):
@@ -948,12 +952,22 @@ def is_terminal(self) -> bool:
# return False for Jupyter, which may have FORCE_COLOR set
return False
- # If FORCE_COLOR env var has any value at all, we assume a terminal.
- force_color = self._environ.get("FORCE_COLOR")
- if force_color is not None:
- self._force_terminal = True
+ environ = self._environ
+
+ tty_compatible = environ.get("TTY_COMPATIBLE", "")
+ # 0 indicates device is not tty compatible
+ if tty_compatible == "0":
+ return False
+ # 1 indicates device is tty compatible
+ if tty_compatible == "1":
return True
+ # https://force-color.org/
+ force_color = environ.get("FORCE_COLOR")
+ if force_color is not None:
+ return force_color != ""
+
+ # Any other value defaults to auto detect
isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None)
try:
return False if isatty is None else isatty()
diff --git a/src/pip/_vendor/rich/default_styles.py b/src/pip/_vendor/rich/default_styles.py
index 6c0d73231d8..61797bf312c 100644
--- a/src/pip/_vendor/rich/default_styles.py
+++ b/src/pip/_vendor/rich/default_styles.py
@@ -120,7 +120,9 @@
"traceback.exc_type": Style(color="bright_red", bold=True),
"traceback.exc_value": Style.null(),
"traceback.offset": Style(color="bright_red", bold=True),
- "traceback.error_range": Style(underline=True, bold=True, dim=False),
+ "traceback.error_range": Style(underline=True, bold=True),
+ "traceback.note": Style(color="green", bold=True),
+ "traceback.group.border": Style(color="magenta"),
"bar.back": Style(color="grey23"),
"bar.complete": Style(color="rgb(249,38,114)"),
"bar.finished": Style(color="rgb(114,156,31)"),
diff --git a/src/pip/_vendor/rich/diagnose.py b/src/pip/_vendor/rich/diagnose.py
index ad36183898e..b8b8c4347e8 100644
--- a/src/pip/_vendor/rich/diagnose.py
+++ b/src/pip/_vendor/rich/diagnose.py
@@ -15,16 +15,17 @@ def report() -> None: # pragma: no cover
inspect(features)
env_names = (
- "TERM",
- "COLORTERM",
"CLICOLOR",
- "NO_COLOR",
- "TERM_PROGRAM",
+ "COLORTERM",
"COLUMNS",
- "LINES",
+ "JPY_PARENT_PID",
"JUPYTER_COLUMNS",
"JUPYTER_LINES",
- "JPY_PARENT_PID",
+ "LINES",
+ "NO_COLOR",
+ "TERM_PROGRAM",
+ "TERM",
+ "TTY_COMPATIBLE",
"VSCODE_VERBOSE_LOGGING",
)
env = {name: os.getenv(name) for name in env_names}
diff --git a/src/pip/_vendor/rich/panel.py b/src/pip/_vendor/rich/panel.py
index 8cfa6f4a243..d411e291533 100644
--- a/src/pip/_vendor/rich/panel.py
+++ b/src/pip/_vendor/rich/panel.py
@@ -22,7 +22,7 @@ class Panel(JupyterMixin):
Args:
renderable (RenderableType): A console renderable object.
- box (Box, optional): A Box instance that defines the look of the border (see :ref:`appendix_box`. Defaults to box.ROUNDED.
+ box (Box): A Box instance that defines the look of the border (see :ref:`appendix_box`. Defaults to box.ROUNDED.
title (Optional[TextType], optional): Optional title displayed in panel header. Defaults to None.
title_align (AlignMethod, optional): Alignment of title. Defaults to "center".
subtitle (Optional[TextType], optional): Optional subtitle displayed in panel footer. Defaults to None.
diff --git a/src/pip/_vendor/rich/style.py b/src/pip/_vendor/rich/style.py
index 262fd6ecad6..835d06f3eb2 100644
--- a/src/pip/_vendor/rich/style.py
+++ b/src/pip/_vendor/rich/style.py
@@ -524,7 +524,7 @@ def parse(cls, style_definition: str) -> "Style":
if not word:
raise errors.StyleSyntaxError("color expected after 'on'")
try:
- Color.parse(word) is None
+ Color.parse(word)
except ColorParseError as error:
raise errors.StyleSyntaxError(
f"unable to parse {word!r} as background color; {error}"
diff --git a/src/pip/_vendor/rich/table.py b/src/pip/_vendor/rich/table.py
index 654c8555411..2b9e3b5d7e8 100644
--- a/src/pip/_vendor/rich/table.py
+++ b/src/pip/_vendor/rich/table.py
@@ -929,7 +929,6 @@ def align_cell(
if __name__ == "__main__": # pragma: no cover
from pip._vendor.rich.console import Console
from pip._vendor.rich.highlighter import ReprHighlighter
- from pip._vendor.rich.table import Table as Table
from ._timer import timer
diff --git a/src/pip/_vendor/rich/traceback.py b/src/pip/_vendor/rich/traceback.py
index 28d742b4fd0..f82d06f6d13 100644
--- a/src/pip/_vendor/rich/traceback.py
+++ b/src/pip/_vendor/rich/traceback.py
@@ -26,15 +26,22 @@
from pip._vendor.pygments.util import ClassNotFound
from . import pretty
-from ._loop import loop_last
+from ._loop import loop_first_last, loop_last
from .columns import Columns
-from .console import Console, ConsoleOptions, ConsoleRenderable, RenderResult, group
+from .console import (
+ Console,
+ ConsoleOptions,
+ ConsoleRenderable,
+ Group,
+ RenderResult,
+ group,
+)
from .constrain import Constrain
from .highlighter import RegexHighlighter, ReprHighlighter
from .panel import Panel
from .scope import render_scope
from .style import Style
-from .syntax import Syntax
+from .syntax import Syntax, SyntaxPosition
from .text import Text
from .theme import Theme
@@ -44,6 +51,34 @@
LOCALS_MAX_STRING = 80
+def _iter_syntax_lines(
+ start: SyntaxPosition, end: SyntaxPosition
+) -> Iterable[Tuple[int, int, int]]:
+ """Yield start and end positions per line.
+
+ Args:
+ start: Start position.
+ end: End position.
+
+ Returns:
+ Iterable of (LINE, COLUMN1, COLUMN2).
+ """
+
+ line1, column1 = start
+ line2, column2 = end
+
+ if line1 == line2:
+ yield line1, column1, column2
+ else:
+ for first, last, line_no in loop_first_last(range(line1, line2 + 1)):
+ if first:
+ yield line_no, column1, -1
+ elif last:
+ yield line_no, 0, column2
+ else:
+ yield line_no, 0, -1
+
+
def install(
*,
console: Optional[Console] = None,
@@ -100,26 +135,25 @@ def excepthook(
value: BaseException,
traceback: Optional[TracebackType],
) -> None:
- traceback_console.print(
- Traceback.from_exception(
- type_,
- value,
- traceback,
- width=width,
- code_width=code_width,
- extra_lines=extra_lines,
- theme=theme,
- word_wrap=word_wrap,
- show_locals=show_locals,
- locals_max_length=locals_max_length,
- locals_max_string=locals_max_string,
- locals_hide_dunder=locals_hide_dunder,
- locals_hide_sunder=bool(locals_hide_sunder),
- indent_guides=indent_guides,
- suppress=suppress,
- max_frames=max_frames,
- )
+ exception_traceback = Traceback.from_exception(
+ type_,
+ value,
+ traceback,
+ width=width,
+ code_width=code_width,
+ extra_lines=extra_lines,
+ theme=theme,
+ word_wrap=word_wrap,
+ show_locals=show_locals,
+ locals_max_length=locals_max_length,
+ locals_max_string=locals_max_string,
+ locals_hide_dunder=locals_hide_dunder,
+ locals_hide_sunder=bool(locals_hide_sunder),
+ indent_guides=indent_guides,
+ suppress=suppress,
+ max_frames=max_frames,
)
+ traceback_console.print(exception_traceback)
def ipy_excepthook_closure(ip: Any) -> None: # pragma: no cover
tb_data = {} # store information about showtraceback call
@@ -191,6 +225,7 @@ class _SyntaxError:
line: str
lineno: int
msg: str
+ notes: List[str] = field(default_factory=list)
@dataclass
@@ -200,6 +235,9 @@ class Stack:
syntax_error: Optional[_SyntaxError] = None
is_cause: bool = False
frames: List[Frame] = field(default_factory=list)
+ notes: List[str] = field(default_factory=list)
+ is_group: bool = False
+ exceptions: List["Trace"] = field(default_factory=list)
@dataclass
@@ -403,6 +441,8 @@ def extract(
from pip._vendor.rich import _IMPORT_CWD
+ notes: List[str] = getattr(exc_value, "__notes__", None) or []
+
def safe_str(_object: Any) -> str:
"""Don't allow exceptions from __str__ to propagate."""
try:
@@ -415,8 +455,25 @@ def safe_str(_object: Any) -> str:
exc_type=safe_str(exc_type.__name__),
exc_value=safe_str(exc_value),
is_cause=is_cause,
+ notes=notes,
)
+ if sys.version_info >= (3, 11):
+ if isinstance(exc_value, (BaseExceptionGroup, ExceptionGroup)):
+ stack.is_group = True
+ for exception in exc_value.exceptions:
+ stack.exceptions.append(
+ Traceback.extract(
+ type(exception),
+ exception,
+ exception.__traceback__,
+ show_locals=show_locals,
+ locals_max_length=locals_max_length,
+ locals_hide_dunder=locals_hide_dunder,
+ locals_hide_sunder=locals_hide_sunder,
+ )
+ )
+
if isinstance(exc_value, SyntaxError):
stack.syntax_error = _SyntaxError(
offset=exc_value.offset or 0,
@@ -424,13 +481,14 @@ def safe_str(_object: Any) -> str:
lineno=exc_value.lineno or 0,
line=exc_value.text or "",
msg=exc_value.msg,
+ notes=notes,
)
stacks.append(stack)
append = stack.frames.append
def get_locals(
- iter_locals: Iterable[Tuple[str, object]]
+ iter_locals: Iterable[Tuple[str, object]],
) -> Iterable[Tuple[str, object]]:
"""Extract locals from an iterator of key pairs."""
if not (locals_hide_dunder or locals_hide_sunder):
@@ -524,6 +582,7 @@ def get_locals(
break # pragma: no cover
trace = Trace(stacks=stacks)
+
return trace
def __rich_console__(
@@ -556,7 +615,9 @@ def __rich_console__(
)
highlighter = ReprHighlighter()
- for last, stack in loop_last(reversed(self.trace.stacks)):
+
+ @group()
+ def render_stack(stack: Stack, last: bool) -> RenderResult:
if stack.frames:
stack_renderable: ConsoleRenderable = Panel(
self._render_stack(stack),
@@ -569,6 +630,7 @@ def __rich_console__(
stack_renderable = Constrain(stack_renderable, self.width)
with console.use_theme(traceback_theme):
yield stack_renderable
+
if stack.syntax_error is not None:
with console.use_theme(traceback_theme):
yield Constrain(
@@ -594,6 +656,24 @@ def __rich_console__(
else:
yield Text.assemble((f"{stack.exc_type}", "traceback.exc_type"))
+ for note in stack.notes:
+ yield Text.assemble(("[NOTE] ", "traceback.note"), highlighter(note))
+
+ if stack.is_group:
+ for group_no, group_exception in enumerate(stack.exceptions, 1):
+ grouped_exceptions: List[Group] = []
+ for group_last, group_stack in loop_last(group_exception.stacks):
+ grouped_exceptions.append(render_stack(group_stack, group_last))
+ yield ""
+ yield Constrain(
+ Panel(
+ Group(*grouped_exceptions),
+ title=f"Sub-exception #{group_no}",
+ border_style="traceback.group.border",
+ ),
+ self.width,
+ )
+
if not last:
if stack.is_cause:
yield Text.from_markup(
@@ -604,6 +684,9 @@ def __rich_console__(
"\n[i]During handling of the above exception, another exception occurred:\n",
)
+ for last, stack in loop_last(reversed(self.trace.stacks)):
+ yield render_stack(stack, last)
+
@group()
def _render_syntax_error(self, syntax_error: _SyntaxError) -> RenderResult:
highlighter = ReprHighlighter()
@@ -648,17 +731,6 @@ def _render_stack(self, stack: Stack) -> RenderResult:
path_highlighter = PathHighlighter()
theme = self.theme
- def read_code(filename: str) -> str:
- """Read files, and cache results on filename.
-
- Args:
- filename (str): Filename to read
-
- Returns:
- str: Contents of file
- """
- return "".join(linecache.getlines(filename))
-
def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
if frame.locals:
yield render_scope(
@@ -720,7 +792,8 @@ def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
continue
if not suppressed:
try:
- code = read_code(frame.filename)
+ code_lines = linecache.getlines(frame.filename)
+ code = "".join(code_lines)
if not code:
# code may be an empty string if the file doesn't exist, OR
# if the traceback filename is generated dynamically
@@ -749,12 +822,26 @@ def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
else:
if frame.last_instruction is not None:
start, end = frame.last_instruction
- syntax.stylize_range(
- style="traceback.error_range",
- start=start,
- end=end,
- style_before=True,
- )
+
+ # Stylize a line at a time
+ # So that indentation isn't underlined (which looks bad)
+ for line1, column1, column2 in _iter_syntax_lines(start, end):
+ try:
+ if column1 == 0:
+ line = code_lines[line1 - 1]
+ column1 = len(line) - len(line.lstrip())
+ if column2 == -1:
+ column2 = len(code_lines[line1 - 1])
+ except IndexError:
+ # Being defensive here
+ # If last_instruction reports a line out-of-bounds, we don't want to crash
+ continue
+
+ syntax.stylize_range(
+ style="traceback.error_range",
+ start=(line1, column1),
+ end=(line1, column2),
+ )
yield (
Columns(
[
diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt
index 78b7ed2dd8e..8962e6fce7d 100644
--- a/src/pip/_vendor/vendor.txt
+++ b/src/pip/_vendor/vendor.txt
@@ -9,7 +9,7 @@ requests==2.32.3
certifi==2025.1.31
idna==3.10
urllib3==1.26.20
-rich==13.9.4
+rich==14.0.0
pygments==2.19.1
typing_extensions==4.12.2
resolvelib==1.1.0
From d8b4cd8482b4ecbf39cebd1bb5e90278aae55835 Mon Sep 17 00:00:00 2001
From: Paul Moore
Date: Tue, 1 Apr 2025 11:53:28 +0100
Subject: [PATCH 3/5] Upgrade typing_extensions to 4.13.0
---
news/typing_extensions.vendor.rst | 1 +
src/pip/_vendor/typing_extensions.py | 1073 ++++++++++++++++++++++++--
src/pip/_vendor/vendor.txt | 2 +-
3 files changed, 1001 insertions(+), 75 deletions(-)
create mode 100644 news/typing_extensions.vendor.rst
diff --git a/news/typing_extensions.vendor.rst b/news/typing_extensions.vendor.rst
new file mode 100644
index 00000000000..76ba97d4a2a
--- /dev/null
+++ b/news/typing_extensions.vendor.rst
@@ -0,0 +1 @@
+Upgrade typing_extensions to 4.13.0
diff --git a/src/pip/_vendor/typing_extensions.py b/src/pip/_vendor/typing_extensions.py
index e429384e76a..e0c2fb62ad5 100644
--- a/src/pip/_vendor/typing_extensions.py
+++ b/src/pip/_vendor/typing_extensions.py
@@ -1,9 +1,12 @@
import abc
+import builtins
import collections
import collections.abc
import contextlib
+import enum
import functools
import inspect
+import keyword
import operator
import sys
import types as _types
@@ -62,8 +65,11 @@
'dataclass_transform',
'deprecated',
'Doc',
+ 'evaluate_forward_ref',
'get_overloads',
'final',
+ 'Format',
+ 'get_annotations',
'get_args',
'get_origin',
'get_original_bases',
@@ -83,6 +89,7 @@
'Text',
'TypeAlias',
'TypeAliasType',
+ 'TypeForm',
'TypeGuard',
'TypeIs',
'TYPE_CHECKING',
@@ -91,6 +98,8 @@
'ReadOnly',
'Required',
'NotRequired',
+ 'NoDefault',
+ 'NoExtraItems',
# Pure aliases, have always been in typing
'AbstractSet',
@@ -117,7 +126,6 @@
'MutableMapping',
'MutableSequence',
'MutableSet',
- 'NoDefault',
'Optional',
'Pattern',
'Reversible',
@@ -138,6 +146,9 @@
GenericMeta = type
_PEP_696_IMPLEMENTED = sys.version_info >= (3, 13, 0, "beta")
+# Added with bpo-45166 to 3.10.1+ and some 3.9 versions
+_FORWARD_REF_HAS_CLASS = "__forward_is_class__" in typing.ForwardRef.__slots__
+
# The functions below are modified copies of typing internal helpers.
# They are needed by _ProtocolMeta and they provide support for PEP 646.
@@ -867,6 +878,63 @@ def inner(func):
return inner
+_NEEDS_SINGLETONMETA = (
+ not hasattr(typing, "NoDefault") or not hasattr(typing, "NoExtraItems")
+)
+
+if _NEEDS_SINGLETONMETA:
+ class SingletonMeta(type):
+ def __setattr__(cls, attr, value):
+ # TypeError is consistent with the behavior of NoneType
+ raise TypeError(
+ f"cannot set {attr!r} attribute of immutable type {cls.__name__!r}"
+ )
+
+
+if hasattr(typing, "NoDefault"):
+ NoDefault = typing.NoDefault
+else:
+ class NoDefaultType(metaclass=SingletonMeta):
+ """The type of the NoDefault singleton."""
+
+ __slots__ = ()
+
+ def __new__(cls):
+ return globals().get("NoDefault") or object.__new__(cls)
+
+ def __repr__(self):
+ return "typing_extensions.NoDefault"
+
+ def __reduce__(self):
+ return "NoDefault"
+
+ NoDefault = NoDefaultType()
+ del NoDefaultType
+
+if hasattr(typing, "NoExtraItems"):
+ NoExtraItems = typing.NoExtraItems
+else:
+ class NoExtraItemsType(metaclass=SingletonMeta):
+ """The type of the NoExtraItems singleton."""
+
+ __slots__ = ()
+
+ def __new__(cls):
+ return globals().get("NoExtraItems") or object.__new__(cls)
+
+ def __repr__(self):
+ return "typing_extensions.NoExtraItems"
+
+ def __reduce__(self):
+ return "NoExtraItems"
+
+ NoExtraItems = NoExtraItemsType()
+ del NoExtraItemsType
+
+if _NEEDS_SINGLETONMETA:
+ del SingletonMeta
+
+
# Update this to something like >=3.13.0b1 if and when
# PEP 728 is implemented in CPython
_PEP_728_IMPLEMENTED = False
@@ -913,7 +981,9 @@ def _get_typeddict_qualifiers(annotation_type):
break
class _TypedDictMeta(type):
- def __new__(cls, name, bases, ns, *, total=True, closed=False):
+
+ def __new__(cls, name, bases, ns, *, total=True, closed=None,
+ extra_items=NoExtraItems):
"""Create new typed dict class object.
This method is called when TypedDict is subclassed,
@@ -925,6 +995,8 @@ def __new__(cls, name, bases, ns, *, total=True, closed=False):
if type(base) is not _TypedDictMeta and base is not typing.Generic:
raise TypeError('cannot inherit from both a TypedDict type '
'and a non-TypedDict base class')
+ if closed is not None and extra_items is not NoExtraItems:
+ raise TypeError(f"Cannot combine closed={closed!r} and extra_items")
if any(issubclass(b, typing.Generic) for b in bases):
generic_base = (typing.Generic,)
@@ -964,7 +1036,7 @@ def __new__(cls, name, bases, ns, *, total=True, closed=False):
optional_keys = set()
readonly_keys = set()
mutable_keys = set()
- extra_items_type = None
+ extra_items_type = extra_items
for base in bases:
base_dict = base.__dict__
@@ -974,13 +1046,12 @@ def __new__(cls, name, bases, ns, *, total=True, closed=False):
optional_keys.update(base_dict.get('__optional_keys__', ()))
readonly_keys.update(base_dict.get('__readonly_keys__', ()))
mutable_keys.update(base_dict.get('__mutable_keys__', ()))
- base_extra_items_type = base_dict.get('__extra_items__', None)
- if base_extra_items_type is not None:
- extra_items_type = base_extra_items_type
- if closed and extra_items_type is None:
- extra_items_type = Never
- if closed and "__extra_items__" in own_annotations:
+ # This was specified in an earlier version of PEP 728. Support
+ # is retained for backwards compatibility, but only for Python
+ # 3.13 and lower.
+ if (closed and sys.version_info < (3, 14)
+ and "__extra_items__" in own_annotations):
annotation_type = own_annotations.pop("__extra_items__")
qualifiers = set(_get_typeddict_qualifiers(annotation_type))
if Required in qualifiers:
@@ -1019,8 +1090,7 @@ def __new__(cls, name, bases, ns, *, total=True, closed=False):
tp_dict.__optional_keys__ = frozenset(optional_keys)
tp_dict.__readonly_keys__ = frozenset(readonly_keys)
tp_dict.__mutable_keys__ = frozenset(mutable_keys)
- if not hasattr(tp_dict, '__total__'):
- tp_dict.__total__ = total
+ tp_dict.__total__ = total
tp_dict.__closed__ = closed
tp_dict.__extra_items__ = extra_items_type
return tp_dict
@@ -1036,7 +1106,16 @@ def __subclasscheck__(cls, other):
_TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
@_ensure_subclassable(lambda bases: (_TypedDict,))
- def TypedDict(typename, fields=_marker, /, *, total=True, closed=False, **kwargs):
+ def TypedDict(
+ typename,
+ fields=_marker,
+ /,
+ *,
+ total=True,
+ closed=None,
+ extra_items=NoExtraItems,
+ **kwargs
+ ):
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
TypedDict creates a dictionary type such that a type checker will expect all
@@ -1096,9 +1175,14 @@ class Point2D(TypedDict):
"using the functional syntax, pass an empty dictionary, e.g. "
) + example + "."
warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2)
- if closed is not False and closed is not True:
+ # Support a field called "closed"
+ if closed is not False and closed is not True and closed is not None:
kwargs["closed"] = closed
- closed = False
+ closed = None
+ # Or "extra_items"
+ if extra_items is not NoExtraItems:
+ kwargs["extra_items"] = extra_items
+ extra_items = NoExtraItems
fields = kwargs
elif kwargs:
raise TypeError("TypedDict takes either a dict or keyword arguments,"
@@ -1120,7 +1204,8 @@ class Point2D(TypedDict):
# Setting correct module is necessary to make typed dict classes pickleable.
ns['__module__'] = module
- td = _TypedDictMeta(typename, (), ns, total=total, closed=closed)
+ td = _TypedDictMeta(typename, (), ns, total=total, closed=closed,
+ extra_items=extra_items)
td.__orig_bases__ = (TypedDict,)
return td
@@ -1232,10 +1317,90 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
)
else: # 3.8
hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
+ if sys.version_info < (3, 11):
+ _clean_optional(obj, hint, globalns, localns)
+ if sys.version_info < (3, 9):
+ # In 3.8 eval_type does not flatten Optional[ForwardRef] correctly
+ # This will recreate and and cache Unions.
+ hint = {
+ k: (t
+ if get_origin(t) != Union
+ else Union[t.__args__])
+ for k, t in hint.items()
+ }
if include_extras:
return hint
return {k: _strip_extras(t) for k, t in hint.items()}
+ _NoneType = type(None)
+
+ def _could_be_inserted_optional(t):
+ """detects Union[..., None] pattern"""
+ # 3.8+ compatible checking before _UnionGenericAlias
+ if get_origin(t) is not Union:
+ return False
+ # Assume if last argument is not None they are user defined
+ if t.__args__[-1] is not _NoneType:
+ return False
+ return True
+
+ # < 3.11
+ def _clean_optional(obj, hints, globalns=None, localns=None):
+ # reverts injected Union[..., None] cases from typing.get_type_hints
+ # when a None default value is used.
+ # see https://github.com/python/typing_extensions/issues/310
+ if not hints or isinstance(obj, type):
+ return
+ defaults = typing._get_defaults(obj) # avoid accessing __annotations___
+ if not defaults:
+ return
+ original_hints = obj.__annotations__
+ for name, value in hints.items():
+ # Not a Union[..., None] or replacement conditions not fullfilled
+ if (not _could_be_inserted_optional(value)
+ or name not in defaults
+ or defaults[name] is not None
+ ):
+ continue
+ original_value = original_hints[name]
+ # value=NoneType should have caused a skip above but check for safety
+ if original_value is None:
+ original_value = _NoneType
+ # Forward reference
+ if isinstance(original_value, str):
+ if globalns is None:
+ if isinstance(obj, _types.ModuleType):
+ globalns = obj.__dict__
+ else:
+ nsobj = obj
+ # Find globalns for the unwrapped object.
+ while hasattr(nsobj, '__wrapped__'):
+ nsobj = nsobj.__wrapped__
+ globalns = getattr(nsobj, '__globals__', {})
+ if localns is None:
+ localns = globalns
+ elif localns is None:
+ localns = globalns
+ if sys.version_info < (3, 9):
+ original_value = ForwardRef(original_value)
+ else:
+ original_value = ForwardRef(
+ original_value,
+ is_argument=not isinstance(obj, _types.ModuleType)
+ )
+ original_evaluated = typing._eval_type(original_value, globalns, localns)
+ if sys.version_info < (3, 9) and get_origin(original_evaluated) is Union:
+ # Union[str, None, "str"] is not reduced to Union[str, None]
+ original_evaluated = Union[original_evaluated.__args__]
+ # Compare if values differ. Note that even if equal
+ # value might be cached by typing._tp_cache contrary to original_evaluated
+ if original_evaluated != value or (
+ # 3.10: ForwardRefs of UnionType might be turned into _UnionGenericAlias
+ hasattr(_types, "UnionType")
+ and isinstance(original_evaluated, _types.UnionType)
+ and not isinstance(value, _types.UnionType)
+ ):
+ hints[name] = original_evaluated
# Python 3.9+ has PEP 593 (Annotated)
if hasattr(typing, 'Annotated'):
@@ -1443,34 +1608,6 @@ def TypeAlias(self, parameters):
)
-if hasattr(typing, "NoDefault"):
- NoDefault = typing.NoDefault
-else:
- class NoDefaultTypeMeta(type):
- def __setattr__(cls, attr, value):
- # TypeError is consistent with the behavior of NoneType
- raise TypeError(
- f"cannot set {attr!r} attribute of immutable type {cls.__name__!r}"
- )
-
- class NoDefaultType(metaclass=NoDefaultTypeMeta):
- """The type of the NoDefault singleton."""
-
- __slots__ = ()
-
- def __new__(cls):
- return globals().get("NoDefault") or object.__new__(cls)
-
- def __repr__(self):
- return "typing_extensions.NoDefault"
-
- def __reduce__(self):
- return "NoDefault"
-
- NoDefault = NoDefaultType()
- del NoDefaultType, NoDefaultTypeMeta
-
-
def _set_default(type_param, default):
type_param.has_default = lambda: default is not NoDefault
type_param.__default__ = default
@@ -1761,6 +1898,23 @@ def __call__(self, *args, **kwargs):
# 3.8-3.9
if not hasattr(typing, 'Concatenate'):
# Inherits from list as a workaround for Callable checks in Python < 3.9.2.
+
+ # 3.9.0-1
+ if not hasattr(typing, '_type_convert'):
+ def _type_convert(arg, module=None, *, allow_special_forms=False):
+ """For converting None to type(None), and strings to ForwardRef."""
+ if arg is None:
+ return type(None)
+ if isinstance(arg, str):
+ if sys.version_info <= (3, 9, 6):
+ return ForwardRef(arg)
+ if sys.version_info <= (3, 9, 7):
+ return ForwardRef(arg, module=module)
+ return ForwardRef(arg, module=module, is_class=allow_special_forms)
+ return arg
+ else:
+ _type_convert = typing._type_convert
+
class _ConcatenateGenericAlias(list):
# Trick Generic into looking into this for __parameters__.
@@ -1792,27 +1946,171 @@ def __parameters__(self):
tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec))
)
+ # 3.8; needed for typing._subst_tvars
+ # 3.9 used by __getitem__ below
+ def copy_with(self, params):
+ if isinstance(params[-1], _ConcatenateGenericAlias):
+ params = (*params[:-1], *params[-1].__args__)
+ elif isinstance(params[-1], (list, tuple)):
+ return (*params[:-1], *params[-1])
+ elif (not (params[-1] is ... or isinstance(params[-1], ParamSpec))):
+ raise TypeError("The last parameter to Concatenate should be a "
+ "ParamSpec variable or ellipsis.")
+ return self.__class__(self.__origin__, params)
+
+ # 3.9; accessed during GenericAlias.__getitem__ when substituting
+ def __getitem__(self, args):
+ if self.__origin__ in (Generic, Protocol):
+ # Can't subscript Generic[...] or Protocol[...].
+ raise TypeError(f"Cannot subscript already-subscripted {self}")
+ if not self.__parameters__:
+ raise TypeError(f"{self} is not a generic class")
+
+ if not isinstance(args, tuple):
+ args = (args,)
+ args = _unpack_args(*(_type_convert(p) for p in args))
+ params = self.__parameters__
+ for param in params:
+ prepare = getattr(param, "__typing_prepare_subst__", None)
+ if prepare is not None:
+ args = prepare(self, args)
+ # 3.8 - 3.9 & typing.ParamSpec
+ elif isinstance(param, ParamSpec):
+ i = params.index(param)
+ if (
+ i == len(args)
+ and getattr(param, '__default__', NoDefault) is not NoDefault
+ ):
+ args = [*args, param.__default__]
+ if i >= len(args):
+ raise TypeError(f"Too few arguments for {self}")
+ # Special case for Z[[int, str, bool]] == Z[int, str, bool]
+ if len(params) == 1 and not _is_param_expr(args[0]):
+ assert i == 0
+ args = (args,)
+ elif (
+ isinstance(args[i], list)
+ # 3.8 - 3.9
+ # This class inherits from list do not convert
+ and not isinstance(args[i], _ConcatenateGenericAlias)
+ ):
+ args = (*args[:i], tuple(args[i]), *args[i + 1:])
-# 3.8-3.9
+ alen = len(args)
+ plen = len(params)
+ if alen != plen:
+ raise TypeError(
+ f"Too {'many' if alen > plen else 'few'} arguments for {self};"
+ f" actual {alen}, expected {plen}"
+ )
+
+ subst = dict(zip(self.__parameters__, args))
+ # determine new args
+ new_args = []
+ for arg in self.__args__:
+ if isinstance(arg, type):
+ new_args.append(arg)
+ continue
+ if isinstance(arg, TypeVar):
+ arg = subst[arg]
+ if (
+ (isinstance(arg, typing._GenericAlias) and _is_unpack(arg))
+ or (
+ hasattr(_types, "GenericAlias")
+ and isinstance(arg, _types.GenericAlias)
+ and getattr(arg, "__unpacked__", False)
+ )
+ ):
+ raise TypeError(f"{arg} is not valid as type argument")
+
+ elif isinstance(arg,
+ typing._GenericAlias
+ if not hasattr(_types, "GenericAlias") else
+ (typing._GenericAlias, _types.GenericAlias)
+ ):
+ subparams = arg.__parameters__
+ if subparams:
+ subargs = tuple(subst[x] for x in subparams)
+ arg = arg[subargs]
+ new_args.append(arg)
+ return self.copy_with(tuple(new_args))
+
+# 3.10+
+else:
+ _ConcatenateGenericAlias = typing._ConcatenateGenericAlias
+
+ # 3.10
+ if sys.version_info < (3, 11):
+
+ class _ConcatenateGenericAlias(typing._ConcatenateGenericAlias, _root=True):
+ # needed for checks in collections.abc.Callable to accept this class
+ __module__ = "typing"
+
+ def copy_with(self, params):
+ if isinstance(params[-1], (list, tuple)):
+ return (*params[:-1], *params[-1])
+ if isinstance(params[-1], typing._ConcatenateGenericAlias):
+ params = (*params[:-1], *params[-1].__args__)
+ elif not (params[-1] is ... or isinstance(params[-1], ParamSpec)):
+ raise TypeError("The last parameter to Concatenate should be a "
+ "ParamSpec variable or ellipsis.")
+ return super(typing._ConcatenateGenericAlias, self).copy_with(params)
+
+ def __getitem__(self, args):
+ value = super().__getitem__(args)
+ if isinstance(value, tuple) and any(_is_unpack(t) for t in value):
+ return tuple(_unpack_args(*(n for n in value)))
+ return value
+
+
+# 3.8-3.9.2
+class _EllipsisDummy: ...
+
+
+# 3.8-3.10
+def _create_concatenate_alias(origin, parameters):
+ if parameters[-1] is ... and sys.version_info < (3, 9, 2):
+ # Hack: Arguments must be types, replace it with one.
+ parameters = (*parameters[:-1], _EllipsisDummy)
+ if sys.version_info >= (3, 10, 2):
+ concatenate = _ConcatenateGenericAlias(origin, parameters,
+ _typevar_types=(TypeVar, ParamSpec),
+ _paramspec_tvars=True)
+ else:
+ concatenate = _ConcatenateGenericAlias(origin, parameters)
+ if parameters[-1] is not _EllipsisDummy:
+ return concatenate
+ # Remove dummy again
+ concatenate.__args__ = tuple(p if p is not _EllipsisDummy else ...
+ for p in concatenate.__args__)
+ if sys.version_info < (3, 10):
+ # backport needs __args__ adjustment only
+ return concatenate
+ concatenate.__parameters__ = tuple(p for p in concatenate.__parameters__
+ if p is not _EllipsisDummy)
+ return concatenate
+
+
+# 3.8-3.10
@typing._tp_cache
def _concatenate_getitem(self, parameters):
if parameters == ():
raise TypeError("Cannot take a Concatenate of no types.")
if not isinstance(parameters, tuple):
parameters = (parameters,)
- if not isinstance(parameters[-1], ParamSpec):
+ if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)):
raise TypeError("The last parameter to Concatenate should be a "
- "ParamSpec variable.")
+ "ParamSpec variable or ellipsis.")
msg = "Concatenate[arg, ...]: each arg must be a type."
- parameters = tuple(typing._type_check(p, msg) for p in parameters)
- return _ConcatenateGenericAlias(self, parameters)
+ parameters = (*(typing._type_check(p, msg) for p in parameters[:-1]),
+ parameters[-1])
+ return _create_concatenate_alias(self, parameters)
-# 3.10+
-if hasattr(typing, 'Concatenate'):
+# 3.11+; Concatenate does not accept ellipsis in 3.10
+if sys.version_info >= (3, 11):
Concatenate = typing.Concatenate
- _ConcatenateGenericAlias = typing._ConcatenateGenericAlias
-# 3.9
+# 3.9-3.10
elif sys.version_info[:2] >= (3, 9):
@_ExtensionsSpecialForm
def Concatenate(self, parameters):
@@ -1976,7 +2274,7 @@ def TypeIs(self, parameters):
1. The return value is a boolean.
2. If the return value is ``True``, the type of its argument
- is the intersection of the type inside ``TypeGuard`` and the argument's
+ is the intersection of the type inside ``TypeIs`` and the argument's
previously known type.
For example::
@@ -2024,7 +2322,7 @@ def __getitem__(self, parameters):
1. The return value is a boolean.
2. If the return value is ``True``, the type of its argument
- is the intersection of the type inside ``TypeGuard`` and the argument's
+ is the intersection of the type inside ``TypeIs`` and the argument's
previously known type.
For example::
@@ -2042,6 +2340,69 @@ def f(val: Union[int, Awaitable[int]]) -> int:
PEP 742 (Narrowing types with TypeIs).
""")
+# 3.14+?
+if hasattr(typing, 'TypeForm'):
+ TypeForm = typing.TypeForm
+# 3.9
+elif sys.version_info[:2] >= (3, 9):
+ class _TypeFormForm(_ExtensionsSpecialForm, _root=True):
+ # TypeForm(X) is equivalent to X but indicates to the type checker
+ # that the object is a TypeForm.
+ def __call__(self, obj, /):
+ return obj
+
+ @_TypeFormForm
+ def TypeForm(self, parameters):
+ """A special form representing the value that results from the evaluation
+ of a type expression. This value encodes the information supplied in the
+ type expression, and it represents the type described by that type expression.
+
+ When used in a type expression, TypeForm describes a set of type form objects.
+ It accepts a single type argument, which must be a valid type expression.
+ ``TypeForm[T]`` describes the set of all type form objects that represent
+ the type T or types that are assignable to T.
+
+ Usage:
+
+ def cast[T](typ: TypeForm[T], value: Any) -> T: ...
+
+ reveal_type(cast(int, "x")) # int
+
+ See PEP 747 for more information.
+ """
+ item = typing._type_check(parameters, f'{self} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+# 3.8
+else:
+ class _TypeFormForm(_ExtensionsSpecialForm, _root=True):
+ def __getitem__(self, parameters):
+ item = typing._type_check(parameters,
+ f'{self._name} accepts only a single type')
+ return typing._GenericAlias(self, (item,))
+
+ def __call__(self, obj, /):
+ return obj
+
+ TypeForm = _TypeFormForm(
+ 'TypeForm',
+ doc="""A special form representing the value that results from the evaluation
+ of a type expression. This value encodes the information supplied in the
+ type expression, and it represents the type described by that type expression.
+
+ When used in a type expression, TypeForm describes a set of type form objects.
+ It accepts a single type argument, which must be a valid type expression.
+ ``TypeForm[T]`` describes the set of all type form objects that represent
+ the type T or types that are assignable to T.
+
+ Usage:
+
+ def cast[T](typ: TypeForm[T], value: Any) -> T: ...
+
+ reveal_type(cast(int, "x")) # int
+
+ See PEP 747 for more information.
+ """)
+
# Vendored from cpython typing._SpecialFrom
class _SpecialForm(typing._Final, _root=True):
@@ -2344,7 +2705,9 @@ def __init__(self, getitem):
self.__doc__ = _UNPACK_DOC
class _UnpackAlias(typing._GenericAlias, _root=True):
- __class__ = typing.TypeVar
+ if sys.version_info < (3, 11):
+ # needed for compatibility with Generic[Unpack[Ts]]
+ __class__ = typing.TypeVar
@property
def __typing_unpacked_tuple_args__(self):
@@ -2357,6 +2720,17 @@ def __typing_unpacked_tuple_args__(self):
return arg.__args__
return None
+ @property
+ def __typing_is_unpacked_typevartuple__(self):
+ assert self.__origin__ is Unpack
+ assert len(self.__args__) == 1
+ return isinstance(self.__args__[0], TypeVarTuple)
+
+ def __getitem__(self, args):
+ if self.__typing_is_unpacked_typevartuple__:
+ return args
+ return super().__getitem__(args)
+
@_UnpackSpecialForm
def Unpack(self, parameters):
item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
@@ -2369,6 +2743,28 @@ def _is_unpack(obj):
class _UnpackAlias(typing._GenericAlias, _root=True):
__class__ = typing.TypeVar
+ @property
+ def __typing_unpacked_tuple_args__(self):
+ assert self.__origin__ is Unpack
+ assert len(self.__args__) == 1
+ arg, = self.__args__
+ if isinstance(arg, typing._GenericAlias):
+ if arg.__origin__ is not tuple:
+ raise TypeError("Unpack[...] must be used with a tuple type")
+ return arg.__args__
+ return None
+
+ @property
+ def __typing_is_unpacked_typevartuple__(self):
+ assert self.__origin__ is Unpack
+ assert len(self.__args__) == 1
+ return isinstance(self.__args__[0], TypeVarTuple)
+
+ def __getitem__(self, args):
+ if self.__typing_is_unpacked_typevartuple__:
+ return args
+ return super().__getitem__(args)
+
class _UnpackForm(_ExtensionsSpecialForm, _root=True):
def __getitem__(self, parameters):
item = typing._type_check(parameters,
@@ -2381,21 +2777,22 @@ def _is_unpack(obj):
return isinstance(obj, _UnpackAlias)
+def _unpack_args(*args):
+ newargs = []
+ for arg in args:
+ subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
+ if subargs is not None and (not (subargs and subargs[-1] is ...)):
+ newargs.extend(subargs)
+ else:
+ newargs.append(arg)
+ return newargs
+
+
if _PEP_696_IMPLEMENTED:
from typing import TypeVarTuple
elif hasattr(typing, "TypeVarTuple"): # 3.11+
- def _unpack_args(*args):
- newargs = []
- for arg in args:
- subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
- if subargs is not None and not (subargs and subargs[-1] is ...):
- newargs.extend(subargs)
- else:
- newargs.append(arg)
- return newargs
-
# Add default parameter - PEP 696
class TypeVarTuple(metaclass=_TypeVarLikeMeta):
"""Type variable tuple."""
@@ -2845,13 +3242,21 @@ def __init_subclass__(*args, **kwargs):
__init_subclass__.__deprecated__ = msg
return arg
elif callable(arg):
+ import asyncio.coroutines
import functools
+ import inspect
@functools.wraps(arg)
def wrapper(*args, **kwargs):
warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
return arg(*args, **kwargs)
+ if asyncio.coroutines.iscoroutinefunction(arg):
+ if sys.version_info >= (3, 12):
+ wrapper = inspect.markcoroutinefunction(wrapper)
+ else:
+ wrapper._is_coroutine = asyncio.coroutines._is_coroutine
+
arg.__deprecated__ = wrapper.__deprecated__ = msg
return wrapper
else:
@@ -2860,6 +3265,24 @@ def wrapper(*args, **kwargs):
f"a class or callable, not {arg!r}"
)
+if sys.version_info < (3, 10):
+ def _is_param_expr(arg):
+ return arg is ... or isinstance(
+ arg, (tuple, list, ParamSpec, _ConcatenateGenericAlias)
+ )
+else:
+ def _is_param_expr(arg):
+ return arg is ... or isinstance(
+ arg,
+ (
+ tuple,
+ list,
+ ParamSpec,
+ _ConcatenateGenericAlias,
+ typing._ConcatenateGenericAlias,
+ ),
+ )
+
# We have to do some monkey patching to deal with the dual nature of
# Unpack/TypeVarTuple:
@@ -2874,6 +3297,17 @@ def _check_generic(cls, parameters, elen=_marker):
This gives a nice error message in case of count mismatch.
"""
+ # If substituting a single ParamSpec with multiple arguments
+ # we do not check the count
+ if (inspect.isclass(cls) and issubclass(cls, typing.Generic)
+ and len(cls.__parameters__) == 1
+ and isinstance(cls.__parameters__[0], ParamSpec)
+ and parameters
+ and not _is_param_expr(parameters[0])
+ ):
+ # Generic modifies parameters variable, but here we cannot do this
+ return
+
if not elen:
raise TypeError(f"{cls} is not a generic class")
if elen is _marker:
@@ -3007,7 +3441,10 @@ def _collect_type_vars(types, typevar_types=None):
for t in types:
if _is_unpacked_typevartuple(t):
type_var_tuple_encountered = True
- elif isinstance(t, typevar_types) and t not in tvars:
+ elif (
+ isinstance(t, typevar_types) and not isinstance(t, _UnpackAlias)
+ and t not in tvars
+ ):
if enforce_default_ordering:
has_default = getattr(t, '__default__', NoDefault) is not NoDefault
if has_default:
@@ -3022,6 +3459,13 @@ def _collect_type_vars(types, typevar_types=None):
tvars.append(t)
if _should_collect_from_parameters(t):
tvars.extend([t for t in t.__parameters__ if t not in tvars])
+ elif isinstance(t, tuple):
+ # Collect nested type_vars
+ # tuple wrapped by _prepare_paramspec_params(cls, params)
+ for x in t:
+ for collected in _collect_type_vars([x]):
+ if collected not in tvars:
+ tvars.append(collected)
return tuple(tvars)
typing._collect_type_vars = _collect_type_vars
@@ -3379,8 +3823,9 @@ def __ror__(self, other):
return typing.Union[other, self]
-if hasattr(typing, "TypeAliasType"):
+if sys.version_info >= (3, 14):
TypeAliasType = typing.TypeAliasType
+# 3.8-3.13
else:
def _is_unionable(obj):
"""Corresponds to is_unionable() in unionobject.c in CPython."""
@@ -3391,6 +3836,37 @@ def _is_unionable(obj):
TypeAliasType,
))
+ if sys.version_info < (3, 10):
+ # Copied and pasted from https://github.com/python/cpython/blob/986a4e1b6fcae7fe7a1d0a26aea446107dd58dd2/Objects/genericaliasobject.c#L568-L582,
+ # so that we emulate the behaviour of `types.GenericAlias`
+ # on the latest versions of CPython
+ _ATTRIBUTE_DELEGATION_EXCLUSIONS = frozenset({
+ "__class__",
+ "__bases__",
+ "__origin__",
+ "__args__",
+ "__unpacked__",
+ "__parameters__",
+ "__typing_unpacked_tuple_args__",
+ "__mro_entries__",
+ "__reduce_ex__",
+ "__reduce__",
+ "__copy__",
+ "__deepcopy__",
+ })
+
+ class _TypeAliasGenericAlias(typing._GenericAlias, _root=True):
+ def __getattr__(self, attr):
+ if attr in _ATTRIBUTE_DELEGATION_EXCLUSIONS:
+ return object.__getattr__(self, attr)
+ return getattr(self.__origin__, attr)
+
+ if sys.version_info < (3, 9):
+ def __getitem__(self, item):
+ result = super().__getitem__(item)
+ result.__class__ = type(self)
+ return result
+
class TypeAliasType:
"""Create named, parameterized type aliases.
@@ -3422,11 +3898,29 @@ class TypeAliasType:
def __init__(self, name: str, value, *, type_params=()):
if not isinstance(name, str):
raise TypeError("TypeAliasType name must be a string")
+ if not isinstance(type_params, tuple):
+ raise TypeError("type_params must be a tuple")
self.__value__ = value
self.__type_params__ = type_params
+ default_value_encountered = False
parameters = []
for type_param in type_params:
+ if (
+ not isinstance(type_param, (TypeVar, TypeVarTuple, ParamSpec))
+ # 3.8-3.11
+ # Unpack Backport passes isinstance(type_param, TypeVar)
+ or _is_unpack(type_param)
+ ):
+ raise TypeError(f"Expected a type param, got {type_param!r}")
+ has_default = (
+ getattr(type_param, '__default__', NoDefault) is not NoDefault
+ )
+ if default_value_encountered and not has_default:
+ raise TypeError(f"non-default type parameter '{type_param!r}'"
+ " follows default type parameter")
+ if has_default:
+ default_value_encountered = True
if isinstance(type_param, TypeVarTuple):
parameters.extend(type_param)
else:
@@ -3463,16 +3957,49 @@ def _raise_attribute_error(self, name: str) -> Never:
def __repr__(self) -> str:
return self.__name__
+ if sys.version_info < (3, 11):
+ def _check_single_param(self, param, recursion=0):
+ # Allow [], [int], [int, str], [int, ...], [int, T]
+ if param is ...:
+ return ...
+ if param is None:
+ return None
+ # Note in <= 3.9 _ConcatenateGenericAlias inherits from list
+ if isinstance(param, list) and recursion == 0:
+ return [self._check_single_param(arg, recursion+1)
+ for arg in param]
+ return typing._type_check(
+ param, f'Subscripting {self.__name__} requires a type.'
+ )
+
+ def _check_parameters(self, parameters):
+ if sys.version_info < (3, 11):
+ return tuple(
+ self._check_single_param(item)
+ for item in parameters
+ )
+ return tuple(typing._type_check(
+ item, f'Subscripting {self.__name__} requires a type.'
+ )
+ for item in parameters
+ )
+
def __getitem__(self, parameters):
+ if not self.__type_params__:
+ raise TypeError("Only generic type aliases are subscriptable")
if not isinstance(parameters, tuple):
parameters = (parameters,)
- parameters = [
- typing._type_check(
- item, f'Subscripting {self.__name__} requires a type.'
- )
- for item in parameters
- ]
- return typing._GenericAlias(self, tuple(parameters))
+ # Using 3.9 here will create problems with Concatenate
+ if sys.version_info >= (3, 10):
+ return _types.GenericAlias(self, parameters)
+ type_vars = _collect_type_vars(parameters)
+ parameters = self._check_parameters(parameters)
+ alias = _TypeAliasGenericAlias(self, parameters)
+ # alias.__parameters__ is not complete if Concatenate is present
+ # as it is converted to a list from which no parameters are extracted.
+ if alias.__parameters__ != type_vars:
+ alias.__parameters__ = type_vars
+ return alias
def __reduce__(self):
return self.__name__
@@ -3599,6 +4126,404 @@ def __eq__(self, other: object) -> bool:
__all__.append("CapsuleType")
+# Using this convoluted approach so that this keeps working
+# whether we end up using PEP 649 as written, PEP 749, or
+# some other variation: in any case, inspect.get_annotations
+# will continue to exist and will gain a `format` parameter.
+_PEP_649_OR_749_IMPLEMENTED = (
+ hasattr(inspect, 'get_annotations')
+ and inspect.get_annotations.__kwdefaults__ is not None
+ and "format" in inspect.get_annotations.__kwdefaults__
+)
+
+
+class Format(enum.IntEnum):
+ VALUE = 1
+ FORWARDREF = 2
+ STRING = 3
+
+
+if _PEP_649_OR_749_IMPLEMENTED:
+ get_annotations = inspect.get_annotations
+else:
+ def get_annotations(obj, *, globals=None, locals=None, eval_str=False,
+ format=Format.VALUE):
+ """Compute the annotations dict for an object.
+
+ obj may be a callable, class, or module.
+ Passing in an object of any other type raises TypeError.
+
+ Returns a dict. get_annotations() returns a new dict every time
+ it's called; calling it twice on the same object will return two
+ different but equivalent dicts.
+
+ This is a backport of `inspect.get_annotations`, which has been
+ in the standard library since Python 3.10. See the standard library
+ documentation for more:
+
+ https://docs.python.org/3/library/inspect.html#inspect.get_annotations
+
+ This backport adds the *format* argument introduced by PEP 649. The
+ three formats supported are:
+ * VALUE: the annotations are returned as-is. This is the default and
+ it is compatible with the behavior on previous Python versions.
+ * FORWARDREF: return annotations as-is if possible, but replace any
+ undefined names with ForwardRef objects. The implementation proposed by
+ PEP 649 relies on language changes that cannot be backported; the
+ typing-extensions implementation simply returns the same result as VALUE.
+ * STRING: return annotations as strings, in a format close to the original
+ source. Again, this behavior cannot be replicated directly in a backport.
+ As an approximation, typing-extensions retrieves the annotations under
+ VALUE semantics and then stringifies them.
+
+ The purpose of this backport is to allow users who would like to use
+ FORWARDREF or STRING semantics once PEP 649 is implemented, but who also
+ want to support earlier Python versions, to simply write:
+
+ typing_extensions.get_annotations(obj, format=Format.FORWARDREF)
+
+ """
+ format = Format(format)
+
+ if eval_str and format is not Format.VALUE:
+ raise ValueError("eval_str=True is only supported with format=Format.VALUE")
+
+ if isinstance(obj, type):
+ # class
+ obj_dict = getattr(obj, '__dict__', None)
+ if obj_dict and hasattr(obj_dict, 'get'):
+ ann = obj_dict.get('__annotations__', None)
+ if isinstance(ann, _types.GetSetDescriptorType):
+ ann = None
+ else:
+ ann = None
+
+ obj_globals = None
+ module_name = getattr(obj, '__module__', None)
+ if module_name:
+ module = sys.modules.get(module_name, None)
+ if module:
+ obj_globals = getattr(module, '__dict__', None)
+ obj_locals = dict(vars(obj))
+ unwrap = obj
+ elif isinstance(obj, _types.ModuleType):
+ # module
+ ann = getattr(obj, '__annotations__', None)
+ obj_globals = obj.__dict__
+ obj_locals = None
+ unwrap = None
+ elif callable(obj):
+ # this includes types.Function, types.BuiltinFunctionType,
+ # types.BuiltinMethodType, functools.partial, functools.singledispatch,
+ # "class funclike" from Lib/test/test_inspect... on and on it goes.
+ ann = getattr(obj, '__annotations__', None)
+ obj_globals = getattr(obj, '__globals__', None)
+ obj_locals = None
+ unwrap = obj
+ elif hasattr(obj, '__annotations__'):
+ ann = obj.__annotations__
+ obj_globals = obj_locals = unwrap = None
+ else:
+ raise TypeError(f"{obj!r} is not a module, class, or callable.")
+
+ if ann is None:
+ return {}
+
+ if not isinstance(ann, dict):
+ raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
+
+ if not ann:
+ return {}
+
+ if not eval_str:
+ if format is Format.STRING:
+ return {
+ key: value if isinstance(value, str) else typing._type_repr(value)
+ for key, value in ann.items()
+ }
+ return dict(ann)
+
+ if unwrap is not None:
+ while True:
+ if hasattr(unwrap, '__wrapped__'):
+ unwrap = unwrap.__wrapped__
+ continue
+ if isinstance(unwrap, functools.partial):
+ unwrap = unwrap.func
+ continue
+ break
+ if hasattr(unwrap, "__globals__"):
+ obj_globals = unwrap.__globals__
+
+ if globals is None:
+ globals = obj_globals
+ if locals is None:
+ locals = obj_locals or {}
+
+ # "Inject" type parameters into the local namespace
+ # (unless they are shadowed by assignments *in* the local namespace),
+ # as a way of emulating annotation scopes when calling `eval()`
+ if type_params := getattr(obj, "__type_params__", ()):
+ locals = {param.__name__: param for param in type_params} | locals
+
+ return_value = {key:
+ value if not isinstance(value, str) else eval(value, globals, locals)
+ for key, value in ann.items() }
+ return return_value
+
+
+if hasattr(typing, "evaluate_forward_ref"):
+ evaluate_forward_ref = typing.evaluate_forward_ref
+else:
+ # Implements annotationlib.ForwardRef.evaluate
+ def _eval_with_owner(
+ forward_ref, *, owner=None, globals=None, locals=None, type_params=None
+ ):
+ if forward_ref.__forward_evaluated__:
+ return forward_ref.__forward_value__
+ if getattr(forward_ref, "__cell__", None) is not None:
+ try:
+ value = forward_ref.__cell__.cell_contents
+ except ValueError:
+ pass
+ else:
+ forward_ref.__forward_evaluated__ = True
+ forward_ref.__forward_value__ = value
+ return value
+ if owner is None:
+ owner = getattr(forward_ref, "__owner__", None)
+
+ if (
+ globals is None
+ and getattr(forward_ref, "__forward_module__", None) is not None
+ ):
+ globals = getattr(
+ sys.modules.get(forward_ref.__forward_module__, None), "__dict__", None
+ )
+ if globals is None:
+ globals = getattr(forward_ref, "__globals__", None)
+ if globals is None:
+ if isinstance(owner, type):
+ module_name = getattr(owner, "__module__", None)
+ if module_name:
+ module = sys.modules.get(module_name, None)
+ if module:
+ globals = getattr(module, "__dict__", None)
+ elif isinstance(owner, _types.ModuleType):
+ globals = getattr(owner, "__dict__", None)
+ elif callable(owner):
+ globals = getattr(owner, "__globals__", None)
+
+ # If we pass None to eval() below, the globals of this module are used.
+ if globals is None:
+ globals = {}
+
+ if locals is None:
+ locals = {}
+ if isinstance(owner, type):
+ locals.update(vars(owner))
+
+ if type_params is None and owner is not None:
+ # "Inject" type parameters into the local namespace
+ # (unless they are shadowed by assignments *in* the local namespace),
+ # as a way of emulating annotation scopes when calling `eval()`
+ type_params = getattr(owner, "__type_params__", None)
+
+ # type parameters require some special handling,
+ # as they exist in their own scope
+ # but `eval()` does not have a dedicated parameter for that scope.
+ # For classes, names in type parameter scopes should override
+ # names in the global scope (which here are called `localns`!),
+ # but should in turn be overridden by names in the class scope
+ # (which here are called `globalns`!)
+ if type_params is not None:
+ globals = dict(globals)
+ locals = dict(locals)
+ for param in type_params:
+ param_name = param.__name__
+ if (
+ _FORWARD_REF_HAS_CLASS and not forward_ref.__forward_is_class__
+ ) or param_name not in globals:
+ globals[param_name] = param
+ locals.pop(param_name, None)
+
+ arg = forward_ref.__forward_arg__
+ if arg.isidentifier() and not keyword.iskeyword(arg):
+ if arg in locals:
+ value = locals[arg]
+ elif arg in globals:
+ value = globals[arg]
+ elif hasattr(builtins, arg):
+ return getattr(builtins, arg)
+ else:
+ raise NameError(arg)
+ else:
+ code = forward_ref.__forward_code__
+ value = eval(code, globals, locals)
+ forward_ref.__forward_evaluated__ = True
+ forward_ref.__forward_value__ = value
+ return value
+
+ def _lax_type_check(
+ value, msg, is_argument=True, *, module=None, allow_special_forms=False
+ ):
+ """
+ A lax Python 3.11+ like version of typing._type_check
+ """
+ if hasattr(typing, "_type_convert"):
+ if _FORWARD_REF_HAS_CLASS:
+ type_ = typing._type_convert(
+ value,
+ module=module,
+ allow_special_forms=allow_special_forms,
+ )
+ # module was added with bpo-41249 before is_class (bpo-46539)
+ elif "__forward_module__" in typing.ForwardRef.__slots__:
+ type_ = typing._type_convert(value, module=module)
+ else:
+ type_ = typing._type_convert(value)
+ else:
+ if value is None:
+ return type(None)
+ if isinstance(value, str):
+ return ForwardRef(value)
+ type_ = value
+ invalid_generic_forms = (Generic, Protocol)
+ if not allow_special_forms:
+ invalid_generic_forms += (ClassVar,)
+ if is_argument:
+ invalid_generic_forms += (Final,)
+ if (
+ isinstance(type_, typing._GenericAlias)
+ and get_origin(type_) in invalid_generic_forms
+ ):
+ raise TypeError(f"{type_} is not valid as type argument") from None
+ if type_ in (Any, LiteralString, NoReturn, Never, Self, TypeAlias):
+ return type_
+ if allow_special_forms and type_ in (ClassVar, Final):
+ return type_
+ if (
+ isinstance(type_, (_SpecialForm, typing._SpecialForm))
+ or type_ in (Generic, Protocol)
+ ):
+ raise TypeError(f"Plain {type_} is not valid as type argument") from None
+ if type(type_) is tuple: # lax version with tuple instead of callable
+ raise TypeError(f"{msg} Got {type_!r:.100}.")
+ return type_
+
+ def evaluate_forward_ref(
+ forward_ref,
+ *,
+ owner=None,
+ globals=None,
+ locals=None,
+ type_params=None,
+ format=Format.VALUE,
+ _recursive_guard=frozenset(),
+ ):
+ """Evaluate a forward reference as a type hint.
+
+ This is similar to calling the ForwardRef.evaluate() method,
+ but unlike that method, evaluate_forward_ref() also:
+
+ * Recursively evaluates forward references nested within the type hint.
+ * Rejects certain objects that are not valid type hints.
+ * Replaces type hints that evaluate to None with types.NoneType.
+ * Supports the *FORWARDREF* and *STRING* formats.
+
+ *forward_ref* must be an instance of ForwardRef. *owner*, if given,
+ should be the object that holds the annotations that the forward reference
+ derived from, such as a module, class object, or function. It is used to
+ infer the namespaces to use for looking up names. *globals* and *locals*
+ can also be explicitly given to provide the global and local namespaces.
+ *type_params* is a tuple of type parameters that are in scope when
+ evaluating the forward reference. This parameter must be provided (though
+ it may be an empty tuple) if *owner* is not given and the forward reference
+ does not already have an owner set. *format* specifies the format of the
+ annotation and is a member of the annotationlib.Format enum.
+
+ """
+ if format == Format.STRING:
+ return forward_ref.__forward_arg__
+ if forward_ref.__forward_arg__ in _recursive_guard:
+ return forward_ref
+
+ # Evaluate the forward reference
+ try:
+ value = _eval_with_owner(
+ forward_ref,
+ owner=owner,
+ globals=globals,
+ locals=locals,
+ type_params=type_params,
+ )
+ except NameError:
+ if format == Format.FORWARDREF:
+ return forward_ref
+ else:
+ raise
+
+ msg = "Forward references must evaluate to types."
+ if not _FORWARD_REF_HAS_CLASS:
+ allow_special_forms = not forward_ref.__forward_is_argument__
+ else:
+ allow_special_forms = forward_ref.__forward_is_class__
+ type_ = _lax_type_check(
+ value,
+ msg,
+ is_argument=forward_ref.__forward_is_argument__,
+ allow_special_forms=allow_special_forms,
+ )
+
+ # Recursively evaluate the type
+ if isinstance(type_, ForwardRef):
+ if getattr(type_, "__forward_module__", True) is not None:
+ globals = None
+ return evaluate_forward_ref(
+ type_,
+ globals=globals,
+ locals=locals,
+ type_params=type_params, owner=owner,
+ _recursive_guard=_recursive_guard, format=format
+ )
+ if sys.version_info < (3, 12, 5) and type_params:
+ # Make use of type_params
+ locals = dict(locals) if locals else {}
+ for tvar in type_params:
+ if tvar.__name__ not in locals: # lets not overwrite something present
+ locals[tvar.__name__] = tvar
+ if sys.version_info < (3, 9):
+ return typing._eval_type(
+ type_,
+ globals,
+ locals,
+ )
+ if sys.version_info < (3, 12, 5):
+ return typing._eval_type(
+ type_,
+ globals,
+ locals,
+ recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
+ )
+ if sys.version_info < (3, 14):
+ return typing._eval_type(
+ type_,
+ globals,
+ locals,
+ type_params,
+ recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
+ )
+ return typing._eval_type(
+ type_,
+ globals,
+ locals,
+ type_params,
+ recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
+ format=format,
+ owner=owner,
+ )
+
+
# Aliases for items that have always been in typing.
# Explicitly assign these (rather than using `from typing import *` at the top),
# so that we get a CI error if one of these is deleted from typing.py
diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt
index 8962e6fce7d..c867341699f 100644
--- a/src/pip/_vendor/vendor.txt
+++ b/src/pip/_vendor/vendor.txt
@@ -11,7 +11,7 @@ requests==2.32.3
urllib3==1.26.20
rich==14.0.0
pygments==2.19.1
- typing_extensions==4.12.2
+ typing_extensions==4.13.0
resolvelib==1.1.0
setuptools==70.3.0
tomli==2.2.1
From b4939beca8276177b1f719efe523e5c9eb4fc3af Mon Sep 17 00:00:00 2001
From: Paul Moore
Date: Tue, 1 Apr 2025 12:09:30 +0100
Subject: [PATCH 4/5] Document command to upgrade vendored libraries
---
src/pip/_vendor/README.rst | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/pip/_vendor/README.rst b/src/pip/_vendor/README.rst
index e3fe8041ce2..a925e8cc6aa 100644
--- a/src/pip/_vendor/README.rst
+++ b/src/pip/_vendor/README.rst
@@ -115,6 +115,16 @@ Vendoring is automated via the `vendoring `
Launch it via ``vendoring sync . -v`` (requires ``vendoring>=0.2.2``).
Tool configuration is done via ``pyproject.toml``.
+To update the vendored library versions, we have a session defined in ``nox``.
+The command to upgrade everything is::
+
+ nox -s vendoring -- --upgrade-all --skip urllib3 --skip setuptools
+
+At the time of writing (April 2025) we do not upgrade ``urllib3`` because the
+next version is a major upgrade and will be handled as an independent PR. We also
+do not upgrade ``setuptools``, because we only rely on ``pkg_resources``, and
+tracking every ``setuptools`` change is unnecessary for our needs.
+
Managing Local Patches
======================
From 3427d969981c01a824e27a89151ec40d911818b2 Mon Sep 17 00:00:00 2001
From: Paul Moore
Date: Fri, 11 Apr 2025 15:28:22 +0100
Subject: [PATCH 5/5] Upgrade typing_extensions to 4.13.2
---
news/typing_extensions.vendor.rst | 2 +-
src/pip/_vendor/typing_extensions.py | 42 ++++++++++++++++++++--------
src/pip/_vendor/vendor.txt | 2 +-
3 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/news/typing_extensions.vendor.rst b/news/typing_extensions.vendor.rst
index 76ba97d4a2a..48de90c4aa4 100644
--- a/news/typing_extensions.vendor.rst
+++ b/news/typing_extensions.vendor.rst
@@ -1 +1 @@
-Upgrade typing_extensions to 4.13.0
+Upgrade typing_extensions to 4.13.2
diff --git a/src/pip/_vendor/typing_extensions.py b/src/pip/_vendor/typing_extensions.py
index e0c2fb62ad5..da8126b5bf6 100644
--- a/src/pip/_vendor/typing_extensions.py
+++ b/src/pip/_vendor/typing_extensions.py
@@ -2072,7 +2072,7 @@ def _create_concatenate_alias(origin, parameters):
if parameters[-1] is ... and sys.version_info < (3, 9, 2):
# Hack: Arguments must be types, replace it with one.
parameters = (*parameters[:-1], _EllipsisDummy)
- if sys.version_info >= (3, 10, 2):
+ if sys.version_info >= (3, 10, 3):
concatenate = _ConcatenateGenericAlias(origin, parameters,
_typevar_types=(TypeVar, ParamSpec),
_paramspec_tvars=True)
@@ -3123,7 +3123,8 @@ def method(self) -> None:
return arg
-if hasattr(warnings, "deprecated"):
+# Python 3.13.3+ contains a fix for the wrapped __new__
+if sys.version_info >= (3, 13, 3):
deprecated = warnings.deprecated
else:
_T = typing.TypeVar("_T")
@@ -3203,7 +3204,7 @@ def __call__(self, arg: _T, /) -> _T:
original_new = arg.__new__
@functools.wraps(original_new)
- def __new__(cls, *args, **kwargs):
+ def __new__(cls, /, *args, **kwargs):
if cls is arg:
warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
if original_new is not object.__new__:
@@ -3827,14 +3828,27 @@ def __ror__(self, other):
TypeAliasType = typing.TypeAliasType
# 3.8-3.13
else:
- def _is_unionable(obj):
- """Corresponds to is_unionable() in unionobject.c in CPython."""
- return obj is None or isinstance(obj, (
- type,
- _types.GenericAlias,
- _types.UnionType,
- TypeAliasType,
- ))
+ if sys.version_info >= (3, 12):
+ # 3.12-3.14
+ def _is_unionable(obj):
+ """Corresponds to is_unionable() in unionobject.c in CPython."""
+ return obj is None or isinstance(obj, (
+ type,
+ _types.GenericAlias,
+ _types.UnionType,
+ typing.TypeAliasType,
+ TypeAliasType,
+ ))
+ else:
+ # 3.8-3.11
+ def _is_unionable(obj):
+ """Corresponds to is_unionable() in unionobject.c in CPython."""
+ return obj is None or isinstance(obj, (
+ type,
+ _types.GenericAlias,
+ _types.UnionType,
+ TypeAliasType,
+ ))
if sys.version_info < (3, 10):
# Copied and pasted from https://github.com/python/cpython/blob/986a4e1b6fcae7fe7a1d0a26aea446107dd58dd2/Objects/genericaliasobject.c#L568-L582,
@@ -4371,7 +4385,11 @@ def _lax_type_check(
A lax Python 3.11+ like version of typing._type_check
"""
if hasattr(typing, "_type_convert"):
- if _FORWARD_REF_HAS_CLASS:
+ if (
+ sys.version_info >= (3, 10, 3)
+ or (3, 9, 10) < sys.version_info[:3] < (3, 10)
+ ):
+ # allow_special_forms introduced later cpython/#30926 (bpo-46539)
type_ = typing._type_convert(
value,
module=module,
diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt
index c867341699f..b3e6fd491b8 100644
--- a/src/pip/_vendor/vendor.txt
+++ b/src/pip/_vendor/vendor.txt
@@ -11,7 +11,7 @@ requests==2.32.3
urllib3==1.26.20
rich==14.0.0
pygments==2.19.1
- typing_extensions==4.13.0
+ typing_extensions==4.13.2
resolvelib==1.1.0
setuptools==70.3.0
tomli==2.2.1