python-discovery 1.6.1__tar.gz → 1.6.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.6.1 → python_discovery-1.6.2}/.github/workflows/check.yaml +19 -17
- {python_discovery-1.6.1 → python_discovery-1.6.2}/.github/workflows/prepare-release.yaml +3 -2
- {python_discovery-1.6.1 → python_discovery-1.6.2}/.github/workflows/release.yaml +7 -6
- {python_discovery-1.6.1 → python_discovery-1.6.2}/.pre-commit-config.yaml +5 -4
- {python_discovery-1.6.1 → python_discovery-1.6.2}/.readthedocs.yaml +1 -1
- python_discovery-1.6.2/.readthedocs.yml +8 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/PKG-INFO +4 -5
- {python_discovery-1.6.1 → python_discovery-1.6.2}/README.md +1 -1
- {python_discovery-1.6.1 → python_discovery-1.6.2}/docs/changelog.rst +11 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/docs/explanation.rst +3 -2
- {python_discovery-1.6.1 → python_discovery-1.6.2}/docs/reference/environment-variables.rst +4 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/pyproject.toml +11 -10
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/_cached_py_info.py +25 -6
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/_py_info_collect.py +1 -1
- python_discovery-1.6.2/tests/test_cache_recovery.py +74 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/test_cached_py_info.py +0 -11
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/test_py_info_collect.py +2 -2
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/test_py_info_extra.py +1 -1
- python_discovery-1.6.2/tests/test_query_timeout.py +88 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/windows/conftest.py +2 -2
- python_discovery-1.6.2/tox.toml +89 -0
- python_discovery-1.6.1/.readthedocs.yml +0 -8
- python_discovery-1.6.1/tox.toml +0 -85
- {python_discovery-1.6.1 → python_discovery-1.6.2}/.github/FUNDING.yaml +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/.github/SECURITY.md +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/.github/dependabot.yaml +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/.github/release.yaml +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/.gitignore +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/CODE_OF_CONDUCT.md +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/LICENSE +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/docs/_static/custom.css +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/docs/_static/logo.svg +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/docs/changelog/template.jinja2 +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/docs/conf.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/docs/how-to/standalone-usage.rst +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/docs/index.rst +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/docs/reference/api.rst +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/docs/tutorial/getting-started.rst +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/__init__.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/_cache.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/_compat.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/_discovery.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/_py_info.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/_py_spec.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/_specifier.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/_windows/__init__.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/_windows/_pep514.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/_windows/_propose.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/src/python_discovery/py.typed +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tasks/old_target_check.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tasks/old_target_check.sh +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tasks/release.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/conftest.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/py_info/test_py_info.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/py_info/test_py_info_exe_based_of.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/test_cache.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/test_discovery.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/test_discovery_extra.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/test_iter_interpreters.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/test_py_spec.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/test_py_spec_extra.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/test_specifier.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/windows/test_windows.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/windows/test_windows_pep514.py +0 -0
- {python_discovery-1.6.1 → python_discovery-1.6.2}/tests/windows/winreg_mock_values.py +0 -0
|
@@ -14,7 +14,7 @@ permissions:
|
|
|
14
14
|
contents: read
|
|
15
15
|
jobs:
|
|
16
16
|
test:
|
|
17
|
-
runs-on: ubuntu-
|
|
17
|
+
runs-on: ubuntu-26.04
|
|
18
18
|
strategy:
|
|
19
19
|
fail-fast: false
|
|
20
20
|
matrix:
|
|
@@ -27,9 +27,8 @@ jobs:
|
|
|
27
27
|
- "3.11"
|
|
28
28
|
- "3.10"
|
|
29
29
|
- "3.9"
|
|
30
|
-
-
|
|
31
|
-
- type-3.
|
|
32
|
-
- type-3.14
|
|
30
|
+
- type-3.9
|
|
31
|
+
- type-3.15
|
|
33
32
|
- dev
|
|
34
33
|
- pkg_meta
|
|
35
34
|
steps:
|
|
@@ -38,15 +37,16 @@ jobs:
|
|
|
38
37
|
fetch-depth: 0
|
|
39
38
|
persist-credentials: false
|
|
40
39
|
- name: Install the latest version of uv
|
|
41
|
-
uses: astral-sh/setup-uv@
|
|
40
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
42
41
|
with:
|
|
42
|
+
python-version: "3.15"
|
|
43
43
|
enable-cache: false
|
|
44
44
|
cache-dependency-glob: "pyproject.toml"
|
|
45
45
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
46
46
|
- name: Install tox
|
|
47
|
-
run: uv tool install --python-preference only-managed --python 3.
|
|
47
|
+
run: uv tool install --python-preference only-managed --python 3.15 tox --with tox-uv
|
|
48
48
|
- name: Install Python
|
|
49
|
-
if: startsWith(matrix.env, '3.') && matrix.env != '3.
|
|
49
|
+
if: startsWith(matrix.env, '3.') && matrix.env != '3.15'
|
|
50
50
|
run: uv python install --python-preference only-managed ${{ matrix.env }}
|
|
51
51
|
- name: Setup test suite
|
|
52
52
|
run: tox run -vv --notest --skip-missing-interpreters false -e ${{ matrix.env }}
|
|
@@ -56,25 +56,27 @@ jobs:
|
|
|
56
56
|
PYTEST_ADDOPTS: "-vv --durations=20"
|
|
57
57
|
DIFF_AGAINST: HEAD
|
|
58
58
|
old-target:
|
|
59
|
-
runs-on: ubuntu-
|
|
59
|
+
runs-on: ubuntu-26.04
|
|
60
60
|
steps:
|
|
61
61
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
62
62
|
with:
|
|
63
63
|
fetch-depth: 0
|
|
64
64
|
persist-credentials: false
|
|
65
65
|
- name: Install the latest version of uv
|
|
66
|
-
uses: astral-sh/setup-uv@
|
|
66
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
67
67
|
with:
|
|
68
|
+
python-version: "3.15"
|
|
68
69
|
enable-cache: false
|
|
69
70
|
cache-dependency-glob: "pyproject.toml"
|
|
70
71
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
71
72
|
- name: Build the wheel
|
|
72
73
|
run: uv build --wheel --out-dir dist .
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
- parallel:
|
|
75
|
+
- name: Discover Python 3.6
|
|
76
|
+
run: tasks/old_target_check.sh 3.6 discover
|
|
77
|
+
- name: Discover Python 3.7
|
|
78
|
+
run: tasks/old_target_check.sh 3.7 discover
|
|
79
|
+
- name: Reject Python 3.5
|
|
80
|
+
run: tasks/old_target_check.sh 3.5 rejected
|
|
81
|
+
- name: Reject Python 2.7
|
|
82
|
+
run: tasks/old_target_check.sh 2.7 rejected
|
|
@@ -29,8 +29,9 @@ jobs:
|
|
|
29
29
|
token: ${{ secrets.RELEASE_PAT }}
|
|
30
30
|
persist-credentials: true
|
|
31
31
|
- name: Install the latest version of uv
|
|
32
|
-
uses: astral-sh/setup-uv@
|
|
32
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
33
33
|
with:
|
|
34
|
+
python-version: "3.15"
|
|
34
35
|
enable-cache: true
|
|
35
36
|
cache-dependency-glob: "pyproject.toml"
|
|
36
37
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -79,7 +80,7 @@ jobs:
|
|
|
79
80
|
echo "Next version: $next"
|
|
80
81
|
echo "version=$next" >> $GITHUB_OUTPUT
|
|
81
82
|
- name: Install tox
|
|
82
|
-
run: uv tool install --python-preference only-managed --python 3.
|
|
83
|
+
run: uv tool install --python-preference only-managed --python 3.15 tox@latest --with tox-uv
|
|
83
84
|
- name: Run release process
|
|
84
85
|
run: tox run -e release -- ${STEPS_VERSION_OUTPUTS_VERSION}
|
|
85
86
|
env:
|
|
@@ -8,29 +8,30 @@ env:
|
|
|
8
8
|
dists-artifact-name: python-package-distributions
|
|
9
9
|
jobs:
|
|
10
10
|
build:
|
|
11
|
-
runs-on: ubuntu-
|
|
11
|
+
runs-on: ubuntu-26.04
|
|
12
12
|
steps:
|
|
13
13
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
14
14
|
with:
|
|
15
15
|
fetch-depth: 0
|
|
16
16
|
persist-credentials: false
|
|
17
17
|
- name: Install the latest version of uv
|
|
18
|
-
uses: astral-sh/setup-uv@
|
|
18
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
19
19
|
with:
|
|
20
|
+
python-version: "3.15"
|
|
20
21
|
enable-cache: false
|
|
21
22
|
cache-dependency-glob: "pyproject.toml"
|
|
22
23
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
23
24
|
- name: Build package
|
|
24
|
-
run: uv build --python 3.
|
|
25
|
+
run: uv build --python 3.15 --python-preference only-managed --sdist --wheel . --out-dir dist
|
|
25
26
|
- name: Store the distribution packages
|
|
26
|
-
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
27
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
27
28
|
with:
|
|
28
29
|
name: ${{ env.dists-artifact-name }}
|
|
29
30
|
path: dist/*
|
|
30
31
|
release:
|
|
31
32
|
needs:
|
|
32
33
|
- build
|
|
33
|
-
runs-on: ubuntu-
|
|
34
|
+
runs-on: ubuntu-26.04
|
|
34
35
|
environment:
|
|
35
36
|
name: release
|
|
36
37
|
url: https://pypi.org/project/python-discovery/${{ github.ref_name }}
|
|
@@ -38,7 +39,7 @@ jobs:
|
|
|
38
39
|
id-token: write
|
|
39
40
|
steps:
|
|
40
41
|
- name: Download all the dists
|
|
41
|
-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
42
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
42
43
|
with:
|
|
43
44
|
name: ${{ env.dists-artifact-name }}
|
|
44
45
|
path: dist/
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
default_language_version:
|
|
2
|
+
python: python3.15
|
|
1
3
|
repos:
|
|
2
4
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
5
|
rev: v6.0.0
|
|
@@ -5,7 +7,7 @@ repos:
|
|
|
5
7
|
- id: end-of-file-fixer
|
|
6
8
|
- id: trailing-whitespace
|
|
7
9
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
|
8
|
-
rev: 0.38.
|
|
10
|
+
rev: 0.38.2
|
|
9
11
|
hooks:
|
|
10
12
|
- id: check-github-workflows
|
|
11
13
|
args: ["--verbose"]
|
|
@@ -13,13 +15,12 @@ repos:
|
|
|
13
15
|
rev: v2.4.3
|
|
14
16
|
hooks:
|
|
15
17
|
- id: codespell
|
|
16
|
-
additional_dependencies: ["tomli>=2.4"]
|
|
17
18
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
|
18
|
-
rev: "v2.
|
|
19
|
+
rev: "v2.30.1"
|
|
19
20
|
hooks:
|
|
20
21
|
- id: pyproject-fmt
|
|
21
22
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
22
|
-
rev: "v0.16.
|
|
23
|
+
rev: "v0.16.10"
|
|
23
24
|
hooks:
|
|
24
25
|
- id: ruff-format
|
|
25
26
|
- id: ruff
|
|
@@ -4,5 +4,5 @@ build:
|
|
|
4
4
|
tools: {}
|
|
5
5
|
commands:
|
|
6
6
|
- curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
7
|
-
- ~/.local/bin/uv tool install tox --with tox-uv -p 3.
|
|
7
|
+
- ~/.local/bin/uv tool install tox --with tox-uv -p 3.15 --managed-python
|
|
8
8
|
- ~/.local/bin/tox run -e docs -- "$READTHEDOCS_OUTPUT/html"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-discovery
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.2
|
|
4
4
|
Summary: Python interpreter discovery
|
|
5
5
|
Project-URL: Changelog, https://github.com/tox-dev/python-discovery/releases
|
|
6
6
|
Project-URL: Documentation, https://python-discovery.readthedocs.io
|
|
@@ -35,7 +35,6 @@ Classifier: Operating System :: MacOS :: MacOS X
|
|
|
35
35
|
Classifier: Operating System :: Microsoft :: Windows
|
|
36
36
|
Classifier: Operating System :: POSIX
|
|
37
37
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
39
38
|
Classifier: Programming Language :: Python :: 3.9
|
|
40
39
|
Classifier: Programming Language :: Python :: 3.10
|
|
41
40
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -46,8 +45,8 @@ Classifier: Programming Language :: Python :: 3.15
|
|
|
46
45
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
47
46
|
Classifier: Topic :: Software Development :: Libraries
|
|
48
47
|
Classifier: Topic :: Utilities
|
|
49
|
-
Requires-Python: >=3.
|
|
50
|
-
Requires-Dist: filelock>=3.
|
|
48
|
+
Requires-Python: >=3.9
|
|
49
|
+
Requires-Dist: filelock>=3.16.1
|
|
51
50
|
Description-Content-Type: text/markdown
|
|
52
51
|
|
|
53
52
|
# [`python-discovery`](https://python-discovery.readthedocs.io/en/latest/)
|
|
@@ -68,7 +67,7 @@ the right one for you.
|
|
|
68
67
|
Give it a requirement like `python3.12` or `>=3.11,<3.13`, and it searches all known locations, verifies each candidate,
|
|
69
68
|
and returns detailed metadata about the match. Results are cached to disk so repeated lookups are fast.
|
|
70
69
|
|
|
71
|
-
The library runs on Python 3.
|
|
70
|
+
The library runs on Python 3.9+ and discovers interpreters as old as Python 3.6; anything older is skipped with a
|
|
72
71
|
warning naming the version it found.
|
|
73
72
|
|
|
74
73
|
## Usage
|
|
@@ -16,7 +16,7 @@ the right one for you.
|
|
|
16
16
|
Give it a requirement like `python3.12` or `>=3.11,<3.13`, and it searches all known locations, verifies each candidate,
|
|
17
17
|
and returns detailed metadata about the match. Results are cached to disk so repeated lookups are fast.
|
|
18
18
|
|
|
19
|
-
The library runs on Python 3.
|
|
19
|
+
The library runs on Python 3.9+ and discovers interpreters as old as Python 3.6; anything older is skipped with a
|
|
20
20
|
warning naming the version it found.
|
|
21
21
|
|
|
22
22
|
## Usage
|
|
@@ -6,6 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
.. towncrier release notes start
|
|
8
8
|
|
|
9
|
+
********************
|
|
10
|
+
v1.6.2 (2026-10-10)
|
|
11
|
+
********************
|
|
12
|
+
|
|
13
|
+
Bug fixes - 1.6.2
|
|
14
|
+
=================
|
|
15
|
+
- Discard malformed interpreter cache entries and query the interpreter again, including entries with missing,
|
|
16
|
+
non-string or unusable ``system_executable`` paths - by :user:`darrenhuai`. (:issue:`141`)
|
|
17
|
+
- Use the default interpreter query timeout and warn for invalid ``PY_DISCOVERY_TIMEOUT`` values. Accept ``inf``
|
|
18
|
+
or values above the subprocess wait limit to disable the timeout - by :user:`darrenhuai`. (:issue:`143`)
|
|
19
|
+
|
|
9
20
|
********************
|
|
10
21
|
v1.6.1 (2026-09-18)
|
|
11
22
|
********************
|
|
@@ -275,12 +275,13 @@ If your system consistently hits timeouts, you can customize the timeout via the
|
|
|
275
275
|
|
|
276
276
|
The timeout applies to each individual interpreter being queried. If you set a value that is too low,
|
|
277
277
|
legitimate interpreters may be skipped; if too high, the discovery process may take longer to fail
|
|
278
|
-
when encountering problematic interpreters.
|
|
278
|
+
when encountering problematic interpreters. For invalid values, python-discovery logs a warning and uses
|
|
279
|
+
the default of 15 seconds. Set ``inf`` to disable the timeout.
|
|
279
280
|
|
|
280
281
|
Supported Python versions
|
|
281
282
|
-------------------------
|
|
282
283
|
|
|
283
|
-
Two floors apply: python-discovery itself runs on Python 3.
|
|
284
|
+
Two floors apply: python-discovery itself runs on Python 3.9+ (``requires-python``), while discovery
|
|
284
285
|
reaches down to Python 3.6 - old enough to cover RHEL 8's system Python.
|
|
285
286
|
|
|
286
287
|
python-discovery skips candidates below 3.6 with one warning naming the executable, the version
|
|
@@ -36,3 +36,7 @@ Setting this variable extends the allowed time for each interpreter query.
|
|
|
36
36
|
- Setting the value too low may skip legitimate interpreters
|
|
37
37
|
- Setting it too high increases discovery time when encountering problematic interpreters
|
|
38
38
|
- The value is read from the environment dict passed to :func:`~python_discovery.get_interpreter`
|
|
39
|
+
- For invalid values, including empty strings, ``nan``, ``0`` and negative numbers, python-discovery logs a warning
|
|
40
|
+
and uses the default of 15 seconds
|
|
41
|
+
- ``inf`` or a value above 2,147,483.647 seconds (about 24.9 days) disables the timeout. This limit applies on all
|
|
42
|
+
platforms because some subprocess waits accept a signed 32-bit count of milliseconds
|
|
@@ -19,7 +19,7 @@ license.file = "LICENSE"
|
|
|
19
19
|
maintainers = [
|
|
20
20
|
{ name = "Bernát Gábor", email = "gaborjbernat@gmail.com" },
|
|
21
21
|
]
|
|
22
|
-
requires-python = ">=3.
|
|
22
|
+
requires-python = ">=3.9"
|
|
23
23
|
classifiers = [
|
|
24
24
|
"Development Status :: 5 - Production/Stable",
|
|
25
25
|
"Intended Audience :: Developers",
|
|
@@ -28,7 +28,6 @@ classifiers = [
|
|
|
28
28
|
"Operating System :: Microsoft :: Windows",
|
|
29
29
|
"Operating System :: POSIX",
|
|
30
30
|
"Programming Language :: Python :: 3 :: Only",
|
|
31
|
-
"Programming Language :: Python :: 3.8",
|
|
32
31
|
"Programming Language :: Python :: 3.9",
|
|
33
32
|
"Programming Language :: Python :: 3.10",
|
|
34
33
|
"Programming Language :: Python :: 3.11",
|
|
@@ -44,7 +43,7 @@ dynamic = [
|
|
|
44
43
|
"version",
|
|
45
44
|
]
|
|
46
45
|
dependencies = [
|
|
47
|
-
"filelock>=3.
|
|
46
|
+
"filelock>=3.16.1",
|
|
48
47
|
]
|
|
49
48
|
urls.Changelog = "https://github.com/tox-dev/python-discovery/releases"
|
|
50
49
|
urls.Documentation = "https://python-discovery.readthedocs.io"
|
|
@@ -55,24 +54,25 @@ urls.Tracker = "https://github.com/tox-dev/python-discovery/issues"
|
|
|
55
54
|
[dependency-groups]
|
|
56
55
|
test = [
|
|
57
56
|
"covdefaults>=2.3",
|
|
58
|
-
"coverage>=7.
|
|
57
|
+
"coverage>=7.6.1",
|
|
59
58
|
"pytest>=8.3.5",
|
|
60
|
-
"pytest-mock>=3.14",
|
|
61
|
-
"setuptools>=75.
|
|
62
|
-
"
|
|
59
|
+
"pytest-mock>=3.14.1",
|
|
60
|
+
"setuptools>=75.3.4",
|
|
61
|
+
"typing-extensions>=4.12",
|
|
62
|
+
"vermin>=1.8",
|
|
63
63
|
]
|
|
64
64
|
docs = [
|
|
65
65
|
"furo>=2025.12.19",
|
|
66
66
|
"sphinx>=9.1",
|
|
67
67
|
"sphinx-autodoc-typehints>=3.6.3",
|
|
68
|
-
"sphinx-llm>=0.4.1",
|
|
68
|
+
"sphinx-llm>=0.4.1,!=1.1",
|
|
69
69
|
"sphinxcontrib-mermaid>=2",
|
|
70
70
|
"sphinxcontrib-towncrier>=0.4",
|
|
71
71
|
"towncrier>=25.8",
|
|
72
72
|
]
|
|
73
73
|
release = [
|
|
74
|
-
"gitpython>=3.
|
|
75
|
-
"packaging>=26",
|
|
74
|
+
"gitpython>=3.2",
|
|
75
|
+
"packaging>=26.2",
|
|
76
76
|
"pre-commit>=4.5.1",
|
|
77
77
|
"towncrier>=25.8",
|
|
78
78
|
]
|
|
@@ -81,6 +81,7 @@ release = [
|
|
|
81
81
|
version.source = "vcs"
|
|
82
82
|
|
|
83
83
|
[tool.ruff]
|
|
84
|
+
target-version = "py39"
|
|
84
85
|
line-length = 120
|
|
85
86
|
format.preview = true
|
|
86
87
|
format.docstring-code-line-length = 100
|
|
@@ -41,6 +41,9 @@ _CACHE: OrderedDict[Path, PythonInfo | Exception] = OrderedDict()
|
|
|
41
41
|
_CACHE[Path(sys.executable)] = PythonInfo()
|
|
42
42
|
_LOGGER: Final[logging.Logger] = logging.getLogger(__name__)
|
|
43
43
|
_PY_INFO_SCRIPT: Final[Path] = Path(__file__).resolve().parent / "_py_info_collect.py"
|
|
44
|
+
_DEFAULT_QUERY_TIMEOUT: Final[float] = 15.0
|
|
45
|
+
# poll(2) limits milliseconds to a signed C int.
|
|
46
|
+
_MAX_QUERY_TIMEOUT: Final[float] = (2**31 - 1) / 1000
|
|
44
47
|
|
|
45
48
|
|
|
46
49
|
class _UnsupportedInterpreterError(RuntimeError):
|
|
@@ -136,7 +139,7 @@ def _read_cache_entry(
|
|
|
136
139
|
) -> PythonInfo | _UnsupportedInterpreterError | None:
|
|
137
140
|
if not py_info_store.exists() or (data := py_info_store.read()) is None:
|
|
138
141
|
return None
|
|
139
|
-
if all(data.get(key) == value for key, value in entry_meta.items()):
|
|
142
|
+
if isinstance(data, dict) and all(data.get(key) == value for key, value in entry_meta.items()):
|
|
140
143
|
if isinstance(unsupported := data.get("unsupported"), str):
|
|
141
144
|
return _UnsupportedInterpreterError(unsupported)
|
|
142
145
|
if isinstance(content := data.get("content"), dict):
|
|
@@ -152,10 +155,10 @@ def _load_cached_py_info(
|
|
|
152
155
|
) -> PythonInfo | None:
|
|
153
156
|
try:
|
|
154
157
|
py_info = cls.from_dict(content.copy())
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if
|
|
158
|
+
stale = (sys_exe := py_info.system_executable) is not None and not Path(sys_exe).is_file()
|
|
159
|
+
except (AttributeError, KeyError, OSError, TypeError):
|
|
160
|
+
stale = True
|
|
161
|
+
if stale:
|
|
159
162
|
py_info_store.remove()
|
|
160
163
|
return None
|
|
161
164
|
return py_info
|
|
@@ -214,7 +217,7 @@ def _run_subprocess(
|
|
|
214
217
|
) -> tuple[Exception | None, PythonInfo | None]:
|
|
215
218
|
start_cookie = gen_cookie()
|
|
216
219
|
end_cookie = gen_cookie()
|
|
217
|
-
timeout =
|
|
220
|
+
timeout = _query_timeout(env.get("PY_DISCOVERY_TIMEOUT"))
|
|
218
221
|
with _resolve_py_info_script() as py_info_script:
|
|
219
222
|
cmd = [exe, str(py_info_script), start_cookie, end_cookie]
|
|
220
223
|
env = dict(env)
|
|
@@ -264,6 +267,22 @@ def _run_subprocess(
|
|
|
264
267
|
return None, result
|
|
265
268
|
|
|
266
269
|
|
|
270
|
+
@lru_cache(maxsize=128)
|
|
271
|
+
def _query_timeout(raw: str | None) -> float | None:
|
|
272
|
+
if raw is None:
|
|
273
|
+
return _DEFAULT_QUERY_TIMEOUT
|
|
274
|
+
try:
|
|
275
|
+
timeout = float(raw)
|
|
276
|
+
except ValueError:
|
|
277
|
+
timeout = 0.0
|
|
278
|
+
if timeout > 0:
|
|
279
|
+
return timeout if timeout <= _MAX_QUERY_TIMEOUT else None
|
|
280
|
+
_LOGGER.warning(
|
|
281
|
+
"ignoring PY_DISCOVERY_TIMEOUT=%r, not a positive number of seconds; using %s", raw, _DEFAULT_QUERY_TIMEOUT
|
|
282
|
+
)
|
|
283
|
+
return _DEFAULT_QUERY_TIMEOUT
|
|
284
|
+
|
|
285
|
+
|
|
267
286
|
def _query_failure(exe: str, out: str, err: str | None, code: int | None) -> RuntimeError:
|
|
268
287
|
# the gate's exit code and stderr marker must both be present: either alone can collide, an errno of 79 from a
|
|
269
288
|
# failed exec or a shim that echoes the marker phrase, but only the gate produces the combination
|
|
@@ -321,7 +321,7 @@ class PythonInfoCollector:
|
|
|
321
321
|
"script_args": "--no-user-cfg",
|
|
322
322
|
}) # conf files not parsed so they do not hijack paths
|
|
323
323
|
if hasattr(sys, "_framework"): # pragma: no cover # macOS framework builds only
|
|
324
|
-
sys._framework = None # ruff:ignore[private-member-access] # disable macOS static paths for framework
|
|
324
|
+
sys._framework = None # ty: ignore[invalid-assignment] # ruff:ignore[private-member-access] # disable macOS static paths for framework
|
|
325
325
|
|
|
326
326
|
with warnings.catch_warnings(): # disable warning for PEP-632
|
|
327
327
|
warnings.simplefilter("ignore")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from python_discovery import DiskCache, PythonInfo
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.parametrize(
|
|
16
|
+
"payload",
|
|
17
|
+
[
|
|
18
|
+
pytest.param("[]", id="list"),
|
|
19
|
+
pytest.param('"python"', id="string"),
|
|
20
|
+
pytest.param("3", id="number"),
|
|
21
|
+
pytest.param("true", id="bool"),
|
|
22
|
+
pytest.param("null", id="null"),
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
def test_from_exe_recovers_non_object_cache(cached_interpreter: tuple[DiskCache, Path, dict], payload: str) -> None:
|
|
26
|
+
cache, cache_file, expected = cached_interpreter
|
|
27
|
+
cache_file.write_text(payload, encoding="utf-8")
|
|
28
|
+
result = PythonInfo.from_exe(sys.executable, cache, ignore_cache=True, resolve_to_host=False)
|
|
29
|
+
assert result is not None
|
|
30
|
+
assert (json.loads(result.to_json()), json.loads(cache_file.read_text(encoding="utf-8"))) == (
|
|
31
|
+
expected["content"],
|
|
32
|
+
expected,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.mark.parametrize(
|
|
37
|
+
"payload",
|
|
38
|
+
[
|
|
39
|
+
pytest.param(None, id="missing"),
|
|
40
|
+
pytest.param("5", id="int"),
|
|
41
|
+
pytest.param('["python"]', id="list"),
|
|
42
|
+
pytest.param("{}", id="dict"),
|
|
43
|
+
pytest.param("true", id="bool"),
|
|
44
|
+
pytest.param('""', id="empty"),
|
|
45
|
+
pytest.param('"."', id="directory"),
|
|
46
|
+
pytest.param('"missing-python"', id="nonexistent"),
|
|
47
|
+
pytest.param('"python\\u0000"', id="nul"),
|
|
48
|
+
pytest.param(json.dumps("a" * 1024), id="path-too-long"),
|
|
49
|
+
],
|
|
50
|
+
)
|
|
51
|
+
def test_from_exe_recovers_invalid_system_executable(
|
|
52
|
+
cached_interpreter: tuple[DiskCache, Path, dict], payload: str | None
|
|
53
|
+
) -> None:
|
|
54
|
+
cache, cache_file, expected = cached_interpreter
|
|
55
|
+
data = json.loads(cache_file.read_text(encoding="utf-8"))
|
|
56
|
+
if payload is None:
|
|
57
|
+
del data["content"]["system_executable"]
|
|
58
|
+
else:
|
|
59
|
+
data["content"]["system_executable"] = json.loads(payload)
|
|
60
|
+
cache_file.write_text(json.dumps(data), encoding="utf-8")
|
|
61
|
+
result = PythonInfo.from_exe(sys.executable, cache, ignore_cache=True, resolve_to_host=False)
|
|
62
|
+
assert result is not None
|
|
63
|
+
assert (json.loads(result.to_json()), json.loads(cache_file.read_text(encoding="utf-8"))) == (
|
|
64
|
+
expected["content"],
|
|
65
|
+
expected,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.fixture
|
|
70
|
+
def cached_interpreter(tmp_path: Path) -> tuple[DiskCache, Path, dict]:
|
|
71
|
+
cache = DiskCache(tmp_path)
|
|
72
|
+
assert PythonInfo.from_exe(sys.executable, cache, ignore_cache=True, resolve_to_host=False) is not None
|
|
73
|
+
cache_file = next(tmp_path.rglob("*.json"))
|
|
74
|
+
return cache, cache_file, json.loads(cache_file.read_text(encoding="utf-8"))
|
|
@@ -124,17 +124,6 @@ def test_run_subprocess_timeout(mocker: MockerFixture) -> None:
|
|
|
124
124
|
assert mock_process.communicate.call_count == 2
|
|
125
125
|
|
|
126
126
|
|
|
127
|
-
def test_run_subprocess_custom_timeout(mocker: MockerFixture) -> None:
|
|
128
|
-
mock_process = MagicMock()
|
|
129
|
-
mock_process.communicate.return_value = (json.dumps(PythonInfo().to_dict()), "")
|
|
130
|
-
mock_process.returncode = 0
|
|
131
|
-
mocker.patch("python_discovery._cached_py_info.Popen", return_value=mock_process)
|
|
132
|
-
env = dict(os.environ)
|
|
133
|
-
env["PY_DISCOVERY_TIMEOUT"] = "30"
|
|
134
|
-
_run_subprocess(PythonInfo, sys.executable, env)
|
|
135
|
-
mock_process.communicate.assert_called_once_with(timeout=30.0)
|
|
136
|
-
|
|
137
|
-
|
|
138
127
|
def test_run_subprocess_nonzero_exit(mocker: MockerFixture) -> None:
|
|
139
128
|
mock_process = MagicMock()
|
|
140
129
|
mock_process.communicate.return_value = ("some output", "some error")
|
|
@@ -79,7 +79,7 @@ def no_framework(mocker: MockerFixture) -> None:
|
|
|
79
79
|
mocker.patch.object(
|
|
80
80
|
sysconfig,
|
|
81
81
|
"get_config_var",
|
|
82
|
-
side_effect=lambda name: "" if name == "PYTHONFRAMEWORK" else get_config_var(name),
|
|
82
|
+
side_effect=lambda name: "" if name == "PYTHONFRAMEWORK" else get_config_var(name), # ty: ignore[deprecated] # resolves the deprecated Literal["SO"] overload even though name is a generic str
|
|
83
83
|
)
|
|
84
84
|
|
|
85
85
|
|
|
@@ -187,7 +187,7 @@ def test_system_executable_framework_symlink_kept(tmp_path: Path, mocker: Mocker
|
|
|
187
187
|
mocker.patch.object(
|
|
188
188
|
sysconfig,
|
|
189
189
|
"get_config_var",
|
|
190
|
-
side_effect=lambda name: "Python" if name == "PYTHONFRAMEWORK" else get_config_var(name),
|
|
190
|
+
side_effect=lambda name: "Python" if name == "PYTHONFRAMEWORK" else get_config_var(name), # ty: ignore[deprecated] # resolves the deprecated Literal["SO"] overload even though name is a generic str
|
|
191
191
|
)
|
|
192
192
|
mocker.patch.object(sys, "executable", str(link))
|
|
193
193
|
assert PythonInfo().system_executable == str(link)
|
|
@@ -225,7 +225,7 @@ def test_satisfies_path_not_abs_basename_match() -> None:
|
|
|
225
225
|
# stem drops everything from the first dot, so python3.15t arrives as python3 and stops naming a free-threaded
|
|
226
226
|
# build; only the Windows .exe belongs off the end.
|
|
227
227
|
name = Path(info.original_executable).name
|
|
228
|
-
basename = name
|
|
228
|
+
basename = name.removesuffix(".exe")
|
|
229
229
|
spec = PythonSpec.from_string_spec(basename)
|
|
230
230
|
assert info.satisfies(spec, impl_must_match=False) is True
|
|
231
231
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from subprocess import Popen
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from python_discovery import PythonInfo
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from pytest_mock import MockerFixture
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.parametrize(
|
|
18
|
+
("raw", "expected"),
|
|
19
|
+
[
|
|
20
|
+
pytest.param(None, 15.0, id="default"),
|
|
21
|
+
pytest.param("30", 30.0, id="seconds"),
|
|
22
|
+
pytest.param(" 0.5 ", 0.5, id="padded-fraction"),
|
|
23
|
+
pytest.param("inf", None, id="inf"),
|
|
24
|
+
pytest.param("1e30", None, id="beyond-platform-wait"),
|
|
25
|
+
pytest.param("2147483.647", 2147483.647, id="wait-limit"),
|
|
26
|
+
pytest.param("2147483.648", None, id="above-wait-limit"),
|
|
27
|
+
],
|
|
28
|
+
)
|
|
29
|
+
def test_from_exe_query_timeout(
|
|
30
|
+
mocker: MockerFixture, caplog: pytest.LogCaptureFixture, raw: str | None, expected: float | None
|
|
31
|
+
) -> None:
|
|
32
|
+
communicate = mocker.spy(Popen, "communicate")
|
|
33
|
+
env = {key: value for key, value in os.environ.items() if key != "PY_DISCOVERY_TIMEOUT"}
|
|
34
|
+
if raw is not None:
|
|
35
|
+
env["PY_DISCOVERY_TIMEOUT"] = raw
|
|
36
|
+
result = PythonInfo.from_exe(sys.executable, env=env, ignore_cache=True, resolve_to_host=False)
|
|
37
|
+
assert result is not None
|
|
38
|
+
assert result.version_info == sys.version_info
|
|
39
|
+
assert communicate.call_args_list[0].kwargs == {"timeout": expected}
|
|
40
|
+
assert not caplog.records
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.mark.parametrize(
|
|
44
|
+
"raw",
|
|
45
|
+
[
|
|
46
|
+
pytest.param("", id="empty"),
|
|
47
|
+
pytest.param("abc", id="words"),
|
|
48
|
+
pytest.param("0x10", id="hex"),
|
|
49
|
+
pytest.param("nan", id="nan"),
|
|
50
|
+
pytest.param("0", id="zero"),
|
|
51
|
+
pytest.param("-1", id="negative"),
|
|
52
|
+
pytest.param("-inf", id="negative-inf"),
|
|
53
|
+
pytest.param("1e-999", id="underflow"),
|
|
54
|
+
],
|
|
55
|
+
)
|
|
56
|
+
def test_from_exe_invalid_timeout_warns_once(mocker: MockerFixture, caplog: pytest.LogCaptureFixture, raw: str) -> None:
|
|
57
|
+
communicate = mocker.spy(Popen, "communicate")
|
|
58
|
+
env = {**os.environ, "PY_DISCOVERY_TIMEOUT": raw}
|
|
59
|
+
for _ in range(2):
|
|
60
|
+
result = PythonInfo.from_exe(sys.executable, env=env, ignore_cache=True, resolve_to_host=False)
|
|
61
|
+
assert result is not None
|
|
62
|
+
assert result.version_info == sys.version_info
|
|
63
|
+
assert [call.kwargs for call in communicate.call_args_list] == [{"timeout": 15.0}] * 2
|
|
64
|
+
assert [(record.levelno, record.getMessage()) for record in caplog.records] == [
|
|
65
|
+
(logging.WARNING, f"ignoring PY_DISCOVERY_TIMEOUT={raw!r}, not a positive number of seconds; using 15.0")
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_from_exe_timeout_warning_cache_is_bounded(caplog: pytest.LogCaptureFixture) -> None:
|
|
70
|
+
for index in range(129):
|
|
71
|
+
result = PythonInfo.from_exe(
|
|
72
|
+
sys.executable,
|
|
73
|
+
env={**os.environ, "PY_DISCOVERY_TIMEOUT": f"invalid-timeout-{index}"},
|
|
74
|
+
ignore_cache=True,
|
|
75
|
+
resolve_to_host=False,
|
|
76
|
+
)
|
|
77
|
+
assert result is not None
|
|
78
|
+
assert result.version_info == sys.version_info
|
|
79
|
+
caplog.clear()
|
|
80
|
+
for raw in ("invalid-timeout-128", "invalid-timeout-0"):
|
|
81
|
+
result = PythonInfo.from_exe(
|
|
82
|
+
sys.executable, env={**os.environ, "PY_DISCOVERY_TIMEOUT": raw}, ignore_cache=True, resolve_to_host=False
|
|
83
|
+
)
|
|
84
|
+
assert result is not None
|
|
85
|
+
assert result.version_info == sys.version_info
|
|
86
|
+
assert [record.getMessage() for record in caplog.records] == [
|
|
87
|
+
"ignoring PY_DISCOVERY_TIMEOUT='invalid-timeout-0', not a positive number of seconds; using 15.0"
|
|
88
|
+
]
|
|
@@ -43,7 +43,7 @@ def _load_registry_data(
|
|
|
43
43
|
glob: dict[str, object] = {"winreg": winreg}
|
|
44
44
|
mock_value_str = (Path(__file__).parent / "winreg_mock_values.py").read_text(encoding="utf-8")
|
|
45
45
|
exec(mock_value_str, glob, loc) # ruff:ignore[exec-builtin]
|
|
46
|
-
return loc["enum_collect"], loc["value_collect"], loc["key_open"], loc["hive_open"] #
|
|
46
|
+
return loc["enum_collect"], loc["value_collect"], loc["key_open"], loc["hive_open"] # ty: ignore[invalid-return-type]
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
class _Key:
|
|
@@ -65,7 +65,7 @@ def _make_enum_key(enum_collect: dict[object, dict[int, object]]) -> object:
|
|
|
65
65
|
result = enum_collect[key_id][at]
|
|
66
66
|
if isinstance(result, OSError):
|
|
67
67
|
raise result
|
|
68
|
-
return result #
|
|
68
|
+
return result # ty: ignore[invalid-return-type]
|
|
69
69
|
|
|
70
70
|
return _enum_key
|
|
71
71
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
requires = [ "tox>=4.64.7", "tox-uv>=1.29" ]
|
|
2
|
+
env_list = [ "3.15", "type-3.15", "3.14", "3.13", "3.12", "3.11", "3.10", "3.9", "type-3.9", "3.15t", "docs", "fix", "pkg_meta" ]
|
|
3
|
+
skip_missing_interpreters = true
|
|
4
|
+
|
|
5
|
+
[env_run_base]
|
|
6
|
+
description = "run the tests with pytest"
|
|
7
|
+
default_base_python = [ "3.15" ]
|
|
8
|
+
package = "wheel"
|
|
9
|
+
wheel_build_env = ".pkg"
|
|
10
|
+
dependency_groups = [ "test" ]
|
|
11
|
+
pass_env = [ "DIFF_AGAINST", "PYTEST_*" ]
|
|
12
|
+
set_env.COVERAGE_FILE = "{work_dir}{/}.coverage.{env_name}"
|
|
13
|
+
commands = [
|
|
14
|
+
[ "coverage", "erase" ],
|
|
15
|
+
[
|
|
16
|
+
"coverage",
|
|
17
|
+
"run",
|
|
18
|
+
"-m",
|
|
19
|
+
"pytest",
|
|
20
|
+
{ replace = "posargs", default = [
|
|
21
|
+
"--junitxml",
|
|
22
|
+
"{work_dir}{/}junit.{env_name}.xml",
|
|
23
|
+
"tests",
|
|
24
|
+
], extend = true },
|
|
25
|
+
],
|
|
26
|
+
[ "coverage", "combine" ],
|
|
27
|
+
[ "coverage", "report" ],
|
|
28
|
+
[ "coverage", "html", "-d", "{env_tmp_dir}{/}htmlcov" ],
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[env_pkg_base]
|
|
32
|
+
default_base_python = [ "3.15" ]
|
|
33
|
+
|
|
34
|
+
[env."type-3.15"]
|
|
35
|
+
description = "run type check on code base (3.15)"
|
|
36
|
+
deps = [ "gitpython>=3.2", "packaging>=26.2", "ty==0.0.84" ]
|
|
37
|
+
commands = [ [ "ty", "check", "--output-format", "concise", "--error-on-warning", "--python-version", "3.15", "." ] ]
|
|
38
|
+
|
|
39
|
+
[env."type-3.9"]
|
|
40
|
+
description = "run type check on code base (3.9)"
|
|
41
|
+
deps = [ "gitpython>=3.2", "packaging>=26.2", "ty==0.0.84" ]
|
|
42
|
+
commands = [ [ "ty", "check", "--output-format", "concise", "--error-on-warning", "--python-version", "3.9", "." ] ]
|
|
43
|
+
|
|
44
|
+
[env.docs]
|
|
45
|
+
description = "build documentation"
|
|
46
|
+
dependency_groups = [ "docs" ]
|
|
47
|
+
commands = [
|
|
48
|
+
[
|
|
49
|
+
"sphinx-build",
|
|
50
|
+
"-d",
|
|
51
|
+
"{env_tmp_dir}{/}doctree",
|
|
52
|
+
"docs",
|
|
53
|
+
"--color",
|
|
54
|
+
"-b",
|
|
55
|
+
"html",
|
|
56
|
+
"-W",
|
|
57
|
+
{ replace = "posargs", default = [ "{work_dir}{/}docs_out" ], extend = true },
|
|
58
|
+
],
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[env.fix]
|
|
62
|
+
description = "run static analysis and style check using flake8"
|
|
63
|
+
skip_install = true
|
|
64
|
+
deps = [ "pre-commit-uv>=4.2.1" ]
|
|
65
|
+
pass_env = [ "HOMEPATH", "PROGRAMDATA" ]
|
|
66
|
+
commands = [ [ "pre-commit", "run", "--all-files", "--show-diff-on-failure" ] ]
|
|
67
|
+
|
|
68
|
+
[env.pkg_meta]
|
|
69
|
+
description = "check that the long description is valid"
|
|
70
|
+
skip_install = true
|
|
71
|
+
deps = [ "check-wheel-contents>=0.6.3", "twine>=6.2", "uv>=0.12.22" ]
|
|
72
|
+
commands = [
|
|
73
|
+
[ "uv", "build", "--sdist", "--wheel", "--out-dir", "{env_tmp_dir}", "." ],
|
|
74
|
+
[ "twine", "check", "{env_tmp_dir}{/}*" ],
|
|
75
|
+
[ "check-wheel-contents", "--no-config", "{env_tmp_dir}" ],
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[env.dev]
|
|
79
|
+
description = "generate a DEV environment"
|
|
80
|
+
package = "editable"
|
|
81
|
+
dependency_groups = [ "test" ]
|
|
82
|
+
commands = [ [ "uv", "pip", "tree" ], [ "python", "-c", "import sys; print(sys.executable)" ] ]
|
|
83
|
+
|
|
84
|
+
[env.release]
|
|
85
|
+
description = "do a release, required posargs of the version number"
|
|
86
|
+
skip_install = true
|
|
87
|
+
dependency_groups = [ "release" ]
|
|
88
|
+
pass_env = [ "GH_TOKEN" ]
|
|
89
|
+
commands = [ [ "python", "{tox_root}/tasks/release.py", "--version", "{posargs}" ] ]
|
python_discovery-1.6.1/tox.toml
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
requires = ["tox>=4.38", "tox-uv>=1.29"]
|
|
2
|
-
env_list = ["fix", "3.15", "3.15t", "3.14", "3.13", "3.12", "3.11", "3.10", "3.9", "3.8", "type-3.8", "type-3.14", "docs", "pkg_meta"]
|
|
3
|
-
skip_missing_interpreters = true
|
|
4
|
-
|
|
5
|
-
[env_run_base]
|
|
6
|
-
description = "run the tests with pytest"
|
|
7
|
-
package = "wheel"
|
|
8
|
-
wheel_build_env = ".pkg"
|
|
9
|
-
dependency_groups = ["test"]
|
|
10
|
-
pass_env = ["DIFF_AGAINST", "PYTEST_*"]
|
|
11
|
-
set_env.COVERAGE_FILE = "{work_dir}{/}.coverage.{env_name}"
|
|
12
|
-
commands = [
|
|
13
|
-
["coverage", "erase"],
|
|
14
|
-
[
|
|
15
|
-
"coverage",
|
|
16
|
-
"run",
|
|
17
|
-
"-m",
|
|
18
|
-
"pytest",
|
|
19
|
-
{ replace = "posargs", extend = true, default = [
|
|
20
|
-
"--junitxml",
|
|
21
|
-
"{work_dir}{/}junit.{env_name}.xml",
|
|
22
|
-
"tests",
|
|
23
|
-
] },
|
|
24
|
-
],
|
|
25
|
-
["coverage", "combine"],
|
|
26
|
-
["coverage", "report"],
|
|
27
|
-
["coverage", "html", "-d", "{env_tmp_dir}{/}htmlcov"],
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
[env.fix]
|
|
31
|
-
description = "run static analysis and style check using flake8"
|
|
32
|
-
skip_install = true
|
|
33
|
-
deps = ["pre-commit-uv>=4.2.1"]
|
|
34
|
-
pass_env = ["HOMEPATH", "PROGRAMDATA"]
|
|
35
|
-
commands = [["pre-commit", "run", "--all-files", "--show-diff-on-failure"]]
|
|
36
|
-
|
|
37
|
-
[env."type-3.8"]
|
|
38
|
-
description = "run type check on code base (3.8)"
|
|
39
|
-
deps = ["gitpython>=3.1.46", "packaging>=26", "ty==0.0.17"]
|
|
40
|
-
commands = [["ty", "check", "--output-format", "concise", "--error-on-warning", "--python-version", "3.8", "."]]
|
|
41
|
-
|
|
42
|
-
[env."type-3.14"]
|
|
43
|
-
description = "run type check on code base (3.14)"
|
|
44
|
-
deps = ["gitpython>=3.1.46", "packaging>=26", "ty==0.0.17"]
|
|
45
|
-
commands = [["ty", "check", "--output-format", "concise", "--error-on-warning", "--python-version", "3.14", "."]]
|
|
46
|
-
|
|
47
|
-
[env.docs]
|
|
48
|
-
description = "build documentation"
|
|
49
|
-
dependency_groups = ["docs"]
|
|
50
|
-
commands = [
|
|
51
|
-
[
|
|
52
|
-
"sphinx-build",
|
|
53
|
-
"-d",
|
|
54
|
-
"{env_tmp_dir}{/}doctree",
|
|
55
|
-
"docs",
|
|
56
|
-
"--color",
|
|
57
|
-
"-b",
|
|
58
|
-
"html",
|
|
59
|
-
"-W",
|
|
60
|
-
{ replace = "posargs", extend = true, default = ["{work_dir}{/}docs_out"] },
|
|
61
|
-
],
|
|
62
|
-
]
|
|
63
|
-
|
|
64
|
-
[env.pkg_meta]
|
|
65
|
-
description = "check that the long description is valid"
|
|
66
|
-
skip_install = true
|
|
67
|
-
deps = ["check-wheel-contents>=0.6.3", "twine>=6.2", "uv>=0.10.4"]
|
|
68
|
-
commands = [
|
|
69
|
-
["uv", "build", "--sdist", "--wheel", "--out-dir", "{env_tmp_dir}", "."],
|
|
70
|
-
["twine", "check", "{env_tmp_dir}{/}*"],
|
|
71
|
-
["check-wheel-contents", "--no-config", "{env_tmp_dir}"],
|
|
72
|
-
]
|
|
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
|
-
|
|
81
|
-
[env.dev]
|
|
82
|
-
description = "generate a DEV environment"
|
|
83
|
-
package = "editable"
|
|
84
|
-
dependency_groups = ["test"]
|
|
85
|
-
commands = [["uv", "pip", "tree"], ["python", "-c", "import sys; print(sys.executable)"]]
|
|
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
|
|
File without changes
|
|
File without changes
|
{python_discovery-1.6.1 → python_discovery-1.6.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
|