python-discovery 1.3.0__tar.gz → 1.3.2__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.3.2/.github/workflows/prepare-release.yaml +100 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/.pre-commit-config.yaml +3 -3
- {python_discovery-1.3.0 → python_discovery-1.3.2}/PKG-INFO +3 -1
- python_discovery-1.3.2/docs/changelog/template.jinja2 +38 -0
- python_discovery-1.3.2/docs/changelog.rst +132 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/docs/conf.py +7 -1
- {python_discovery-1.3.0 → python_discovery-1.3.2}/docs/index.rst +6 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/pyproject.toml +34 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/_discovery.py +9 -7
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/_py_info.py +29 -31
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/_py_spec.py +5 -1
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/_windows/_pep514.py +11 -11
- python_discovery-1.3.2/tasks/release.py +173 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/test_py_info_extra.py +6 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/test_py_spec.py +14 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tox.toml +9 -2
- python_discovery-1.3.0/docs/changelog/59.bugfix.rst +0 -1
- python_discovery-1.3.0/docs/changelog/65.bugfix.rst +0 -3
- python_discovery-1.3.0/docs/changelog/65.feature.rst +0 -3
- {python_discovery-1.3.0 → python_discovery-1.3.2}/.github/FUNDING.yaml +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/.github/SECURITY.md +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/.github/dependabot.yaml +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/.github/release.yaml +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/.github/workflows/check.yaml +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/.github/workflows/release.yaml +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/.gitignore +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/.readthedocs.yaml +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/.readthedocs.yml +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/CODE_OF_CONDUCT.md +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/LICENSE +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/README.md +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/docs/_static/custom.css +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/docs/_static/logo.svg +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/docs/explanation.rst +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/docs/how-to/standalone-usage.rst +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/docs/reference/api.rst +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/docs/reference/environment-variables.rst +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/docs/tutorial/getting-started.rst +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/__init__.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/_cache.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/_cached_py_info.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/_compat.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/_specifier.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/_windows/__init__.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/_windows/_propose.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/src/python_discovery/py.typed +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/conftest.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/py_info/test_py_info.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/py_info/test_py_info_exe_based_of.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/test_cache.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/test_cached_py_info.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/test_discovery.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/test_discovery_extra.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/test_iter_interpreters.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/test_py_spec_extra.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/test_specifier.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/windows/conftest.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/windows/test_windows.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/windows/test_windows_pep514.py +0 -0
- {python_discovery-1.3.0 → python_discovery-1.3.2}/tests/windows/winreg_mock_values.py +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
env:
|
|
20
|
+
FORCE_COLOR: 1
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
prepare-release:
|
|
24
|
+
runs-on: ubuntu-24.04
|
|
25
|
+
environment: release-auth
|
|
26
|
+
permissions:
|
|
27
|
+
contents: write
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
token: ${{ secrets.RELEASE_PAT }}
|
|
33
|
+
persist-credentials: true
|
|
34
|
+
|
|
35
|
+
- name: Install the latest version of uv
|
|
36
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
37
|
+
with:
|
|
38
|
+
enable-cache: true
|
|
39
|
+
cache-dependency-glob: "pyproject.toml"
|
|
40
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
|
|
42
|
+
- name: Configure git
|
|
43
|
+
run: |
|
|
44
|
+
git config user.name "${GITHUB_ACTOR}"
|
|
45
|
+
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
|
|
46
|
+
|
|
47
|
+
- name: Calculate next version
|
|
48
|
+
id: version
|
|
49
|
+
env:
|
|
50
|
+
BUMP: ${{ inputs.bump }}
|
|
51
|
+
run: |
|
|
52
|
+
current=$(git describe --tags --abbrev=0 2>/dev/null || echo "0.0.0")
|
|
53
|
+
echo "Current version: $current"
|
|
54
|
+
|
|
55
|
+
IFS='.' read -r major minor patch <<< "$current"
|
|
56
|
+
|
|
57
|
+
bump_type="$BUMP"
|
|
58
|
+
if [ "$bump_type" = "auto" ]; then
|
|
59
|
+
feature_count=$(find docs/changelog -name "*.feature.rst" 2>/dev/null | wc -l)
|
|
60
|
+
breaking_count=$(find docs/changelog -name "*.breaking.rst" 2>/dev/null | wc -l)
|
|
61
|
+
if [ "$breaking_count" -gt 0 ]; then
|
|
62
|
+
bump_type="major"
|
|
63
|
+
echo "Auto-detected: major bump (found $breaking_count breaking changelog(s))"
|
|
64
|
+
elif [ "$feature_count" -gt 0 ]; then
|
|
65
|
+
bump_type="minor"
|
|
66
|
+
echo "Auto-detected: minor bump (found $feature_count feature changelog(s))"
|
|
67
|
+
else
|
|
68
|
+
bump_type="patch"
|
|
69
|
+
echo "Auto-detected: patch bump (no feature changelogs)"
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
case "$bump_type" in
|
|
74
|
+
major)
|
|
75
|
+
next="$((major + 1)).0.0"
|
|
76
|
+
;;
|
|
77
|
+
minor)
|
|
78
|
+
next="$major.$((minor + 1)).0"
|
|
79
|
+
;;
|
|
80
|
+
patch)
|
|
81
|
+
next="$major.$minor.$((patch + 1))"
|
|
82
|
+
;;
|
|
83
|
+
esac
|
|
84
|
+
|
|
85
|
+
echo "Next version: $next"
|
|
86
|
+
echo "version=$next" >> $GITHUB_OUTPUT
|
|
87
|
+
|
|
88
|
+
- name: Install tox
|
|
89
|
+
run: uv tool install --python-preference only-managed --python 3.14 tox@latest --with tox-uv
|
|
90
|
+
|
|
91
|
+
- name: Run release process
|
|
92
|
+
run: tox run -e release -- ${STEPS_VERSION_OUTPUTS_VERSION}
|
|
93
|
+
env:
|
|
94
|
+
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
|
95
|
+
STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
|
|
96
|
+
|
|
97
|
+
- name: Display completion message
|
|
98
|
+
run: echo "Release ${STEPS_VERSION_OUTPUTS_VERSION} prepared and pushed successfully!"
|
|
99
|
+
env:
|
|
100
|
+
STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
|
|
@@ -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.21.
|
|
18
|
+
rev: "v2.21.2"
|
|
19
19
|
hooks:
|
|
20
20
|
- id: pyproject-fmt
|
|
21
21
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
22
|
-
rev: "v0.15.
|
|
22
|
+
rev: "v0.15.14"
|
|
23
23
|
hooks:
|
|
24
24
|
- id: ruff-format
|
|
25
25
|
- id: ruff
|
|
@@ -32,7 +32,7 @@ repos:
|
|
|
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.
|
|
35
|
+
rev: v1.25.2
|
|
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.3.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -53,6 +53,8 @@ Requires-Dist: furo>=2025.12.19; extra == 'docs'
|
|
|
53
53
|
Requires-Dist: sphinx-autodoc-typehints>=3.6.3; extra == 'docs'
|
|
54
54
|
Requires-Dist: sphinx>=9.1; extra == 'docs'
|
|
55
55
|
Requires-Dist: sphinxcontrib-mermaid>=2; extra == 'docs'
|
|
56
|
+
Requires-Dist: sphinxcontrib-towncrier>=0.4; extra == 'docs'
|
|
57
|
+
Requires-Dist: towncrier>=25.8; extra == 'docs'
|
|
56
58
|
Provides-Extra: testing
|
|
57
59
|
Requires-Dist: covdefaults>=2.3; extra == 'testing'
|
|
58
60
|
Requires-Dist: coverage>=7.5.4; extra == 'testing'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{% set top_underline = "*" %}
|
|
2
|
+
{% set underline = "=" %}
|
|
3
|
+
{% if versiondata.name %}
|
|
4
|
+
{{ top_underline * ((versiondata.version + versiondata.date)|length + 5)}}
|
|
5
|
+
v{{ versiondata.version }} ({{ versiondata.date }})
|
|
6
|
+
{{ top_underline * ((versiondata.version + versiondata.date)|length + 5)}}
|
|
7
|
+
{% else %}
|
|
8
|
+
{{ top_underline * ((versiondata.version + versiondata.date)|length + 4)}}
|
|
9
|
+
{{ versiondata.version }} ({{ versiondata.date }})
|
|
10
|
+
{{ top_underline * ((versiondata.version + versiondata.date)|length + 4)}}
|
|
11
|
+
{% endif %}
|
|
12
|
+
|
|
13
|
+
{% for section, _ in sections.items() %}
|
|
14
|
+
{% if sections[section] %}
|
|
15
|
+
{% for category, val in definitions.items() if category in sections[section]%}
|
|
16
|
+
{{ definitions[category]['name'] }} - {{ versiondata.version }}
|
|
17
|
+
{{ underline * ((definitions[category]['name'] + versiondata.version)|length + 3)}}
|
|
18
|
+
{% if definitions[category]['showcontent'] %}
|
|
19
|
+
{% for text, values in sections[section][category].items() %}
|
|
20
|
+
- {{ text }} ({{ values|join(', ') }})
|
|
21
|
+
{% endfor %}
|
|
22
|
+
|
|
23
|
+
{% else %}
|
|
24
|
+
- {{ sections[section][category]['']|join(', ') }}
|
|
25
|
+
|
|
26
|
+
{% endif %}
|
|
27
|
+
{% if sections[section][category]|length == 0 %}
|
|
28
|
+
No significant changes.
|
|
29
|
+
|
|
30
|
+
{% else %}
|
|
31
|
+
{% endif %}
|
|
32
|
+
{% endfor %}
|
|
33
|
+
{% else %}
|
|
34
|
+
No significant changes.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
{% endif %}
|
|
38
|
+
{% endfor %}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#################
|
|
2
|
+
Release History
|
|
3
|
+
#################
|
|
4
|
+
|
|
5
|
+
.. towncrier-draft-entries::
|
|
6
|
+
|
|
7
|
+
.. towncrier release notes start
|
|
8
|
+
|
|
9
|
+
********************
|
|
10
|
+
v1.3.2 (2026-05-27)
|
|
11
|
+
********************
|
|
12
|
+
|
|
13
|
+
No significant changes.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
********************
|
|
17
|
+
v1.3.1 (2026-05-12)
|
|
18
|
+
********************
|
|
19
|
+
|
|
20
|
+
Bug fixes - 1.3.1
|
|
21
|
+
=================
|
|
22
|
+
- export normalize_isa and deprecate KNOWN_ARCHITECTURES - by :user:`rahuldevikar`. (:issue:`59`)
|
|
23
|
+
- discover uv-managed Pythons on Windows. Previously the glob assumed Unix layout (``<root>/<key>/bin/python``) and
|
|
24
|
+
silently found nothing on Windows, where uv places ``python.exe`` directly under the install root - by
|
|
25
|
+
:user:`gaborbernat`. (:issue:`65`)
|
|
26
|
+
- Canonicalize GraalVM to match GraalPy Python interpreter in PythonSpec and PythonInfo. - by :user:`timfel`. (:issue:`73`)
|
|
27
|
+
|
|
28
|
+
********************
|
|
29
|
+
v1.3.0 (2026-05-05)
|
|
30
|
+
********************
|
|
31
|
+
|
|
32
|
+
Features - 1.3.0
|
|
33
|
+
================
|
|
34
|
+
|
|
35
|
+
- Add :func:`~python_discovery.iter_interpreters` for enumerating every discovered interpreter, with PATH and
|
|
36
|
+
UV-install support for non-CPython implementations listed in :data:`~python_discovery.KNOWN_IMPLEMENTATIONS`
|
|
37
|
+
(:pull:`71`)
|
|
38
|
+
|
|
39
|
+
********************
|
|
40
|
+
v1.2.2 (2026-04-06)
|
|
41
|
+
********************
|
|
42
|
+
|
|
43
|
+
Features - 1.2.2
|
|
44
|
+
================
|
|
45
|
+
|
|
46
|
+
- Export ``normalize_isa`` and deprecate ``KNOWN_ARCHITECTURES`` (:pull:`62`)
|
|
47
|
+
|
|
48
|
+
********************
|
|
49
|
+
v1.2.1 (2026-03-26)
|
|
50
|
+
********************
|
|
51
|
+
|
|
52
|
+
Features - 1.2.1
|
|
53
|
+
================
|
|
54
|
+
|
|
55
|
+
- Expose ``KNOWN_ARCHITECTURES`` as public API (:pull:`56`)
|
|
56
|
+
|
|
57
|
+
Contributor-facing changes - 1.2.1
|
|
58
|
+
==================================
|
|
59
|
+
|
|
60
|
+
- Add zizmor security auditing for workflows (:pull:`55`)
|
|
61
|
+
|
|
62
|
+
********************
|
|
63
|
+
v1.2.0 (2026-03-18)
|
|
64
|
+
********************
|
|
65
|
+
|
|
66
|
+
Features - 1.2.0
|
|
67
|
+
================
|
|
68
|
+
|
|
69
|
+
- Increase interpreter query timeout to 15s, with an override (:pull:`53`)
|
|
70
|
+
|
|
71
|
+
********************
|
|
72
|
+
v1.1.3 (2026-03-10)
|
|
73
|
+
********************
|
|
74
|
+
|
|
75
|
+
Bug fixes - 1.1.3
|
|
76
|
+
=================
|
|
77
|
+
|
|
78
|
+
- Add ``loongarch64`` to known ISAs (:pull:`50`)
|
|
79
|
+
|
|
80
|
+
********************
|
|
81
|
+
v1.1.2 (2026-03-09)
|
|
82
|
+
********************
|
|
83
|
+
|
|
84
|
+
Bug fixes - 1.1.2
|
|
85
|
+
=================
|
|
86
|
+
|
|
87
|
+
- Match prerelease versions against ``major.minor`` specs (:pull:`48`)
|
|
88
|
+
- Drain pipes after killing a timed-out interpreter probe (:pull:`49`)
|
|
89
|
+
|
|
90
|
+
Improved documentation - 1.1.2
|
|
91
|
+
==============================
|
|
92
|
+
|
|
93
|
+
- Add package description and usage to the README (:pull:`46`)
|
|
94
|
+
|
|
95
|
+
********************
|
|
96
|
+
v1.1.1 (2026-03-06)
|
|
97
|
+
********************
|
|
98
|
+
|
|
99
|
+
Bug fixes - 1.1.1
|
|
100
|
+
=================
|
|
101
|
+
|
|
102
|
+
- Add a timeout to interpreter probing (:pull:`42`)
|
|
103
|
+
- Add ``i686`` to known ISAs (:pull:`43`)
|
|
104
|
+
|
|
105
|
+
Contributor-facing changes - 1.1.1
|
|
106
|
+
==================================
|
|
107
|
+
|
|
108
|
+
- Add a security policy and workflow permissions hardening (:pull:`33`)
|
|
109
|
+
|
|
110
|
+
********************
|
|
111
|
+
v1.1.0 (2026-02-26)
|
|
112
|
+
********************
|
|
113
|
+
|
|
114
|
+
Features - 1.1.0
|
|
115
|
+
================
|
|
116
|
+
|
|
117
|
+
- Add a ``predicate`` parameter to :func:`~python_discovery.get_interpreter` (:pull:`31`)
|
|
118
|
+
|
|
119
|
+
Improved documentation - 1.1.0
|
|
120
|
+
==============================
|
|
121
|
+
|
|
122
|
+
- Fix the ReadTheDocs build (:pull:`29`)
|
|
123
|
+
- Add ``:param:`` descriptions to all public APIs (:pull:`32`)
|
|
124
|
+
|
|
125
|
+
********************
|
|
126
|
+
v1.0.0 (2026-02-25)
|
|
127
|
+
********************
|
|
128
|
+
|
|
129
|
+
Features - 1.0.0
|
|
130
|
+
================
|
|
131
|
+
|
|
132
|
+
- Initial release as a standalone package, extracted from ``virtualenv`` (:pull:`28`)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
from python_discovery import __version__
|
|
8
9
|
|
|
@@ -19,8 +20,13 @@ extensions = [
|
|
|
19
20
|
"sphinx.ext.intersphinx",
|
|
20
21
|
"sphinx_autodoc_typehints",
|
|
21
22
|
"sphinxcontrib.mermaid",
|
|
23
|
+
"sphinxcontrib.towncrier.ext",
|
|
22
24
|
]
|
|
23
25
|
|
|
26
|
+
towncrier_draft_autoversion_mode = "draft"
|
|
27
|
+
towncrier_draft_include_empty = True
|
|
28
|
+
towncrier_draft_working_directory = Path(__file__).parent.parent
|
|
29
|
+
|
|
24
30
|
extlinks = {
|
|
25
31
|
"issue": ("https://github.com/tox-dev/python-discovery/issues/%s", "#%s"),
|
|
26
32
|
"pull": ("https://github.com/tox-dev/python-discovery/pull/%s", "PR #%s"),
|
|
@@ -33,7 +39,7 @@ intersphinx_mapping = {
|
|
|
33
39
|
|
|
34
40
|
templates_path = []
|
|
35
41
|
source_suffix = ".rst"
|
|
36
|
-
exclude_patterns = ["_build", "changelog
|
|
42
|
+
exclude_patterns = ["_build", "changelog/*"]
|
|
37
43
|
|
|
38
44
|
main_doc = "index"
|
|
39
45
|
pygments_style = "default"
|
|
@@ -50,6 +50,8 @@ optional-dependencies.docs = [
|
|
|
50
50
|
"sphinx>=9.1",
|
|
51
51
|
"sphinx-autodoc-typehints>=3.6.3",
|
|
52
52
|
"sphinxcontrib-mermaid>=2",
|
|
53
|
+
"sphinxcontrib-towncrier>=0.4",
|
|
54
|
+
"towncrier>=25.8",
|
|
53
55
|
]
|
|
54
56
|
optional-dependencies.testing = [
|
|
55
57
|
"covdefaults>=2.3",
|
|
@@ -64,6 +66,14 @@ urls.Homepage = "https://github.com/tox-dev/python-discovery"
|
|
|
64
66
|
urls.Source = "https://github.com/tox-dev/python-discovery"
|
|
65
67
|
urls.Tracker = "https://github.com/tox-dev/python-discovery/issues"
|
|
66
68
|
|
|
69
|
+
[dependency-groups]
|
|
70
|
+
release = [
|
|
71
|
+
"gitpython>=3.1.46",
|
|
72
|
+
"packaging>=26",
|
|
73
|
+
"pre-commit>=4.5.1",
|
|
74
|
+
"towncrier>=25.8",
|
|
75
|
+
]
|
|
76
|
+
|
|
67
77
|
[tool.hatch]
|
|
68
78
|
version.source = "vcs"
|
|
69
79
|
|
|
@@ -98,6 +108,12 @@ lint.per-file-ignores."src/python_discovery/_py_info.py" = [
|
|
|
98
108
|
lint.per-file-ignores."src/python_discovery/_windows/_pep514.py" = [
|
|
99
109
|
"PTH", # os.path.exists is monkeypatched in tests; pathlib.Path.exists bypasses the mock
|
|
100
110
|
]
|
|
111
|
+
lint.per-file-ignores."tasks/**/*.py" = [
|
|
112
|
+
"D", # release helper scripts, not API
|
|
113
|
+
"INP001", # no __init__.py in tasks directory
|
|
114
|
+
"S404", # subprocess import (release tooling)
|
|
115
|
+
"S603", # `subprocess` call: argv built from trusted inputs (version, gh CLI)
|
|
116
|
+
]
|
|
101
117
|
lint.per-file-ignores."tests/**/*.py" = [
|
|
102
118
|
"D", # don't care about documentation in tests
|
|
103
119
|
"FBT", # don't care about booleans as positional arguments in tests
|
|
@@ -180,3 +196,21 @@ report.partial_branches = [
|
|
|
180
196
|
report.show_missing = true
|
|
181
197
|
html.show_contexts = true
|
|
182
198
|
html.skip_covered = false
|
|
199
|
+
|
|
200
|
+
[tool.towncrier]
|
|
201
|
+
name = "python-discovery"
|
|
202
|
+
filename = "docs/changelog.rst"
|
|
203
|
+
directory = "docs/changelog"
|
|
204
|
+
title_format = false
|
|
205
|
+
issue_format = ":issue:`{issue}`"
|
|
206
|
+
template = "docs/changelog/template.jinja2"
|
|
207
|
+
type = [
|
|
208
|
+
{ directory = "breaking", name = "Backward incompatible changes", showcontent = true },
|
|
209
|
+
{ directory = "deprecation", name = "Deprecations (removal in next major release)", showcontent = true },
|
|
210
|
+
{ directory = "feature", name = "Features", showcontent = true },
|
|
211
|
+
{ directory = "bugfix", name = "Bug fixes", showcontent = true },
|
|
212
|
+
{ directory = "doc", name = "Improved documentation", showcontent = true },
|
|
213
|
+
{ directory = "packaging", name = "Packaging updates and notes for downstreams", showcontent = true },
|
|
214
|
+
{ directory = "contrib", name = "Contributor-facing changes", showcontent = true },
|
|
215
|
+
{ directory = "misc", name = "Miscellaneous internal changes", showcontent = true },
|
|
216
|
+
]
|
|
@@ -314,13 +314,7 @@ class LazyPathDump:
|
|
|
314
314
|
content += " with =>"
|
|
315
315
|
for file_path in self.path.iterdir():
|
|
316
316
|
try:
|
|
317
|
-
if
|
|
318
|
-
continue
|
|
319
|
-
if IS_WIN: # pragma: win32 cover
|
|
320
|
-
pathext = self.env.get("PATHEXT", ".COM;.EXE;.BAT;.CMD").split(";")
|
|
321
|
-
if not any(file_path.name.upper().endswith(ext) for ext in pathext):
|
|
322
|
-
continue
|
|
323
|
-
elif not (file_path.stat().st_mode & os.X_OK):
|
|
317
|
+
if not self._is_executable(file_path):
|
|
324
318
|
continue
|
|
325
319
|
except OSError:
|
|
326
320
|
pass
|
|
@@ -328,6 +322,14 @@ class LazyPathDump:
|
|
|
328
322
|
content += file_path.name
|
|
329
323
|
return content
|
|
330
324
|
|
|
325
|
+
def _is_executable(self, file_path: Path) -> bool:
|
|
326
|
+
if file_path.is_dir():
|
|
327
|
+
return False
|
|
328
|
+
if IS_WIN: # pragma: win32 cover
|
|
329
|
+
pathext = self.env.get("PATHEXT", ".COM;.EXE;.BAT;.CMD").split(";")
|
|
330
|
+
return any(file_path.name.upper().endswith(ext) for ext in pathext)
|
|
331
|
+
return bool(file_path.stat().st_mode & os.X_OK)
|
|
332
|
+
|
|
331
333
|
|
|
332
334
|
def path_exe_finder(
|
|
333
335
|
spec: PythonSpec, *, all_implementations: bool = False
|
|
@@ -16,6 +16,7 @@ from string import digits
|
|
|
16
16
|
from typing import TYPE_CHECKING, ClassVar, Final, NamedTuple
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
|
+
import tkinter as tk
|
|
19
20
|
from collections.abc import Generator, Mapping
|
|
20
21
|
|
|
21
22
|
from ._cache import PyInfoCache
|
|
@@ -59,6 +60,8 @@ class PythonInfo: # noqa: PLR0904
|
|
|
59
60
|
def _init_identity(self) -> None:
|
|
60
61
|
self.platform = sys.platform
|
|
61
62
|
self.implementation = platform.python_implementation()
|
|
63
|
+
if self.implementation == "GraalVM":
|
|
64
|
+
self.implementation = "GraalPy"
|
|
62
65
|
if self.implementation == "PyPy":
|
|
63
66
|
self.pypy_version_info = tuple(sys.pypy_version_info) # ty: ignore[unresolved-attribute] # pypy only
|
|
64
67
|
|
|
@@ -163,42 +166,37 @@ class PythonInfo: # noqa: PLR0904
|
|
|
163
166
|
try:
|
|
164
167
|
tcl = tk.Tcl()
|
|
165
168
|
tcl_lib = tcl.eval("info library")
|
|
166
|
-
|
|
167
|
-
# Try to get TK library path directly first
|
|
168
|
-
try:
|
|
169
|
-
tk_lib = tcl.eval("set tk_library")
|
|
170
|
-
if tk_lib and os.path.isdir(tk_lib):
|
|
171
|
-
pass # We found it directly
|
|
172
|
-
else:
|
|
173
|
-
tk_lib = None # Reset if invalid
|
|
174
|
-
except tk.TclError:
|
|
175
|
-
tk_lib = None
|
|
176
|
-
|
|
177
|
-
# If direct query failed, try constructing the path
|
|
178
|
-
if tk_lib is None:
|
|
179
|
-
tk_version = tcl.eval("package require Tk")
|
|
180
|
-
tcl_parent = os.path.dirname(tcl_lib)
|
|
181
|
-
|
|
182
|
-
# Try different version formats
|
|
183
|
-
version_variants = [
|
|
184
|
-
tk_version, # Full version like "8.6.12"
|
|
185
|
-
".".join(tk_version.split(".")[:2]), # Major.minor like "8.6"
|
|
186
|
-
tk_version.split(".")[0], # Just major like "8"
|
|
187
|
-
]
|
|
188
|
-
|
|
189
|
-
for version in version_variants:
|
|
190
|
-
tk_lib_path = os.path.join(tcl_parent, f"tk{version}")
|
|
191
|
-
if not os.path.isdir(tk_lib_path):
|
|
192
|
-
continue
|
|
193
|
-
if os.path.exists(os.path.join(tk_lib_path, "tk.tcl")):
|
|
194
|
-
tk_lib = tk_lib_path
|
|
195
|
-
break
|
|
196
|
-
|
|
169
|
+
tk_lib = PythonInfo._resolve_tk_lib(tcl, tcl_lib)
|
|
197
170
|
except tk.TclError:
|
|
198
171
|
pass
|
|
199
172
|
|
|
200
173
|
return tcl_lib, tk_lib
|
|
201
174
|
|
|
175
|
+
@staticmethod
|
|
176
|
+
def _query_tk_library(tcl: tk.Tk) -> str | None: # pragma: no cover
|
|
177
|
+
"""Try to get the TK library path directly from Tcl."""
|
|
178
|
+
import tkinter as tk # noqa: PLC0415
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
if (tk_lib := tcl.eval("set tk_library")) and os.path.isdir(tk_lib):
|
|
182
|
+
return tk_lib
|
|
183
|
+
except tk.TclError:
|
|
184
|
+
pass
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
@staticmethod
|
|
188
|
+
def _resolve_tk_lib(tcl: tk.Tk, tcl_lib: str) -> str | None: # pragma: no cover
|
|
189
|
+
"""Resolve the TK library path by direct query or path construction."""
|
|
190
|
+
if (tk_lib := PythonInfo._query_tk_library(tcl)) is not None:
|
|
191
|
+
return tk_lib
|
|
192
|
+
tk_version = tcl.eval("package require Tk")
|
|
193
|
+
tcl_parent = os.path.dirname(tcl_lib)
|
|
194
|
+
for version in (tk_version, ".".join(tk_version.split(".")[:2]), tk_version.split(".")[0]):
|
|
195
|
+
tk_lib_path = os.path.join(tcl_parent, f"tk{version}")
|
|
196
|
+
if os.path.isdir(tk_lib_path) and os.path.exists(os.path.join(tk_lib_path, "tk.tcl")):
|
|
197
|
+
return tk_lib_path
|
|
198
|
+
return None
|
|
199
|
+
|
|
202
200
|
def _fast_get_system_executable(self) -> str | None:
|
|
203
201
|
"""Try to get the system executable by just looking at properties."""
|
|
204
202
|
# if we're not in a virtual environment, this is already a system python, so return the original executable
|
|
@@ -17,7 +17,7 @@ PATTERN = re.compile(
|
|
|
17
17
|
(?P<version>[0-9.]+)? # version (e.g. 3.12, 3.12.1)
|
|
18
18
|
(?P<threaded>t)? # free-threaded flag
|
|
19
19
|
(?:-(?P<arch>32|64))? # architecture bitness
|
|
20
|
-
(?:-(?P<machine>[a-zA-Z0-9_]+))? # ISA (e.g. arm64, x86_64)
|
|
20
|
+
(?:-(?P<machine>[a-zA-Z0-9_.]+))? # ISA (e.g. arm64, x86_64, i86pc.64bit)
|
|
21
21
|
$
|
|
22
22
|
""",
|
|
23
23
|
re.VERBOSE,
|
|
@@ -77,6 +77,8 @@ def _parse_spec_pattern(string_spec: str) -> PythonSpec | None:
|
|
|
77
77
|
impl = groups["impl"]
|
|
78
78
|
if impl in {"py", "python"}:
|
|
79
79
|
impl = None
|
|
80
|
+
if impl == "graalvm":
|
|
81
|
+
impl = "graalpy"
|
|
80
82
|
arch = _int_or_none(groups["arch"])
|
|
81
83
|
machine = groups.get("machine")
|
|
82
84
|
if machine is not None:
|
|
@@ -97,6 +99,8 @@ def _parse_specifier(string_spec: str) -> PythonSpec | None:
|
|
|
97
99
|
return None
|
|
98
100
|
if impl in {"py", "python"}:
|
|
99
101
|
impl = None
|
|
102
|
+
if impl == "graalvm":
|
|
103
|
+
impl = "graalpy"
|
|
100
104
|
return PythonSpec(string_spec, impl, None, None, None, None, None, version_specifier=version_specifier)
|
|
101
105
|
|
|
102
106
|
|
|
@@ -124,23 +124,23 @@ def load_exe(hive_name: str, company: str, company_key: Any, tag: str) -> tuple[
|
|
|
124
124
|
key_path = f"{hive_name}/{company}/{tag}"
|
|
125
125
|
try:
|
|
126
126
|
with winreg.OpenKeyEx(company_key, rf"{tag}\InstallPath") as ip_key, ip_key: # ty: ignore[unresolved-attribute]
|
|
127
|
-
exe
|
|
128
|
-
|
|
129
|
-
ip = get_value(ip_key, None)
|
|
130
|
-
if ip is None:
|
|
131
|
-
msg(key_path, "no ExecutablePath or default for it")
|
|
132
|
-
|
|
133
|
-
else:
|
|
134
|
-
exe = os.path.join(ip, "python.exe")
|
|
135
|
-
if exe is not None and os.path.exists(exe):
|
|
136
|
-
args = get_value(ip_key, "ExecutableArguments")
|
|
137
|
-
return exe, args
|
|
127
|
+
if (exe := _resolve_exe(ip_key, key_path)) is not None and os.path.exists(exe):
|
|
128
|
+
return exe, get_value(ip_key, "ExecutableArguments")
|
|
138
129
|
msg(key_path, f"could not load exe with value {exe}")
|
|
139
130
|
except OSError:
|
|
140
131
|
msg(f"{key_path}/InstallPath", "missing")
|
|
141
132
|
return None
|
|
142
133
|
|
|
143
134
|
|
|
135
|
+
def _resolve_exe(ip_key: Any, key_path: str) -> str | None: # noqa: ANN401
|
|
136
|
+
if (exe := get_value(ip_key, "ExecutablePath")) is not None:
|
|
137
|
+
return exe
|
|
138
|
+
if (ip := get_value(ip_key, None)) is None:
|
|
139
|
+
msg(key_path, "no ExecutablePath or default for it")
|
|
140
|
+
return None
|
|
141
|
+
return os.path.join(ip, "python.exe")
|
|
142
|
+
|
|
143
|
+
|
|
144
144
|
def load_arch_data(hive_name: str, company: str, tag: str, tag_key: Any, default_arch: int) -> int | None: # noqa: ANN401
|
|
145
145
|
arch_str = get_value(tag_key, "SysArchitecture")
|
|
146
146
|
if arch_str is not None:
|
|
@@ -0,0 +1,173 @@
|
|
|
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") # noqa: T201
|
|
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") # noqa: T201
|
|
67
|
+
repo.git.push(upstream.name, f"{release_branch}:main", "-f")
|
|
68
|
+
print("push release tag") # noqa: T201
|
|
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") # noqa: T201
|
|
76
|
+
check_call(["towncrier", "build", "--yes", "--version", version.public], cwd=str(ROOT_SRC_DIR)) # noqa: S607
|
|
77
|
+
print("format changelog with pre-commit") # noqa: T201
|
|
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)) # noqa: S607
|
|
81
|
+
except CalledProcessError:
|
|
82
|
+
print("pre-commit made formatting changes, staging them") # noqa: T201
|
|
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") # noqa: T201
|
|
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}") # noqa: T201
|
|
92
|
+
repo.delete_tag(repo.tags[version_str])
|
|
93
|
+
print(f"create tag {version_str}") # noqa: T201
|
|
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") # noqa: T201
|
|
99
|
+
version_str = str(version)
|
|
100
|
+
try:
|
|
101
|
+
result = run(
|
|
102
|
+
["gh", "release", "create", version_str, "--title", f"v{version_str}", "--generate-notes"], # noqa: S607
|
|
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) # noqa: T201
|
|
110
|
+
except CalledProcessError as e:
|
|
111
|
+
print(f"gh release create failed with exit code {e.returncode}") # noqa: T201
|
|
112
|
+
if e.stdout:
|
|
113
|
+
print(f"stdout: {e.stdout}") # noqa: T201
|
|
114
|
+
if e.stderr:
|
|
115
|
+
print(f"stderr: {e.stderr}") # noqa: T201
|
|
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") # noqa: T201
|
|
121
|
+
repo.heads.main.checkout()
|
|
122
|
+
repo.delete_head(release_branch, force=True)
|
|
123
|
+
print("delete remote release branch") # noqa: T201
|
|
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!") # noqa: T201
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def cleanup_failed_release( # noqa: PLR0913
|
|
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...") # noqa: T201
|
|
142
|
+
if release_created:
|
|
143
|
+
print(f"Deleting GitHub release {version}") # noqa: T201
|
|
144
|
+
try:
|
|
145
|
+
check_call(["gh", "release", "delete", str(version), "--yes"], cwd=str(ROOT_SRC_DIR)) # noqa: S607
|
|
146
|
+
except Exception as cleanup_error: # noqa: BLE001
|
|
147
|
+
print(f"Warning: Failed to delete GitHub release: {cleanup_error}") # noqa: T201
|
|
148
|
+
if tag_pushed:
|
|
149
|
+
print(f"Deleting remote tag {version}") # noqa: T201
|
|
150
|
+
try:
|
|
151
|
+
repo.git.push(upstream.name, f":refs/tags/{version}", "--no-verify")
|
|
152
|
+
except Exception as cleanup_error: # noqa: BLE001
|
|
153
|
+
print(f"Warning: Failed to delete remote tag: {cleanup_error}") # noqa: T201
|
|
154
|
+
if main_pushed:
|
|
155
|
+
print(f"Reverting main to {original_main_sha[:8]}") # noqa: T201
|
|
156
|
+
try:
|
|
157
|
+
repo.git.push(upstream.name, f"{original_main_sha}:main", "-f", "--no-verify")
|
|
158
|
+
except Exception as cleanup_error: # noqa: BLE001
|
|
159
|
+
print(f"Warning: Failed to revert main: {cleanup_error}") # noqa: T201
|
|
160
|
+
print("Deleting remote release branch") # noqa: T201
|
|
161
|
+
try:
|
|
162
|
+
repo.git.push(upstream.name, f":{release_branch}", "--no-verify")
|
|
163
|
+
except Exception as cleanup_error: # noqa: BLE001
|
|
164
|
+
print(f"Warning: Failed to delete remote branch: {cleanup_error}") # noqa: T201
|
|
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)
|
|
@@ -32,6 +32,12 @@ def test_py_info_pypy_version(mocker: MockerFixture) -> None:
|
|
|
32
32
|
assert info.pypy_version_info == (7, 3, 11, "final", 0)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def test_py_info_canonicalizes_graalvm(mocker: MockerFixture) -> None:
|
|
36
|
+
mocker.patch("platform.python_implementation", return_value="GraalVM")
|
|
37
|
+
info = PythonInfo()
|
|
38
|
+
assert info.implementation == "GraalPy"
|
|
39
|
+
|
|
40
|
+
|
|
35
41
|
def test_has_venv_attribute() -> None:
|
|
36
42
|
info = PythonInfo()
|
|
37
43
|
assert isinstance(info.has_venv, bool)
|
|
@@ -155,6 +155,12 @@ def test_specifier_with_implementation() -> None:
|
|
|
155
155
|
assert spec.version_specifier == SpecifierSet.from_string(">=3.12")
|
|
156
156
|
|
|
157
157
|
|
|
158
|
+
def test_specifier_with_graalpy_implementation_alias() -> None:
|
|
159
|
+
spec = PythonSpec.from_string_spec("graalvm>=3.12")
|
|
160
|
+
assert spec.implementation == "graalpy"
|
|
161
|
+
assert spec.version_specifier == SpecifierSet.from_string(">=3.12")
|
|
162
|
+
|
|
163
|
+
|
|
158
164
|
def test_specifier_satisfies_with_partial_information() -> None:
|
|
159
165
|
spec = PythonSpec.from_string_spec(">=3.12")
|
|
160
166
|
candidate = PythonSpec.from_string_spec("python3.12")
|
|
@@ -174,6 +180,7 @@ def test_specifier_satisfies_with_partial_information() -> None:
|
|
|
174
180
|
pytest.param("cpython3.12-64", None, id="no-machine"),
|
|
175
181
|
pytest.param("cpython3.12", None, id="no-arch-no-machine"),
|
|
176
182
|
pytest.param("python3.12-64-arm64", "arm64", id="python-impl"),
|
|
183
|
+
pytest.param("cpython3.14-64-i86pc.64bit", "i86pc.64bit", id="dotted-machine"),
|
|
177
184
|
],
|
|
178
185
|
)
|
|
179
186
|
def test_spec_parse_machine(spec_str: str, expected_machine: str | None) -> None:
|
|
@@ -245,3 +252,10 @@ def test_normalize_isa(isa: str, normalized: str) -> None:
|
|
|
245
252
|
)
|
|
246
253
|
def test_spec_repr_machine(spec_str: str, in_repr: str) -> None:
|
|
247
254
|
assert in_repr in repr(PythonSpec.from_string_spec(spec_str))
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@pytest.mark.parametrize(("left", "right"), [("graalpy", "graalvm"), ("graalvm", "graalpy")])
|
|
258
|
+
def test_spec_satisfies_graalpy_implementation_aliases(left: str, right: str) -> None:
|
|
259
|
+
spec_1 = PythonSpec.from_string_spec(left)
|
|
260
|
+
spec_2 = PythonSpec.from_string_spec(right)
|
|
261
|
+
assert spec_1.satisfies(spec_2) is True
|
|
@@ -36,12 +36,12 @@ commands = [["pre-commit", "run", "--all-files", "--show-diff-on-failure"]]
|
|
|
36
36
|
|
|
37
37
|
[env."type-3.8"]
|
|
38
38
|
description = "run type check on code base (3.8)"
|
|
39
|
-
deps = ["ty==0.0.17"]
|
|
39
|
+
deps = ["gitpython>=3.1.46", "packaging>=26", "ty==0.0.17"]
|
|
40
40
|
commands = [["ty", "check", "--output-format", "concise", "--error-on-warning", "--python-version", "3.8", "."]]
|
|
41
41
|
|
|
42
42
|
[env."type-3.14"]
|
|
43
43
|
description = "run type check on code base (3.14)"
|
|
44
|
-
deps = ["ty==0.0.17"]
|
|
44
|
+
deps = ["gitpython>=3.1.46", "packaging>=26", "ty==0.0.17"]
|
|
45
45
|
commands = [["ty", "check", "--output-format", "concise", "--error-on-warning", "--python-version", "3.14", "."]]
|
|
46
46
|
|
|
47
47
|
[env.docs]
|
|
@@ -71,6 +71,13 @@ commands = [
|
|
|
71
71
|
["check-wheel-contents", "--no-config", "{env_tmp_dir}"],
|
|
72
72
|
]
|
|
73
73
|
|
|
74
|
+
[env.release]
|
|
75
|
+
description = "do a release, required posargs of the version number"
|
|
76
|
+
skip_install = true
|
|
77
|
+
dependency_groups = ["release"]
|
|
78
|
+
pass_env = ["GH_TOKEN"]
|
|
79
|
+
commands = [["python", "{tox_root}/tasks/release.py", "--version", "{posargs}"]]
|
|
80
|
+
|
|
74
81
|
[env.dev]
|
|
75
82
|
description = "generate a DEV environment"
|
|
76
83
|
package = "editable"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export normalize_isa and deprecate KNOWN_ARCHITECTURES - by :user:`rahuldevikar`.
|
|
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.3.0 → python_discovery-1.3.2}/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
|