python-discovery 1.6.3__tar.gz → 1.6.4__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 (68) hide show
  1. {python_discovery-1.6.3 → python_discovery-1.6.4}/.github/dependabot.yaml +4 -0
  2. {python_discovery-1.6.3 → python_discovery-1.6.4}/.github/workflows/check.yaml +32 -2
  3. python_discovery-1.6.4/.github/workflows/prepare-release.yaml +156 -0
  4. python_discovery-1.6.4/.github/workflows/release.yaml +105 -0
  5. {python_discovery-1.6.3 → python_discovery-1.6.4}/PKG-INFO +1 -1
  6. {python_discovery-1.6.3 → python_discovery-1.6.4}/docs/changelog.rst +28 -0
  7. {python_discovery-1.6.3 → python_discovery-1.6.4}/docs/how-to/standalone-usage.rst +4 -4
  8. {python_discovery-1.6.3 → python_discovery-1.6.4}/pyproject.toml +0 -1
  9. python_discovery-1.6.4/tasks/release.py +82 -0
  10. python_discovery-1.6.3/.github/workflows/prepare-release.yaml +0 -92
  11. python_discovery-1.6.3/.github/workflows/release.yaml +0 -49
  12. python_discovery-1.6.3/docs/changelog/147.bugfix.rst +0 -2
  13. python_discovery-1.6.3/docs/changelog/148.bugfix.rst +0 -2
  14. python_discovery-1.6.3/tasks/release.py +0 -173
  15. {python_discovery-1.6.3 → python_discovery-1.6.4}/.github/FUNDING.yaml +0 -0
  16. {python_discovery-1.6.3 → python_discovery-1.6.4}/.github/SECURITY.md +0 -0
  17. {python_discovery-1.6.3 → python_discovery-1.6.4}/.github/release.yaml +0 -0
  18. {python_discovery-1.6.3 → python_discovery-1.6.4}/.gitignore +0 -0
  19. {python_discovery-1.6.3 → python_discovery-1.6.4}/.pre-commit-config.yaml +0 -0
  20. {python_discovery-1.6.3 → python_discovery-1.6.4}/.readthedocs.yaml +0 -0
  21. {python_discovery-1.6.3 → python_discovery-1.6.4}/.readthedocs.yml +0 -0
  22. {python_discovery-1.6.3 → python_discovery-1.6.4}/CODE_OF_CONDUCT.md +0 -0
  23. {python_discovery-1.6.3 → python_discovery-1.6.4}/LICENSE +0 -0
  24. {python_discovery-1.6.3 → python_discovery-1.6.4}/README.md +0 -0
  25. {python_discovery-1.6.3 → python_discovery-1.6.4}/docs/_static/custom.css +0 -0
  26. {python_discovery-1.6.3 → python_discovery-1.6.4}/docs/_static/logo.svg +0 -0
  27. {python_discovery-1.6.3 → python_discovery-1.6.4}/docs/changelog/template.jinja2 +0 -0
  28. {python_discovery-1.6.3 → python_discovery-1.6.4}/docs/conf.py +0 -0
  29. {python_discovery-1.6.3 → python_discovery-1.6.4}/docs/explanation.rst +0 -0
  30. {python_discovery-1.6.3 → python_discovery-1.6.4}/docs/index.rst +0 -0
  31. {python_discovery-1.6.3 → python_discovery-1.6.4}/docs/reference/api.rst +0 -0
  32. {python_discovery-1.6.3 → python_discovery-1.6.4}/docs/reference/environment-variables.rst +0 -0
  33. {python_discovery-1.6.3 → python_discovery-1.6.4}/docs/tutorial/getting-started.rst +0 -0
  34. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/__init__.py +0 -0
  35. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/_cache.py +0 -0
  36. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/_cached_py_info.py +0 -0
  37. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/_compat.py +0 -0
  38. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/_discovery.py +0 -0
  39. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/_py_info.py +0 -0
  40. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/_py_info_collect.py +0 -0
  41. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/_py_spec.py +0 -0
  42. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/_specifier.py +0 -0
  43. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/_windows/__init__.py +0 -0
  44. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/_windows/_pep514.py +0 -0
  45. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/_windows/_propose.py +0 -0
  46. {python_discovery-1.6.3 → python_discovery-1.6.4}/src/python_discovery/py.typed +0 -0
  47. {python_discovery-1.6.3 → python_discovery-1.6.4}/tasks/old_target_check.py +0 -0
  48. {python_discovery-1.6.3 → python_discovery-1.6.4}/tasks/old_target_check.sh +0 -0
  49. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/conftest.py +0 -0
  50. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/py_info/test_py_info.py +0 -0
  51. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/py_info/test_py_info_exe_based_of.py +0 -0
  52. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_cache.py +0 -0
  53. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_cache_recovery.py +0 -0
  54. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_cached_py_info.py +0 -0
  55. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_discovery.py +0 -0
  56. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_discovery_extra.py +0 -0
  57. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_iter_interpreters.py +0 -0
  58. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_py_info_collect.py +0 -0
  59. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_py_info_extra.py +0 -0
  60. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_py_spec.py +0 -0
  61. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_py_spec_extra.py +0 -0
  62. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_query_timeout.py +0 -0
  63. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/test_specifier.py +0 -0
  64. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/windows/conftest.py +0 -0
  65. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/windows/test_windows.py +0 -0
  66. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/windows/test_windows_pep514.py +0 -0
  67. {python_discovery-1.6.3 → python_discovery-1.6.4}/tests/windows/winreg_mock_values.py +0 -0
  68. {python_discovery-1.6.3 → python_discovery-1.6.4}/tox.toml +0 -0
