koichi12 commited on
Commit
8d80d71
·
verified ·
1 Parent(s): 87489a9

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. .venv/lib/python3.11/site-packages/_pytest/__pycache__/__init__.cpython-311.pyc +0 -0
  3. .venv/lib/python3.11/site-packages/_pytest/__pycache__/_argcomplete.cpython-311.pyc +0 -0
  4. .venv/lib/python3.11/site-packages/_pytest/__pycache__/_version.cpython-311.pyc +0 -0
  5. .venv/lib/python3.11/site-packages/_pytest/__pycache__/capture.cpython-311.pyc +0 -0
  6. .venv/lib/python3.11/site-packages/_pytest/__pycache__/compat.cpython-311.pyc +0 -0
  7. .venv/lib/python3.11/site-packages/_pytest/__pycache__/debugging.cpython-311.pyc +0 -0
  8. .venv/lib/python3.11/site-packages/_pytest/__pycache__/deprecated.cpython-311.pyc +0 -0
  9. .venv/lib/python3.11/site-packages/_pytest/__pycache__/faulthandler.cpython-311.pyc +0 -0
  10. .venv/lib/python3.11/site-packages/_pytest/__pycache__/fixtures.cpython-311.pyc +0 -0
  11. .venv/lib/python3.11/site-packages/_pytest/__pycache__/freeze_support.cpython-311.pyc +0 -0
  12. .venv/lib/python3.11/site-packages/_pytest/__pycache__/helpconfig.cpython-311.pyc +0 -0
  13. .venv/lib/python3.11/site-packages/_pytest/__pycache__/hookspec.cpython-311.pyc +0 -0
  14. .venv/lib/python3.11/site-packages/_pytest/__pycache__/junitxml.cpython-311.pyc +0 -0
  15. .venv/lib/python3.11/site-packages/_pytest/__pycache__/legacypath.cpython-311.pyc +0 -0
  16. .venv/lib/python3.11/site-packages/_pytest/__pycache__/logging.cpython-311.pyc +0 -0
  17. .venv/lib/python3.11/site-packages/_pytest/__pycache__/main.cpython-311.pyc +0 -0
  18. .venv/lib/python3.11/site-packages/_pytest/__pycache__/monkeypatch.cpython-311.pyc +0 -0
  19. .venv/lib/python3.11/site-packages/_pytest/__pycache__/nodes.cpython-311.pyc +0 -0
  20. .venv/lib/python3.11/site-packages/_pytest/__pycache__/outcomes.cpython-311.pyc +0 -0
  21. .venv/lib/python3.11/site-packages/_pytest/__pycache__/pastebin.cpython-311.pyc +0 -0
  22. .venv/lib/python3.11/site-packages/_pytest/__pycache__/pathlib.cpython-311.pyc +0 -0
  23. .venv/lib/python3.11/site-packages/_pytest/__pycache__/pytester.cpython-311.pyc +0 -0
  24. .venv/lib/python3.11/site-packages/_pytest/__pycache__/pytester_assertions.cpython-311.pyc +0 -0
  25. .venv/lib/python3.11/site-packages/_pytest/__pycache__/python.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/_pytest/__pycache__/python_api.cpython-311.pyc +0 -0
  27. .venv/lib/python3.11/site-packages/_pytest/__pycache__/python_path.cpython-311.pyc +0 -0
  28. .venv/lib/python3.11/site-packages/_pytest/__pycache__/recwarn.cpython-311.pyc +0 -0
  29. .venv/lib/python3.11/site-packages/_pytest/__pycache__/reports.cpython-311.pyc +0 -0
  30. .venv/lib/python3.11/site-packages/_pytest/__pycache__/runner.cpython-311.pyc +0 -0
  31. .venv/lib/python3.11/site-packages/_pytest/__pycache__/scope.cpython-311.pyc +0 -0
  32. .venv/lib/python3.11/site-packages/_pytest/__pycache__/setuponly.cpython-311.pyc +0 -0
  33. .venv/lib/python3.11/site-packages/_pytest/__pycache__/setupplan.cpython-311.pyc +0 -0
  34. .venv/lib/python3.11/site-packages/_pytest/__pycache__/skipping.cpython-311.pyc +0 -0
  35. .venv/lib/python3.11/site-packages/_pytest/__pycache__/stash.cpython-311.pyc +0 -0
  36. .venv/lib/python3.11/site-packages/_pytest/__pycache__/stepwise.cpython-311.pyc +0 -0
  37. .venv/lib/python3.11/site-packages/_pytest/__pycache__/terminal.cpython-311.pyc +0 -0
  38. .venv/lib/python3.11/site-packages/_pytest/__pycache__/threadexception.cpython-311.pyc +0 -0
  39. .venv/lib/python3.11/site-packages/_pytest/__pycache__/timing.cpython-311.pyc +0 -0
  40. .venv/lib/python3.11/site-packages/_pytest/__pycache__/tmpdir.cpython-311.pyc +0 -0
  41. .venv/lib/python3.11/site-packages/_pytest/__pycache__/unittest.cpython-311.pyc +0 -0
  42. .venv/lib/python3.11/site-packages/_pytest/__pycache__/warning_types.cpython-311.pyc +0 -0
  43. .venv/lib/python3.11/site-packages/_pytest/__pycache__/warnings.cpython-311.pyc +0 -0
  44. .venv/lib/python3.11/site-packages/_pytest/_py/__init__.py +0 -0
  45. .venv/lib/python3.11/site-packages/_pytest/_py/__pycache__/__init__.cpython-311.pyc +0 -0
  46. .venv/lib/python3.11/site-packages/_pytest/_py/__pycache__/error.cpython-311.pyc +0 -0
  47. .venv/lib/python3.11/site-packages/_pytest/_py/__pycache__/path.cpython-311.pyc +0 -0
  48. .venv/lib/python3.11/site-packages/_pytest/_py/error.py +111 -0
  49. .venv/lib/python3.11/site-packages/_pytest/_py/path.py +1475 -0
  50. .venv/lib/python3.11/site-packages/_pytest/config/__init__.py +1973 -0
.gitattributes CHANGED
@@ -183,3 +183,4 @@ tuning-competition-baseline/.venv/lib/python3.11/site-packages/torch/_inductor/_
183
  .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psutil_linux.abi3.so filter=lfs diff=lfs merge=lfs -text
184
  .venv/lib/python3.11/site-packages/ray/core/src/ray/gcs/gcs_server filter=lfs diff=lfs merge=lfs -text
185
  .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_pslinux.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
 
 
183
  .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psutil_linux.abi3.so filter=lfs diff=lfs merge=lfs -text
184
  .venv/lib/python3.11/site-packages/ray/core/src/ray/gcs/gcs_server filter=lfs diff=lfs merge=lfs -text
185
  .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_pslinux.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
