Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ffe49ac

Browse files
authoredOct 21, 2022
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 0 additions & 1 deletion
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 109 additions & 0 deletions
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]

‎src/_pytest/_py/path.py

Lines changed: 1474 additions & 0 deletions
Large diffs are not rendered by default.

‎src/_pytest/compat.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from typing import Union
1919

2020
import attr
21+
2122
import py
2223

2324
# fmt: off

‎src/py.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# shim for pylib going away
2+
# if pylib is installed this file will get skipped
3+
# (`py/__init__.py` has higher precedence)
4+
import sys
5+
6+
import _pytest._py.error as error
7+
import _pytest._py.path as path
8+
9+
sys.modules["py.error"] = error
10+
sys.modules["py.path"] = path

‎testing/_py/test_local.py

Lines changed: 1556 additions & 0 deletions
Large diffs are not rendered by default.

‎testing/test_pathlib.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ def path1(self, tmp_path_factory: TempPathFactory) -> Generator[Path, None, None
9191
yield path
9292
assert path.joinpath("samplefile").exists()
9393

94+
@pytest.fixture(autouse=True)
95+
def preserve_sys(self):
96+
with unittest.mock.patch.dict(sys.modules):
97+
with unittest.mock.patch.object(sys, "path", list(sys.path)):
98+
yield
99+
94100
def setuptestfs(self, path: Path) -> None:
95101
# print "setting up test fs for", repr(path)
96102
samplefile = path / "samplefile"

‎tox.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ envlist =
1010
py310
1111
py311
1212
pypy3
13-
py37-{pexpect,xdist,unittestextras,numpy,pluggymain}
13+
py37-{pexpect,xdist,unittestextras,numpy,pluggymain,pylib}
1414
doctesting
1515
plugins
1616
py37-freeze
@@ -54,6 +54,7 @@ deps =
5454
numpy: numpy>=1.19.4
5555
pexpect: pexpect>=4.8.0
5656
pluggymain: pluggy @ git+https://github.com/pytest-dev/pluggy.git
57+
pylib: py>=1.8.2
5758
unittestextras: twisted
5859
unittestextras: asynctest
5960
xdist: pytest-xdist>=2.1.0

0 commit comments

Comments
 (0)
Please sign in to comment.