@@ -2,6 +2,10 @@ version: 2
2
2
  updates:
3
3
  - package-ecosystem: "github-actions"
4
4
  directory: "/"
5
+ labels:
6
+ - dependencies
7
+ - github_actions
8
+ - skip news
5
9
  schedule:
6
10
  interval: "weekly"
7
11
  day: "tuesday"
@@ -5,6 +5,7 @@ on:
5
5
  branches: ["main"]
6
6
  tags-ignore: ["**"]
7
7
  pull_request:
8
+ types: [opened, synchronize, reopened, labeled, unlabeled]
8
9
  schedule:
9
10
  - cron: "0 8 * * *"
10
11
  concurrency:
@@ -37,7 +38,7 @@ jobs:
37
38
  fetch-depth: 0
38
39
  persist-credentials: false
39
40
  - name: Install the latest version of uv
40
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
41
+ uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
41
42
  with:
42
43
  python-version: "3.15"
43
44
  enable-cache: false
@@ -63,7 +64,7 @@ jobs:
63
64
  fetch-depth: 0
64
65
  persist-credentials: false
65
66
  - name: Install the latest version of uv
66
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
67
+ uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
67
68
  with:
68
69
  python-version: "3.15"
69
70
  enable-cache: false
@@ -80,3 +81,32 @@ jobs:
80
81
  run: tasks/old_target_check.sh 3.5 rejected
81
82
  - name: Reject Python 2.7
82
83
  run: tasks/old_target_check.sh 2.7 rejected
