Skip to content

Commit eeb3d8f

Browse files
synthesize a traceback to get a normal exc_info from an exception
- as per https://docs.python.org/3.12/whatsnew/3.12.html#shutil, we must expect only an exception and *not* the full exc_info from the new onexc function (the documentation of this is very misleading and still uses the label "excinfo": https://docs.python.org/3.12/library/shutil.html#shutil.rmtree)
1 parent d064174 commit eeb3d8f

File tree

2 files changed

+33
-6
lines changed

2 files changed

+33
-6
lines changed

src/pip/_internal/utils/misc.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import contextlib
22
import errno
3+
import functools
34
import getpass
45
import hashlib
56
import io
@@ -14,7 +15,8 @@
1415
from functools import partial
1516
from io import StringIO
1617
from itertools import filterfalse, tee, zip_longest
17-
from types import TracebackType
18+
from pathlib import Path
19+
from types import FunctionType, TracebackType
1820
from typing import (
1921
Any,
2022
BinaryIO,
@@ -67,6 +69,8 @@
6769
ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType]
6870
VersionInfo = Tuple[int, int, int]
6971
NetlocTuple = Tuple[str, Tuple[Optional[str], Optional[str]]]
72+
OnExc = Callable[[FunctionType, Path, BaseException], Any]
73+
OnErr = Callable[[FunctionType, Path, ExcInfo], Any]
7074

7175

7276
def get_pip_version() -> str:
@@ -121,22 +125,44 @@ def get_prog() -> str:
121125
return "pip"
122126

123127

128+
def bare_exc_to_onexc(exc_val: BaseException) -> ExcInfo:
129+
exc_ty = type(exc_val)
130+
tb = exc_val.__traceback__
131+
if tb is None:
132+
import inspect
133+
134+
frame = inspect.currentframe()
135+
assert frame is not None
136+
tb = TracebackType(None, frame, frame.f_lasti, frame.f_lineno)
137+
return (exc_ty, exc_val, tb)
138+
139+
140+
def extract_exc_info_arg(f: OnErr) -> OnExc:
141+
def g(fn: FunctionType, p: Path, e: BaseException) -> Any:
142+
info = bare_exc_to_onexc(e)
143+
return f(fn, p, info)
144+
145+
return functools.update_wrapper(g, f)
146+
147+
124148
# Retry every half second for up to 3 seconds
125149
# Tenacity raises RetryError by default, explicitly raise the original exception
126150
@retry(reraise=True, stop=stop_after_delay(3), wait=wait_fixed(0.5))
127151
def rmtree(
128152
dir: str,
129153
ignore_errors: bool = False,
130-
onexc: Optional[Callable[[Any, Any, Any], Any]] = None,
154+
onexc: Optional[OnErr] = None,
131155
) -> None:
132156
if ignore_errors:
133157
onexc = _onerror_ignore
134-
elif onexc is None:
158+
if onexc is None:
135159
onexc = _onerror_reraise
160+
handler: OnErr = partial(rmtree_errorhandler, onexc=onexc)
136161
if sys.version_info >= (3, 12):
137-
shutil.rmtree(dir, onexc=partial(rmtree_errorhandler, onexc=onexc))
162+
exc_handler = extract_exc_info_arg(handler)
163+
shutil.rmtree(dir, onexc=exc_handler)
138164
else:
139-
shutil.rmtree(dir, onerror=partial(rmtree_errorhandler, onexc=onexc))
165+
shutil.rmtree(dir, onerror=handler)
140166

141167

142168
def _onerror_ignore(*_args: Any) -> None:

src/pip/_internal/utils/temp_dir.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import tempfile
66
import traceback
77
from contextlib import ExitStack, contextmanager
8+
from pathlib import Path
89
from typing import (
910
Any,
1011
Callable,
@@ -189,7 +190,7 @@ def cleanup(self) -> None:
189190

190191
def onerror(
191192
func: Callable[[str], Any],
192-
path: str,
193+
path: Path,
193194
exc_info: Tuple[Type[BaseException], BaseException, Any],
194195
) -> None:
195196
"""Log a warning for a `rmtree` error and continue"""

0 commit comments

Comments
 (0)