186
+ .venv/lib/python3.11/site-packages/ray/scripts/__pycache__/scripts.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
.venv/lib/python3.11/site-packages/_pytest/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (540 Bytes). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/_argcomplete.cpython-311.pyc ADDED
Binary file (5.28 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/_version.cpython-311.pyc ADDED
Binary file (638 Bytes). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/capture.cpython-311.pyc ADDED
Binary file (58.8 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/compat.cpython-311.pyc ADDED
Binary file (14 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/debugging.cpython-311.pyc ADDED
Binary file (19.8 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/deprecated.cpython-311.pyc ADDED
Binary file (2.81 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/faulthandler.cpython-311.pyc ADDED
Binary file (4.96 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/fixtures.cpython-311.pyc ADDED
Binary file (85 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/freeze_support.cpython-311.pyc ADDED
Binary file (1.99 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/helpconfig.cpython-311.pyc ADDED
Binary file (13.2 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/hookspec.cpython-311.pyc ADDED
Binary file (46.9 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/junitxml.cpython-311.pyc ADDED
Binary file (36.4 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/legacypath.cpython-311.pyc ADDED
Binary file (26.7 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/logging.cpython-311.pyc ADDED
Binary file (51.2 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/main.cpython-311.pyc ADDED
Binary file (47 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/monkeypatch.cpython-311.pyc ADDED
Binary file (18 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/nodes.cpython-311.pyc ADDED
Binary file (33.8 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/outcomes.cpython-311.pyc ADDED
Binary file (13.2 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/pastebin.cpython-311.pyc ADDED
Binary file (6.28 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/pathlib.cpython-311.pyc ADDED
Binary file (45.3 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/pytester.cpython-311.pyc ADDED
Binary file (94 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/pytester_assertions.cpython-311.pyc ADDED
Binary file (2.63 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/python.cpython-311.pyc ADDED
Binary file (76.7 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/python_api.cpython-311.pyc ADDED
Binary file (49.1 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/python_path.cpython-311.pyc ADDED
Binary file (1.85 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/recwarn.cpython-311.pyc ADDED
Binary file (17.8 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/reports.cpython-311.pyc ADDED
Binary file (27.7 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/runner.cpython-311.pyc ADDED
Binary file (25.7 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/scope.cpython-311.pyc ADDED
Binary file (4.32 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/setuponly.cpython-311.pyc ADDED
Binary file (5.81 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/setupplan.cpython-311.pyc ADDED
Binary file (2.05 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/skipping.cpython-311.pyc ADDED
Binary file (14.6 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/stash.cpython-311.pyc ADDED
Binary file (4.81 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/stepwise.cpython-311.pyc ADDED
Binary file (5.88 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/terminal.cpython-311.pyc ADDED
Binary file (81.5 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/threadexception.cpython-311.pyc ADDED
Binary file (5.38 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/timing.cpython-311.pyc ADDED
Binary file (696 Bytes). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/tmpdir.cpython-311.pyc ADDED
Binary file (14 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/unittest.cpython-311.pyc ADDED
Binary file (21.5 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/warning_types.cpython-311.pyc ADDED
Binary file (7.86 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/__pycache__/warnings.cpython-311.pyc ADDED
Binary file (7.61 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/_py/__init__.py ADDED
File without changes
.venv/lib/python3.11/site-packages/_pytest/_py/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (184 Bytes). View file
 
.venv/lib/python3.11/site-packages/_pytest/_py/__pycache__/error.cpython-311.pyc ADDED
Binary file (5.19 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/_py/__pycache__/path.cpython-311.pyc ADDED
Binary file (76.7 kB). View file
 
.venv/lib/python3.11/site-packages/_pytest/_py/error.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """create errno-specific classes for IO or os calls."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import errno
6
+ import os
7
+ import sys
8
+ from typing import Callable
9
+ from typing import TYPE_CHECKING
10
+ from typing import TypeVar
11
+
12
+
13
+ if TYPE_CHECKING:
14
+ from typing_extensions import ParamSpec
15
+
16
+ P = ParamSpec("P")
17
+
18
+ R = TypeVar("R")
19
+
20
+
21
+ class Error(EnvironmentError):
22
+ def __repr__(self) -> str:
23
+ return "{}.{} {!r}: {} ".format(
24
+ self.__class__.__module__,
25
+ self.__class__.__name__,
26
+ self.__class__.__doc__,
27
+ " ".join(map(str, self.args)),
28
+ # repr(self.args)
29
+ )
30
+
31
+ def __str__(self) -> str:
32
+ s = "[{}]: {}".format(
33
+ self.__class__.__doc__,
34
+ " ".join(map(str, self.args)),
35
+ )
36
+ return s
37
+
38
+
39
+ _winerrnomap = {
40
+ 2: errno.ENOENT,
41
+ 3: errno.ENOENT,
42
+ 17: errno.EEXIST,
43
+ 18: errno.EXDEV,
44
+ 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailable
45
+ 22: errno.ENOTDIR,
46
+ 20: errno.ENOTDIR,
47
+ 267: errno.ENOTDIR,
48
+ 5: errno.EACCES, # anything better?
49
+ }
50
+
51
+
52
+ class ErrorMaker:
53
+ """lazily provides Exception classes for each possible POSIX errno
54
+ (as defined per the 'errno' module). All such instances
55
+ subclass EnvironmentError.
56
+ """
57
+
58
+ _errno2class: dict[int, type[Error]] = {}
59
+
60
+ def __getattr__(self, name: str) -> type[Error]:
61
+ if name[0] == "_":
62
+ raise AttributeError(name)
63
+ eno = getattr(errno, name)
64
+ cls = self._geterrnoclass(eno)
65
+ setattr(self, name, cls)
66
+ return cls
67
+
68
+ def _geterrnoclass(self, eno: int) -> type[Error]:
69
+ try:
70
+ return self._errno2class[eno]
71
+ except KeyError:
72
+ clsname = errno.errorcode.get(eno, "UnknownErrno%d" % (eno,))
73
+ errorcls = type(
74
+ clsname,
75
+ (Error,),
76
+ {"__module__": "py.error", "__doc__": os.strerror(eno)},
77
+ )
78
+ self._errno2class[eno] = errorcls
79
+ return errorcls
80
+
81
+ def checked_call(
82
+ self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs
83
+ ) -> R:
84
+ """Call a function and raise an errno-exception if applicable."""
85
+ __tracebackhide__ = True
86
+ try:
87
+ return func(*args, **kwargs)
88
+ except Error:
89
+ raise
90
+ except OSError as value:
91
+ if not hasattr(value, "errno"):
92
+ raise
93
+ errno = value.errno
94
+ if sys.platform == "win32":
95
+ try:
96
+ cls = self._geterrnoclass(_winerrnomap[errno])
97
+ except KeyError:
98
+ raise value
99
+ else:
100
+ # we are not on Windows, or we got a proper OSError
101
+ cls = self._geterrnoclass(errno)
102
+
103
+ raise cls(f"{func.__name__}{args!r}")
104
+
105
+
106
+ _error_maker = ErrorMaker()
107
+ checked_call = _error_maker.checked_call
108
+
109
+
110
+ def __getattr__(attr: str) -> type[Error]:
111
+ return getattr(_error_maker, attr) # type: ignore[no-any-return]
.venv/lib/python3.11/site-packages/_pytest/_py/path.py ADDED
@@ -0,0 +1,1475 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ """local path implementation."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import atexit
7
+ from contextlib import contextmanager
8
+ import fnmatch
9
+ import importlib.util
10
+ import io
11
+ import os
12
+ from os.path import abspath
13
+ from os.path import dirname
14
+ from os.path import exists
15
+ from os.path import isabs
16
+ from os.path import isdir
17
+ from os.path import isfile
18
+ from os.path import islink
19
+ from os.path import normpath
20
+ import posixpath
21
+ from stat import S_ISDIR
22
+ from stat import S_ISLNK
23
+ from stat import S_ISREG
24
+ import sys
25
+ from typing import Any
26
+ from typing import Callable
27
+ from typing import cast
28
+ from typing import Literal
29
+ from typing import overload
30
+ from typing import TYPE_CHECKING
31
+ import uuid
32
+ import warnings
33
+
34
+ from . import error
35
+
36
+
37
+ # Moved from local.py.
38
+ iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt")
39
+
40
+
41
+ class Checkers:
42
+ _depend_on_existence = "exists", "link", "dir", "file"
43
+
44
+ def __init__(self, path):
45
+ self.path = path
46
+
47
+ def dotfile(self):
48
+ return self.path.basename.startswith(".")
49
+
50
+ def ext(self, arg):
51
+ if not arg.startswith("."):
52
+ arg = "." + arg
53
+ return self.path.ext == arg
54
+
55
+ def basename(self, arg):
56
+ return self.path.basename == arg
57
+
58
+ def basestarts(self, arg):
59
+ return self.path.basename.startswith(arg)
60
+
61
+ def relto(self, arg):
62
+ return self.path.relto(arg)
63
+
64
+ def fnmatch(self, arg):
65
+ return self.path.fnmatch(arg)
66
+
67
+ def endswith(self, arg):
68
+ return str(self.path).endswith(arg)
69
+
70
+ def _evaluate(self, kw):
71
+ from .._code.source import getrawcode
72
+
73
+ for name, value in kw.items():
74
+ invert = False
75
+ meth = None
76
+ try:
77
+ meth = getattr(self, name)
78
+ except AttributeError:
79
+ if name[:3] == "not":
80
+ invert = True
81
+ try:
82
+ meth = getattr(self, name[3:])
83
+ except AttributeError:
84
+ pass
85
+ if meth is None:
86
+ raise TypeError(f"no {name!r} checker available for {self.path!r}")
87
+ try:
88
+ if getrawcode(meth).co_argcount > 1:
89
+ if (not meth(value)) ^ invert:
90
+ return False
91
+ else:
92
+ if bool(value) ^ bool(meth()) ^ invert:
93
+ return False
94
+ except (error.ENOENT, error.ENOTDIR, error.EBUSY):
95
+ # EBUSY feels not entirely correct,
96
+ # but its kind of necessary since ENOMEDIUM
97
+ # is not accessible in python
98
+ for name in self._depend_on_existence:
99
+ if name in kw:
100
+ if kw.get(name):
101
+ return False
102
+ name = "not" + name
103
+ if name in kw:
104
+ if not kw.get(name):
105
+ return False
106
+ return True
107
+
108
+ _statcache: Stat
109
+
110
+ def _stat(self) -> Stat:
111
+ try:
112
+ return self._statcache
113
+ except AttributeError:
114
+ try:
115
+ self._statcache = self.path.stat()
116
+ except error.ELOOP:
117
+ self._statcache = self.path.lstat()
118
+ return self._statcache
119
+
120
+ def dir(self):
121
+ return S_ISDIR(self._stat().mode)
122
+
123
+ def file(self):
124
+ return S_ISREG(self._stat().mode)
125
+
126
+ def exists(self):
127
+ return self._stat()
128
+
129
+ def link(self):
130
+ st = self.path.lstat()
131
+ return S_ISLNK(st.mode)
132
+
133
+
134
+ class NeverRaised(Exception):
135
+ pass
136
+
137
+
138
+ class Visitor:
139
+ def __init__(self, fil, rec, ignore, bf, sort):
140
+ if isinstance(fil, str):
141
+ fil = FNMatcher(fil)
142
+ if isinstance(rec, str):
143
+ self.rec: Callable[[LocalPath], bool] = FNMatcher(rec)
144
+ elif not hasattr(rec, "__call__") and rec:
145
+ self.rec = lambda path: True
146
+ else:
147
+ self.rec = rec
148
+ self.fil = fil
149
+ self.ignore = ignore
150
+ self.breadthfirst = bf
151
+ self.optsort = cast(Callable[[Any], Any], sorted) if sort else (lambda x: x)
152
+
153
+ def gen(self, path):
154
+ try:
155
+ entries = path.listdir()
156
+ except self.ignore:
157
+ return
158
+ rec = self.rec
159
+ dirs = self.optsort(
160
+ [p for p in entries if p.check(dir=1) and (rec is None or rec(p))]
161
+ )
162
+ if not self.breadthfirst:
163
+ for subdir in dirs:
164
+ yield from self.gen(subdir)
165
+ for p in self.optsort(entries):
166
+ if self.fil is None or self.fil(p):
167
+ yield p
168
+ if self.breadthfirst:
169
+ for subdir in dirs:
170
+ yield from self.gen(subdir)
171
+
172
+
173
+ class FNMatcher:
174
+ def __init__(self, pattern):
175
+ self.pattern = pattern
176
+
177
+ def __call__(self, path):
178
+ pattern = self.pattern
179
+
180
+ if (
181
+ pattern.find(path.sep) == -1
182
+ and iswin32
183
+ and pattern.find(posixpath.sep) != -1
184
+ ):
185
+ # Running on Windows, the pattern has no Windows path separators,
186
+ # and the pattern has one or more Posix path separators. Replace
187
+ # the Posix path separators with the Windows path separator.
188
+ pattern = pattern.replace(posixpath.sep, path.sep)
189
+
190
+ if pattern.find(path.sep) == -1:
191
+ name = path.basename
192
+ else:
193
+ name = str(path) # path.strpath # XXX svn?
194
+ if not os.path.isabs(pattern):
195
+ pattern = "*" + path.sep + pattern
196
+ return fnmatch.fnmatch(name, pattern)
197
+
198
+
199
+ def map_as_list(func, iter):
200
+ return list(map(func, iter))
201
+
202
+
203
+ class Stat:
204
+ if TYPE_CHECKING:
205
+
206
+ @property
207
+ def size(self) -> int: ...
208
+
209
+ @property
210
+ def mtime(self) -> float: ...
211
+
212
+ def __getattr__(self, name: str) -> Any:
213
+ return getattr(self._osstatresult, "st_" + name)
214
+
215
+ def __init__(self, path, osstatresult):
216
+ self.path = path
217
+ self._osstatresult = osstatresult
218
+
219
+ @property
220
+ def owner(self):
221
+ if iswin32:
222
+ raise NotImplementedError("XXX win32")
223
+ import pwd
224
+
225
+ entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined,unused-ignore]
226
+ return entry[0]
227
+
228
+ @property
229
+ def group(self):
230
+ """Return group name of file."""
231
+ if iswin32:
232
+ raise NotImplementedError("XXX win32")
233
+ import grp
234
+
235
+ entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined,unused-ignore]
236
+ return entry[0]
237
+
238
+ def isdir(self):
239
+ return S_ISDIR(self._osstatresult.st_mode)
240
+
241
+ def isfile(self):
242
+ return S_ISREG(self._osstatresult.st_mode)
243
+
244
+ def islink(self):
245
+ self.path.lstat()
246
+ return S_ISLNK(self._osstatresult.st_mode)
247
+
248
+
249
+ def getuserid(user):
250
+ import pwd
251
+
252
+ if not isinstance(user, int):
253
+ user = pwd.getpwnam(user)[2] # type:ignore[attr-defined,unused-ignore]
254
+ return user
255
+
256
+
257
+ def getgroupid(group):
258
+ import grp
259
+
260
+ if not isinstance(group, int):
261
+ group = grp.getgrnam(group)[2] # type:ignore[attr-defined,unused-ignore]
262
+ return group
263
+
264
+
265
+ class LocalPath:
266
+ """Object oriented interface to os.path and other local filesystem
267
+ related information.
268
+ """
269
+
270
+ class ImportMismatchError(ImportError):
271
+ """raised on pyimport() if there is a mismatch of __file__'s"""
272
+
273
+ sep = os.sep
274
+
275
+ def __init__(self, path=None, expanduser=False):
276
+ """Initialize and return a local Path instance.
277
+
278
+ Path can be relative to the current directory.
279
+ If path is None it defaults to the current working directory.
280
+ If expanduser is True, tilde-expansion is performed.
281
+ Note that Path instances always carry an absolute path.
282
+ Note also that passing in a local path object will simply return
283
+ the exact same path object. Use new() to get a new copy.
284
+ """
285
+ if path is None:
286
+ self.strpath = error.checked_call(os.getcwd)
287
+ else:
288
+ try:
289
+ path = os.fspath(path)
290
+ except TypeError:
291
+ raise ValueError(
292
+ "can only pass None, Path instances "
293
+ "or non-empty strings to LocalPath"
294
+ )
295
+ if expanduser:
296
+ path = os.path.expanduser(path)
297
+ self.strpath = abspath(path)
298
+
299
+ if sys.platform != "win32":
300
+
301
+ def chown(self, user, group, rec=0):
302
+ """Change ownership to the given user and group.
303
+ user and group may be specified by a number or
304
+ by a name. if rec is True change ownership
305
+ recursively.
306
+ """
307
+ uid = getuserid(user)
308
+ gid = getgroupid(group)
309
+ if rec:
310
+ for x in self.visit(rec=lambda x: x.check(link=0)):
311
+ if x.check(link=0):
312
+ error.checked_call(os.chown, str(x), uid, gid)
313
+ error.checked_call(os.chown, str(self), uid, gid)
314
+
315
+ def readlink(self) -> str:
316
+ """Return value of a symbolic link."""
317
+ # https://github.com/python/mypy/issues/12278
318
+ return error.checked_call(os.readlink, self.strpath) # type: ignore[arg-type,return-value,unused-ignore]
319
+
320
+ def mklinkto(self, oldname):
321
+ """Posix style hard link to another name."""
322
+ error.checked_call(os.link, str(oldname), str(self))
323
+
324
+ def mksymlinkto(self, value, absolute=1):
325
+ """Create a symbolic link with the given value (pointing to another name)."""
326
+ if absolute:
327
+ error.checked_call(os.symlink, str(value), self.strpath)
328
+ else:
329
+ base = self.common(value)
330
+ # with posix local paths '/' is always a common base
331
+ relsource = self.__class__(value).relto(base)
332
+ reldest = self.relto(base)
333
+ n = reldest.count(self.sep)
334
+ target = self.sep.join(("..",) * n + (relsource,))
335
+ error.checked_call(os.symlink, target, self.strpath)
336
+
337
+ def __div__(self, other):
338
+ return self.join(os.fspath(other))
339
+
340
+ __truediv__ = __div__ # py3k
341
+
342
+ @property
343
+ def basename(self):
344
+ """Basename part of path."""
345
+ return self._getbyspec("basename")[0]
346
+
347
+ @property
348
+ def dirname(self):
349
+ """Dirname part of path."""
350
+ return self._getbyspec("dirname")[0]
351
+
352
+ @property
353
+ def purebasename(self):
354
+ """Pure base name of the path."""
355
+ return self._getbyspec("purebasename")[0]
356
+
357
+ @property
358
+ def ext(self):
359
+ """Extension of the path (including the '.')."""
360
+ return self._getbyspec("ext")[0]
361
+
362
+ def read_binary(self):
363
+ """Read and return a bytestring from reading the path."""
364
+ with self.open("rb") as f:
365
+ return f.read()
366
+
367
+ def read_text(self, encoding):
368
+ """Read and return a Unicode string from reading the path."""
369
+ with self.open("r", encoding=encoding) as f:
370
+ return f.read()
371
+
372
+ def read(self, mode="r"):
373
+ """Read and return a bytestring from reading the path."""
374
+ with self.open(mode) as f:
375
+ return f.read()
376
+
377
+ def readlines(self, cr=1):
378
+ """Read and return a list of lines from the path. if cr is False, the
379
+ newline will be removed from the end of each line."""
380
+ mode = "r"
381
+
382
+ if not cr:
383
+ content = self.read(mode)
384
+ return content.split("\n")
385
+ else:
386
+ f = self.open(mode)
387
+ try:
388
+ return f.readlines()
389
+ finally:
390
+ f.close()
391
+
392
+ def load(self):
393
+ """(deprecated) return object unpickled from self.read()"""
394
+ f = self.open("rb")
395
+ try:
396
+ import pickle
397
+
398
+ return error.checked_call(pickle.load, f)
399
+ finally:
400
+ f.close()
401
+
402
+ def move(self, target):
403
+ """Move this path to target."""
404
+ if target.relto(self):
405
+ raise error.EINVAL(target, "cannot move path into a subdirectory of itself")
406
+ try:
407
+ self.rename(target)
408
+ except error.EXDEV: # invalid cross-device link
409
+ self.copy(target)
410
+ self.remove()
411
+
412
+ def fnmatch(self, pattern):
413
+ """Return true if the basename/fullname matches the glob-'pattern'.
414
+
415
+ valid pattern characters::
416
+
417
+ * matches everything
418
+ ? matches any single character
419
+ [seq] matches any character in seq
420
+ [!seq] matches any char not in seq
421
+
422
+ If the pattern contains a path-separator then the full path
423
+ is used for pattern matching and a '*' is prepended to the
424
+ pattern.
425
+
426
+ if the pattern doesn't contain a path-separator the pattern
427
+ is only matched against the basename.
428
+ """
429
+ return FNMatcher(pattern)(self)
430
+
431
+ def relto(self, relpath):
432
+ """Return a string which is the relative part of the path
433
+ to the given 'relpath'.
434
+ """
435
+ if not isinstance(relpath, (str, LocalPath)):
436
+ raise TypeError(f"{relpath!r}: not a string or path object")
437
+ strrelpath = str(relpath)
438
+ if strrelpath and strrelpath[-1] != self.sep:
439
+ strrelpath += self.sep
440
+ # assert strrelpath[-1] == self.sep
441
+ # assert strrelpath[-2] != self.sep
442
+ strself = self.strpath
443
+ if sys.platform == "win32" or getattr(os, "_name", None) == "nt":
444
+ if os.path.normcase(strself).startswith(os.path.normcase(strrelpath)):
445
+ return strself[len(strrelpath) :]
446
+ elif strself.startswith(strrelpath):
447
+ return strself[len(strrelpath) :]
448
+ return ""
449
+
450
+ def ensure_dir(self, *args):
451
+ """Ensure the path joined with args is a directory."""
452
+ return self.ensure(*args, dir=True)
453
+
454
+ def bestrelpath(self, dest):
455
+ """Return a string which is a relative path from self
456
+ (assumed to be a directory) to dest such that
457
+ self.join(bestrelpath) == dest and if not such
458
+ path can be determined return dest.
459
+ """
460
+ try:
461
+ if self == dest:
462
+ return os.curdir
463
+ base = self.common(dest)
464
+ if not base: # can be the case on windows
465
+ return str(dest)
466
+ self2base = self.relto(base)
467
+ reldest = dest.relto(base)
468
+ if self2base:
469
+ n = self2base.count(self.sep) + 1
470
+ else:
471
+ n = 0
472
+ lst = [os.pardir] * n
473
+ if reldest:
474
+ lst.append(reldest)
475
+ target = dest.sep.join(lst)
476
+ return target
477
+ except AttributeError:
478
+ return str(dest)
479
+
480
+ def exists(self):
481
+ return self.check()
482
+
483
+ def isdir(self):
484
+ return self.check(dir=1)
485
+
486
+ def isfile(self):
487
+ return self.check(file=1)
488
+
489
+ def parts(self, reverse=False):
490
+ """Return a root-first list of all ancestor directories
491
+ plus the path itself.
492
+ """
493
+ current = self
494
+ lst = [self]
495
+ while 1:
496
+ last = current
497
+ current = current.dirpath()
498
+ if last == current:
499
+ break
500
+ lst.append(current)
501
+ if not reverse:
502
+ lst.reverse()
503
+ return lst
504
+
505
+ def common(self, other):
506
+ """Return the common part shared with the other path
507
+ or None if there is no common part.
508
+ """
509
+ last = None
510
+ for x, y in zip(self.parts(), other.parts()):
511
+ if x != y:
512
+ return last
513
+ last = x
514
+ return last
515
+
516
+ def __add__(self, other):
517
+ """Return new path object with 'other' added to the basename"""
518
+ return self.new(basename=self.basename + str(other))
519
+
520
+ def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False):
521
+ """Yields all paths below the current one
522
+
523
+ fil is a filter (glob pattern or callable), if not matching the
524
+ path will not be yielded, defaulting to None (everything is
525
+ returned)
526
+
527
+ rec is a filter (glob pattern or callable) that controls whether
528
+ a node is descended, defaulting to None
529
+
530
+ ignore is an Exception class that is ignoredwhen calling dirlist()
531
+ on any of the paths (by default, all exceptions are reported)
532
+
533
+ bf if True will cause a breadthfirst search instead of the
534
+ default depthfirst. Default: False
535
+
536
+ sort if True will sort entries within each directory level.
537
+ """
538
+ yield from Visitor(fil, rec, ignore, bf, sort).gen(self)
539
+
540
+ def _sortlist(self, res, sort):
541
+ if sort:
542
+ if hasattr(sort, "__call__"):
543
+ warnings.warn(
544
+ DeprecationWarning(
545
+ "listdir(sort=callable) is deprecated and breaks on python3"
546
+ ),
547
+ stacklevel=3,
548
+ )
549
+ res.sort(sort)
550
+ else:
551
+ res.sort()
552
+
553
+ def __fspath__(self):
554
+ return self.strpath
555
+
556
+ def __hash__(self):
557
+ s = self.strpath
558
+ if iswin32:
559
+ s = s.lower()
560
+ return hash(s)
561
+
562
+ def __eq__(self, other):
563
+ s1 = os.fspath(self)
564
+ try:
565
+ s2 = os.fspath(other)
566
+ except TypeError:
567
+ return False
568
+ if iswin32:
569
+ s1 = s1.lower()
570
+ try:
571
+ s2 = s2.lower()
572
+ except AttributeError:
573
+ return False
574
+ return s1 == s2
575
+
576
+ def __ne__(self, other):
577
+ return not (self == other)
578
+
579
+ def __lt__(self, other):
580
+ return os.fspath(self) < os.fspath(other)
581
+
582
+ def __gt__(self, other):
583
+ return os.fspath(self) > os.fspath(other)
584
+
585
+ def samefile(self, other):
586
+ """Return True if 'other' references the same file as 'self'."""
587
+ other = os.fspath(other)
588
+ if not isabs(other):
589
+ other = abspath(other)
590
+ if self == other:
591
+ return True
592
+ if not hasattr(os.path, "samefile"):
593
+ return False
594
+ return error.checked_call(os.path.samefile, self.strpath, other)
595
+
596
+ def remove(self, rec=1, ignore_errors=False):
597
+ """Remove a file or directory (or a directory tree if rec=1).
598
+ if ignore_errors is True, errors while removing directories will
599
+ be ignored.
600
+ """
601
+ if self.check(dir=1, link=0):
602
+ if rec:
603
+ # force remove of readonly files on windows
604
+ if iswin32:
605
+ self.chmod(0o700, rec=1)
606
+ import shutil
607
+
608
+ error.checked_call(
609
+ shutil.rmtree, self.strpath, ignore_errors=ignore_errors
610
+ )
611
+ else:
612
+ error.checked_call(os.rmdir, self.strpath)
613
+ else:
614
+ if iswin32:
615
+ self.chmod(0o700)
616
+ error.checked_call(os.remove, self.strpath)
617
+
618
+ def computehash(self, hashtype="md5", chunksize=524288):
619
+ """Return hexdigest of hashvalue for this file."""
620
+ try:
621
+ try:
622
+ import hashlib as mod
623
+ except ImportError:
624
+ if hashtype == "sha1":
625
+ hashtype = "sha"
626
+ mod = __import__(hashtype)
627
+ hash = getattr(mod, hashtype)()
628
+ except (AttributeError, ImportError):
629
+ raise ValueError(f"Don't know how to compute {hashtype!r} hash")
630
+ f = self.open("rb")
631
+ try:
632
+ while 1:
633
+ buf = f.read(chunksize)
634
+ if not buf:
635
+ return hash.hexdigest()
636
+ hash.update(buf)
637
+ finally:
638
+ f.close()
639
+
640
+ def new(self, **kw):
641
+ """Create a modified version of this path.
642
+ the following keyword arguments modify various path parts::
643
+
644
+ a:/some/path/to/a/file.ext
645
+ xx drive
646
+ xxxxxxxxxxxxxxxxx dirname
647
+ xxxxxxxx basename
648
+ xxxx purebasename
649
+ xxx ext
650
+ """
651
+ obj = object.__new__(self.__class__)
652
+ if not kw:
653
+ obj.strpath = self.strpath
654
+ return obj
655
+ drive, dirname, basename, purebasename, ext = self._getbyspec(
656
+ "drive,dirname,basename,purebasename,ext"
657
+ )
658
+ if "basename" in kw:
659
+ if "purebasename" in kw or "ext" in kw:
660
+ raise ValueError(f"invalid specification {kw!r}")
661
+ else:
662
+ pb = kw.setdefault("purebasename", purebasename)
663
+ try:
664
+ ext = kw["ext"]
665
+ except KeyError:
666
+ pass
667
+ else:
668
+ if ext and not ext.startswith("."):
669
+ ext = "." + ext
670
+ kw["basename"] = pb + ext
671
+
672
+ if "dirname" in kw and not kw["dirname"]:
673
+ kw["dirname"] = drive
674
+ else:
675
+ kw.setdefault("dirname", dirname)
676
+ kw.setdefault("sep", self.sep)
677
+ obj.strpath = normpath("{dirname}{sep}{basename}".format(**kw))
678
+ return obj
679
+
680
+ def _getbyspec(self, spec: str) -> list[str]:
681
+ """See new for what 'spec' can be."""
682
+ res = []
683
+ parts = self.strpath.split(self.sep)
684
+
685
+ args = filter(None, spec.split(","))
686
+ for name in args:
687
+ if name == "drive":
688
+ res.append(parts[0])
689
+ elif name == "dirname":
690
+ res.append(self.sep.join(parts[:-1]))
691
+ else:
692
+ basename = parts[-1]
693
+ if name == "basename":
694
+ res.append(basename)
695
+ else:
696
+ i = basename.rfind(".")
697
+ if i == -1:
698
+ purebasename, ext = basename, ""
699
+ else:
700
+ purebasename, ext = basename[:i], basename[i:]
701
+ if name == "purebasename":
702
+ res.append(purebasename)
703
+ elif name == "ext":
704
+ res.append(ext)
705
+ else:
706
+ raise ValueError(f"invalid part specification {name!r}")
707
+ return res
708
+
709
+ def dirpath(self, *args, **kwargs):
710
+ """Return the directory path joined with any given path arguments."""
711
+ if not kwargs:
712
+ path = object.__new__(self.__class__)
713
+ path.strpath = dirname(self.strpath)
714
+ if args:
715
+ path = path.join(*args)
716
+ return path
717
+ return self.new(basename="").join(*args, **kwargs)
718
+
719
+ def join(self, *args: os.PathLike[str], abs: bool = False) -> LocalPath:
720
+ """Return a new path by appending all 'args' as path
721
+ components. if abs=1 is used restart from root if any
722
+ of the args is an absolute path.
723
+ """
724
+ sep = self.sep
725
+ strargs = [os.fspath(arg) for arg in args]
726
+ strpath = self.strpath
727
+ if abs:
728
+ newargs: list[str] = []
729
+ for arg in reversed(strargs):
730
+ if isabs(arg):
731
+ strpath = arg
732
+ strargs = newargs
733
+ break
734
+ newargs.insert(0, arg)
735
+ # special case for when we have e.g. strpath == "/"
736
+ actual_sep = "" if strpath.endswith(sep) else sep
737
+ for arg in strargs:
738
+ arg = arg.strip(sep)
739
+ if iswin32:
740
+ # allow unix style paths even on windows.
741
+ arg = arg.strip("/")
742
+ arg = arg.replace("/", sep)
743
+ strpath = strpath + actual_sep + arg
744
+ actual_sep = sep
745
+ obj = object.__new__(self.__class__)
746
+ obj.strpath = normpath(strpath)
747
+ return obj
748
+
749
+ def open(self, mode="r", ensure=False, encoding=None):
750
+ """Return an opened file with the given mode.
751
+
752
+ If ensure is True, create parent directories if needed.
753
+ """
754
+ if ensure:
755
+ self.dirpath().ensure(dir=1)
756
+ if encoding:
757
+ return error.checked_call(
758
+ io.open,
759
+ self.strpath,
760
+ mode,
761
+ encoding=encoding,
762
+ )
763
+ return error.checked_call(open, self.strpath, mode)
764
+
765
+ def _fastjoin(self, name):
766
+ child = object.__new__(self.__class__)
767
+ child.strpath = self.strpath + self.sep + name
768
+ return child
769
+
770
+ def islink(self):
771
+ return islink(self.strpath)
772
+
773
+ def check(self, **kw):
774
+ """Check a path for existence and properties.
775
+
776
+ Without arguments, return True if the path exists, otherwise False.
777
+
778
+ valid checkers::
779
+
780
+ file = 1 # is a file
781
+ file = 0 # is not a file (may not even exist)
782
+ dir = 1 # is a dir
783
+ link = 1 # is a link
784
+ exists = 1 # exists
785
+
786
+ You can specify multiple checker definitions, for example::
787
+
788
+ path.check(file=1, link=1) # a link pointing to a file
789
+ """
790
+ if not kw:
791
+ return exists(self.strpath)
792
+ if len(kw) == 1:
793
+ if "dir" in kw:
794
+ return not kw["dir"] ^ isdir(self.strpath)
795
+ if "file" in kw:
796
+ return not kw["file"] ^ isfile(self.strpath)
797
+ if not kw:
798
+ kw = {"exists": 1}
799
+ return Checkers(self)._evaluate(kw)
800
+
801
+ _patternchars = set("*?[" + os.sep)
802
+
803
+ def listdir(self, fil=None, sort=None):
804
+ """List directory contents, possibly filter by the given fil func
805
+ and possibly sorted.
806
+ """
807
+ if fil is None and sort is None:
808
+ names = error.checked_call(os.listdir, self.strpath)
809
+ return map_as_list(self._fastjoin, names)
810
+ if isinstance(fil, str):
811
+ if not self._patternchars.intersection(fil):
812
+ child = self._fastjoin(fil)
813
+ if exists(child.strpath):
814
+ return [child]
815
+ return []
816
+ fil = FNMatcher(fil)
817
+ names = error.checked_call(os.listdir, self.strpath)
818
+ res = []
819
+ for name in names:
820
+ child = self._fastjoin(name)
821
+ if fil is None or fil(child):
822
+ res.append(child)
823
+ self._sortlist(res, sort)
824
+ return res
825
+
826
+ def size(self) -> int:
827
+ """Return size of the underlying file object"""
828
+ return self.stat().size
829
+
830
+ def mtime(self) -> float:
831
+ """Return last modification time of the path."""
832
+ return self.stat().mtime
833
+
834
+ def copy(self, target, mode=False, stat=False):
835
+ """Copy path to target.
836
+
837
+ If mode is True, will copy permission from path to target.
838
+ If stat is True, copy permission, last modification
839
+ time, last access time, and flags from path to target.
840
+ """
841
+ if self.check(file=1):
842
+ if target.check(dir=1):
843
+ target = target.join(self.basename)
844
+ assert self != target
845
+ copychunked(self, target)
846
+ if mode:
847
+ copymode(self.strpath, target.strpath)
848
+ if stat:
849
+ copystat(self, target)
850
+ else:
851
+
852
+ def rec(p):
853
+ return p.check(link=0)
854
+
855
+ for x in self.visit(rec=rec):
856
+ relpath = x.relto(self)
857
+ newx = target.join(relpath)
858
+ newx.dirpath().ensure(dir=1)
859
+ if x.check(link=1):
860
+ newx.mksymlinkto(x.readlink())
861
+ continue
862
+ elif x.check(file=1):
863
+ copychunked(x, newx)
864
+ elif x.check(dir=1):
865
+ newx.ensure(dir=1)
866
+ if mode:
867
+ copymode(x.strpath, newx.strpath)
868
+ if stat:
869
+ copystat(x, newx)
870
+
871
+ def rename(self, target):
872
+ """Rename this path to target."""
873
+ target = os.fspath(target)
874
+ return error.checked_call(os.rename, self.strpath, target)
875
+
876
+ def dump(self, obj, bin=1):
877
+ """Pickle object into path location"""
878
+ f = self.open("wb")
879
+ import pickle
880
+
881
+ try:
882
+ error.checked_call(pickle.dump, obj, f, bin)
883
+ finally:
884
+ f.close()
885
+
886
+ def mkdir(self, *args):
887
+ """Create & return the directory joined with args."""
888
+ p = self.join(*args)
889
+ error.checked_call(os.mkdir, os.fspath(p))
890
+ return p
891
+
892
+ def write_binary(self, data, ensure=False):
893
+ """Write binary data into path. If ensure is True create
894
+ missing parent directories.
895
+ """
896
+ if ensure:
897
+ self.dirpath().ensure(dir=1)
898
+ with self.open("wb") as f:
899
+ f.write(data)
900
+
901
+ def write_text(self, data, encoding, ensure=False):
902
+ """Write text data into path using the specified encoding.
903
+ If ensure is True create missing parent directories.
904
+ """
905
+ if ensure:
906
+ self.dirpath().ensure(dir=1)
907
+ with self.open("w", encoding=encoding) as f:
908
+ f.write(data)
909
+
910
+ def write(self, data, mode="w", ensure=False):
911
+ """Write data into path. If ensure is True create
912
+ missing parent directories.
913
+ """
914
+ if ensure:
915
+ self.dirpath().ensure(dir=1)
916
+ if "b" in mode:
917
+ if not isinstance(data, bytes):
918
+ raise ValueError("can only process bytes")
919
+ else:
920
+ if not isinstance(data, str):
921
+ if not isinstance(data, bytes):
922
+ data = str(data)
923
+ else:
924
+ data = data.decode(sys.getdefaultencoding())
925
+ f = self.open(mode)
926
+ try:
927
+ f.write(data)
928
+ finally:
929
+ f.close()
930
+
931
+ def _ensuredirs(self):
932
+ parent = self.dirpath()
933
+ if parent == self:
934
+ return self
935
+ if parent.check(dir=0):
936
+ parent._ensuredirs()
937
+ if self.check(dir=0):
938
+ try:
939
+ self.mkdir()
940
+ except error.EEXIST:
941
+ # race condition: file/dir created by another thread/process.
942
+ # complain if it is not a dir
943
+ if self.check(dir=0):
944
+ raise
945
+ return self
946
+
947
+ def ensure(self, *args, **kwargs):
948
+ """Ensure that an args-joined path exists (by default as
949
+ a file). if you specify a keyword argument 'dir=True'
950
+ then the path is forced to be a directory path.
951
+ """
952
+ p = self.join(*args)
953
+ if kwargs.get("dir", 0):
954
+ return p._ensuredirs()
955
+ else:
956
+ p.dirpath()._ensuredirs()
957
+ if not p.check(file=1):
958
+ p.open("wb").close()
959
+ return p
960
+
961
+ @overload
962
+ def stat(self, raising: Literal[True] = ...) -> Stat: ...
963
+
964
+ @overload
965
+ def stat(self, raising: Literal[False]) -> Stat | None: ...
966
+
967
+ def stat(self, raising: bool = True) -> Stat | None:
968
+ """Return an os.stat() tuple."""
969
+ if raising:
970
+ return Stat(self, error.checked_call(os.stat, self.strpath))
971
+ try:
972
+ return Stat(self, os.stat(self.strpath))
973
+ except KeyboardInterrupt:
974
+ raise
975
+ except Exception:
976
+ return None
977
+
978
+ def lstat(self) -> Stat:
979
+ """Return an os.lstat() tuple."""
980
+ return Stat(self, error.checked_call(os.lstat, self.strpath))
981
+
982
+ def setmtime(self, mtime=None):
983
+ """Set modification time for the given path. if 'mtime' is None
984
+ (the default) then the file's mtime is set to current time.
985
+
986
+ Note that the resolution for 'mtime' is platform dependent.
987
+ """
988
+ if mtime is None:
989
+ return error.checked_call(os.utime, self.strpath, mtime)
990
+ try:
991
+ return error.checked_call(os.utime, self.strpath, (-1, mtime))
992
+ except error.EINVAL:
993
+ return error.checked_call(os.utime, self.strpath, (self.atime(), mtime))
994
+
995
+ def chdir(self):
996
+ """Change directory to self and return old current directory"""
997
+ try:
998
+ old = self.__class__()
999
+ except error.ENOENT:
1000
+ old = None
1001
+ error.checked_call(os.chdir, self.strpath)
1002
+ return old
1003
+
1004
+ @contextmanager
1005
+ def as_cwd(self):
1006
+ """
1007
+ Return a context manager, which changes to the path's dir during the
1008
+ managed "with" context.
1009
+ On __enter__ it returns the old dir, which might be ``None``.
1010
+ """
1011
+ old = self.chdir()
1012
+ try:
1013
+ yield old
1014
+ finally:
1015
+ if old is not None:
1016
+ old.chdir()
1017
+
1018
+ def realpath(self):
1019
+ """Return a new path which contains no symbolic links."""
1020
+ return self.__class__(os.path.realpath(self.strpath))
1021
+
1022
+ def atime(self):
1023
+ """Return last access time of the path."""
1024
+ return self.stat().atime
1025
+
1026
+ def __repr__(self):
1027
+ return f"local({self.strpath!r})"
1028
+
1029
+ def __str__(self):
1030
+ """Return string representation of the Path."""
1031
+ return self.strpath
1032
+
1033
+ def chmod(self, mode, rec=0):
1034
+ """Change permissions to the given mode. If mode is an
1035
+ integer it directly encodes the os-specific modes.
1036
+ if rec is True perform recursively.
1037
+ """
1038
+ if not isinstance(mode, int):
1039
+ raise TypeError(f"mode {mode!r} must be an integer")
1040
+ if rec:
1041
+ for x in self.visit(rec=rec):
1042
+ error.checked_call(os.chmod, str(x), mode)
1043
+ error.checked_call(os.chmod, self.strpath, mode)
1044
+
1045
+ def pypkgpath(self):
1046
+ """Return the Python package path by looking for the last
1047
+ directory upwards which still contains an __init__.py.
1048
+ Return None if a pkgpath cannot be determined.
1049
+ """
1050
+ pkgpath = None
1051
+ for parent in self.parts(reverse=True):
1052
+ if parent.isdir():
1053
+ if not parent.join("__init__.py").exists():
1054
+ break
1055
+ if not isimportable(parent.basename):
1056
+ break
1057
+ pkgpath = parent
1058
+ return pkgpath
1059
+
1060
+ def _ensuresyspath(self, ensuremode, path):
1061
+ if ensuremode:
1062
+ s = str(path)
1063
+ if ensuremode == "append":
1064
+ if s not in sys.path:
1065
+ sys.path.append(s)
1066
+ else:
1067
+ if s != sys.path[0]:
1068
+ sys.path.insert(0, s)
1069
+
1070
+ def pyimport(self, modname=None, ensuresyspath=True):
1071
+ """Return path as an imported python module.
1072
+
1073
+ If modname is None, look for the containing package
1074
+ and construct an according module name.
1075
+ The module will be put/looked up in sys.modules.
1076
+ if ensuresyspath is True then the root dir for importing
1077
+ the file (taking __init__.py files into account) will
1078
+ be prepended to sys.path if it isn't there already.
1079
+ If ensuresyspath=="append" the root dir will be appended
1080
+ if it isn't already contained in sys.path.
1081
+ if ensuresyspath is False no modification of syspath happens.
1082
+
1083
+ Special value of ensuresyspath=="importlib" is intended
1084
+ purely for using in pytest, it is capable only of importing
1085
+ separate .py files outside packages, e.g. for test suite
1086
+ without any __init__.py file. It effectively allows having
1087
+ same-named test modules in different places and offers
1088
+ mild opt-in via this option. Note that it works only in
1089
+ recent versions of python.
1090
+ """
1091
+ if not self.check():
1092
+ raise error.ENOENT(self)
1093
+
1094
+ if ensuresyspath == "importlib":
1095
+ if modname is None:
1096
+ modname = self.purebasename
1097
+ spec = importlib.util.spec_from_file_location(modname, str(self))
1098
+ if spec is None or spec.loader is None:
1099
+ raise ImportError(f"Can't find module {modname} at location {self!s}")
1100
+ mod = importlib.util.module_from_spec(spec)
1101
+ spec.loader.exec_module(mod)
1102
+ return mod
1103
+
1104
+ pkgpath = None
1105
+ if modname is None:
1106
+ pkgpath = self.pypkgpath()
1107
+ if pkgpath is not None:
1108
+ pkgroot = pkgpath.dirpath()
1109
+ names = self.new(ext="").relto(pkgroot).split(self.sep)
1110
+ if names[-1] == "__init__":
1111
+ names.pop()
1112
+ modname = ".".join(names)
1113
+ else:
1114
+ pkgroot = self.dirpath()
1115
+ modname = self.purebasename
1116
+
1117
+ self._ensuresyspath(ensuresyspath, pkgroot)
1118
+ __import__(modname)
1119
+ mod = sys.modules[modname]
1120
+ if self.basename == "__init__.py":
1121
+ return mod # we don't check anything as we might
1122
+ # be in a namespace package ... too icky to check
1123
+ modfile = mod.__file__
1124
+ assert modfile is not None
1125
+ if modfile[-4:] in (".pyc", ".pyo"):
1126
+ modfile = modfile[:-1]
1127
+ elif modfile.endswith("$py.class"):
1128
+ modfile = modfile[:-9] + ".py"
1129
+ if modfile.endswith(os.sep + "__init__.py"):
1130
+ if self.basename != "__init__.py":
1131
+ modfile = modfile[:-12]
1132
+ try:
1133
+ issame = self.samefile(modfile)
1134
+ except error.ENOENT:
1135
+ issame = False
1136
+ if not issame:
1137
+ ignore = os.getenv("PY_IGNORE_IMPORTMISMATCH")
1138
+ if ignore != "1":
1139
+ raise self.ImportMismatchError(modname, modfile, self)
1140
+ return mod
1141
+ else:
1142
+ try:
1143
+ return sys.modules[modname]
1144
+ except KeyError:
1145
+ # we have a custom modname, do a pseudo-import
1146
+ import types
1147
+
1148
+ mod = types.ModuleType(modname)
1149
+ mod.__file__ = str(self)
1150
+ sys.modules[modname] = mod
1151
+ try:
1152
+ with open(str(self), "rb") as f:
1153
+ exec(f.read(), mod.__dict__)
1154
+ except BaseException:
1155
+ del sys.modules[modname]
1156
+ raise
1157
+ return mod
1158
+
1159
+ def sysexec(self, *argv: os.PathLike[str], **popen_opts: Any) -> str:
1160
+ """Return stdout text from executing a system child process,
1161
+ where the 'self' path points to executable.
1162
+ The process is directly invoked and not through a system shell.
1163
+ """
1164
+ from subprocess import PIPE
1165
+ from subprocess import Popen
1166
+
1167
+ popen_opts.pop("stdout", None)
1168
+ popen_opts.pop("stderr", None)
1169
+ proc = Popen(
1170
+ [str(self)] + [str(arg) for arg in argv],
1171
+ **popen_opts,
1172
+ stdout=PIPE,
1173
+ stderr=PIPE,
1174
+ )
1175
+ stdout: str | bytes
1176
+ stdout, stderr = proc.communicate()
1177
+ ret = proc.wait()
1178
+ if isinstance(stdout, bytes):
1179
+ stdout = stdout.decode(sys.getdefaultencoding())
1180
+ if ret != 0:
1181
+ if isinstance(stderr, bytes):
1182
+ stderr = stderr.decode(sys.getdefaultencoding())
1183
+ raise RuntimeError(
1184
+ ret,
1185
+ ret,
1186
+ str(self),
1187
+ stdout,
1188
+ stderr,
1189
+ )
1190
+ return stdout
1191
+
1192
+ @classmethod
1193
+ def sysfind(cls, name, checker=None, paths=None):
1194
+ """Return a path object found by looking at the systems
1195
+ underlying PATH specification. If the checker is not None
1196
+ it will be invoked to filter matching paths. If a binary
1197
+ cannot be found, None is returned
1198
+ Note: This is probably not working on plain win32 systems
1199
+ but may work on cygwin.
1200
+ """
1201
+ if isabs(name):
1202
+ p = local(name)
1203
+ if p.check(file=1):
1204
+ return p
1205
+ else:
1206
+ if paths is None:
1207
+ if iswin32:
1208
+ paths = os.environ["Path"].split(";")
1209
+ if "" not in paths and "." not in paths:
1210
+ paths.append(".")
1211
+ try:
1212
+ systemroot = os.environ["SYSTEMROOT"]
1213
+ except KeyError:
1214
+ pass
1215
+ else:
1216
+ paths = [
1217
+ path.replace("%SystemRoot%", systemroot) for path in paths
1218
+ ]
1219
+ else:
1220
+ paths = os.environ["PATH"].split(":")
1221
+ tryadd = []
1222
+ if iswin32:
1223
+ tryadd += os.environ["PATHEXT"].split(os.pathsep)
1224
+ tryadd.append("")
1225
+
1226
+ for x in paths:
1227
+ for addext in tryadd:
1228
+ p = local(x).join(name, abs=True) + addext
1229
+ try:
1230
+ if p.check(file=1):
1231
+ if checker:
1232
+ if not checker(p):
1233
+ continue
1234
+ return p
1235
+ except error.EACCES:
1236
+ pass
1237
+ return None
1238
+
1239
+ @classmethod
1240
+ def _gethomedir(cls):
1241
+ try:
1242
+ x = os.environ["HOME"]
1243
+ except KeyError:
1244
+ try:
1245
+ x = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"]
1246
+ except KeyError:
1247
+ return None
1248
+ return cls(x)
1249
+
1250
+ # """
1251
+ # special class constructors for local filesystem paths
1252
+ # """
1253
+ @classmethod
1254
+ def get_temproot(cls):
1255
+ """Return the system's temporary directory
1256
+ (where tempfiles are usually created in)
1257
+ """
1258
+ import tempfile
1259
+
1260
+ return local(tempfile.gettempdir())
1261
+
1262
+ @classmethod
1263
+ def mkdtemp(cls, rootdir=None):
1264
+ """Return a Path object pointing to a fresh new temporary directory
1265
+ (which we created ourselves).
1266
+ """
1267
+ import tempfile
1268
+
1269
+ if rootdir is None:
1270
+ rootdir = cls.get_temproot()
1271
+ path = error.checked_call(tempfile.mkdtemp, dir=str(rootdir))
1272
+ return cls(path)
1273
+
1274
+ @classmethod
1275
+ def make_numbered_dir(
1276
+ cls, prefix="session-", rootdir=None, keep=3, lock_timeout=172800
1277
+ ): # two days
1278
+ """Return unique directory with a number greater than the current
1279
+ maximum one. The number is assumed to start directly after prefix.
1280
+ if keep is true directories with a number less than (maxnum-keep)
1281
+ will be removed. If .lock files are used (lock_timeout non-zero),
1282
+ algorithm is multi-process safe.
1283
+ """
1284
+ if rootdir is None:
1285
+ rootdir = cls.get_temproot()
1286
+
1287
+ nprefix = prefix.lower()
1288
+
1289
+ def parse_num(path):
1290
+ """Parse the number out of a path (if it matches the prefix)"""
1291
+ nbasename = path.basename.lower()
1292
+ if nbasename.startswith(nprefix):
1293
+ try:
1294
+ return int(nbasename[len(nprefix) :])
1295
+ except ValueError:
1296
+ pass
1297
+
1298
+ def create_lockfile(path):
1299
+ """Exclusively create lockfile. Throws when failed"""
1300
+ mypid = os.getpid()
1301
+ lockfile = path.join(".lock")
1302
+ if hasattr(lockfile, "mksymlinkto"):
1303
+ lockfile.mksymlinkto(str(mypid))
1304
+ else:
1305
+ fd = error.checked_call(
1306
+ os.open, str(lockfile), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644
1307
+ )
1308
+ with os.fdopen(fd, "w") as f:
1309
+ f.write(str(mypid))
1310
+ return lockfile
1311
+
1312
+ def atexit_remove_lockfile(lockfile):
1313
+ """Ensure lockfile is removed at process exit"""
1314
+ mypid = os.getpid()
1315
+
1316
+ def try_remove_lockfile():
1317
+ # in a fork() situation, only the last process should
1318
+ # remove the .lock, otherwise the other processes run the
1319
+ # risk of seeing their temporary dir disappear. For now
1320
+ # we remove the .lock in the parent only (i.e. we assume
1321
+ # that the children finish before the parent).
1322
+ if os.getpid() != mypid:
1323
+ return
1324
+ try:
1325
+ lockfile.remove()
1326
+ except error.Error:
1327
+ pass
1328
+
1329
+ atexit.register(try_remove_lockfile)
1330
+
1331
+ # compute the maximum number currently in use with the prefix
1332
+ lastmax = None
1333
+ while True:
1334
+ maxnum = -1
1335
+ for path in rootdir.listdir():
1336
+ num = parse_num(path)
1337
+ if num is not None:
1338
+ maxnum = max(maxnum, num)
1339
+
1340
+ # make the new directory
1341
+ try:
1342
+ udir = rootdir.mkdir(prefix + str(maxnum + 1))
1343
+ if lock_timeout:
1344
+ lockfile = create_lockfile(udir)
1345
+ atexit_remove_lockfile(lockfile)
1346
+ except (error.EEXIST, error.ENOENT, error.EBUSY):
1347
+ # race condition (1): another thread/process created the dir
1348
+ # in the meantime - try again
1349
+ # race condition (2): another thread/process spuriously acquired
1350
+ # lock treating empty directory as candidate
1351
+ # for removal - try again
1352
+ # race condition (3): another thread/process tried to create the lock at
1353
+ # the same time (happened in Python 3.3 on Windows)
1354
+ # https://ci.appveyor.com/project/pytestbot/py/build/1.0.21/job/ffi85j4c0lqwsfwa
1355
+ if lastmax == maxnum:
1356
+ raise
1357
+ lastmax = maxnum
1358
+ continue
1359
+ break
1360
+
1361
+ def get_mtime(path):
1362
+ """Read file modification time"""
1363
+ try:
1364
+ return path.lstat().mtime
1365
+ except error.Error:
1366
+ pass
1367
+
1368
+ garbage_prefix = prefix + "garbage-"
1369
+
1370
+ def is_garbage(path):
1371
+ """Check if path denotes directory scheduled for removal"""
1372
+ bn = path.basename
1373
+ return bn.startswith(garbage_prefix)
1374
+
1375
+ # prune old directories
1376
+ udir_time = get_mtime(udir)
1377
+ if keep and udir_time:
1378
+ for path in rootdir.listdir():
1379
+ num = parse_num(path)
1380
+ if num is not None and num <= (maxnum - keep):
1381
+ try:
1382
+ # try acquiring lock to remove directory as exclusive user
1383
+ if lock_timeout:
1384
+ create_lockfile(path)
1385
+ except (error.EEXIST, error.ENOENT, error.EBUSY):
1386
+ path_time = get_mtime(path)
1387
+ if not path_time:
1388
+ # assume directory doesn't exist now
1389
+ continue
1390
+ if abs(udir_time - path_time) < lock_timeout:
1391
+ # assume directory with lockfile exists
1392
+ # and lock timeout hasn't expired yet
1393
+ continue
1394
+
1395
+ # path dir locked for exclusive use
1396
+ # and scheduled for removal to avoid another thread/process
1397
+ # treating it as a new directory or removal candidate
1398
+ garbage_path = rootdir.join(garbage_prefix + str(uuid.uuid4()))
1399
+ try:
1400
+ path.rename(garbage_path)
1401
+ garbage_path.remove(rec=1)
1402
+ except KeyboardInterrupt:
1403
+ raise
1404
+ except Exception: # this might be error.Error, WindowsError ...
1405
+ pass
1406
+ if is_garbage(path):
1407
+ try:
1408
+ path.remove(rec=1)
1409
+ except KeyboardInterrupt:
1410
+ raise
1411
+ except Exception: # this might be error.Error, WindowsError ...
1412
+ pass
1413
+
1414
+ # make link...
1415
+ try:
1416
+ username = os.environ["USER"] # linux, et al
1417
+ except KeyError:
1418
+ try:
1419
+ username = os.environ["USERNAME"] # windows
1420
+ except KeyError:
1421
+ username = "current"
1422
+
1423
+ src = str(udir)
1424
+ dest = src[: src.rfind("-")] + "-" + username
1425
+ try:
1426
+ os.unlink(dest)
1427
+ except OSError:
1428
+ pass
1429
+ try:
1430
+ os.symlink(src, dest)
1431
+ except (OSError, AttributeError, NotImplementedError):
1432
+ pass
1433
+
1434
+ return udir
1435
+
1436
+
1437
+ def copymode(src, dest):
1438
+ """Copy permission from src to dst."""
1439
+ import shutil
1440
+
1441
+ shutil.copymode(src, dest)
1442
+
1443
+
1444
+ def copystat(src, dest):
1445
+ """Copy permission, last modification time,
1446
+ last access time, and flags from src to dst."""
1447
+ import shutil
1448
+
1449
+ shutil.copystat(str(src), str(dest))
1450
+
1451
+
1452
+ def copychunked(src, dest):
1453
+ chunksize = 524288 # half a meg of bytes
1454
+ fsrc = src.open("rb")
1455
+ try:
1456
+ fdest = dest.open("wb")
1457
+ try:
1458
+ while 1:
1459
+ buf = fsrc.read(chunksize)
1460
+ if not buf:
1461
+ break
1462
+ fdest.write(buf)
1463
+ finally:
1464
+ fdest.close()
1465
+ finally:
1466
+ fsrc.close()
1467
+
1468
+
1469
+ def isimportable(name):
1470
+ if name and (name[0].isalpha() or name[0] == "_"):
1471
+ name = name.replace("_", "")
1472
+ return not name or name.isalnum()
1473
+
1474
+
1475
+ local = LocalPath
.venv/lib/python3.11/site-packages/_pytest/config/__init__.py ADDED
@@ -0,0 +1,1973 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mypy: allow-untyped-defs
2
+ """Command line options, ini-file and conftest.py processing."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import collections.abc
8
+ import copy
9
+ import dataclasses
10
+ import enum
11
+ from functools import lru_cache
12
+ import glob
13
+ import importlib.metadata
14
+ import inspect
15
+ import os
16
+ import pathlib
17
+ import re
18
+ import shlex
19
+ import sys
20
+ from textwrap import dedent
21
+ import types
22
+ from types import FunctionType
23
+ from typing import Any
24
+ from typing import Callable
25
+ from typing import cast
26
+ from typing import Final
27
+ from typing import final
28
+ from typing import Generator
29
+ from typing import IO
30
+ from typing import Iterable
31
+ from typing import Iterator
32
+ from typing import Sequence
33
+ from typing import TextIO
34
+ from typing import Type
35
+ from typing import TYPE_CHECKING
36
+ import warnings
37
+
38
+ import pluggy
39
+ from pluggy import HookimplMarker
40
+ from pluggy import HookimplOpts
41
+ from pluggy import HookspecMarker
42
+ from pluggy import HookspecOpts
43
+ from pluggy import PluginManager
44
+
45
+ from .compat import PathAwareHookProxy
46
+ from .exceptions import PrintHelp as PrintHelp
47
+ from .exceptions import UsageError as UsageError
48
+ from .findpaths import determine_setup
49
+ from _pytest import __version__
50
+ import _pytest._code
51
+ from _pytest._code import ExceptionInfo
52
+ from _pytest._code import filter_traceback
53
+ from _pytest._code.code import TracebackStyle
54
+ from _pytest._io import TerminalWriter
55
+ from _pytest.config.argparsing import Argument
56
+ from _pytest.config.argparsing import Parser
57
+ import _pytest.deprecated
58
+ import _pytest.hookspec
59
+ from _pytest.outcomes import fail
60
+ from _pytest.outcomes import Skipped
61
+ from _pytest.pathlib import absolutepath
62
+ from _pytest.pathlib import bestrelpath
63
+ from _pytest.pathlib import import_path
64
+ from _pytest.pathlib import ImportMode
65
+ from _pytest.pathlib import resolve_package_path
66
+ from _pytest.pathlib import safe_exists
67
+ from _pytest.stash import Stash
68
+ from _pytest.warning_types import PytestConfigWarning
69
+ from _pytest.warning_types import warn_explicit_for
70
+
71
+
72
+ if TYPE_CHECKING:
73
+ from _pytest.cacheprovider import Cache
74
+ from _pytest.terminal import TerminalReporter
75
+
76
+
77
+ _PluggyPlugin = object
78
+ """A type to represent plugin objects.
79
+
80
+ Plugins can be any namespace, so we can't narrow it down much, but we use an
81
+ alias to make the intent clear.
82
+
83
+ Ideally this type would be provided by pluggy itself.
84
+ """
85
+
86
+
87
+ hookimpl = HookimplMarker("pytest")
88
+ hookspec = HookspecMarker("pytest")
89
+
90
+
91
+ @final
92
+ class ExitCode(enum.IntEnum):
93
+ """Encodes the valid exit codes by pytest.
94
+
95
+ Currently users and plugins may supply other exit codes as well.
96
+
97
+ .. versionadded:: 5.0
98
+ """
99
+
100
+ #: Tests passed.
101
+ OK = 0
102
+ #: Tests failed.
103
+ TESTS_FAILED = 1
104
+ #: pytest was interrupted.
105
+ INTERRUPTED = 2
106
+ #: An internal error got in the way.
107
+ INTERNAL_ERROR = 3
108
+ #: pytest was misused.
109
+ USAGE_ERROR = 4
110
+ #: pytest couldn't find tests.
111
+ NO_TESTS_COLLECTED = 5
112
+
113
+
114
+ class ConftestImportFailure(Exception):
115
+ def __init__(
116
+ self,
117
+ path: pathlib.Path,
118
+ *,
119
+ cause: Exception,
120
+ ) -> None:
121
+ self.path = path
122
+ self.cause = cause
123
+
124
+ def __str__(self) -> str:
125
+ return f"{type(self.cause).__name__}: {self.cause} (from {self.path})"
126
+
127
+
128
+ def filter_traceback_for_conftest_import_failure(
129
+ entry: _pytest._code.TracebackEntry,
130
+ ) -> bool:
131
+ """Filter tracebacks entries which point to pytest internals or importlib.
132
+
133
+ Make a special case for importlib because we use it to import test modules and conftest files
134
+ in _pytest.pathlib.import_path.
135
+ """
136
+ return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep)
137
+
138
+
139
+ def main(
140
+ args: list[str] | os.PathLike[str] | None = None,
141
+ plugins: Sequence[str | _PluggyPlugin] | None = None,
142
+ ) -> int | ExitCode:
143
+ """Perform an in-process test run.
144
+
145
+ :param args:
146
+ List of command line arguments. If `None` or not given, defaults to reading
147
+ arguments directly from the process command line (:data:`sys.argv`).
148
+ :param plugins: List of plugin objects to be auto-registered during initialization.
149
+
150
+ :returns: An exit code.
151
+ """
152
+ old_pytest_version = os.environ.get("PYTEST_VERSION")
153
+ try:
154
+ os.environ["PYTEST_VERSION"] = __version__
155
+ try:
156
+ config = _prepareconfig(args, plugins)
157
+ except ConftestImportFailure as e:
158
+ exc_info = ExceptionInfo.from_exception(e.cause)
159
+ tw = TerminalWriter(sys.stderr)
160
+ tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
161
+ exc_info.traceback = exc_info.traceback.filter(
162
+ filter_traceback_for_conftest_import_failure
163
+ )
164
+ exc_repr = (
165
+ exc_info.getrepr(style="short", chain=False)
166
+ if exc_info.traceback
167
+ else exc_info.exconly()
168
+ )
169
+ formatted_tb = str(exc_repr)
170
+ for line in formatted_tb.splitlines():
171
+ tw.line(line.rstrip(), red=True)
172
+ return ExitCode.USAGE_ERROR
173
+ else:
174
+ try:
175
+ ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
176
+ try:
177
+ return ExitCode(ret)
178
+ except ValueError:
179
+ return ret
180
+ finally:
181
+ config._ensure_unconfigure()
182
+ except UsageError as e:
183
+ tw = TerminalWriter(sys.stderr)
184
+ for msg in e.args:
185
+ tw.line(f"ERROR: {msg}\n", red=True)
186
+ return ExitCode.USAGE_ERROR
187
+ finally:
188
+ if old_pytest_version is None:
189
+ os.environ.pop("PYTEST_VERSION", None)
190
+ else:
191
+ os.environ["PYTEST_VERSION"] = old_pytest_version
192
+
193
+
194
+ def console_main() -> int:
195
+ """The CLI entry point of pytest.
196
+
197
+ This function is not meant for programmable use; use `main()` instead.
198
+ """
199
+ # https://docs.python.org/3/library/signal.html#note-on-sigpipe
200
+ try:
201
+ code = main()
202
+ sys.stdout.flush()
203
+ return code
204
+ except BrokenPipeError:
205
+ # Python flushes standard streams on exit; redirect remaining output
206
+ # to devnull to avoid another BrokenPipeError at shutdown
207
+ devnull = os.open(os.devnull, os.O_WRONLY)
208
+ os.dup2(devnull, sys.stdout.fileno())
209
+ return 1 # Python exits with error code 1 on EPIPE
210
+
211
+
212
+ class cmdline: # compatibility namespace
213
+ main = staticmethod(main)
214
+
215
+
216
+ def filename_arg(path: str, optname: str) -> str:
217
+ """Argparse type validator for filename arguments.
218
+
219
+ :path: Path of filename.
220
+ :optname: Name of the option.
221
+ """
222
+ if os.path.isdir(path):
223
+ raise UsageError(f"{optname} must be a filename, given: {path}")
224
+ return path
225
+
226
+
227
+ def directory_arg(path: str, optname: str) -> str:
228
+ """Argparse type validator for directory arguments.
229
+
230
+ :path: Path of directory.
231
+ :optname: Name of the option.
232
+ """
233
+ if not os.path.isdir(path):
234
+ raise UsageError(f"{optname} must be a directory, given: {path}")
235
+ return path
236
+
237
+
238
+ # Plugins that cannot be disabled via "-p no:X" currently.
239
+ essential_plugins = (
240
+ "mark",
241
+ "main",
242
+ "runner",
243
+ "fixtures",
244
+ "helpconfig", # Provides -p.
245
+ )
246
+
247
+ default_plugins = (
248
+ *essential_plugins,
249
+ "python",
250
+ "terminal",
251
+ "debugging",
252
+ "unittest",
253
+ "capture",
254
+ "skipping",
255
+ "legacypath",
256
+ "tmpdir",
257
+ "monkeypatch",
258
+ "recwarn",
259
+ "pastebin",
260
+ "assertion",
261
+ "junitxml",
262
+ "doctest",
263
+ "cacheprovider",
264
+ "freeze_support",
265
+ "setuponly",
266
+ "setupplan",
267
+ "stepwise",
268
+ "warnings",
269
+ "logging",
270
+ "reports",
271
+ "python_path",
272
+ "unraisableexception",
273
+ "threadexception",
274
+ "faulthandler",
275
+ )
276
+
277
+ builtin_plugins = set(default_plugins)
278
+ builtin_plugins.add("pytester")
279
+ builtin_plugins.add("pytester_assertions")
280
+
281
+
282
+ def get_config(
283
+ args: list[str] | None = None,
284
+ plugins: Sequence[str | _PluggyPlugin] | None = None,
285
+ ) -> Config:
286
+ # subsequent calls to main will create a fresh instance
287
+ pluginmanager = PytestPluginManager()
288
+ config = Config(
289
+ pluginmanager,
290
+ invocation_params=Config.InvocationParams(
291
+ args=args or (),
292
+ plugins=plugins,
293
+ dir=pathlib.Path.cwd(),
294
+ ),
295
+ )
296
+
297
+ if args is not None:
298
+ # Handle any "-p no:plugin" args.
299
+ pluginmanager.consider_preparse(args, exclude_only=True)
300
+
301
+ for spec in default_plugins:
302
+ pluginmanager.import_plugin(spec)
303
+
304
+ return config
305
+
306
+
307
+ def get_plugin_manager() -> PytestPluginManager:
308
+ """Obtain a new instance of the
309
+ :py:class:`pytest.PytestPluginManager`, with default plugins
310
+ already loaded.
311
+
312
+ This function can be used by integration with other tools, like hooking
313
+ into pytest to run tests into an IDE.
314
+ """
315
+ return get_config().pluginmanager
316
+
317
+
318
+ def _prepareconfig(
319
+ args: list[str] | os.PathLike[str] | None = None,
320
+ plugins: Sequence[str | _PluggyPlugin] | None = None,
321
+ ) -> Config:
322
+ if args is None:
323
+ args = sys.argv[1:]
324
+ elif isinstance(args, os.PathLike):
325
+ args = [os.fspath(args)]
326
+ elif not isinstance(args, list):
327
+ msg = ( # type:ignore[unreachable]
328
+ "`args` parameter expected to be a list of strings, got: {!r} (type: {})"
329
+ )
330
+ raise TypeError(msg.format(args, type(args)))
331
+
332
+ config = get_config(args, plugins)
333
+ pluginmanager = config.pluginmanager
334
+ try:
335
+ if plugins:
336
+ for plugin in plugins:
337
+ if isinstance(plugin, str):
338
+ pluginmanager.consider_pluginarg(plugin)
339
+ else:
340
+ pluginmanager.register(plugin)
341
+ config = pluginmanager.hook.pytest_cmdline_parse(
342
+ pluginmanager=pluginmanager, args=args
343
+ )
344
+ return config
345
+ except BaseException:
346
+ config._ensure_unconfigure()
347
+ raise
348
+
349
+
350
+ def _get_directory(path: pathlib.Path) -> pathlib.Path:
351
+ """Get the directory of a path - itself if already a directory."""
352
+ if path.is_file():
353
+ return path.parent
354
+ else:
355
+ return path
356
+
357
+
358
+ def _get_legacy_hook_marks(
359
+ method: Any,
360
+ hook_type: str,
361
+ opt_names: tuple[str, ...],
362
+ ) -> dict[str, bool]:
363
+ if TYPE_CHECKING:
364
+ # abuse typeguard from importlib to avoid massive method type union that's lacking an alias
365
+ assert inspect.isroutine(method)
366
+ known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])}
367
+ must_warn: list[str] = []
368
+ opts: dict[str, bool] = {}
369
+ for opt_name in opt_names:
370
+ opt_attr = getattr(method, opt_name, AttributeError)
371
+ if opt_attr is not AttributeError:
372
+ must_warn.append(f"{opt_name}={opt_attr}")
373
+ opts[opt_name] = True
374
+ elif opt_name in known_marks:
375
+ must_warn.append(f"{opt_name}=True")
376
+ opts[opt_name] = True
377
+ else:
378
+ opts[opt_name] = False
379
+ if must_warn:
380
+ hook_opts = ", ".join(must_warn)
381
+ message = _pytest.deprecated.HOOK_LEGACY_MARKING.format(
382
+ type=hook_type,
383
+ fullname=method.__qualname__,
384
+ hook_opts=hook_opts,
385
+ )
386
+ warn_explicit_for(cast(FunctionType, method), message)
387
+ return opts
388
+
389
+
390
+ @final
391
+ class PytestPluginManager(PluginManager):
392
+ """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
393
+ additional pytest-specific functionality:
394
+
395
+ * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and
396
+ ``pytest_plugins`` global variables found in plugins being loaded.
397
+ * ``conftest.py`` loading during start-up.
398
+ """
399
+
400
+ def __init__(self) -> None:
401
+ import _pytest.assertion
402
+
403
+ super().__init__("pytest")
404
+
405
+ # -- State related to local conftest plugins.
406
+ # All loaded conftest modules.
407
+ self._conftest_plugins: set[types.ModuleType] = set()
408
+ # All conftest modules applicable for a directory.
409
+ # This includes the directory's own conftest modules as well
410
+ # as those of its parent directories.
411
+ self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {}
412
+ # Cutoff directory above which conftests are no longer discovered.
413
+ self._confcutdir: pathlib.Path | None = None
414
+ # If set, conftest loading is skipped.
415
+ self._noconftest = False
416
+
417
+ # _getconftestmodules()'s call to _get_directory() causes a stat
418
+ # storm when it's called potentially thousands of times in a test
419
+ # session (#9478), often with the same path, so cache it.
420
+ self._get_directory = lru_cache(256)(_get_directory)
421
+
422
+ # plugins that were explicitly skipped with pytest.skip
423
+ # list of (module name, skip reason)
424
+ # previously we would issue a warning when a plugin was skipped, but
425
+ # since we refactored warnings as first citizens of Config, they are
426
+ # just stored here to be used later.
427
+ self.skipped_plugins: list[tuple[str, str]] = []
428
+
429
+ self.add_hookspecs(_pytest.hookspec)
430
+ self.register(self)
431
+ if os.environ.get("PYTEST_DEBUG"):
432
+ err: IO[str] = sys.stderr
433
+ encoding: str = getattr(err, "encoding", "utf8")
434
+ try:
435
+ err = open(
436
+ os.dup(err.fileno()),
437
+ mode=err.mode,
438
+ buffering=1,
439
+ encoding=encoding,
440
+ )
441
+ except Exception:
442
+ pass
443
+ self.trace.root.setwriter(err.write)
444
+ self.enable_tracing()
445
+
446
+ # Config._consider_importhook will set a real object if required.
447
+ self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
448
+ # Used to know when we are importing conftests after the pytest_configure stage.
449
+ self._configured = False
450
+
451
+ def parse_hookimpl_opts(
452
+ self, plugin: _PluggyPlugin, name: str
453
+ ) -> HookimplOpts | None:
454
+ """:meta private:"""
455
+ # pytest hooks are always prefixed with "pytest_",
456
+ # so we avoid accessing possibly non-readable attributes
457
+ # (see issue #1073).
458
+ if not name.startswith("pytest_"):
459
+ return None
460
+ # Ignore names which cannot be hooks.
461
+ if name == "pytest_plugins":
462
+ return None
463
+
464
+ opts = super().parse_hookimpl_opts(plugin, name)
465
+ if opts is not None:
466
+ return opts
467
+
468
+ method = getattr(plugin, name)
469
+ # Consider only actual functions for hooks (#3775).
470
+ if not inspect.isroutine(method):
471
+ return None
472
+ # Collect unmarked hooks as long as they have the `pytest_' prefix.
473
+ return _get_legacy_hook_marks( # type: ignore[return-value]
474
+ method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
475
+ )
476
+
477
+ def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None:
478
+ """:meta private:"""
479
+ opts = super().parse_hookspec_opts(module_or_class, name)
480
+ if opts is None:
481
+ method = getattr(module_or_class, name)
482
+ if name.startswith("pytest_"):
483
+ opts = _get_legacy_hook_marks( # type: ignore[assignment]
484
+ method,
485
+ "spec",
486
+ ("firstresult", "historic"),
487
+ )
488
+ return opts
489
+
490
+ def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None:
491
+ if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS:
492
+ warnings.warn(
493
+ PytestConfigWarning(
494
+ "{} plugin has been merged into the core, "
495
+ "please remove it from your requirements.".format(
496
+ name.replace("_", "-")
497
+ )
498
+ )
499
+ )
500
+ return None
501
+ plugin_name = super().register(plugin, name)
502
+ if plugin_name is not None:
503
+ self.hook.pytest_plugin_registered.call_historic(
504
+ kwargs=dict(
505
+ plugin=plugin,
506
+ plugin_name=plugin_name,
507
+ manager=self,
508
+ )
509
+ )
510
+
511
+ if isinstance(plugin, types.ModuleType):
512
+ self.consider_module(plugin)
513
+ return plugin_name
514
+
515
+ def getplugin(self, name: str):
516
+ # Support deprecated naming because plugins (xdist e.g.) use it.
517
+ plugin: _PluggyPlugin | None = self.get_plugin(name)
518
+ return plugin
519
+
520
+ def hasplugin(self, name: str) -> bool:
521
+ """Return whether a plugin with the given name is registered."""
522
+ return bool(self.get_plugin(name))
523
+
524
+ def pytest_configure(self, config: Config) -> None:
525
+ """:meta private:"""
526
+ # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
527
+ # we should remove tryfirst/trylast as markers.
528
+ config.addinivalue_line(
529
+ "markers",
530
+ "tryfirst: mark a hook implementation function such that the "
531
+ "plugin machinery will try to call it first/as early as possible. "
532
+ "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.",
533
+ )
534
+ config.addinivalue_line(
535
+ "markers",
536
+ "trylast: mark a hook implementation function such that the "
537
+ "plugin machinery will try to call it last/as late as possible. "
538
+ "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.",
539
+ )
540
+ self._configured = True
541
+
542
+ #
543
+ # Internal API for local conftest plugin handling.
544
+ #
545
+ def _set_initial_conftests(
546
+ self,
547
+ args: Sequence[str | pathlib.Path],
548
+ pyargs: bool,
549
+ noconftest: bool,
550
+ rootpath: pathlib.Path,
551
+ confcutdir: pathlib.Path | None,
552
+ invocation_dir: pathlib.Path,
553
+ importmode: ImportMode | str,
554
+ *,
555
+ consider_namespace_packages: bool,
556
+ ) -> None:
557
+ """Load initial conftest files given a preparsed "namespace".
558
+
559
+ As conftest files may add their own command line options which have
560
+ arguments ('--my-opt somepath') we might get some false positives.
561
+ All builtin and 3rd party plugins will have been loaded, however, so
562
+ common options will not confuse our logic here.
563
+ """
564
+ self._confcutdir = (
565
+ absolutepath(invocation_dir / confcutdir) if confcutdir else None
566
+ )
567
+ self._noconftest = noconftest
568
+ self._using_pyargs = pyargs
569
+ foundanchor = False
570
+ for initial_path in args:
571
+ path = str(initial_path)
572
+ # remove node-id syntax
573
+ i = path.find("::")
574
+ if i != -1:
575
+ path = path[:i]
576
+ anchor = absolutepath(invocation_dir / path)
577
+
578
+ # Ensure we do not break if what appears to be an anchor
579
+ # is in fact a very long option (#10169, #11394).
580
+ if safe_exists(anchor):
581
+ self._try_load_conftest(
582
+ anchor,
583
+ importmode,
584
+ rootpath,
585
+ consider_namespace_packages=consider_namespace_packages,
586
+ )
587
+ foundanchor = True
588
+ if not foundanchor:
589
+ self._try_load_conftest(
590
+ invocation_dir,
591
+ importmode,
592
+ rootpath,
593
+ consider_namespace_packages=consider_namespace_packages,
594
+ )
595
+
596
+ def _is_in_confcutdir(self, path: pathlib.Path) -> bool:
597
+ """Whether to consider the given path to load conftests from."""
598
+ if self._confcutdir is None:
599
+ return True
600
+ # The semantics here are literally:
601
+ # Do not load a conftest if it is found upwards from confcut dir.
602
+ # But this is *not* the same as:
603
+ # Load only conftests from confcutdir or below.
604
+ # At first glance they might seem the same thing, however we do support use cases where
605
+ # we want to load conftests that are not found in confcutdir or below, but are found
606
+ # in completely different directory hierarchies like packages installed
607
+ # in out-of-source trees.
608
+ # (see #9767 for a regression where the logic was inverted).
609
+ return path not in self._confcutdir.parents
610
+
611
+ def _try_load_conftest(
612
+ self,
613
+ anchor: pathlib.Path,
614
+ importmode: str | ImportMode,
615
+ rootpath: pathlib.Path,
616
+ *,
617
+ consider_namespace_packages: bool,
618
+ ) -> None:
619
+ self._loadconftestmodules(
620
+ anchor,
621
+ importmode,
622
+ rootpath,
623
+ consider_namespace_packages=consider_namespace_packages,
624
+ )
625
+ # let's also consider test* subdirs
626
+ if anchor.is_dir():
627
+ for x in anchor.glob("test*"):
628
+ if x.is_dir():
629
+ self._loadconftestmodules(
630
+ x,
631
+ importmode,
632
+ rootpath,
633
+ consider_namespace_packages=consider_namespace_packages,
634
+ )
635
+
636
+ def _loadconftestmodules(
637
+ self,
638
+ path: pathlib.Path,
639
+ importmode: str | ImportMode,
640
+ rootpath: pathlib.Path,
641
+ *,
642
+ consider_namespace_packages: bool,
643
+ ) -> None:
644
+ if self._noconftest:
645
+ return
646
+
647
+ directory = self._get_directory(path)
648
+
649
+ # Optimization: avoid repeated searches in the same directory.
650
+ # Assumes always called with same importmode and rootpath.
651
+ if directory in self._dirpath2confmods:
652
+ return
653
+
654
+ clist = []
655
+ for parent in reversed((directory, *directory.parents)):
656
+ if self._is_in_confcutdir(parent):
657
+ conftestpath = parent / "conftest.py"
658
+ if conftestpath.is_file():
659
+ mod = self._importconftest(
660
+ conftestpath,
661
+ importmode,
662
+ rootpath,
663
+ consider_namespace_packages=consider_namespace_packages,
664
+ )
665
+ clist.append(mod)
666
+ self._dirpath2confmods[directory] = clist
667
+
668
+ def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]:
669
+ directory = self._get_directory(path)
670
+ return self._dirpath2confmods.get(directory, ())
671
+
672
+ def _rget_with_confmod(
673
+ self,
674
+ name: str,
675
+ path: pathlib.Path,
676
+ ) -> tuple[types.ModuleType, Any]:
677
+ modules = self._getconftestmodules(path)
678
+ for mod in reversed(modules):
679
+ try:
680
+ return mod, getattr(mod, name)
681
+ except AttributeError:
682
+ continue
683
+ raise KeyError(name)
684
+
685
+ def _importconftest(
686
+ self,
687
+ conftestpath: pathlib.Path,
688
+ importmode: str | ImportMode,
689
+ rootpath: pathlib.Path,
690
+ *,
691
+ consider_namespace_packages: bool,
692
+ ) -> types.ModuleType:
693
+ conftestpath_plugin_name = str(conftestpath)
694
+ existing = self.get_plugin(conftestpath_plugin_name)
695
+ if existing is not None:
696
+ return cast(types.ModuleType, existing)
697
+
698
+ # conftest.py files there are not in a Python package all have module
699
+ # name "conftest", and thus conflict with each other. Clear the existing
700
+ # before loading the new one, otherwise the existing one will be
701
+ # returned from the module cache.
702
+ pkgpath = resolve_package_path(conftestpath)
703
+ if pkgpath is None:
704
+ try:
705
+ del sys.modules[conftestpath.stem]
706
+ except KeyError:
707
+ pass
708
+
709
+ try:
710
+ mod = import_path(
711
+ conftestpath,
712
+ mode=importmode,
713
+ root=rootpath,
714
+ consider_namespace_packages=consider_namespace_packages,
715
+ )
716
+ except Exception as e:
717
+ assert e.__traceback__ is not None
718
+ raise ConftestImportFailure(conftestpath, cause=e) from e
719
+
720
+ self._check_non_top_pytest_plugins(mod, conftestpath)
721
+
722
+ self._conftest_plugins.add(mod)
723
+ dirpath = conftestpath.parent
724
+ if dirpath in self._dirpath2confmods:
725
+ for path, mods in self._dirpath2confmods.items():
726
+ if dirpath in path.parents or path == dirpath:
727
+ if mod in mods:
728
+ raise AssertionError(
729
+ f"While trying to load conftest path {conftestpath!s}, "
730
+ f"found that the module {mod} is already loaded with path {mod.__file__}. "
731
+ "This is not supposed to happen. Please report this issue to pytest."
732
+ )
733
+ mods.append(mod)
734
+ self.trace(f"loading conftestmodule {mod!r}")
735
+ self.consider_conftest(mod, registration_name=conftestpath_plugin_name)
736
+ return mod
737
+
738
+ def _check_non_top_pytest_plugins(
739
+ self,
740
+ mod: types.ModuleType,
741
+ conftestpath: pathlib.Path,
742
+ ) -> None:
743
+ if (
744
+ hasattr(mod, "pytest_plugins")
745
+ and self._configured
746
+ and not self._using_pyargs
747
+ ):
748
+ msg = (
749
+ "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n"
750
+ "It affects the entire test suite instead of just below the conftest as expected.\n"
751
+ " {}\n"
752
+ "Please move it to a top level conftest file at the rootdir:\n"
753
+ " {}\n"
754
+ "For more information, visit:\n"
755
+ " https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
756
+ )
757
+ fail(msg.format(conftestpath, self._confcutdir), pytrace=False)
758
+
759
+ #
760
+ # API for bootstrapping plugin loading
761
+ #
762
+ #
763
+
764
+ def consider_preparse(
765
+ self, args: Sequence[str], *, exclude_only: bool = False
766
+ ) -> None:
767
+ """:meta private:"""
768
+ i = 0
769
+ n = len(args)
770
+ while i < n:
771
+ opt = args[i]
772
+ i += 1
773
+ if isinstance(opt, str):
774
+ if opt == "-p":
775
+ try:
776
+ parg = args[i]
777
+ except IndexError:
778
+ return
779
+ i += 1
780
+ elif opt.startswith("-p"):
781
+ parg = opt[2:]
782
+ else:
783
+ continue
784
+ parg = parg.strip()
785
+ if exclude_only and not parg.startswith("no:"):
786
+ continue
787
+ self.consider_pluginarg(parg)
788
+
789
+ def consider_pluginarg(self, arg: str) -> None:
790
+ """:meta private:"""
791
+ if arg.startswith("no:"):
792
+ name = arg[3:]
793
+ if name in essential_plugins:
794
+ raise UsageError(f"plugin {name} cannot be disabled")
795
+
796
+ # PR #4304: remove stepwise if cacheprovider is blocked.
797
+ if name == "cacheprovider":
798
+ self.set_blocked("stepwise")
799
+ self.set_blocked("pytest_stepwise")
800
+
801
+ self.set_blocked(name)
802
+ if not name.startswith("pytest_"):
803
+ self.set_blocked("pytest_" + name)
804
+ else:
805
+ name = arg
806
+ # Unblock the plugin.
807
+ self.unblock(name)
808
+ if not name.startswith("pytest_"):
809
+ self.unblock("pytest_" + name)
810
+ self.import_plugin(arg, consider_entry_points=True)
811
+
812
+ def consider_conftest(
813
+ self, conftestmodule: types.ModuleType, registration_name: str
814
+ ) -> None:
815
+ """:meta private:"""
816
+ self.register(conftestmodule, name=registration_name)
817
+
818
+ def consider_env(self) -> None:
819
+ """:meta private:"""
820
+ self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
821
+
822
+ def consider_module(self, mod: types.ModuleType) -> None:
823
+ """:meta private:"""
824
+ self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
825
+
826
+ def _import_plugin_specs(
827
+ self, spec: None | types.ModuleType | str | Sequence[str]
828
+ ) -> None:
829
+ plugins = _get_plugin_specs_as_list(spec)
830
+ for import_spec in plugins:
831
+ self.import_plugin(import_spec)
832
+
833
+ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None:
834
+ """Import a plugin with ``modname``.
835
+
836
+ If ``consider_entry_points`` is True, entry point names are also
837
+ considered to find a plugin.
838
+ """
839
+ # Most often modname refers to builtin modules, e.g. "pytester",
840
+ # "terminal" or "capture". Those plugins are registered under their
841
+ # basename for historic purposes but must be imported with the
842
+ # _pytest prefix.
843
+ assert isinstance(
844
+ modname, str
845
+ ), f"module name as text required, got {modname!r}"
846
+ if self.is_blocked(modname) or self.get_plugin(modname) is not None:
847
+ return
848
+
849
+ importspec = "_pytest." + modname if modname in builtin_plugins else modname
850
+ self.rewrite_hook.mark_rewrite(importspec)
851
+
852
+ if consider_entry_points:
853
+ loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
854
+ if loaded:
855
+ return
856
+
857
+ try:
858
+ __import__(importspec)
859
+ except ImportError as e:
860
+ raise ImportError(
861
+ f'Error importing plugin "{modname}": {e.args[0]}'
862
+ ).with_traceback(e.__traceback__) from e
863
+
864
+ except Skipped as e:
865
+ self.skipped_plugins.append((modname, e.msg or ""))
866
+ else:
867
+ mod = sys.modules[importspec]
868
+ self.register(mod, modname)
869
+
870
+
871
+ def _get_plugin_specs_as_list(
872
+ specs: None | types.ModuleType | str | Sequence[str],
873
+ ) -> list[str]:
874
+ """Parse a plugins specification into a list of plugin names."""
875
+ # None means empty.
876
+ if specs is None:
877
+ return []
878
+ # Workaround for #3899 - a submodule which happens to be called "pytest_plugins".
879
+ if isinstance(specs, types.ModuleType):
880
+ return []
881
+ # Comma-separated list.
882
+ if isinstance(specs, str):
883
+ return specs.split(",") if specs else []
884
+ # Direct specification.
885
+ if isinstance(specs, collections.abc.Sequence):
886
+ return list(specs)
887
+ raise UsageError(
888
+ f"Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: {specs!r}"
889
+ )
890
+
891
+
892
+ class Notset:
893
+ def __repr__(self):
894
+ return "<NOTSET>"
895
+
896
+
897
+ notset = Notset()
898
+
899
+
900
+ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
901
+ """Given an iterable of file names in a source distribution, return the "names" that should
902
+ be marked for assertion rewrite.
903
+
904
+ For example the package "pytest_mock/__init__.py" should be added as "pytest_mock" in
905
+ the assertion rewrite mechanism.
906
+
907
+ This function has to deal with dist-info based distributions and egg based distributions
908
+ (which are still very much in use for "editable" installs).
909
+
910
+ Here are the file names as seen in a dist-info based distribution:
911
+
912
+ pytest_mock/__init__.py
913
+ pytest_mock/_version.py
914
+ pytest_mock/plugin.py
915
+ pytest_mock.egg-info/PKG-INFO
916
+
917
+ Here are the file names as seen in an egg based distribution:
918
+
919
+ src/pytest_mock/__init__.py
920
+ src/pytest_mock/_version.py
921
+ src/pytest_mock/plugin.py
922
+ src/pytest_mock.egg-info/PKG-INFO
923
+ LICENSE
924
+ setup.py
925
+
926
+ We have to take in account those two distribution flavors in order to determine which
927
+ names should be considered for assertion rewriting.
928
+
929
+ More information:
930
+ https://github.com/pytest-dev/pytest-mock/issues/167
931
+ """
932
+ package_files = list(package_files)
933
+ seen_some = False
934
+ for fn in package_files:
935
+ is_simple_module = "/" not in fn and fn.endswith(".py")
936
+ is_package = fn.count("/") == 1 and fn.endswith("__init__.py")
937
+ if is_simple_module:
938
+ module_name, _ = os.path.splitext(fn)
939
+ # we ignore "setup.py" at the root of the distribution
940
+ # as well as editable installation finder modules made by setuptools
941
+ if module_name != "setup" and not module_name.startswith("__editable__"):
942
+ seen_some = True
943
+ yield module_name
944
+ elif is_package:
945
+ package_name = os.path.dirname(fn)
946
+ seen_some = True
947
+ yield package_name
948
+
949
+ if not seen_some:
950
+ # At this point we did not find any packages or modules suitable for assertion
951
+ # rewriting, so we try again by stripping the first path component (to account for
952
+ # "src" based source trees for example).
953
+ # This approach lets us have the common case continue to be fast, as egg-distributions
954
+ # are rarer.
955
+ new_package_files = []
956
+ for fn in package_files:
957
+ parts = fn.split("/")
958
+ new_fn = "/".join(parts[1:])
959
+ if new_fn:
960
+ new_package_files.append(new_fn)
961
+ if new_package_files:
962
+ yield from _iter_rewritable_modules(new_package_files)
963
+
964
+
965
+ @final
966
+ class Config:
967
+ """Access to configuration values, pluginmanager and plugin hooks.
968
+
969
+ :param PytestPluginManager pluginmanager:
970
+ A pytest PluginManager.
971
+
972
+ :param InvocationParams invocation_params:
973
+ Object containing parameters regarding the :func:`pytest.main`
974
+ invocation.
975
+ """
976
+
977
+ @final
978
+ @dataclasses.dataclass(frozen=True)
979
+ class InvocationParams:
980
+ """Holds parameters passed during :func:`pytest.main`.
981
+
982
+ The object attributes are read-only.
983
+
984
+ .. versionadded:: 5.1
985
+
986
+ .. note::
987
+
988
+ Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts``
989
+ ini option are handled by pytest, not being included in the ``args`` attribute.
990
+
991
+ Plugins accessing ``InvocationParams`` must be aware of that.
992
+ """
993
+
994
+ args: tuple[str, ...]
995
+ """The command-line arguments as passed to :func:`pytest.main`."""
996
+ plugins: Sequence[str | _PluggyPlugin] | None
997
+ """Extra plugins, might be `None`."""
998
+ dir: pathlib.Path
999
+ """The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path"""
1000
+
1001
+ def __init__(
1002
+ self,
1003
+ *,
1004
+ args: Iterable[str],
1005
+ plugins: Sequence[str | _PluggyPlugin] | None,
1006
+ dir: pathlib.Path,
1007
+ ) -> None:
1008
+ object.__setattr__(self, "args", tuple(args))
1009
+ object.__setattr__(self, "plugins", plugins)
1010
+ object.__setattr__(self, "dir", dir)
1011
+
1012
+ class ArgsSource(enum.Enum):
1013
+ """Indicates the source of the test arguments.
1014
+
1015
+ .. versionadded:: 7.2
1016
+ """
1017
+
1018
+ #: Command line arguments.
1019
+ ARGS = enum.auto()
1020
+ #: Invocation directory.
1021
+ INVOCATION_DIR = enum.auto()
1022
+ INCOVATION_DIR = INVOCATION_DIR # backwards compatibility alias
1023
+ #: 'testpaths' configuration value.
1024
+ TESTPATHS = enum.auto()
1025
+
1026
+ # Set by cacheprovider plugin.
1027
+ cache: Cache
1028
+
1029
+ def __init__(
1030
+ self,
1031
+ pluginmanager: PytestPluginManager,
1032
+ *,
1033
+ invocation_params: InvocationParams | None = None,
1034
+ ) -> None:
1035
+ from .argparsing import FILE_OR_DIR
1036
+ from .argparsing import Parser
1037
+
1038
+ if invocation_params is None:
1039
+ invocation_params = self.InvocationParams(
1040
+ args=(), plugins=None, dir=pathlib.Path.cwd()
1041
+ )
1042
+
1043
+ self.option = argparse.Namespace()
1044
+ """Access to command line option as attributes.
1045
+
1046
+ :type: argparse.Namespace
1047
+ """
1048
+
1049
+ self.invocation_params = invocation_params
1050
+ """The parameters with which pytest was invoked.
1051
+
1052
+ :type: InvocationParams
1053
+ """
1054
+
1055
+ _a = FILE_OR_DIR
1056
+ self._parser = Parser(
1057
+ usage=f"%(prog)s [options] [{_a}] [{_a}] [...]",
1058
+ processopt=self._processopt,
1059
+ _ispytest=True,
1060
+ )
1061
+ self.pluginmanager = pluginmanager
1062
+ """The plugin manager handles plugin registration and hook invocation.
1063
+
1064
+ :type: PytestPluginManager
1065
+ """
1066
+
1067
+ self.stash = Stash()
1068
+ """A place where plugins can store information on the config for their
1069
+ own use.
1070
+
1071
+ :type: Stash
1072
+ """
1073
+ # Deprecated alias. Was never public. Can be removed in a few releases.
1074
+ self._store = self.stash
1075
+
1076
+ self.trace = self.pluginmanager.trace.root.get("config")
1077
+ self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment]
1078
+ self._inicache: dict[str, Any] = {}
1079
+ self._override_ini: Sequence[str] = ()
1080
+ self._opt2dest: dict[str, str] = {}
1081
+ self._cleanup: list[Callable[[], None]] = []
1082
+ self.pluginmanager.register(self, "pytestconfig")
1083
+ self._configured = False
1084
+ self.hook.pytest_addoption.call_historic(
1085
+ kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
1086
+ )
1087
+ self.args_source = Config.ArgsSource.ARGS
1088
+ self.args: list[str] = []
1089
+
1090
+ @property
1091
+ def rootpath(self) -> pathlib.Path:
1092
+ """The path to the :ref:`rootdir <rootdir>`.
1093
+
1094
+ :type: pathlib.Path
1095
+
1096
+ .. versionadded:: 6.1
1097
+ """
1098
+ return self._rootpath
1099
+
1100
+ @property
1101
+ def inipath(self) -> pathlib.Path | None:
1102
+ """The path to the :ref:`configfile <configfiles>`.
1103
+
1104
+ .. versionadded:: 6.1
1105
+ """
1106
+ return self._inipath
1107
+
1108
+ def add_cleanup(self, func: Callable[[], None]) -> None:
1109
+ """Add a function to be called when the config object gets out of
1110
+ use (usually coinciding with pytest_unconfigure)."""
1111
+ self._cleanup.append(func)
1112
+
1113
+ def _do_configure(self) -> None:
1114
+ assert not self._configured
1115
+ self._configured = True
1116
+ with warnings.catch_warnings():
1117
+ warnings.simplefilter("default")
1118
+ self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
1119
+
1120
+ def _ensure_unconfigure(self) -> None:
1121
+ if self._configured:
1122
+ self._configured = False
1123
+ self.hook.pytest_unconfigure(config=self)
1124
+ self.hook.pytest_configure._call_history = []
1125
+ while self._cleanup:
1126
+ fin = self._cleanup.pop()
1127
+ fin()
1128
+
1129
+ def get_terminal_writer(self) -> TerminalWriter:
1130
+ terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin(
1131
+ "terminalreporter"
1132
+ )
1133
+ assert terminalreporter is not None
1134
+ return terminalreporter._tw
1135
+
1136
+ def pytest_cmdline_parse(
1137
+ self, pluginmanager: PytestPluginManager, args: list[str]
1138
+ ) -> Config:
1139
+ try:
1140
+ self.parse(args)
1141
+ except UsageError:
1142
+ # Handle --version and --help here in a minimal fashion.
1143
+ # This gets done via helpconfig normally, but its
1144
+ # pytest_cmdline_main is not called in case of errors.
1145
+ if getattr(self.option, "version", False) or "--version" in args:
1146
+ from _pytest.helpconfig import showversion
1147
+
1148
+ showversion(self)
1149
+ elif (
1150
+ getattr(self.option, "help", False) or "--help" in args or "-h" in args
1151
+ ):
1152
+ self._parser._getparser().print_help()
1153
+ sys.stdout.write(
1154
+ "\nNOTE: displaying only minimal help due to UsageError.\n\n"
1155
+ )
1156
+
1157
+ raise
1158
+
1159
+ return self
1160
+
1161
+ def notify_exception(
1162
+ self,
1163
+ excinfo: ExceptionInfo[BaseException],
1164
+ option: argparse.Namespace | None = None,
1165
+ ) -> None:
1166
+ if option and getattr(option, "fulltrace", False):
1167
+ style: TracebackStyle = "long"
1168
+ else:
1169
+ style = "native"
1170
+ excrepr = excinfo.getrepr(
1171
+ funcargs=True, showlocals=getattr(option, "showlocals", False), style=style
1172
+ )
1173
+ res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
1174
+ if not any(res):
1175
+ for line in str(excrepr).split("\n"):
1176
+ sys.stderr.write(f"INTERNALERROR> {line}\n")
1177
+ sys.stderr.flush()
1178
+
1179
+ def cwd_relative_nodeid(self, nodeid: str) -> str:
1180
+ # nodeid's are relative to the rootpath, compute relative to cwd.
1181
+ if self.invocation_params.dir != self.rootpath:
1182
+ base_path_part, *nodeid_part = nodeid.split("::")
1183
+ # Only process path part
1184
+ fullpath = self.rootpath / base_path_part
1185
+ relative_path = bestrelpath(self.invocation_params.dir, fullpath)
1186
+
1187
+ nodeid = "::".join([relative_path, *nodeid_part])
1188
+ return nodeid
1189
+
1190
+ @classmethod
1191
+ def fromdictargs(cls, option_dict, args) -> Config:
1192
+ """Constructor usable for subprocesses."""
1193
+ config = get_config(args)
1194
+ config.option.__dict__.update(option_dict)
1195
+ config.parse(args, addopts=False)
1196
+ for x in config.option.plugins:
1197
+ config.pluginmanager.consider_pluginarg(x)
1198
+ return config
1199
+
1200
+ def _processopt(self, opt: Argument) -> None:
1201
+ for name in opt._short_opts + opt._long_opts:
1202
+ self._opt2dest[name] = opt.dest
1203
+
1204
+ if hasattr(opt, "default"):
1205
+ if not hasattr(self.option, opt.dest):
1206
+ setattr(self.option, opt.dest, opt.default)
1207
+
1208
+ @hookimpl(trylast=True)
1209
+ def pytest_load_initial_conftests(self, early_config: Config) -> None:
1210
+ # We haven't fully parsed the command line arguments yet, so
1211
+ # early_config.args it not set yet. But we need it for
1212
+ # discovering the initial conftests. So "pre-run" the logic here.
1213
+ # It will be done for real in `parse()`.
1214
+ args, args_source = early_config._decide_args(
1215
+ args=early_config.known_args_namespace.file_or_dir,
1216
+ pyargs=early_config.known_args_namespace.pyargs,
1217
+ testpaths=early_config.getini("testpaths"),
1218
+ invocation_dir=early_config.invocation_params.dir,
1219
+ rootpath=early_config.rootpath,
1220
+ warn=False,
1221
+ )
1222
+ self.pluginmanager._set_initial_conftests(
1223
+ args=args,
1224
+ pyargs=early_config.known_args_namespace.pyargs,
1225
+ noconftest=early_config.known_args_namespace.noconftest,
1226
+ rootpath=early_config.rootpath,
1227
+ confcutdir=early_config.known_args_namespace.confcutdir,
1228
+ invocation_dir=early_config.invocation_params.dir,
1229
+ importmode=early_config.known_args_namespace.importmode,
1230
+ consider_namespace_packages=early_config.getini(
1231
+ "consider_namespace_packages"
1232
+ ),
1233
+ )
1234
+
1235
+ def _initini(self, args: Sequence[str]) -> None:
1236
+ ns, unknown_args = self._parser.parse_known_and_unknown_args(
1237
+ args, namespace=copy.copy(self.option)
1238
+ )
1239
+ rootpath, inipath, inicfg = determine_setup(
1240
+ inifile=ns.inifilename,
1241
+ args=ns.file_or_dir + unknown_args,
1242
+ rootdir_cmd_arg=ns.rootdir or None,
1243
+ invocation_dir=self.invocation_params.dir,
1244
+ )
1245
+ self._rootpath = rootpath
1246
+ self._inipath = inipath
1247
+ self.inicfg = inicfg
1248
+ self._parser.extra_info["rootdir"] = str(self.rootpath)
1249
+ self._parser.extra_info["inifile"] = str(self.inipath)
1250
+ self._parser.addini("addopts", "Extra command line options", "args")
1251
+ self._parser.addini("minversion", "Minimally required pytest version")
1252
+ self._parser.addini(
1253
+ "required_plugins",
1254
+ "Plugins that must be present for pytest to run",
1255
+ type="args",
1256
+ default=[],
1257
+ )
1258
+ self._override_ini = ns.override_ini or ()
1259
+
1260
+ def _consider_importhook(self, args: Sequence[str]) -> None:
1261
+ """Install the PEP 302 import hook if using assertion rewriting.
1262
+
1263
+ Needs to parse the --assert=<mode> option from the commandline
1264
+ and find all the installed plugins to mark them for rewriting
1265
+ by the importhook.
1266
+ """
1267
+ ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
1268
+ mode = getattr(ns, "assertmode", "plain")
1269
+ if mode == "rewrite":
1270
+ import _pytest.assertion
1271
+
1272
+ try:
1273
+ hook = _pytest.assertion.install_importhook(self)
1274
+ except SystemError:
1275
+ mode = "plain"
1276
+ else:
1277
+ self._mark_plugins_for_rewrite(hook)
1278
+ self._warn_about_missing_assertion(mode)
1279
+
1280
+ def _mark_plugins_for_rewrite(self, hook) -> None:
1281
+ """Given an importhook, mark for rewrite any top-level
1282
+ modules or packages in the distribution package for
1283
+ all pytest plugins."""
1284
+ self.pluginmanager.rewrite_hook = hook
1285
+
1286
+ if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
1287
+ # We don't autoload from distribution package entry points,
1288
+ # no need to continue.
1289
+ return
1290
+
1291
+ package_files = (
1292
+ str(file)
1293
+ for dist in importlib.metadata.distributions()
1294
+ if any(ep.group == "pytest11" for ep in dist.entry_points)
1295
+ for file in dist.files or []
1296
+ )
1297
+
1298
+ for name in _iter_rewritable_modules(package_files):
1299
+ hook.mark_rewrite(name)
1300
+
1301
+ def _validate_args(self, args: list[str], via: str) -> list[str]:
1302
+ """Validate known args."""
1303
+ self._parser._config_source_hint = via # type: ignore
1304
+ try:
1305
+ self._parser.parse_known_and_unknown_args(
1306
+ args, namespace=copy.copy(self.option)
1307
+ )
1308
+ finally:
1309
+ del self._parser._config_source_hint # type: ignore
1310
+
1311
+ return args
1312
+
1313
+ def _decide_args(
1314
+ self,
1315
+ *,
1316
+ args: list[str],
1317
+ pyargs: bool,
1318
+ testpaths: list[str],
1319
+ invocation_dir: pathlib.Path,
1320
+ rootpath: pathlib.Path,
1321
+ warn: bool,
1322
+ ) -> tuple[list[str], ArgsSource]:
1323
+ """Decide the args (initial paths/nodeids) to use given the relevant inputs.
1324
+
1325
+ :param warn: Whether can issue warnings.
1326
+
1327
+ :returns: The args and the args source. Guaranteed to be non-empty.
1328
+ """
1329
+ if args:
1330
+ source = Config.ArgsSource.ARGS
1331
+ result = args
1332
+ else:
1333
+ if invocation_dir == rootpath:
1334
+ source = Config.ArgsSource.TESTPATHS
1335
+ if pyargs:
1336
+ result = testpaths
1337
+ else:
1338
+ result = []
1339
+ for path in testpaths:
1340
+ result.extend(sorted(glob.iglob(path, recursive=True)))
1341
+ if testpaths and not result:
1342
+ if warn:
1343
+ warning_text = (
1344
+ "No files were found in testpaths; "
1345
+ "consider removing or adjusting your testpaths configuration. "
1346
+ "Searching recursively from the current directory instead."
1347
+ )
1348
+ self.issue_config_time_warning(
1349
+ PytestConfigWarning(warning_text), stacklevel=3
1350
+ )
1351
+ else:
1352
+ result = []
1353
+ if not result:
1354
+ source = Config.ArgsSource.INVOCATION_DIR
1355
+ result = [str(invocation_dir)]
1356
+ return result, source
1357
+
1358
+ def _preparse(self, args: list[str], addopts: bool = True) -> None:
1359
+ if addopts:
1360
+ env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
1361
+ if len(env_addopts):
1362
+ args[:] = (
1363
+ self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
1364
+ + args
1365
+ )
1366
+ self._initini(args)
1367
+ if addopts:
1368
+ args[:] = (
1369
+ self._validate_args(self.getini("addopts"), "via addopts config") + args
1370
+ )
1371
+
1372
+ self.known_args_namespace = self._parser.parse_known_args(
1373
+ args, namespace=copy.copy(self.option)
1374
+ )
1375
+ self._checkversion()
1376
+ self._consider_importhook(args)
1377
+ self.pluginmanager.consider_preparse(args, exclude_only=False)
1378
+ if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
1379
+ # Don't autoload from distribution package entry point. Only
1380
+ # explicitly specified plugins are going to be loaded.
1381
+ self.pluginmanager.load_setuptools_entrypoints("pytest11")
1382
+ self.pluginmanager.consider_env()
1383
+
1384
+ self.known_args_namespace = self._parser.parse_known_args(
1385
+ args, namespace=copy.copy(self.known_args_namespace)
1386
+ )
1387
+
1388
+ self._validate_plugins()
1389
+ self._warn_about_skipped_plugins()
1390
+
1391
+ if self.known_args_namespace.confcutdir is None:
1392
+ if self.inipath is not None:
1393
+ confcutdir = str(self.inipath.parent)
1394
+ else:
1395
+ confcutdir = str(self.rootpath)
1396
+ self.known_args_namespace.confcutdir = confcutdir
1397
+ try:
1398
+ self.hook.pytest_load_initial_conftests(
1399
+ early_config=self, args=args, parser=self._parser
1400
+ )
1401
+ except ConftestImportFailure as e:
1402
+ if self.known_args_namespace.help or self.known_args_namespace.version:
1403
+ # we don't want to prevent --help/--version to work
1404
+ # so just let is pass and print a warning at the end
1405
+ self.issue_config_time_warning(
1406
+ PytestConfigWarning(f"could not load initial conftests: {e.path}"),
1407
+ stacklevel=2,
1408
+ )
1409
+ else:
1410
+ raise
1411
+
1412
+ @hookimpl(wrapper=True)
1413
+ def pytest_collection(self) -> Generator[None, object, object]:
1414
+ # Validate invalid ini keys after collection is done so we take in account
1415
+ # options added by late-loading conftest files.
1416
+ try:
1417
+ return (yield)
1418
+ finally:
1419
+ self._validate_config_options()
1420
+
1421
+ def _checkversion(self) -> None:
1422
+ import pytest
1423
+
1424
+ minver = self.inicfg.get("minversion", None)
1425
+ if minver:
1426
+ # Imported lazily to improve start-up time.
1427
+ from packaging.version import Version
1428
+
1429
+ if not isinstance(minver, str):
1430
+ raise pytest.UsageError(
1431
+ f"{self.inipath}: 'minversion' must be a single value"
1432
+ )
1433
+
1434
+ if Version(minver) > Version(pytest.__version__):
1435
+ raise pytest.UsageError(
1436
+ f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'"
1437
+ )
1438
+
1439
+ def _validate_config_options(self) -> None:
1440
+ for key in sorted(self._get_unknown_ini_keys()):
1441
+ self._warn_or_fail_if_strict(f"Unknown config option: {key}\n")
1442
+
1443
+ def _validate_plugins(self) -> None:
1444
+ required_plugins = sorted(self.getini("required_plugins"))
1445
+ if not required_plugins:
1446
+ return
1447
+
1448
+ # Imported lazily to improve start-up time.
1449
+ from packaging.requirements import InvalidRequirement
1450
+ from packaging.requirements import Requirement
1451
+ from packaging.version import Version
1452
+
1453
+ plugin_info = self.pluginmanager.list_plugin_distinfo()
1454
+ plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info}
1455
+
1456
+ missing_plugins = []
1457
+ for required_plugin in required_plugins:
1458
+ try:
1459
+ req = Requirement(required_plugin)
1460
+ except InvalidRequirement:
1461
+ missing_plugins.append(required_plugin)
1462
+ continue
1463
+
1464
+ if req.name not in plugin_dist_info:
1465
+ missing_plugins.append(required_plugin)
1466
+ elif not req.specifier.contains(
1467
+ Version(plugin_dist_info[req.name]), prereleases=True
1468
+ ):
1469
+ missing_plugins.append(required_plugin)
1470
+
1471
+ if missing_plugins:
1472
+ raise UsageError(
1473
+ "Missing required plugins: {}".format(", ".join(missing_plugins)),
1474
+ )
1475
+
1476
+ def _warn_or_fail_if_strict(self, message: str) -> None:
1477
+ if self.known_args_namespace.strict_config:
1478
+ raise UsageError(message)
1479
+
1480
+ self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3)
1481
+
1482
+ def _get_unknown_ini_keys(self) -> list[str]:
1483
+ parser_inicfg = self._parser._inidict
1484
+ return [name for name in self.inicfg if name not in parser_inicfg]
1485
+
1486
+ def parse(self, args: list[str], addopts: bool = True) -> None:
1487
+ # Parse given cmdline arguments into this config object.
1488
+ assert (
1489
+ self.args == []
1490
+ ), "can only parse cmdline args at most once per Config object"
1491
+ self.hook.pytest_addhooks.call_historic(
1492
+ kwargs=dict(pluginmanager=self.pluginmanager)
1493
+ )
1494
+ self._preparse(args, addopts=addopts)
1495
+ self._parser.after_preparse = True # type: ignore
1496
+ try:
1497
+ args = self._parser.parse_setoption(
1498
+ args, self.option, namespace=self.option
1499
+ )
1500
+ self.args, self.args_source = self._decide_args(
1501
+ args=args,
1502
+ pyargs=self.known_args_namespace.pyargs,
1503
+ testpaths=self.getini("testpaths"),
1504
+ invocation_dir=self.invocation_params.dir,
1505
+ rootpath=self.rootpath,
1506
+ warn=True,
1507
+ )
1508
+ except PrintHelp:
1509
+ pass
1510
+
1511
+ def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None:
1512
+ """Issue and handle a warning during the "configure" stage.
1513
+
1514
+ During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item``
1515
+ function because it is not possible to have hook wrappers around ``pytest_configure``.
1516
+
1517
+ This function is mainly intended for plugins that need to issue warnings during
1518
+ ``pytest_configure`` (or similar stages).
1519
+
1520
+ :param warning: The warning instance.
1521
+ :param stacklevel: stacklevel forwarded to warnings.warn.
1522
+ """
1523
+ if self.pluginmanager.is_blocked("warnings"):
1524
+ return
1525
+
1526
+ cmdline_filters = self.known_args_namespace.pythonwarnings or []
1527
+ config_filters = self.getini("filterwarnings")
1528
+
1529
+ with warnings.catch_warnings(record=True) as records:
1530
+ warnings.simplefilter("always", type(warning))
1531
+ apply_warning_filters(config_filters, cmdline_filters)
1532
+ warnings.warn(warning, stacklevel=stacklevel)
1533
+
1534
+ if records:
1535
+ frame = sys._getframe(stacklevel - 1)
1536
+ location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name
1537
+ self.hook.pytest_warning_recorded.call_historic(
1538
+ kwargs=dict(
1539
+ warning_message=records[0],
1540
+ when="config",
1541
+ nodeid="",
1542
+ location=location,
1543
+ )
1544
+ )
1545
+
1546
+ def addinivalue_line(self, name: str, line: str) -> None:
1547
+ """Add a line to an ini-file option. The option must have been
1548
+ declared but might not yet be set in which case the line becomes
1549
+ the first line in its value."""
1550
+ x = self.getini(name)
1551
+ assert isinstance(x, list)
1552
+ x.append(line) # modifies the cached list inline
1553
+
1554
+ def getini(self, name: str):
1555
+ """Return configuration value from an :ref:`ini file <configfiles>`.
1556
+
1557
+ If a configuration value is not defined in an
1558
+ :ref:`ini file <configfiles>`, then the ``default`` value provided while
1559
+ registering the configuration through
1560
+ :func:`parser.addini <pytest.Parser.addini>` will be returned.
1561
+ Please note that you can even provide ``None`` as a valid
1562
+ default value.
1563
+
1564
+ If ``default`` is not provided while registering using
1565
+ :func:`parser.addini <pytest.Parser.addini>`, then a default value
1566
+ based on the ``type`` parameter passed to
1567
+ :func:`parser.addini <pytest.Parser.addini>` will be returned.
1568
+ The default values based on ``type`` are:
1569
+ ``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]``
1570
+ ``bool`` : ``False``
1571
+ ``string`` : empty string ``""``
1572
+
1573
+ If neither the ``default`` nor the ``type`` parameter is passed
1574
+ while registering the configuration through
1575
+ :func:`parser.addini <pytest.Parser.addini>`, then the configuration
1576
+ is treated as a string and a default empty string '' is returned.
1577
+
1578
+ If the specified name hasn't been registered through a prior
1579
+ :func:`parser.addini <pytest.Parser.addini>` call (usually from a
1580
+ plugin), a ValueError is raised.
1581
+ """
1582
+ try:
1583
+ return self._inicache[name]
1584
+ except KeyError:
1585
+ self._inicache[name] = val = self._getini(name)
1586
+ return val
1587
+
1588
+ # Meant for easy monkeypatching by legacypath plugin.
1589
+ # Can be inlined back (with no cover removed) once legacypath is gone.
1590
+ def _getini_unknown_type(self, name: str, type: str, value: str | list[str]):
1591
+ msg = f"unknown configuration type: {type}"
1592
+ raise ValueError(msg, value) # pragma: no cover
1593
+
1594
+ def _getini(self, name: str):
1595
+ try:
1596
+ description, type, default = self._parser._inidict[name]
1597
+ except KeyError as e:
1598
+ raise ValueError(f"unknown configuration value: {name!r}") from e
1599
+ override_value = self._get_override_ini_value(name)
1600
+ if override_value is None:
1601
+ try:
1602
+ value = self.inicfg[name]
1603
+ except KeyError:
1604
+ return default
1605
+ else:
1606
+ value = override_value
1607
+ # Coerce the values based on types.
1608
+ #
1609
+ # Note: some coercions are only required if we are reading from .ini files, because
1610
+ # the file format doesn't contain type information, but when reading from toml we will
1611
+ # get either str or list of str values (see _parse_ini_config_from_pyproject_toml).
1612
+ # For example:
1613
+ #
1614
+ # ini:
1615
+ # a_line_list = "tests acceptance"
1616
+ # in this case, we need to split the string to obtain a list of strings.
1617
+ #
1618
+ # toml:
1619
+ # a_line_list = ["tests", "acceptance"]
1620
+ # in this case, we already have a list ready to use.
1621
+ #
1622
+ if type == "paths":
1623
+ dp = (
1624
+ self.inipath.parent
1625
+ if self.inipath is not None
1626
+ else self.invocation_params.dir
1627
+ )
1628
+ input_values = shlex.split(value) if isinstance(value, str) else value
1629
+ return [dp / x for x in input_values]
1630
+ elif type == "args":
1631
+ return shlex.split(value) if isinstance(value, str) else value
1632
+ elif type == "linelist":
1633
+ if isinstance(value, str):
1634
+ return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
1635
+ else:
1636
+ return value
1637
+ elif type == "bool":
1638
+ return _strtobool(str(value).strip())
1639
+ elif type == "string":
1640
+ return value
1641
+ elif type is None:
1642
+ return value
1643
+ else:
1644
+ return self._getini_unknown_type(name, type, value)
1645
+
1646
+ def _getconftest_pathlist(
1647
+ self, name: str, path: pathlib.Path
1648
+ ) -> list[pathlib.Path] | None:
1649
+ try:
1650
+ mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
1651
+ except KeyError:
1652
+ return None
1653
+ assert mod.__file__ is not None
1654
+ modpath = pathlib.Path(mod.__file__).parent
1655
+ values: list[pathlib.Path] = []
1656
+ for relroot in relroots:
1657
+ if isinstance(relroot, os.PathLike):
1658
+ relroot = pathlib.Path(relroot)
1659
+ else:
1660
+ relroot = relroot.replace("/", os.sep)
1661
+ relroot = absolutepath(modpath / relroot)
1662
+ values.append(relroot)
1663
+ return values
1664
+
1665
+ def _get_override_ini_value(self, name: str) -> str | None:
1666
+ value = None
1667
+ # override_ini is a list of "ini=value" options.
1668
+ # Always use the last item if multiple values are set for same ini-name,
1669
+ # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2.
1670
+ for ini_config in self._override_ini:
1671
+ try:
1672
+ key, user_ini_value = ini_config.split("=", 1)
1673
+ except ValueError as e:
1674
+ raise UsageError(
1675
+ f"-o/--override-ini expects option=value style (got: {ini_config!r})."
1676
+ ) from e
1677
+ else:
1678
+ if key == name:
1679
+ value = user_ini_value
1680
+ return value
1681
+
1682
+ def getoption(self, name: str, default=notset, skip: bool = False):
1683
+ """Return command line option value.
1684
+
1685
+ :param name: Name of the option. You may also specify
1686
+ the literal ``--OPT`` option instead of the "dest" option name.
1687
+ :param default: Fallback value if no option of that name is **declared** via :hook:`pytest_addoption`.
1688
+ Note this parameter will be ignored when the option is **declared** even if the option's value is ``None``.
1689
+ :param skip: If ``True``, raise :func:`pytest.skip` if option is undeclared or has a ``None`` value.
1690
+ Note that even if ``True``, if a default was specified it will be returned instead of a skip.
1691
+ """
1692
+ name = self._opt2dest.get(name, name)
1693
+ try:
1694
+ val = getattr(self.option, name)
1695
+ if val is None and skip:
1696
+ raise AttributeError(name)
1697
+ return val
1698
+ except AttributeError as e:
1699
+ if default is not notset:
1700
+ return default
1701
+ if skip:
1702
+ import pytest
1703
+
1704
+ pytest.skip(f"no {name!r} option found")
1705
+ raise ValueError(f"no option named {name!r}") from e
1706
+
1707
+ def getvalue(self, name: str, path=None):
1708
+ """Deprecated, use getoption() instead."""
1709
+ return self.getoption(name)
1710
+
1711
+ def getvalueorskip(self, name: str, path=None):
1712
+ """Deprecated, use getoption(skip=True) instead."""
1713
+ return self.getoption(name, skip=True)
1714
+
1715
+ #: Verbosity type for failed assertions (see :confval:`verbosity_assertions`).
1716
+ VERBOSITY_ASSERTIONS: Final = "assertions"
1717
+ #: Verbosity type for test case execution (see :confval:`verbosity_test_cases`).
1718
+ VERBOSITY_TEST_CASES: Final = "test_cases"
1719
+ _VERBOSITY_INI_DEFAULT: Final = "auto"
1720
+
1721
+ def get_verbosity(self, verbosity_type: str | None = None) -> int:
1722
+ r"""Retrieve the verbosity level for a fine-grained verbosity type.
1723
+
1724
+ :param verbosity_type: Verbosity type to get level for. If a level is
1725
+ configured for the given type, that value will be returned. If the
1726
+ given type is not a known verbosity type, the global verbosity
1727
+ level will be returned. If the given type is None (default), the
1728
+ global verbosity level will be returned.
1729
+
1730
+ To configure a level for a fine-grained verbosity type, the
1731
+ configuration file should have a setting for the configuration name
1732
+ and a numeric value for the verbosity level. A special value of "auto"
1733
+ can be used to explicitly use the global verbosity level.
1734
+
1735
+ Example:
1736
+
1737
+ .. code-block:: ini
1738
+
1739
+ # content of pytest.ini
1740
+ [pytest]
1741
+ verbosity_assertions = 2
1742
+
1743
+ .. code-block:: console
1744
+
1745
+ pytest -v
1746
+
1747
+ .. code-block:: python
1748
+
1749
+ print(config.get_verbosity()) # 1
1750
+ print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS)) # 2
1751
+ """
1752
+ global_level = self.getoption("verbose", default=0)
1753
+ assert isinstance(global_level, int)
1754
+ if verbosity_type is None:
1755
+ return global_level
1756
+
1757
+ ini_name = Config._verbosity_ini_name(verbosity_type)
1758
+ if ini_name not in self._parser._inidict:
1759
+ return global_level
1760
+
1761
+ level = self.getini(ini_name)
1762
+ if level == Config._VERBOSITY_INI_DEFAULT:
1763
+ return global_level
1764
+
1765
+ return int(level)
1766
+
1767
+ @staticmethod
1768
+ def _verbosity_ini_name(verbosity_type: str) -> str:
1769
+ return f"verbosity_{verbosity_type}"
1770
+
1771
+ @staticmethod
1772
+ def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None:
1773
+ """Add a output verbosity configuration option for the given output type.
1774
+
1775
+ :param parser: Parser for command line arguments and ini-file values.
1776
+ :param verbosity_type: Fine-grained verbosity category.
1777
+ :param help: Description of the output this type controls.
1778
+
1779
+ The value should be retrieved via a call to
1780
+ :py:func:`config.get_verbosity(type) <pytest.Config.get_verbosity>`.
1781
+ """
1782
+ parser.addini(
1783
+ Config._verbosity_ini_name(verbosity_type),
1784
+ help=help,
1785
+ type="string",
1786
+ default=Config._VERBOSITY_INI_DEFAULT,
1787
+ )
1788
+
1789
+ def _warn_about_missing_assertion(self, mode: str) -> None:
1790
+ if not _assertion_supported():
1791
+ if mode == "plain":
1792
+ warning_text = (
1793
+ "ASSERTIONS ARE NOT EXECUTED"
1794
+ " and FAILING TESTS WILL PASS. Are you"
1795
+ " using python -O?"
1796
+ )
1797
+ else:
1798
+ warning_text = (
1799
+ "assertions not in test modules or"
1800
+ " plugins will be ignored"
1801
+ " because assert statements are not executed "
1802
+ "by the underlying Python interpreter "
1803
+ "(are you using python -O?)\n"
1804
+ )
1805
+ self.issue_config_time_warning(
1806
+ PytestConfigWarning(warning_text),
1807
+ stacklevel=3,
1808
+ )
1809
+
1810
+ def _warn_about_skipped_plugins(self) -> None:
1811
+ for module_name, msg in self.pluginmanager.skipped_plugins:
1812
+ self.issue_config_time_warning(
1813
+ PytestConfigWarning(f"skipped plugin {module_name!r}: {msg}"),
1814
+ stacklevel=2,
1815
+ )
1816
+
1817
+
1818
+ def _assertion_supported() -> bool:
1819
+ try:
1820
+ assert False
1821
+ except AssertionError:
1822
+ return True
1823
+ else:
1824
+ return False # type: ignore[unreachable]
1825
+
1826
+
1827
+ def create_terminal_writer(
1828
+ config: Config, file: TextIO | None = None
1829
+ ) -> TerminalWriter:
1830
+ """Create a TerminalWriter instance configured according to the options
1831
+ in the config object.
1832
+
1833
+ Every code which requires a TerminalWriter object and has access to a
1834
+ config object should use this function.
1835
+ """
1836
+ tw = TerminalWriter(file=file)
1837
+
1838
+ if config.option.color == "yes":
1839
+ tw.hasmarkup = True
1840
+ elif config.option.color == "no":
1841
+ tw.hasmarkup = False
1842
+
1843
+ if config.option.code_highlight == "yes":
1844
+ tw.code_highlight = True
1845
+ elif config.option.code_highlight == "no":
1846
+ tw.code_highlight = False
1847
+
1848
+ return tw
1849
+
1850
+
1851
+ def _strtobool(val: str) -> bool:
1852
+ """Convert a string representation of truth to True or False.
1853
+
1854
+ True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
1855
+ are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
1856
+ 'val' is anything else.
1857
+
1858
+ .. note:: Copied from distutils.util.
1859
+ """
1860
+ val = val.lower()
1861
+ if val in ("y", "yes", "t", "true", "on", "1"):
1862
+ return True
1863
+ elif val in ("n", "no", "f", "false", "off", "0"):
1864
+ return False
1865
+ else:
1866
+ raise ValueError(f"invalid truth value {val!r}")
1867
+
1868
+
1869
+ @lru_cache(maxsize=50)
1870
+ def parse_warning_filter(
1871
+ arg: str, *, escape: bool
1872
+ ) -> tuple[warnings._ActionKind, str, type[Warning], str, int]:
1873
+ """Parse a warnings filter string.
1874
+
1875
+ This is copied from warnings._setoption with the following changes:
1876
+
1877
+ * Does not apply the filter.
1878
+ * Escaping is optional.
1879
+ * Raises UsageError so we get nice error messages on failure.
1880
+ """
1881
+ __tracebackhide__ = True
1882
+ error_template = dedent(
1883
+ f"""\
1884
+ while parsing the following warning configuration:
1885
+
1886
+ {arg}
1887
+
1888
+ This error occurred:
1889
+
1890
+ {{error}}
1891
+ """
1892
+ )
1893
+
1894
+ parts = arg.split(":")
1895
+ if len(parts) > 5:
1896
+ doc_url = (
1897
+ "https://docs.python.org/3/library/warnings.html#describing-warning-filters"
1898
+ )
1899
+ error = dedent(
1900
+ f"""\
1901
+ Too many fields ({len(parts)}), expected at most 5 separated by colons:
1902
+
1903
+ action:message:category:module:line
1904
+
1905
+ For more information please consult: {doc_url}
1906
+ """
1907
+ )
1908
+ raise UsageError(error_template.format(error=error))
1909
+
1910
+ while len(parts) < 5:
1911
+ parts.append("")
1912
+ action_, message, category_, module, lineno_ = (s.strip() for s in parts)
1913
+ try:
1914
+ action: warnings._ActionKind = warnings._getaction(action_) # type: ignore[attr-defined]
1915
+ except warnings._OptionError as e:
1916
+ raise UsageError(error_template.format(error=str(e))) from None
1917
+ try:
1918
+ category: type[Warning] = _resolve_warning_category(category_)
1919
+ except Exception:
1920
+ exc_info = ExceptionInfo.from_current()
1921
+ exception_text = exc_info.getrepr(style="native")
1922
+ raise UsageError(error_template.format(error=exception_text)) from None
1923
+ if message and escape:
1924
+ message = re.escape(message)
1925
+ if module and escape:
1926
+ module = re.escape(module) + r"\Z"
1927
+ if lineno_:
1928
+ try:
1929
+ lineno = int(lineno_)
1930
+ if lineno < 0:
1931
+ raise ValueError("number is negative")
1932
+ except ValueError as e:
1933
+ raise UsageError(
1934
+ error_template.format(error=f"invalid lineno {lineno_!r}: {e}")
1935
+ ) from None
1936
+ else:
1937
+ lineno = 0
1938
+ return action, message, category, module, lineno
1939
+
1940
+
1941
+ def _resolve_warning_category(category: str) -> type[Warning]:
1942
+ """
1943
+ Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors)
1944
+ propagate so we can get access to their tracebacks (#9218).
1945
+ """
1946
+ __tracebackhide__ = True
1947
+ if not category:
1948
+ return Warning
1949
+
1950
+ if "." not in category:
1951
+ import builtins as m
1952
+
1953
+ klass = category
1954
+ else:
1955
+ module, _, klass = category.rpartition(".")
1956
+ m = __import__(module, None, None, [klass])
1957
+ cat = getattr(m, klass)
1958
+ if not issubclass(cat, Warning):
1959
+ raise UsageError(f"{cat} is not a Warning subclass")
1960
+ return cast(Type[Warning], cat)
1961
+
1962
+
1963
+ def apply_warning_filters(
1964
+ config_filters: Iterable[str], cmdline_filters: Iterable[str]
1965
+ ) -> None:
1966
+ """Applies pytest-configured filters to the warnings module"""
1967
+ # Filters should have this precedence: cmdline options, config.
1968
+ # Filters should be applied in the inverse order of precedence.
1969
+ for arg in config_filters:
1970
+ warnings.filterwarnings(*parse_warning_filter(arg, escape=False))
1971
+
1972
+ for arg in cmdline_filters:
1973
+ warnings.filterwarnings(*parse_warning_filter(arg, escape=True))