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.
@@ -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
- except (KeyError, TypeError):
156
- py_info_store.remove()
157
- return None
158
- if (sys_exe := py_info.system_executable) is not None and not Path(sys_exe).exists():
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 = float(env.get("PY_DISCOVERY_TIMEOUT", "15"))
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
@@ -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
- for entry in map(Path, path.split(os.pathsep)):
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.0
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.8
50
- Requires-Dist: filelock>=3.15.4
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.8+ and discovers interpreters as old as Python 3.6; anything older is skipped with a
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=MKV2sjrrypq-7gcOdw9GpAooMJl9kYCTAlYeUwO3ndQ,10473
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=ABPD8MRweUO6B7cG3U3ZBfUIYs08rh6dYPA225EURyY,22032
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=YhF5U-kKcwMXZMfRNDlZK6kCPJJI9tHIvd5SjQqMJqY,16126
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.0.dist-info/METADATA,sha256=bBHr6H593h-sswZAXhVWXrxIp1XfPZOocZBf80H2hRU,5132
15
- python_discovery-1.6.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
16
- python_discovery-1.6.0.dist-info/licenses/LICENSE,sha256=kOJeH68qSq6o2V7o5_VziwUqGswMnFgGgQH5Mahr1Yg,1023
17
- python_discovery-1.6.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any