python-discovery 1.4.1__tar.gz → 1.4.3__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.
Files changed (57) hide show
  1. {python_discovery-1.4.1 → python_discovery-1.4.3}/.github/workflows/check.yaml +1 -1
  2. {python_discovery-1.4.1 → python_discovery-1.4.3}/.github/workflows/prepare-release.yaml +1 -1
  3. {python_discovery-1.4.1 → python_discovery-1.4.3}/.github/workflows/release.yaml +1 -1
  4. {python_discovery-1.4.1 → python_discovery-1.4.3}/.pre-commit-config.yaml +5 -5
  5. {python_discovery-1.4.1 → python_discovery-1.4.3}/PKG-INFO +1 -1
  6. {python_discovery-1.4.1 → python_discovery-1.4.3}/docs/changelog.rst +20 -0
  7. {python_discovery-1.4.1 → python_discovery-1.4.3}/pyproject.toml +2 -1
  8. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/_py_info.py +17 -3
  9. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/test_py_info_extra.py +28 -3
  10. {python_discovery-1.4.1 → python_discovery-1.4.3}/.github/FUNDING.yaml +0 -0
  11. {python_discovery-1.4.1 → python_discovery-1.4.3}/.github/SECURITY.md +0 -0
  12. {python_discovery-1.4.1 → python_discovery-1.4.3}/.github/dependabot.yaml +0 -0
  13. {python_discovery-1.4.1 → python_discovery-1.4.3}/.github/release.yaml +0 -0
  14. {python_discovery-1.4.1 → python_discovery-1.4.3}/.gitignore +0 -0
  15. {python_discovery-1.4.1 → python_discovery-1.4.3}/.readthedocs.yaml +0 -0
  16. {python_discovery-1.4.1 → python_discovery-1.4.3}/.readthedocs.yml +0 -0
  17. {python_discovery-1.4.1 → python_discovery-1.4.3}/CODE_OF_CONDUCT.md +0 -0
  18. {python_discovery-1.4.1 → python_discovery-1.4.3}/LICENSE +0 -0
  19. {python_discovery-1.4.1 → python_discovery-1.4.3}/README.md +0 -0
  20. {python_discovery-1.4.1 → python_discovery-1.4.3}/docs/_static/custom.css +0 -0
  21. {python_discovery-1.4.1 → python_discovery-1.4.3}/docs/_static/logo.svg +0 -0
  22. {python_discovery-1.4.1 → python_discovery-1.4.3}/docs/changelog/template.jinja2 +0 -0
  23. {python_discovery-1.4.1 → python_discovery-1.4.3}/docs/conf.py +0 -0
  24. {python_discovery-1.4.1 → python_discovery-1.4.3}/docs/explanation.rst +0 -0
  25. {python_discovery-1.4.1 → python_discovery-1.4.3}/docs/how-to/standalone-usage.rst +0 -0
  26. {python_discovery-1.4.1 → python_discovery-1.4.3}/docs/index.rst +0 -0
  27. {python_discovery-1.4.1 → python_discovery-1.4.3}/docs/reference/api.rst +0 -0
  28. {python_discovery-1.4.1 → python_discovery-1.4.3}/docs/reference/environment-variables.rst +0 -0
  29. {python_discovery-1.4.1 → python_discovery-1.4.3}/docs/tutorial/getting-started.rst +0 -0
  30. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/__init__.py +0 -0
  31. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/_cache.py +0 -0
  32. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/_cached_py_info.py +0 -0
  33. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/_compat.py +0 -0
  34. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/_discovery.py +0 -0
  35. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/_py_spec.py +0 -0
  36. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/_specifier.py +0 -0
  37. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/_windows/__init__.py +0 -0
  38. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/_windows/_pep514.py +0 -0
  39. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/_windows/_propose.py +0 -0
  40. {python_discovery-1.4.1 → python_discovery-1.4.3}/src/python_discovery/py.typed +0 -0
  41. {python_discovery-1.4.1 → python_discovery-1.4.3}/tasks/release.py +0 -0
  42. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/conftest.py +0 -0
  43. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/py_info/test_py_info.py +0 -0
  44. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/py_info/test_py_info_exe_based_of.py +0 -0
  45. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/test_cache.py +0 -0
  46. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/test_cached_py_info.py +0 -0
  47. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/test_discovery.py +0 -0
  48. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/test_discovery_extra.py +0 -0
  49. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/test_iter_interpreters.py +0 -0
  50. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/test_py_spec.py +0 -0
  51. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/test_py_spec_extra.py +0 -0
  52. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/test_specifier.py +0 -0
  53. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/windows/conftest.py +0 -0
  54. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/windows/test_windows.py +0 -0
  55. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/windows/test_windows_pep514.py +0 -0
  56. {python_discovery-1.4.1 → python_discovery-1.4.3}/tests/windows/winreg_mock_values.py +0 -0
  57. {python_discovery-1.4.1 → python_discovery-1.4.3}/tox.toml +0 -0
@@ -34,7 +34,7 @@ jobs:
34
34
  - dev