84
+ changelog:
85
+ if: github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'skip news')
86
+ runs-on: ubuntu-24.04
87
+ steps:
88
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
89
+ with:
90
+ fetch-depth: 0
91
+ persist-credentials: false
92
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
93
+ with:
94
+ python-version: "3.15"
95
+ enable-cache: false
96
+ - run: uvx towncrier check --compare-with "origin/${GITHUB_BASE_REF}"
97
+ gate:
98
+ name: Required checks pass
99
+ if: always()
100
+ needs: [test, old-target, changelog]
101
+ runs-on: ubuntu-24.04
102
+ steps:
103
+ - env:
104
+ NEEDS_JSON: ${{ toJSON(needs) }}
105
+ run: |
106
+ jq -e '
107
+ all(
108
+ to_entries[];
109
+ .value.result == "success" or
110
+ (.key == "changelog" and .value.result == "skipped")
111
+ )
112
+ ' <<< "$NEEDS_JSON"
@@ -0,0 +1,156 @@
1
+ name: Prepare Release
2
+ on:
3
+ workflow_dispatch:
4
+ inputs:
5
+ bump:
6
+ description: "Version bump type"
7
+ required: true
8
+ type: choice
9
+ options:
10
+ - auto
11
+ - major
12
+ - minor
13
+ - patch
14
+ default: auto
15
+ permissions:
16
+ contents: read
17
+ env:
18
+ FORCE_COLOR: 1
19
+ jobs:
20
+ assemble:
21
+ if: github.ref == 'refs/heads/main'
22
+ runs-on: ubuntu-24.04
23
+ permissions:
24
+ contents: read
25
+ outputs:
26
+ version: ${{ steps.version.outputs.version }}
27
+ steps:
28
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29
+ with:
30
+ fetch-depth: 0 # The next version comes from the tags.
31
+ persist-credentials: false
32
+ - name: Install uv
33
+ uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
34
+ with:
35
+ python-version: "3.15"
36
+ enable-cache: false
37
+ - name: Require a news fragment
38
+ run: |
39
+ if ! compgen -G 'docs/changelog/*.*.rst' > /dev/null; then
40
+ echo '::error::add a docs/changelog/*.rst fragment before preparing a release'
41
+ exit 1
42
+ fi
43
+ - name: Set up remote tracking
44
+ run: |
45
+ git remote -v
46
+ git fetch origin
47
+ git branch -a
48
+ - name: Calculate next version
49
+ id: version
50
+ env:
51
+ BUMP: ${{ inputs.bump }}
52
+ run: |
53
+ current=$(git describe --tags --abbrev=0 2>/dev/null || echo "0.0.0")
54
+ echo "Current version: $current"
55
+
56
+ IFS='.' read -r major minor patch <<< "$current"
57
+
58
+ bump_type="$BUMP"
59
+ if [ "$bump_type" = "auto" ]; then
60
+ feature_count=$(find docs/changelog -name "*.feature.rst" 2>/dev/null | wc -l)
61
+ breaking_count=$(find docs/changelog -name "*.breaking.rst" 2>/dev/null | wc -l)
62
+ if [ "$breaking_count" -gt 0 ]; then
63
+ bump_type="major"
64
+ echo "Auto-detected: major bump (found $breaking_count breaking changelog(s))"
65
+ elif [ "$feature_count" -gt 0 ]; then
66
+ bump_type="minor"
67
+ echo "Auto-detected: minor bump (found $feature_count feature changelog(s))"
68
+ else
69
+ bump_type="patch"
70
+ echo "Auto-detected: patch bump (no feature changelogs)"
71
+ fi
72
+ fi
73
+
74
+ case "$bump_type" in
75
+ major)
76
+ next="$((major + 1)).0.0"
77
+ ;;
78
+ minor)
79
+ next="$major.$((minor + 1)).0"
80
+ ;;
81
+ patch)
82
+ next="$major.$minor.$((patch + 1))"
83
+ ;;
84
+ esac
85
+
86
+ echo "Next version: $next"
87
+ echo "version=$next" >> "$GITHUB_OUTPUT"
88
+ - name: Build release patch
89
+ env:
90
+ VERSION: ${{ steps.version.outputs.version }}
91
+ run: |
92
+ set -euo pipefail
93
+ uvx towncrier build --yes --version "$VERSION"
94
+ uvx --with pre-commit-uv pre-commit run --files docs/changelog.rst ||
95
+ uvx --with pre-commit-uv pre-commit run --files docs/changelog.rst
96
+ git diff HEAD --binary > "$RUNNER_TEMP/release.patch"
97
+ test -s "$RUNNER_TEMP/release.patch"
98
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
99
+ with:
100
+ name: release-patch
101
+ path: ${{ runner.temp }}/release.patch
102
+ if-no-files-found: error
103
+ push:
104
+ needs: assemble
105
+ runs-on: ubuntu-24.04
106
+ environment: release-auth
107
+ permissions:
108
+ contents: read
109
+ steps:
110
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
111
+ with:
112
+ fetch-depth: 0
113
+ persist-credentials: false
114
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
115
+ with:
116
+ name: release-patch
117
+ path: ${{ runner.temp }}/release-patch
118
+ - name: Validate release patch
119
+ env:
120
+ VERSION: ${{ needs.assemble.outputs.version }}
121
+ run: |
122
+ set -euo pipefail
123
+ git fetch origin main
124
+ git reset --hard origin/main
125
+ git apply --check "$RUNNER_TEMP/release-patch/release.patch"
126
+ git apply --index "$RUNNER_TEMP/release-patch/release.patch"
127
+ while IFS= read -r path; do
128
+ case "$path" in
129
+ docs/changelog.rst|docs/changelog/*.rst) ;;
130
+ *) echo "::error::unexpected release path: $path"; exit 1 ;;
131
+ esac
132
+ done < <(git diff --cached --name-only)
133
+ grep -qE "^ v${VERSION//./\\.} \(" docs/changelog.rst
134
+ git diff --cached --check
135
+ - id: token
136
+ uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
137
+ with:
138
+ client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
139
+ private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
140
+ owner: ${{ github.repository_owner }}
141
+ repositories: ${{ github.event.repository.name }}
142
+ permission-contents: write
143
+ - name: Commit and tag the release
144
+ env:
145
+ VERSION: ${{ needs.assemble.outputs.version }}
146
+ GH_TOKEN: ${{ steps.token.outputs.token }}
147
+ APP_SLUG: ${{ steps.token.outputs.app-slug }}
148
+ run: |
149
+ set -euo pipefail
150
+ app_user_id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)
151
+ git config user.name "${APP_SLUG}[bot]"
152
+ git config user.email "${app_user_id}+${APP_SLUG}[bot]@users.noreply.github.com"
153
+ git commit -m "release $VERSION"
154
+ git tag -a "$VERSION" -m "release $VERSION"
155
+ gh auth setup-git --hostname github.com --force
156
+ git push --atomic origin HEAD:main "refs/tags/$VERSION"
@@ -0,0 +1,105 @@
1
+ name: Release to PyPI
2
+ on:
3
+ push:
4
+ tags: ["*"]
5
+ permissions:
6
+ contents: read
7
+ env:
8
+ dists-artifact-name: python-package-distributions
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-26.04
12
+ steps:
13
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
14
+ with:
15
+ fetch-depth: 0
16
+ persist-credentials: false
17
+ - name: Require a changelog section for the tag
18
+ env:
19
+ TAG: ${{ github.ref_name }}
20
+ run: |
21
+ if [[ ! "$TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
22
+ echo "::error::unexpected release tag $TAG"
23
+ exit 1
24
+ fi
25
+ if ! grep -qE "^ v${TAG//./\\.} \\(" docs/changelog.rst; then
26
+ echo "::error::docs/changelog.rst has no section for $TAG"
27
+ exit 1
28
+ fi
29
+ - name: Install the latest version of uv
30
+ uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
31
+ with:
32
+ python-version: "3.15"
33
+ enable-cache: false
34
+ cache-dependency-glob: "pyproject.toml"
35
+ github-token: ${{ secrets.GITHUB_TOKEN }}
36
+ - name: Build package
37
+ run: uv build --python 3.15 --python-preference only-managed --sdist --wheel . --out-dir dist
38
+ - name: Store the distribution packages
39
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
40
+ with:
41
+ name: ${{ env.dists-artifact-name }}
42
+ path: dist/*
43
+ release:
44
+ needs:
45
+ - build
46
+ runs-on: ubuntu-26.04
47
+ environment:
48
+ name: release
49
+ url: https://pypi.org/project/python-discovery/${{ github.ref_name }}
50
+ permissions:
51
+ id-token: write
52
+ steps:
53
+ - name: Download all the dists
54
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
55
+ with:
56
+ name: ${{ env.dists-artifact-name }}
57
+ path: dist/
58
+ - name: Publish to PyPI
59
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
60
+ with:
61
+ attestations: true
62
+ github-release:
63
+ needs: release
64
+ runs-on: ubuntu-26.04
65
+ permissions:
66
+ contents: write
67
+ steps:
68
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
69
+ with:
70
+ fetch-depth: 0
71
+ persist-credentials: false
72
+ - name: Publish GitHub Release
73
+ env:
74
+ GH_TOKEN: ${{ github.token }}
75
+ TAG: ${{ github.ref_name }}
76
+ run: |
77
+ set -euo pipefail
78
+ awk -v tag="$TAG" '
79
+ index($0, " v" tag " (") == 1 { found = 1; next }
80
+ found && /^ v[0-9]+\.[0-9]+\.[0-9]+ \(/ { exit }
81
+ found && $0 !~ /^\*+$/ { print }
82
+ ' docs/changelog.rst > "$RUNNER_TEMP/release-notes.md"
83
+ python3 - "$RUNNER_TEMP/release-notes.md" <<'PY'
84
+ import re
85
+ import sys
86
+ from pathlib import Path
87
+
88
+ path = Path(sys.argv[1])
89
+ body = path.read_text().strip()
90
+ if not body:
91
+ raise SystemExit("release notes are empty")
92
+ body = re.sub(r":user:`([^`]+)`", r"\1", body)
93
+ body = re.sub(r":(?:issue|pull):`(\d+)`", r"#\1", body)
94
+ body = re.sub(r":[a-z]+:`~?([^`]+)`", lambda match: f"`{match.group(1).split('.')[-1]}`", body)
95
+ path.write_text(body.replace("``", "`") + "\n")
96
+ PY
97
+ test -s "$RUNNER_TEMP/release-notes.md"
98
+ printf '\n[PyPI release](https://pypi.org/project/python-discovery/%s/)\n' "$TAG" >> "$RUNNER_TEMP/release-notes.md"
99
+ previous=$(git tag --list '[0-9]*' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | awk -v tag="$TAG" '$0 == tag { print previous; exit } { previous = $0 }')
100
+ if [[ -n "$previous" ]]; then
101
+ printf '\n**Full Changelog**: https://github.com/%s/compare/%s...%s\n' "$GITHUB_REPOSITORY" "$previous" "$TAG" >> "$RUNNER_TEMP/release-notes.md"
102
+ else
103
+ printf '\n**Full Changelog**: https://github.com/%s/commits/%s\n' "$GITHUB_REPOSITORY" "$TAG" >> "$RUNNER_TEMP/release-notes.md"
104
+ fi
105
+ gh release create "$TAG" --verify-tag --title "v$TAG" --notes-file "$RUNNER_TEMP/release-notes.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-discovery
3
- Version: 1.6.3
3
+ Version: 1.6.4
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.6.4 (2026-10-19)
11
+ ********************
12
+
13
+ Improved documentation - 1.6.4
14
+ ==============================
15
+ - Clarify the executable paths accepted by ``try_first_with`` in the standalone usage example. (:issue:`150`)
16
+
17
+ ********************
18
+ v1.6.3 (2026-10-16)
19
+ ********************
20
+
21
+ Bug fixes - 1.6.3
22
+ =================
23
+ - Treat specs with dot-only version components, such as ``python.``, as literal executable paths instead of raising
24
+ ``IndexError`` - by :user:`harbinresearcher`. (:issue:`147`)
25
+ - Pass PEP 514 ``ExecutableArguments`` to registered Windows interpreters when querying them, so a registration that
26
+ points at a launcher such as ``py.exe -3.11`` reports the interpreter it selects - by :user:`blouirts123`.
27
+ (:issue:`148`)
28
+
9
29
  ********************
10
30
  v1.6.2 (2026-10-10)
11
31
  ********************
@@ -61,6 +81,14 @@ Improved documentation - 1.5.3
61
81
  ==============================
62
82
  - Document the version floors: runs on Python 3.8+, discovers interpreters down to 3.6 - by :user:`gaborbernat`. (:issue:`116`)
63
83
 
84
+ ********************
85
+ v1.5.2 (2026-08-12)
86
+ ********************
87
+
88
+ Bug fixes - 1.5.2
89
+ =================
90
+ - Move interpreter interrogation into a Python 3.6-compatible collector. (:issue:`117`)
91
+
64
92
  ********************
65
93
  v1.5.1 (2026-07-31)
66
94
  ********************
@@ -1,10 +1,10 @@
1
1
  How-to guides
2
2
  =============
3
3
 
4
- Search specific directories first
5
- -----------------------------------
4
+ Try specific executables first
5
+ ------------------------------
6
6
 
7
- If you know a likely location for the interpreter, pass it via ``try_first_with`` to check there
7
+ If you know the path to an interpreter executable, pass it via ``try_first_with`` to check it
8
8
  before the normal search. This is useful when you have a custom Python install outside the
9
9
  standard locations.
10
10
 
@@ -12,7 +12,7 @@ standard locations.
12
12
 
13
13
  from python_discovery import get_interpreter
14
14
 
15
- info = get_interpreter("python3.12", try_first_with=["/opt/python/bin"])
15
+ info = get_interpreter("python3.12", try_first_with=["/opt/python/bin/python3.12"])
16
16
  if info is not None:
17
17
  print(info.executable)
18
18
 
@@ -228,5 +228,4 @@ type = [
228
228
  { directory = "doc", name = "Improved documentation", showcontent = true },
229
229
  { directory = "packaging", name = "Packaging updates and notes for downstreams", showcontent = true },
230
230
  { directory = "contrib", name = "Contributor-facing changes", showcontent = true },
231
- { directory = "misc", name = "Miscellaneous internal changes", showcontent = true },
232
231
  ]
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from subprocess import CalledProcessError, check_call, run
5
+ from typing import Final
6
+
7
+ from git import Commit, Remote, Repo
8
+ from packaging.version import Version
9
+
10
+ _ROOT_SRC_DIR: Final[Path] = Path(__file__).parents[1]
11
+
12
+
13
+ def main(version_str: str) -> None:
14
+ version = Version(version_str)
15
+ repo = Repo(str(_ROOT_SRC_DIR))
16
+
17
+ if repo.is_dirty():
18
+ msg = "Current repository is dirty. Please commit any changes and try again."
19
+ raise RuntimeError(msg)
20
+ upstream = get_upstream(repo)
21
+ upstream.fetch()
22
+ if str(version) in {tag.name for tag in repo.tags}:
23
+ msg = f"release tag {version} already exists"
24
+ raise RuntimeError(msg)
25
+ repo.git.checkout("-B", f"release-{version}", upstream.refs.main)
26
+ release_commit = release_changelog(repo, version)
27
+ repo.create_tag(str(version), ref=release_commit.hexsha, message=f"release {version}")
28
+ repo.git.push(upstream.name, "HEAD:main")
29
+ repo.git.push(upstream.name, f"refs/tags/{version}")
30
+ create_github_release(version)
31
+
32
+
33
+ def get_upstream(repo: Repo) -> Remote:
34
+ for remote in repo.remotes:
35
+ if any("tox-dev/python-discovery" in url for url in remote.urls):
36
+ return remote
37
+ msg = "could not find tox-dev/python-discovery remote"
38
+ raise RuntimeError(msg)
39
+
40
+
41
+ def release_changelog(repo: Repo, version: Version) -> Commit:
42
+ print("generate release commit") # ruff:ignore[print]
43
+ check_call(["towncrier", "build", "--yes", "--version", version.public], cwd=str(_ROOT_SRC_DIR)) # ruff:ignore[start-process-with-partial-path]
44
+ print("format changelog with pre-commit") # ruff:ignore[print]
45
+ changelog_path = _ROOT_SRC_DIR / "docs" / "changelog.rst"
46
+ try:
47
+ check_call(["pre-commit", "run", "--files", str(changelog_path)], cwd=str(_ROOT_SRC_DIR)) # ruff:ignore[start-process-with-partial-path]
48
+ except CalledProcessError:
49
+ check_call(["pre-commit", "run", "--files", str(changelog_path)], cwd=str(_ROOT_SRC_DIR)) # ruff:ignore[start-process-with-partial-path]
50
+ repo.git.add("-A", "--", "docs/changelog.rst", "docs/changelog")
51
+ return repo.index.commit(f"release {version}")
52
+
53
+
54
+ def create_github_release(version: Version) -> None:
55
+ print("create github release") # ruff:ignore[print]
56
+ version_str = str(version)
57
+ try:
58
+ result = run(
59
+ ["gh", "release", "create", version_str, "--title", f"v{version_str}", "--generate-notes"], # ruff:ignore[start-process-with-partial-path]
60
+ cwd=str(_ROOT_SRC_DIR),
61
+ capture_output=True,
62
+ text=True,
63
+ check=True,
64
+ )
65
+ if result.stdout:
66
+ print(result.stdout) # ruff:ignore[print]
67
+ except CalledProcessError as e:
68
+ print(f"gh release create failed with exit code {e.returncode}") # ruff:ignore[print]
69
+ if e.stdout:
70
+ print(f"stdout: {e.stdout}") # ruff:ignore[print]
71
+ if e.stderr:
72
+ print(f"stderr: {e.stderr}") # ruff:ignore[print]
73
+ raise
74
+
75
+
76
+ if __name__ == "__main__":
77
+ import argparse
78
+
79
+ parser = argparse.ArgumentParser(prog="release")
80
+ parser.add_argument("--version", required=True)
81
+ options = parser.parse_args()
82
+ main(options.version)
@@ -1,92 +0,0 @@
1
- name: Prepare Release
2
- on:
3
- workflow_dispatch:
4
- inputs:
5
- bump:
6
- description: "Version bump type"
7
- required: true
8
- type: choice
9
- options:
10
- - auto
11
- - major
12
- - minor
13
- - patch
14
- default: auto
15
- permissions:
16
- contents: read
17
- env:
18
- FORCE_COLOR: 1
19
- jobs:
20
- prepare-release:
21
- runs-on: ubuntu-24.04
22
- environment: release-auth
23
- permissions:
24
- contents: write
25
- steps:
26
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27
- with:
28
- fetch-depth: 0
29
- token: ${{ secrets.RELEASE_PAT }}
30
- persist-credentials: true
31
- - name: Install the latest version of uv
32
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
33
- with:
34
- python-version: "3.15"
35
- enable-cache: true
36
- cache-dependency-glob: "pyproject.toml"
37
- github-token: ${{ secrets.GITHUB_TOKEN }}
38
- - name: Configure git
39
- run: |
40
- git config user.name "${GITHUB_ACTOR}"
41
- git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
42
- - name: Calculate next version
43
- id: version
44
- env:
45
- BUMP: ${{ inputs.bump }}
46
- run: |
47
- current=$(git describe --tags --abbrev=0 2>/dev/null || echo "0.0.0")
48
- echo "Current version: $current"
49
-
50
- IFS='.' read -r major minor patch <<< "$current"
51
-
52
- bump_type="$BUMP"
53
- if [ "$bump_type" = "auto" ]; then
54
- feature_count=$(find docs/changelog -name "*.feature.rst" 2>/dev/null | wc -l)
55
- breaking_count=$(find docs/changelog -name "*.breaking.rst" 2>/dev/null | wc -l)
56
- if [ "$breaking_count" -gt 0 ]; then
57
- bump_type="major"
58
- echo "Auto-detected: major bump (found $breaking_count breaking changelog(s))"
59
- elif [ "$feature_count" -gt 0 ]; then
60
- bump_type="minor"
61
- echo "Auto-detected: minor bump (found $feature_count feature changelog(s))"
62
- else
63
- bump_type="patch"
64
- echo "Auto-detected: patch bump (no feature changelogs)"
65
- fi
66
- fi
67
-
68
- case "$bump_type" in
69
- major)
70
- next="$((major + 1)).0.0"
71
- ;;
72
- minor)
73
- next="$major.$((minor + 1)).0"
74
- ;;
75
- patch)
76
- next="$major.$minor.$((patch + 1))"
77
- ;;
78
- esac
79
-
80
- echo "Next version: $next"
81
- echo "version=$next" >> $GITHUB_OUTPUT
82
- - name: Install tox
83
- run: uv tool install --python-preference only-managed --python 3.15 tox@latest --with tox-uv
84
- - name: Run release process
85
- run: tox run -e release -- ${STEPS_VERSION_OUTPUTS_VERSION}
86
- env:
87
- GH_TOKEN: ${{ secrets.RELEASE_PAT }}
88
- STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
89
- - name: Display completion message
90
- run: echo "Release ${STEPS_VERSION_OUTPUTS_VERSION} prepared and pushed successfully!"
91
- env:
92
- STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
@@ -1,49 +0,0 @@
1
- name: Release to PyPI
2
- on:
3
- push:
4
- tags: ["*"]
5
- permissions:
6
- contents: read
7
- env:
8
- dists-artifact-name: python-package-distributions
9
- jobs:
10
- build:
11
- runs-on: ubuntu-26.04
12
- steps:
13
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
14
- with:
15
- fetch-depth: 0
16
- persist-credentials: false
17
- - name: Install the latest version of uv
18
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
19
- with:
20
- python-version: "3.15"
21
- enable-cache: false
22
- cache-dependency-glob: "pyproject.toml"
23
- github-token: ${{ secrets.GITHUB_TOKEN }}
24
- - name: Build package
25
- run: uv build --python 3.15 --python-preference only-managed --sdist --wheel . --out-dir dist
26
- - name: Store the distribution packages
27
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
28
- with:
29
- name: ${{ env.dists-artifact-name }}
30
- path: dist/*
31
- release:
32
- needs:
33
- - build
34
- runs-on: ubuntu-26.04
35
- environment:
36
- name: release
37
- url: https://pypi.org/project/python-discovery/${{ github.ref_name }}
38
- permissions:
39
- id-token: write
40
- steps:
41
- - name: Download all the dists
42
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
43
- with:
44
- name: ${{ env.dists-artifact-name }}
45
- path: dist/
46
- - name: Publish to PyPI
47
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
48
- with:
49
- attestations: true
@@ -1,2 +0,0 @@
1
- Treat specs with dot-only version components, such as ``python.``, as literal executable paths instead of raising
2
- ``IndexError`` - by :user:`harbinresearcher`.
@@ -1,2 +0,0 @@
1
- Pass PEP 514 ``ExecutableArguments`` to registered Windows interpreters when querying them, so a registration that
2
- points at a launcher such as ``py.exe -3.11`` reports the interpreter it selects - by :user:`blouirts123`.
@@ -1,173 +0,0 @@
1
- """Handles creating a release commit and tag, then publishes the GitHub release."""
2
-
3
- from __future__ import annotations
4
-
5
- from pathlib import Path
6
- from subprocess import CalledProcessError, check_call, run
7
-
8
- from git import Commit, Head, Remote, Repo, TagReference
9
- from packaging.version import Version
10
-
11
- ROOT_SRC_DIR = Path(__file__).parents[1]
12
-
13
-
14
- def main(version_str: str) -> None:
15
- version = Version(version_str)
16
- repo = Repo(str(ROOT_SRC_DIR))
17
-
18
- if repo.is_dirty():
19
- msg = "Current repository is dirty. Please commit any changes and try again."
20
- raise RuntimeError(msg)
21
- upstream, release_branch = create_release_branch(repo, version)
22
- original_main_sha = upstream.refs.main.commit.hexsha
23
- main_pushed = False
24
- tag_pushed = False
25
- release_created = False
26
- try:
27
- main_pushed, tag_pushed, release_created = push_release(repo, upstream, release_branch, version)
28
- finalize_release(repo, upstream, release_branch)
29
- except Exception:
30
- cleanup_failed_release(
31
- repo,
32
- upstream,
33
- version,
34
- release_branch,
35
- original_main_sha,
36
- release_created=release_created,
37
- tag_pushed=tag_pushed,
38
- main_pushed=main_pushed,
39
- )
40
- raise
41
-
42
-
43
- def create_release_branch(repo: Repo, version: Version) -> tuple[Remote, Head]:
44
- print("create release branch from upstream main") # ruff:ignore[print]
45
- upstream = get_upstream(repo)
46
- upstream.fetch()
47
- branch_name = f"release-{version}"
48
- release_branch = repo.create_head(branch_name, upstream.refs.main, force=True)
49
- upstream.push(refspec=f"{branch_name}:{branch_name}", force=True)
50
- release_branch.set_tracking_branch(upstream.refs[branch_name])
51
- release_branch.checkout()
52
- return upstream, release_branch
53
-
54
-
55
- def get_upstream(repo: Repo) -> Remote:
56
- for remote in repo.remotes:
57
- if any("tox-dev/python-discovery" in url for url in remote.urls):
58
- return remote
59
- msg = "could not find tox-dev/python-discovery remote"
60
- raise RuntimeError(msg)
61
-
62
-
63
- def push_release(repo: Repo, upstream: Remote, release_branch: Head, version: Version) -> tuple[bool, bool, bool]:
64
- release_commit = release_changelog(repo, version)
65
- tag = tag_release_commit(release_commit, repo, version)
66
- print("push release commit") # ruff:ignore[print]
67
- repo.git.push(upstream.name, f"{release_branch}:main", "-f")
68
- print("push release tag") # ruff:ignore[print]
69
- repo.git.push(upstream.name, tag, "-f")
70
- create_github_release(version)
71
- return True, True, True
72
-
73
-
74
- def release_changelog(repo: Repo, version: Version) -> Commit:
75
- print("generate release commit") # ruff:ignore[print]
76
- check_call(["towncrier", "build", "--yes", "--version", version.public], cwd=str(ROOT_SRC_DIR)) # ruff:ignore[start-process-with-partial-path]
77
- print("format changelog with pre-commit") # ruff:ignore[print]
78
- changelog_path = ROOT_SRC_DIR / "docs" / "changelog.rst"
79
- try:
80
- check_call(["pre-commit", "run", "--files", str(changelog_path)], cwd=str(ROOT_SRC_DIR)) # ruff:ignore[start-process-with-partial-path]
81
- except CalledProcessError:
82
- print("pre-commit made formatting changes, staging them") # ruff:ignore[print]
83
- repo.index.add([str(changelog_path)])
84
- return repo.index.commit(f"release {version}")
85
-
86
-
87
- def tag_release_commit(release_commit: Commit, repo: Repo, version: Version) -> TagReference:
88
- print("tag release commit") # ruff:ignore[print]
89
- version_str = str(version)
90
- if version_str in {tag.name for tag in repo.tags}:
91
- print(f"delete existing tag {version_str}") # ruff:ignore[print]
92
- repo.delete_tag(repo.tags[version_str])
93
- print(f"create tag {version_str}") # ruff:ignore[print]
94
- return repo.create_tag(version_str, ref=release_commit.hexsha, force=True)
95
-
96
-
97
- def create_github_release(version: Version) -> None:
98
- print("create github release") # ruff:ignore[print]
99
- version_str = str(version)
100
- try:
101
- result = run(
102
- ["gh", "release", "create", version_str, "--title", f"v{version_str}", "--generate-notes"], # ruff:ignore[start-process-with-partial-path]
103
- cwd=str(ROOT_SRC_DIR),
104
- capture_output=True,
105
- text=True,
106
- check=True,
107
- )
108
- if result.stdout:
109
- print(result.stdout) # ruff:ignore[print]
110
- except CalledProcessError as e:
111
- print(f"gh release create failed with exit code {e.returncode}") # ruff:ignore[print]
112
- if e.stdout:
113
- print(f"stdout: {e.stdout}") # ruff:ignore[print]
114
- if e.stderr:
115
- print(f"stderr: {e.stderr}") # ruff:ignore[print]
116
- raise
117
-
118
-
119
- def finalize_release(repo: Repo, upstream: Remote, release_branch: Head) -> None:
120
- print("checkout main to new release and delete release branch") # ruff:ignore[print]
121
- repo.heads.main.checkout()
122
- repo.delete_head(release_branch, force=True)
123
- print("delete remote release branch") # ruff:ignore[print]
124
- repo.git.push(upstream.name, f":{release_branch}", "--no-verify")
125
- upstream.fetch()
126
- repo.git.reset("--hard", f"{upstream.name}/main")
127
- print("All done!") # ruff:ignore[print]
128
-
129
-
130
- def cleanup_failed_release( # ruff:ignore[too-many-arguments]
131
- repo: Repo,
132
- upstream: Remote,
133
- version: Version,
134
- release_branch: Head,
135
- original_main_sha: str,
136
- *,
137
- release_created: bool,
138
- tag_pushed: bool,
139
- main_pushed: bool,
140
- ) -> None:
141
- print("Release failed! Cleaning up...") # ruff:ignore[print]
142
- if release_created:
143
- print(f"Deleting GitHub release {version}") # ruff:ignore[print]
144
- try:
145
- check_call(["gh", "release", "delete", str(version), "--yes"], cwd=str(ROOT_SRC_DIR)) # ruff:ignore[start-process-with-partial-path]
146
- except Exception as cleanup_error: # ruff:ignore[blind-except]
147
- print(f"Warning: Failed to delete GitHub release: {cleanup_error}") # ruff:ignore[print]
148
- if tag_pushed:
149
- print(f"Deleting remote tag {version}") # ruff:ignore[print]
150
- try:
151
- repo.git.push(upstream.name, f":refs/tags/{version}", "--no-verify")
152
- except Exception as cleanup_error: # ruff:ignore[blind-except]
153
- print(f"Warning: Failed to delete remote tag: {cleanup_error}") # ruff:ignore[print]
154
- if main_pushed:
155
- print(f"Reverting main to {original_main_sha[:8]}") # ruff:ignore[print]
156
- try:
157
- repo.git.push(upstream.name, f"{original_main_sha}:main", "-f", "--no-verify")
158
- except Exception as cleanup_error: # ruff:ignore[blind-except]
159
- print(f"Warning: Failed to revert main: {cleanup_error}") # ruff:ignore[print]
160
- print("Deleting remote release branch") # ruff:ignore[print]
161
- try:
162
- repo.git.push(upstream.name, f":{release_branch}", "--no-verify")
163
- except Exception as cleanup_error: # ruff:ignore[blind-except]
164
- print(f"Warning: Failed to delete remote branch: {cleanup_error}") # ruff:ignore[print]
165
-
166
-
167
- if __name__ == "__main__":
168
- import argparse
169
-
170
- parser = argparse.ArgumentParser(prog="release")
171
- parser.add_argument("--version", required=True)
172
- options = parser.parse_args()
173
- main(options.version)