python-discovery 1.5.3__tar.gz → 1.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_discovery-1.5.3 → python_discovery-1.6.1}/.pre-commit-config.yaml +3 -3
- {python_discovery-1.5.3 → python_discovery-1.6.1}/PKG-INFO +1 -1
- {python_discovery-1.5.3 → python_discovery-1.6.1}/docs/changelog.rst +27 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/docs/explanation.rst +35 -1
- {python_discovery-1.5.3 → python_discovery-1.6.1}/docs/how-to/standalone-usage.rst +10 -2
- {python_discovery-1.5.3 → python_discovery-1.6.1}/docs/tutorial/getting-started.rst +7 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/pyproject.toml +1 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/_discovery.py +6 -1
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/_py_info.py +11 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/py_info/test_py_info.py +14 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/test_discovery.py +32 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/.github/FUNDING.yaml +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/.github/SECURITY.md +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/.github/dependabot.yaml +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/.github/release.yaml +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/.github/workflows/check.yaml +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/.github/workflows/prepare-release.yaml +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/.github/workflows/release.yaml +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/.gitignore +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/.readthedocs.yaml +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/.readthedocs.yml +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/CODE_OF_CONDUCT.md +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/LICENSE +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/README.md +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/docs/_static/custom.css +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/docs/_static/logo.svg +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/docs/changelog/template.jinja2 +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/docs/conf.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/docs/index.rst +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/docs/reference/api.rst +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/docs/reference/environment-variables.rst +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/__init__.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/_cache.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/_cached_py_info.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/_compat.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/_py_info_collect.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/_py_spec.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/_specifier.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/_windows/__init__.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/_windows/_pep514.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/_windows/_propose.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/src/python_discovery/py.typed +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tasks/old_target_check.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tasks/old_target_check.sh +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tasks/release.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/conftest.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/py_info/test_py_info_exe_based_of.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/test_cache.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/test_cached_py_info.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/test_discovery_extra.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/test_iter_interpreters.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/test_py_info_collect.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/test_py_info_extra.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/test_py_spec.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/test_py_spec_extra.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/test_specifier.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/windows/conftest.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/windows/test_windows.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/windows/test_windows_pep514.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tests/windows/winreg_mock_values.py +0 -0
- {python_discovery-1.5.3 → python_discovery-1.6.1}/tox.toml +0 -0
|
@@ -15,11 +15,11 @@ repos:
|
|
|
15
15
|
- id: codespell
|
|
16
16
|
additional_dependencies: ["tomli>=2.4"]
|
|
17
17
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
|
18
|
-
rev: "v2.
|
|
18
|
+
rev: "v2.29.4"
|
|
19
19
|
hooks:
|
|
20
20
|
- id: pyproject-fmt
|
|
21
21
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
22
|
-
rev: "v0.16.
|
|
22
|
+
rev: "v0.16.7"
|
|
23
23
|
hooks:
|
|
24
24
|
- id: ruff-format
|
|
25
25
|
- id: ruff
|
|
@@ -39,7 +39,7 @@ repos:
|
|
|
39
39
|
- mdformat-gfm>=1
|
|
40
40
|
- mdformat-ruff>=0.1.3
|
|
41
41
|
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
|
42
|
-
rev: v1.
|
|
42
|
+
rev: v1.30.1
|
|
43
43
|
hooks:
|
|
44
44
|
- id: zizmor
|
|
45
45
|
- repo: meta
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-discovery
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.1
|
|
4
4
|
Summary: Python interpreter discovery
|
|
5
5
|
Project-URL: Changelog, https://github.com/tox-dev/python-discovery/releases
|
|
6
6
|
Project-URL: Documentation, https://python-discovery.readthedocs.io
|
|
@@ -6,6 +6,33 @@
|
|
|
6
6
|
|
|
7
7
|
.. towncrier release notes start
|
|
8
8
|
|
|
9
|
+
********************
|
|
10
|
+
v1.6.1 (2026-09-18)
|
|
11
|
+
********************
|
|
12
|
+
|
|
13
|
+
Bug fixes - 1.6.1
|
|
14
|
+
=================
|
|
15
|
+
- Skip empty ``PATH`` entries during interpreter discovery - by :user:`gaborbernat`. (:issue:`129`)
|
|
16
|
+
|
|
17
|
+
Improved documentation - 1.6.1
|
|
18
|
+
==============================
|
|
19
|
+
- Document :attr:`~python_discovery.PythonInfo.system_exe` across the tutorial, the how-to guide and the explanation of
|
|
20
|
+
how resolution reaches a base interpreter. The class diagram in the how-to guide had
|
|
21
|
+
:attr:`~python_discovery.PythonInfo.system_executable` typed ``str`` rather than ``str | None`` - by
|
|
22
|
+
:user:`gaborbernat`. (:issue:`128`)
|
|
23
|
+
|
|
24
|
+
********************
|
|
25
|
+
v1.6.0 (2026-08-28)
|
|
26
|
+
********************
|
|
27
|
+
|
|
28
|
+
Features - 1.6.0
|
|
29
|
+
================
|
|
30
|
+
- :attr:`~python_discovery.PythonInfo.system_exe` returns the executable of the system Python an interpreter is based on,
|
|
31
|
+
falling back to :attr:`~python_discovery.PythonInfo.executable` the way
|
|
32
|
+
:meth:`~python_discovery.PythonInfo.resolve_to_system` does. :attr:`~python_discovery.PythonInfo.system_executable` is
|
|
33
|
+
``None`` until resolution runs, so every consumer had to narrow a value discovery has already settled - by
|
|
34
|
+
:user:`gaborbernat`. (:issue:`127`)
|
|
35
|
+
|
|
9
36
|
********************
|
|
10
37
|
v1.5.3 (2026-08-24)
|
|
11
38
|
********************
|
|
@@ -186,7 +186,7 @@ APIs whenever a spec is given.
|
|
|
186
186
|
|
|
187
187
|
**Deduplication.** :func:`~python_discovery.get_interpreter` deduplicates per call so it does not interrogate the
|
|
188
188
|
same binary twice while searching, and stops as soon as a match is found. :func:`~python_discovery.iter_interpreters`
|
|
189
|
-
deduplicates by the resolved real path of each candidate's
|
|
189
|
+
deduplicates by the resolved real path of each candidate's :attr:`~python_discovery.PythonInfo.system_exe`.
|
|
190
190
|
That means symlinked aliases like ``/bin/python3`` and ``/usr/bin/python3``, or a virtualenv whose ``python``
|
|
191
191
|
symlinks to its base interpreter, collapse to a single yield. The semantic is "one entry per distinct install,"
|
|
192
192
|
which is what callers building choosers or version-range pickers usually want.
|
|
@@ -198,6 +198,40 @@ version first, smallest install root, etc.), wrap the call in :func:`sorted` --
|
|
|
198
198
|
include a ``sort_by`` parameter because keeping discovery order preserves the priority signal for callers who
|
|
199
199
|
want it.
|
|
200
200
|
|
|
201
|
+
Resolving a virtual environment to its base
|
|
202
|
+
--------------------------------------------
|
|
203
|
+
|
|
204
|
+
A :class:`~python_discovery.PythonInfo` reaches you in one of two states, and the difference shows up in a single
|
|
205
|
+
field.
|
|
206
|
+
|
|
207
|
+
Collection fills in :attr:`~python_discovery.PythonInfo.system_executable` by inspection alone, which works whenever
|
|
208
|
+
the interpreter is not in a virtual environment or names its base outright. When inspection comes up short the field
|
|
209
|
+
holds ``None``, and :meth:`~python_discovery.PythonInfo.resolve_to_system` walks the prefix chain a layer at a time,
|
|
210
|
+
interrogating each prefix until it reaches a real install. Every discovery entry point runs that walk, so an
|
|
211
|
+
interpreter you got from :func:`~python_discovery.get_interpreter`,
|
|
212
|
+
:func:`~python_discovery.iter_interpreters` or :meth:`~python_discovery.PythonInfo.from_exe` is resolved before you
|
|
213
|
+
see it.
|
|
214
|
+
|
|
215
|
+
.. mermaid::
|
|
216
|
+
|
|
217
|
+
flowchart TD
|
|
218
|
+
Collect["collection<br>system_executable = None"] --> Walk["resolve_to_system()<br>walk prefix chain"]
|
|
219
|
+
Walk --> Done["system_executable set"]
|
|
220
|
+
Collect -- "base named outright" --> Done
|
|
221
|
+
Done --> Read["system_exe"]
|
|
222
|
+
|
|
223
|
+
style Collect fill:#ffe3a3,stroke:#d29200,color:#3a2c00
|
|
224
|
+
style Walk fill:#4a90d9,stroke:#2a5f8f,color:#fff
|
|
225
|
+
style Done fill:#4a9f4a,stroke:#2a6f2a,color:#fff
|
|
226
|
+
style Read fill:#4a9f4a,stroke:#2a6f2a,color:#fff
|
|
227
|
+
|
|
228
|
+
The annotation cannot describe that ordering, so ``system_executable`` stays ``str | None`` and a type checker asks
|
|
229
|
+
every reader to narrow a value resolution has settled.
|
|
230
|
+
:attr:`~python_discovery.PythonInfo.system_exe` closes that gap by falling back to
|
|
231
|
+
:attr:`~python_discovery.PythonInfo.executable`, which is the value ``resolve_to_system`` writes anyway when a prefix
|
|
232
|
+
links back to its own interpreter. Reading the raw field still tells the two states apart, which is what
|
|
233
|
+
:meth:`~python_discovery.PythonInfo.from_exe` with ``resolve_to_host=False`` leaves you holding.
|
|
234
|
+
|
|
201
235
|
How caching works
|
|
202
236
|
-------------------
|
|
203
237
|
|
|
@@ -145,7 +145,8 @@ Once you have a :class:`~python_discovery.PythonInfo`, you can inspect everythin
|
|
|
145
145
|
classDiagram
|
|
146
146
|
class PythonInfo {
|
|
147
147
|
+executable: str
|
|
148
|
-
+
|
|
148
|
+
+system_exe: str
|
|
149
|
+
+system_executable: str | None
|
|
149
150
|
+implementation: str
|
|
150
151
|
+version_info: VersionInfo
|
|
151
152
|
+architecture: int
|
|
@@ -167,7 +168,7 @@ Once you have a :class:`~python_discovery.PythonInfo`, you can inspect everythin
|
|
|
167
168
|
info = get_interpreter("python3.12", cache=cache)
|
|
168
169
|
|
|
169
170
|
info.executable # Resolved path to the binary.
|
|
170
|
-
info.
|
|
171
|
+
info.system_exe # The underlying system interpreter (outside any venv).
|
|
171
172
|
info.implementation # "CPython", "PyPy", "GraalPy", etc.
|
|
172
173
|
info.version_info # VersionInfo(major, minor, micro, releaselevel, serial).
|
|
173
174
|
info.architecture # 64 or 32.
|
|
@@ -178,6 +179,13 @@ Once you have a :class:`~python_discovery.PythonInfo`, you can inspect everythin
|
|
|
178
179
|
info.sysconfig_vars # All sysconfig.get_config_vars() values.
|
|
179
180
|
info.sysconfig_paths # All sysconfig.get_paths() values.
|
|
180
181
|
|
|
182
|
+
Prefer :attr:`~python_discovery.PythonInfo.system_exe` over the raw
|
|
183
|
+
:attr:`~python_discovery.PythonInfo.system_executable` field it reads. The field is typed ``str | None`` because it
|
|
184
|
+
holds nothing until resolution runs, so a type checker makes you narrow it at every use even though discovery has
|
|
185
|
+
filled it in by the time you hold a :class:`~python_discovery.PythonInfo`. Read the raw field only when you build a
|
|
186
|
+
:class:`~python_discovery.PythonInfo` yourself, or call
|
|
187
|
+
:meth:`~python_discovery.PythonInfo.from_exe` with ``resolve_to_host=False``, and want to tell the two states apart.
|
|
188
|
+
|
|
181
189
|
Implement a custom cache backend
|
|
182
190
|
-----------------------------------
|
|
183
191
|
|
|
@@ -17,6 +17,7 @@ Before diving into code, here are the key ideas:
|
|
|
17
17
|
- **Spec** -- a short string describing what you are looking for (e.g., ``python3.12``, ``pypy3.9``, ``>=3.11``).
|
|
18
18
|
- **Discovery** -- the process of searching your system for an interpreter that matches a spec.
|
|
19
19
|
- **Cache** -- a disk store that remembers previously discovered interpreters so the next lookup is instant.
|
|
20
|
+
- **Resolution** -- following a virtual environment back to the base interpreter it was built from.
|
|
20
21
|
|
|
21
22
|
Inspecting the current interpreter
|
|
22
23
|
------------------------------------
|
|
@@ -45,6 +46,7 @@ The simplest use case: get information about the Python that is running right no
|
|
|
45
46
|
info = PythonInfo.current_system(cache)
|
|
46
47
|
|
|
47
48
|
print(info.executable) # /usr/bin/python3.12
|
|
49
|
+
print(info.system_exe) # /usr/bin/python3.12
|
|
48
50
|
print(info.version_info[:3]) # (3, 12, 1)
|
|
49
51
|
print(info.implementation) # CPython (or PyPy, GraalPy, etc.)
|
|
50
52
|
print(info.architecture) # 64 (or 32)
|
|
@@ -52,6 +54,11 @@ The simplest use case: get information about the Python that is running right no
|
|
|
52
54
|
The returned :class:`~python_discovery.PythonInfo` object contains everything the library knows about that interpreter:
|
|
53
55
|
paths, version numbers, sysconfig variables, platform details, and more.
|
|
54
56
|
|
|
57
|
+
Run the same snippet from inside a virtual environment and the two paths part company.
|
|
58
|
+
:attr:`~python_discovery.PythonInfo.executable` stays the environment's own ``python``, while
|
|
59
|
+
:attr:`~python_discovery.PythonInfo.system_exe` names the base interpreter that environment was built from. Reach for
|
|
60
|
+
``system_exe`` whenever you want the real install rather than a link to it.
|
|
61
|
+
|
|
55
62
|
Finding a different interpreter
|
|
56
63
|
--------------------------------
|
|
57
64
|
|
|
@@ -133,6 +133,7 @@ lint.per-file-ignores."tests/**/*.py" = [
|
|
|
133
133
|
"import-private-name", # private imports needed to test internal APIs
|
|
134
134
|
"magic-value-comparison", # Magic value used in comparison
|
|
135
135
|
"private-member-access", # private member access needed to test internals
|
|
136
|
+
"pytest-fixture-autouse", # shared setup fixtures are meant to apply to every test
|
|
136
137
|
"subprocess-without-shell-equals-true", # `subprocess` call: check for execution of untrusted input
|
|
137
138
|
"suspicious-subprocess-import", # subprocess import
|
|
138
139
|
"too-many-arguments", # too many arguments (pytest fixtures)
|
|
@@ -423,7 +423,12 @@ def get_paths(env: Mapping[str, str]) -> Generator[Path, None, None]:
|
|
|
423
423
|
except (AttributeError, ValueError): # pragma: no cover # Windows only (no confstr)
|
|
424
424
|
path = os.defpath
|
|
425
425
|
if path:
|
|
426
|
-
|
|
426
|
+
# An empty component (a leading/trailing/doubled separator) means "the current directory", the
|
|
427
|
+
# same footgun a shell has when PATH is misconfigured - Path("") resolves to it. Since every
|
|
428
|
+
# yielded path here is later searched for interpreters to execute and interrogate, silently
|
|
429
|
+
# including the caller's CWD would let whatever directory virtualenv happens to run from smuggle
|
|
430
|
+
# in a candidate binary.
|
|
431
|
+
for entry in map(Path, filter(None, path.split(os.pathsep))):
|
|
427
432
|
with suppress(OSError):
|
|
428
433
|
if entry.is_dir() and next(entry.iterdir(), None):
|
|
429
434
|
yield entry
|
|
@@ -167,6 +167,17 @@ class PythonInfo: # ruff:ignore[too-many-public-methods]
|
|
|
167
167
|
"""The exec prefix of the system Python this interpreter is based on."""
|
|
168
168
|
return self.real_prefix or self.base_exec_prefix or self.exec_prefix
|
|
169
169
|
|
|
170
|
+
@property
|
|
171
|
+
def system_exe(self) -> str:
|
|
172
|
+
"""
|
|
173
|
+
The executable of the system Python this interpreter is based on.
|
|
174
|
+
|
|
175
|
+
:attr:`system_executable` stays ``None`` until :meth:`resolve_to_system` walks the prefix chain, so reading it
|
|
176
|
+
forces every caller to narrow a value discovery has already settled. The fallback here matches what resolution
|
|
177
|
+
itself writes when a prefix links back to its own interpreter.
|
|
178
|
+
"""
|
|
179
|
+
return self.system_executable or self.executable
|
|
180
|
+
|
|
170
181
|
def __repr__(self) -> str:
|
|
171
182
|
return "{}({!r})".format(
|
|
172
183
|
self.__class__.__name__,
|
|
@@ -511,3 +511,17 @@ def test_select_most_likely_prefers_machine_match(
|
|
|
511
511
|
d.sysconfig_platform = plat
|
|
512
512
|
result = PythonInfo._select_most_likely(discovered, target)
|
|
513
513
|
assert result.sysconfig_platform == discovered_platforms[expected_idx]
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
@pytest.mark.parametrize(
|
|
517
|
+
("system_executable", "expected"),
|
|
518
|
+
[
|
|
519
|
+
pytest.param("/usr/bin/python3", "/usr/bin/python3", id="resolved"),
|
|
520
|
+
pytest.param(None, "/venv/bin/python", id="unresolved falls back to executable"),
|
|
521
|
+
],
|
|
522
|
+
)
|
|
523
|
+
def test_py_info_system_exe(system_executable: str | None, expected: str) -> None:
|
|
524
|
+
info = copy.deepcopy(CURRENT)
|
|
525
|
+
info.executable = "/venv/bin/python"
|
|
526
|
+
info.system_executable = system_executable
|
|
527
|
+
assert info.system_exe == expected
|
|
@@ -161,6 +161,38 @@ def test_get_paths_no_path_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
161
161
|
assert paths
|
|
162
162
|
|
|
163
163
|
|
|
164
|
+
@pytest.mark.parametrize(
|
|
165
|
+
("raw_path", "expected"),
|
|
166
|
+
[
|
|
167
|
+
pytest.param("{first}{sep}", ["first"], id="trailing"),
|
|
168
|
+
pytest.param("{sep}{first}", ["first"], id="leading"),
|
|
169
|
+
pytest.param("{first}{sep}{sep}{second}", ["first", "second"], id="doubled"),
|
|
170
|
+
],
|
|
171
|
+
)
|
|
172
|
+
def test_get_paths_skips_empty_entries(tmp_path: Path, raw_path: str, expected: list[str]) -> None:
|
|
173
|
+
"""An empty PATH entry means "current directory" - it must never be searched for interpreters."""
|
|
174
|
+
dirs = {}
|
|
175
|
+
for name in ("first", "second"):
|
|
176
|
+
dirs[name] = tmp_path / name
|
|
177
|
+
dirs[name].mkdir()
|
|
178
|
+
(dirs[name] / "dummy").touch()
|
|
179
|
+
|
|
180
|
+
paths = list(get_paths({"PATH": raw_path.format(first=dirs["first"], second=dirs["second"], sep=os.pathsep)}))
|
|
181
|
+
|
|
182
|
+
assert paths == [dirs[name] for name in expected]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_get_paths_empty_entry_does_not_yield_cwd(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
186
|
+
"""Reproduces the exploit: an empty PATH entry used to resolve to the caller's CWD."""
|
|
187
|
+
cwd_marker = tmp_path / "python3.11"
|
|
188
|
+
cwd_marker.touch(mode=0o755)
|
|
189
|
+
monkeypatch.chdir(tmp_path)
|
|
190
|
+
|
|
191
|
+
paths = list(get_paths({"PATH": f"{os.pathsep}"}))
|
|
192
|
+
|
|
193
|
+
assert not any(path.resolve() == tmp_path.resolve() for path in paths)
|
|
194
|
+
|
|
195
|
+
|
|
164
196
|
def test_lazy_path_dump_debug(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
165
197
|
monkeypatch.setenv("_VIRTUALENV_DEBUG", "1")
|
|
166
198
|
a_dir = tmp_path
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_discovery-1.5.3 → python_discovery-1.6.1}/tests/py_info/test_py_info_exe_based_of.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|