python-discovery 1.6.0__py3-none-any.whl → 1.6.2__py3-none-any.whl
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/_cached_py_info.py +25 -6
- python_discovery/_discovery.py +6 -1
- python_discovery/_py_info_collect.py +1 -1
- {python_discovery-1.6.0.dist-info → python_discovery-1.6.2.dist-info}/METADATA +4 -5
- {python_discovery-1.6.0.dist-info → python_discovery-1.6.2.dist-info}/RECORD +7 -7
- {python_discovery-1.6.0.dist-info → python_discovery-1.6.2.dist-info}/WHEEL +1 -1
- {python_discovery-1.6.0.dist-info → python_discovery-1.6.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -41,6 +41,9 @@ _CACHE: OrderedDict[Path, PythonInfo | Exception] = OrderedDict()
|
|
|
41
41
|
_CACHE[Path(sys.executable)] = PythonInfo()
|
|
42
42
|
_LOGGER: Final[logging.Logger] = logging.getLogger(__name__)
|
|
43
43
|
_PY_INFO_SCRIPT: Final[Path] = Path(__file__).resolve().parent / "_py_info_collect.py"
|
|
44
|
+
_DEFAULT_QUERY_TIMEOUT: Final[float] = 15.0
|
|
45
|
+
# poll(2) limits milliseconds to a signed C int.
|
|
46
|
+
_MAX_QUERY_TIMEOUT: Final[float] = (2**31 - 1) / 1000
|
|
44
47
|
|
|
45
48
|
|
|
46
49
|
class _UnsupportedInterpreterError(RuntimeError):
|
|
@@ -136,7 +139,7 @@ def _read_cache_entry(
|
|
|
136
139
|
) -> PythonInfo | _UnsupportedInterpreterError | None:
|
|
137
140
|
if not py_info_store.exists() or (data := py_info_store.read()) is None:
|
|
138
141
|
return None
|
|
139
|
-
if all(data.get(key) == value for key, value in entry_meta.items()):
|
|
142
|
+
if isinstance(data, dict) and all(data.get(key) == value for key, value in entry_meta.items()):
|
|
140
143
|
if isinstance(unsupported := data.get("unsupported"), str):
|
|
141
144
|
return _UnsupportedInterpreterError(unsupported)
|
|
142
145
|
if isinstance(content := data.get("content"), dict):
|
|
@@ -152,10 +155,10 @@ def _load_cached_py_info(
|
|
|
152
155
|
) -> PythonInfo | None:
|
|
153
156
|
try:
|
|
154
157
|
py_info = cls.from_dict(content.copy())
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if
|
|
158
|
+
stale = (sys_exe := py_info.system_executable) is not None and not Path(sys_exe).is_file()
|
|
159
|
+
except (AttributeError, KeyError, OSError, TypeError):
|
|
160
|
+
stale = True
|
|
161
|
+
if stale:
|
|
159
162
|
py_info_store.remove()
|
|
160
163
|
return None
|
|
161
164
|
return py_info
|
|
@@ -214,7 +217,7 @@ def _run_subprocess(
|
|
|
214
217
|
) -> tuple[Exception | None, PythonInfo | None]:
|
|
215
218
|
start_cookie = gen_cookie()
|
|
216
219
|
end_cookie = gen_cookie()
|
|
217
|
-
timeout =
|
|
220
|
+
timeout = _query_timeout(env.get("PY_DISCOVERY_TIMEOUT"))
|
|
218
221
|
with _resolve_py_info_script() as py_info_script:
|
|
219
222
|
cmd = [exe, str(py_info_script), start_cookie, end_cookie]
|
|
220
223
|
env = dict(env)
|
|
@@ -264,6 +267,22 @@ def _run_subprocess(
|
|
|
264
267
|
return None, result
|
|
265
268
|
|
|
266
269
|
|
|
270
|
+
@lru_cache(maxsize=128)
|
|
271
|
+
def _query_timeout(raw: str | None) -> float | None:
|
|
272
|
+
if raw is None:
|
|
273
|
+
return _DEFAULT_QUERY_TIMEOUT
|
|
274
|
+
try:
|
|
275
|
+
timeout = float(raw)
|
|
276
|
+
except ValueError:
|
|
277
|
+
timeout = 0.0
|
|
278
|
+
if timeout > 0:
|
|
279
|
+
return timeout if timeout <= _MAX_QUERY_TIMEOUT else None
|
|
280
|
+
_LOGGER.warning(
|
|
281
|
+
"ignoring PY_DISCOVERY_TIMEOUT=%r, not a positive number of seconds; using %s", raw, _DEFAULT_QUERY_TIMEOUT
|
|
282
|
+
)
|
|
283
|
+
return _DEFAULT_QUERY_TIMEOUT
|
|
284
|
+
|
|
285
|
+
|
|
267
286
|
def _query_failure(exe: str, out: str, err: str | None, code: int | None) -> RuntimeError:
|
|
268
287
|
# the gate's exit code and stderr marker must both be present: either alone can collide, an errno of 79 from a
|
|
269
288
|
# failed exec or a shim that echoes the marker phrase, but only the gate produces the combination
|
python_discovery/_discovery.py
CHANGED
|
@@ -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
|
|
@@ -321,7 +321,7 @@ class PythonInfoCollector:
|
|
|
321
321
|
"script_args": "--no-user-cfg",
|
|
322
322
|
}) # conf files not parsed so they do not hijack paths
|
|
323
323
|
if hasattr(sys, "_framework"): # pragma: no cover # macOS framework builds only
|
|
324
|
-
sys._framework = None # ruff:ignore[private-member-access] # disable macOS static paths for framework
|
|
324
|
+
sys._framework = None # ty: ignore[invalid-assignment] # ruff:ignore[private-member-access] # disable macOS static paths for framework
|
|
325
325
|
|
|
326
326
|
with warnings.catch_warnings(): # disable warning for PEP-632
|
|
327
327
|
warnings.simplefilter("ignore")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-discovery
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.2
|
|
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
|
|
@@ -35,7 +35,6 @@ Classifier: Operating System :: MacOS :: MacOS X
|
|
|
35
35
|
Classifier: Operating System :: Microsoft :: Windows
|
|
36
36
|
Classifier: Operating System :: POSIX
|
|
37
37
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
39
38
|
Classifier: Programming Language :: Python :: 3.9
|
|
40
39
|
Classifier: Programming Language :: Python :: 3.10
|
|
41
40
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -46,8 +45,8 @@ Classifier: Programming Language :: Python :: 3.15
|
|
|
46
45
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
47
46
|
Classifier: Topic :: Software Development :: Libraries
|
|
48
47
|
Classifier: Topic :: Utilities
|
|
49
|
-
Requires-Python: >=3.
|
|
50
|
-
Requires-Dist: filelock>=3.
|
|
48
|
+
Requires-Python: >=3.9
|
|
49
|
+
Requires-Dist: filelock>=3.16.1
|
|
51
50
|
Description-Content-Type: text/markdown
|
|
52
51
|
|
|
53
52
|
# [`python-discovery`](https://python-discovery.readthedocs.io/en/latest/)
|
|
@@ -68,7 +67,7 @@ the right one for you.
|
|
|
68
67
|
Give it a requirement like `python3.12` or `>=3.11,<3.13`, and it searches all known locations, verifies each candidate,
|
|
69
68
|
and returns detailed metadata about the match. Results are cached to disk so repeated lookups are fast.
|
|
70
69
|
|
|
71
|
-
The library runs on Python 3.
|
|
70
|
+
The library runs on Python 3.9+ and discovers interpreters as old as Python 3.6; anything older is skipped with a
|
|
72
71
|
warning naming the version it found.
|
|
73
72
|
|
|
74
73
|
## Usage
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
python_discovery/__init__.py,sha256=Hi4j3pMvC8Fo8KoNL-wOHsPSCUHwY4c0qvv5sjnFzME,808
|
|
2
2
|
python_discovery/_cache.py,sha256=svmG2GzUQzd2xTjAbm2eX1CKxxGAglfv4kxaPuxwPIA,5147
|
|
3
|
-
python_discovery/_cached_py_info.py,sha256=
|
|
3
|
+
python_discovery/_cached_py_info.py,sha256=d-46_ydj-tJNUwaJK5CHp5OKooJkW2t7lWVNBg9Fa4U,11151
|
|
4
4
|
python_discovery/_compat.py,sha256=iqxGuhK9rPaiui80G1H1KrweoMJ16gyLYEmUrSXrp5c,709
|
|
5
|
-
python_discovery/_discovery.py,sha256=
|
|
5
|
+
python_discovery/_discovery.py,sha256=BDHC1XEx0XNStEIbXjujpHHm0nkBpe26bl26YVHSWhM,22488
|
|
6
6
|
python_discovery/_py_info.py,sha256=dov0UFDbcbKlNW765puRgLQuAvbc_ze0Mpsdb7Tixgs,25797
|
|
7
|
-
python_discovery/_py_info_collect.py,sha256=
|
|
7
|
+
python_discovery/_py_info_collect.py,sha256=3PNlySbxNSHDjBw8KJ_olVpOXww1qhGdY28YoLfF0Z8,16160
|
|
8
8
|
python_discovery/_py_spec.py,sha256=O1RMC9JQ7m5SRzF6cEELxnIWZpadXYWO4bWDD7TemUs,10965
|
|
9
9
|
python_discovery/_specifier.py,sha256=o7MHb0z_mFSLz9gXqMwJoNkHYjgi2biq3Ibvd0_slvc,10457
|
|
10
10
|
python_discovery/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
python_discovery/_windows/__init__.py,sha256=qthcyGh36b3WkeSbZ4ECRRYLbCOCaQu5X_DRZ98tXfk,315
|
|
12
12
|
python_discovery/_windows/_pep514.py,sha256=1TqLTdofSCPu3VFE7D4u2fXweyaqhKJR7b48VmJ9AQQ,7787
|
|
13
13
|
python_discovery/_windows/_propose.py,sha256=iMJf6l0TU4SvuV8UsCHHINAe9L5dUT949xyeyMVdong,1565
|
|
14
|
-
python_discovery-1.6.
|
|
15
|
-
python_discovery-1.6.
|
|
16
|
-
python_discovery-1.6.
|
|
17
|
-
python_discovery-1.6.
|
|
14
|
+
python_discovery-1.6.2.dist-info/METADATA,sha256=siAqGU63m_LkmFYyYUsdDKAFl5m9ur1upsL1mNvKrJs,5082
|
|
15
|
+
python_discovery-1.6.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
16
|
+
python_discovery-1.6.2.dist-info/licenses/LICENSE,sha256=kOJeH68qSq6o2V7o5_VziwUqGswMnFgGgQH5Mahr1Yg,1023
|
|
17
|
+
python_discovery-1.6.2.dist-info/RECORD,,
|
|
File without changes
|