35
35
  - pkg_meta
36
36
  steps:
37
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
37
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
38
38
  with:
39
39
  fetch-depth: 0
40
40
  persist-credentials: false
@@ -26,7 +26,7 @@ jobs:
26
26
  permissions:
27
27
  contents: write
28
28
  steps:
29
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
29
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
30
30
  with:
31
31
  fetch-depth: 0
32
32
  token: ${{ secrets.RELEASE_PAT }}
@@ -13,7 +13,7 @@ jobs:
13
13
  build:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
16
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
17
17
  with:
18
18
  fetch-depth: 0
19
19
  persist-credentials: false
@@ -5,7 +5,7 @@ repos:
5
5
  - id: end-of-file-fixer
6
6
  - id: trailing-whitespace
7
7
  - repo: https://github.com/python-jsonschema/check-jsonschema
8
- rev: 0.37.2
8
+ rev: 0.37.3
9
9
  hooks:
10
10
  - id: check-github-workflows
11
11
  args: ["--verbose"]
@@ -15,24 +15,24 @@ 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.23.0"
18
+ rev: "v2.25.1"
19
19
  hooks:
20
20
  - id: pyproject-fmt
21
21
  - repo: https://github.com/astral-sh/ruff-pre-commit
22
- rev: "v0.15.16"
22
+ rev: "v0.15.20"
23
23
  hooks:
24
24
  - id: ruff-format
25
25
  - id: ruff
26
26
  args: ["--fix", "--unsafe-fixes", "--exit-non-zero-on-fix"]
27
27
  - repo: https://github.com/rbubley/mirrors-prettier
28
- rev: "v3.8.3"
28
+ rev: "v3.9.1"
29
29
  hooks:
30
30
  - id: prettier
31
31
  additional_dependencies:
32
32
  - prettier@3.8.1
33
33
  - "@prettier/plugin-xml@3.4.2"
34
34
  - repo: https://github.com/zizmorcore/zizmor-pre-commit
35
- rev: v1.25.2
35
+ rev: v1.26.1
36
36
  hooks:
37
37
  - id: zizmor
38
38
  - repo: meta
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-discovery
3
- Version: 1.4.1
3
+ Version: 1.4.3
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,26 @@
6
6
 
7
7
  .. towncrier release notes start
8
8
 
9
+ ********************
10
+ v1.4.3 (2026-07-03)
11
+ ********************
12
+
13
+ Packaging updates and notes for downstreams - 1.4.3
14
+ ===================================================
15
+ - Constrain the ``hatchling`` build requirement per Python version so sdist builds resolve a compatible backend on the
16
+ declared ``>=3.8`` floor - ``hatchling>=1.28`` dropped Python 3.9, leaving 3.8/3.9 unbuildable from source - by
17
+ :user:`gaborbernat`. (:issue:`92`)
18
+
19
+ ********************
20
+ v1.4.2 (2026-06-11)
21
+ ********************
22
+
23
+ Bug fixes - 1.4.2
24
+ =================
25
+ - Stop executable symlink resolution once the stdlib landmark is reachable and keep macOS framework builds untouched,
26
+ matching ``getpath`` - Homebrew interpreters no longer get version-pinned ``Cellar`` paths recorded and stable
27
+ aliases such as Debian's ``/usr/bin/python3`` are preserved - by :user:`gaborbernat`. (:issue:`86`)
28
+
9
29
  ********************
10
30
  v1.4.1 (2026-06-11)
11
31
  ********************
@@ -2,7 +2,8 @@
2
2
  build-backend = "hatchling.build"
3
3
  requires = [
4
4
  "hatch-vcs>=0.5",
5
- "hatchling>=1.28",
5
+ "hatchling>=1.27; python_version<'3.10'",
6
+ "hatchling>=1.28; python_version>='3.10'",
6
7
  ]
7
8
 
8
9
  [project]
@@ -225,21 +225,28 @@ class PythonInfo: # noqa: PLR0904
225
225
  # Try fallback for POSIX virtual environments
226
226
  return self._try_posix_fallback_executable(base_executable) # pragma: >=3.11 cover
227
227
 
228
- def _resolve_executable_symlink(self, path: str) -> str:
228
+ def _resolve_executable_symlink(self, path: str, *, framework: bool | None = None) -> str:
229
229
  """
230
230
  Resolve symlinks of the executable itself, but never of its parent directories.
231
231
 
232
232
  Mirrors CPython's ``getpath.realpath`` (and ``venv`` in python/cpython#115237): an executable-only symlink
233
233
  resolves to the real interpreter so its home can be located, while a fully symlinked interpreter tree is
234
- kept as-is.
234
+ kept as-is. Like ``getpath``, resolution stops as soon as the stdlib landmark is reachable from the current
235
+ directory - an alias such as Debian's ``/usr/bin/python3`` is a usable home and stays untouched.
235
236
  """
236
237
  result = os.path.abspath(path)
237
238
  if self.os != "posix": # CPython only does this where HAVE_READLINK
238
239
  return result
