Skip to content

Commit ffe49ac

Browse files
authored
Merge pull request #10396 from pytest-dev/pylib-hax
vendor py.path and py.error
2 parents f341a5c + d543a45 commit ffe49ac

File tree

12 files changed

+3163
-5
lines changed

12 files changed

+3163
-5
lines changed

.github/workflows/test.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666
- name: "windows-py37-pluggy"
6767
python: "3.7"
6868
os: windows-latest
69-
tox_env: "py37-pluggymain-xdist"
69+
tox_env: "py37-pluggymain-pylib-xdist"
7070
- name: "windows-py38"
7171
python: "3.8"
7272
os: windows-latest
@@ -93,7 +93,7 @@ jobs:
9393
- name: "ubuntu-py37-pluggy"
9494
python: "3.7"
9595
os: ubuntu-latest
96-
tox_env: "py37-pluggymain-xdist"
96+
tox_env: "py37-pluggymain-pylib-xdist"
9797
- name: "ubuntu-py37-freeze"
9898
python: "3.7"
9999
os: ubuntu-latest

.pre-commit-config.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ repos:
6565
args: []
6666
additional_dependencies:
6767
- iniconfig>=1.1.0
68-
- py>=1.8.2
6968
- attrs>=19.2.0
7069
- packaging
7170
- tomli

changelog/10396.deprecation.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest no longer depends on the ``py`` library. ``pytest`` provides a vendored copy of ``py.error`` and ``py.path`` modules but will use the ``py`` library if it is installed. If you need other ``py.*`` modules, continue to install the deprecated ``py`` library separately, otherwise it can usually be removed as a dependency.

setup.cfg

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,17 @@ packages =
3636
_pytest
3737
_pytest._code
3838
_pytest._io
39+
_pytest._py
3940
_pytest.assertion
4041
_pytest.config
4142
_pytest.mark
4243
pytest
44+
py_modules = py
4345
install_requires =
4446
attrs>=19.2.0
4547
iniconfig
4648
packaging
4749
pluggy>=0.12,<2.0
48-
py>=1.8.2
4950
colorama;sys_platform=="win32"
5051
exceptiongroup>=1.0.0rc8;python_version<"3.11"
5152
importlib-metadata>=0.12;python_version<"3.8"

src/_pytest/_py/__init__.py

Whitespace-only changes.

src/_pytest/_py/error.py

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""create errno-specific classes for IO or os calls."""
2+
from __future__ import annotations
3+
4+
import errno
5+
import os
6+
import sys
7+
from typing import Callable
8+
from typing import TYPE_CHECKING
9+
from typing import TypeVar
10+
11+
if TYPE_CHECKING:
12+
from typing_extensions import ParamSpec
13+
14+
P = ParamSpec("P")
15+
16+
R = TypeVar("R")
17+
18+
19+
class Error(EnvironmentError):
20+
def __repr__(self) -> str:
21+
return "{}.{} {!r}: {} ".format(
22+
self.__class__.__module__,
23+
self.__class__.__name__,
24+
self.__class__.__doc__,
25+
" ".join(map(str, self.args)),
26+
# repr(self.args)
27+
)
28+
29+
def __str__(self) -> str:
30+
s = "[{}]: {}".format(
31+
self.__class__.__doc__,
32+
" ".join(map(str, self.args)),
33+
)
34+
return s
35+
36+
37+
_winerrnomap = {
38+
2: errno.ENOENT,
39+
3: errno.ENOENT,
40+
17: errno.EEXIST,
41+
18: errno.EXDEV,
42+
13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable
43+
22: errno.ENOTDIR,
44+
20: errno.ENOTDIR,
45+
267: errno.ENOTDIR,
46+
5: errno.EACCES, # anything better?
47+
}
48+
49+
50+
class ErrorMaker:
51+
"""lazily provides Exception classes for each possible POSIX errno
52+
(as defined per the 'errno' module). All such instances
53+
subclass EnvironmentError.
54+
"""
55+
56+
_errno2class: dict[int, type[Error]] = {}
57+
58+
def __getattr__(self, name: str) -> type[Error]:
59+
if name[0] == "_":
60+
raise AttributeError(name)
61+
eno = getattr(errno, name)
62+
cls = self._geterrnoclass(eno)
63+
setattr(self, name, cls)
64+
return cls
65+
66+
def _geterrnoclass(self, eno: int) -> type[Error]:
67+
try:
68+
return self._errno2class[eno]
69+
except KeyError:
70+
clsname = errno.errorcode.get(eno, "UnknownErrno%d" % (eno,))
71+
errorcls = type(
72+
clsname,
73+
(Error,),
74+
{"__module__": "py.error", "__doc__": os.strerror(eno)},
75+
)
76+
self._errno2class[eno] = errorcls
77+
return errorcls
78+
79+
def checked_call(
80+
self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs
81+
) -> R:
82+
"""Call a function and raise an errno-exception if applicable."""
83+
__tracebackhide__ = True
84+
try:
85+
return func(*args, **kwargs)
86+
except Error:
87+
raise
88+
except OSError as value:
89+
if not hasattr(value, "errno"):
90+
raise
91+
errno = value.errno
92+
if sys.platform == "win32":
93+
try:
94+
cls = self._geterrnoclass(_winerrnomap[errno])
95+
except KeyError:
96+
raise value
97+
else:
98+
# we are not on Windows, or we got a proper OSError
99+
cls = self._geterrnoclass(errno)
100+
101+
raise cls(f"{func.__name__}{args!r}")
102+
103+
104+
_error_maker = ErrorMaker()
105+
checked_call = _error_maker.checked_call
106+
107+
108+
def __getattr__(attr: str) -> type[Error]:
109+
return getattr(_error_maker, attr) # type: ignore[no-any-return]

0 commit comments

Comments
 (0)