240
+ if framework is None:
241
+ framework = bool(sysconfig.get_config_var("PYTHONFRAMEWORK"))
242
+ if framework: # macOS framework builds self-locate via dyld from the real binary; e.g. for Homebrew
243
+ return result # resolving would pin the versioned Cellar path into the recorded home
239
244
  real_path = os.path.realpath(result)
240
245
  if not os.path.exists(real_path): # symlink loop or broken symlink
241
246
  return result
242
247
  while os.path.islink(result):
248
+ if self._stdlib_landmark_exists(os.path.dirname(result)):
249
+ return result
243
250
  link = os.readlink(result)
244
251
  candidate = link if os.path.isabs(link) else os.path.normpath(os.path.join(os.path.dirname(result), link))
245
252
  # normpath through a symlinked directory may point at a different file - stop resolving there
@@ -248,6 +255,13 @@ class PythonInfo: # noqa: PLR0904
248
255
  result = candidate
249
256
  return result
250
257
 
258
+ @staticmethod
259
+ def _stdlib_landmark_exists(dir_path: str) -> bool:
260
+ lib_name = os.path.basename(os.path.dirname(os.__file__))
261
+ return any(
262
+ os.path.exists(os.path.join(dir_path, os.pardir, lib, lib_name, "os.py")) for lib in ("lib", "lib64")
263
+ )
264
+
251
265
  def _try_posix_fallback_executable(self, base_executable: str) -> str | None:
252
266
  """Find a versioned Python binary as fallback for POSIX virtual environments."""
253
267
  major, minor = self.version_info.major, self.version_info.minor
@@ -415,7 +429,7 @@ class PythonInfo: # noqa: PLR0904
415
429
 
416
430
  @property
417
431
  def machine(self) -> str:
418
- """Return the instruction set architecture (ISA) derived from :func:`sysconfig.get_platform`."""
432
+ """The instruction set architecture (ISA) derived from :func:`sysconfig.get_platform`."""
419
433
  plat = self.sysconfig_platform
420
434
  if plat is None:
421
435
  return "unknown"
@@ -183,11 +183,36 @@ def test_resolve_executable_symlink(
183
183
  layout: Callable[[Path], tuple[Path, Path]],
184
184
  ) -> None:
185
185
  path, expected = layout(tmp_path)
186
- assert posix_info._resolve_executable_symlink(str(path)) == str(expected)
186
+ assert posix_info._resolve_executable_symlink(str(path), framework=False) == str(expected)
187
187
 
188
188
 
189
189
  @pytest.mark.skipif(sys.platform == "win32", reason="POSIX only")
190
- def test_from_exe_resolves_executable_only_symlink(tmp_path: Path, session_cache: DiskCache) -> None:
190
+ def test_resolve_executable_symlink_framework_kept(tmp_path: Path, posix_info: PythonInfo) -> None:
191
+ link, _exe = _layout_absolute_symlink(tmp_path)
192
+ assert posix_info._resolve_executable_symlink(str(link), framework=True) == str(link)
193
+
194
+
195
+ @pytest.mark.skipif(sys.platform == "win32", reason="POSIX only")
196
+ def test_resolve_executable_symlink_stdlib_landmark_kept(tmp_path: Path, posix_info: PythonInfo) -> None:
197
+ exe = tmp_path / "install" / "bin" / "python3.12"
198
+ exe.parent.mkdir(parents=True)
199
+ exe.touch()
200
+ alias_bin = tmp_path / "alias" / "bin"
201
+ alias_bin.mkdir(parents=True)
202
+ landmark = tmp_path / "alias" / "lib" / Path(os.__file__).parent.name / "os.py"
203
+ landmark.parent.mkdir(parents=True)
204
+ landmark.touch()
205
+ link = alias_bin / "python3"
206
+ link.symlink_to(exe)
207
+ assert posix_info._resolve_executable_symlink(str(link), framework=False) == str(link)
208
+
209
+
210
+ @pytest.mark.skipif(sys.platform == "win32", reason="POSIX only")
211
+ @pytest.mark.skipif(bool(CURRENT.sysconfig_vars.get("PYTHONFRAMEWORK")), reason="framework builds keep recorded path")
212
+ def test_from_exe_resolves_executable_only_symlink( # pragma: no cover # skipped on framework interpreter hosts
213
+ tmp_path: Path,
214
+ session_cache: DiskCache,
215
+ ) -> None:
191
216
  system_exe = CURRENT.system_executable
192
217
  assert system_exe is not None
193
218
  link = tmp_path / "python3"
@@ -196,7 +221,7 @@ def test_from_exe_resolves_executable_only_symlink(tmp_path: Path, session_cache
196
221
  assert info is not None
197
222
  assert info.system_executable is not None
198
223
  assert Path(info.system_executable).samefile(system_exe)
199
- assert not Path(info.system_executable).is_symlink()
224
+ assert Path(info.system_executable).parent != tmp_path
200
225
 
201
226
 
202
227
  def test_try_posix_fallback_not_posix() -> None: