pystand 2.33__tar.gz → 2.35__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.
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pystand
3
- Version: 2.33
3
+ Version: 2.35
4
4
  Summary: Install Python versions from python-build-standalone project
5
5
  Author-email: Mark Blakeney <mark.blakeney@bullet-systems.net>
6
6
  License-Expression: GPL-3.0-or-later
7
7
  Project-URL: Homepage, https://github.com/bulletmark/pystand
8
8
  Keywords: python-build-standalone,pyenv,hatch,pdm
9
- Classifier: Programming Language :: Python :: 3
10
9
  Requires-Python: >=3.8
11
10
  Description-Content-Type: text/markdown
12
11
  Requires-Dist: packaging
@@ -15,7 +14,7 @@ Requires-Dist: argparse-from-file
15
14
  Requires-Dist: pygithub
16
15
  Requires-Dist: certifi
17
16
  Requires-Dist: filelock
18
- Requires-Dist: zstandard; python_version < "3.14"
17
+ Requires-Dist: backports.zstd; python_version < "3.14"
19
18
 
20
19
  ## PYSTAND - Install Python Versions From The Python-Build-Standalone Project
21
20
  [![PyPi](https://img.shields.io/pypi/v/pystand)](https://pypi.org/project/pystand/)
@@ -141,10 +140,10 @@ Type `pystand` or `pystand -h` to view the usage summary:
141
140
 
142
141
  ```
143
142
  usage: pystand [-h] [-D DISTRIBUTION] [-P PREFIX_DIR] [-C CACHE_DIR]
144
- [-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
145
- [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
146
- [--no-color] [--cert {system,certifi,none}] [-V]
147
- {install,i,update,u,up,remove,r,rm,list,l,show,s,path,p,cache,c,uv,uvx} ...
143
+ [-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
144
+ [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
145
+ [--no-color] [--cert {system,certifi,none}] [-V]
146
+ {install,i,update,u,up,remove,r,rm,list,l,show,s,path,p,cache,c,uv,uvx} ...
148
147
 
149
148
  Command line tool to download, install, and update pre-built Python versions
150
149
  from the python-build-standalone project at https://github.com/astral-
@@ -172,7 +171,7 @@ options:
172
171
  --purge-days PURGE_DAYS
173
172
  cache YYYYMMDD release file lists and downloads for
174
173
  this number of days after last version referencing
175
- that release is removed. Default is 90 days.
174
+ that release is removed. Default is 30 days.
176
175
  --github-access-token GITHUB_ACCESS_TOKEN
177
176
  optional Github access token. Can specify to reduce
178
177
  rate limiting.
@@ -203,25 +202,24 @@ Some commands offer aliases as shown in parentheses above. Note you can set
203
202
  default starting global options in ~/.config/pystand-flags.conf.
204
203
  ```
205
204
 
206
- Type `pystand <command> -h` to see specific help/usage for any
207
- individual command:
205
+ Type `pystand <command> -h` to see specific help/usage for any individual command:
208
206
 
209
207
  ### Command `install`
210
208
 
211
209
  ```
212
210
  usage: pystand install [-h] [-r RELEASE] [-a] [-A] [--skip] [-f] [-s]
213
- [version ...]
211
+ [version ...]
214
212
 
215
213
  Install one, more, or all versions from a python-build-standalone release.
216
214
 
217
215
  positional arguments:
218
- version version to install. E.g. 3.12 or 3.12.3
216
+ version version to install. E.g. 3.14 or 3.14.1
219
217
 
220
218
  options:
221
219
  -h, --help show this help message and exit
222
220
  -r, --release RELEASE
223
221
  install from specified python-build-standalone
224
- YYYYMMDD release (e.g. 20240415), default is latest
222
+ YYYYMMDD release (e.g. 20260807), default is latest
225
223
  release
226
224
  -a, --all install ALL versions from release
227
225
  -A, --all-prerelease install ALL versions from release, including pre-
@@ -239,7 +237,7 @@ aliases: i
239
237
 
240
238
  ```
241
239
  usage: pystand update [-h] [-r RELEASE] [-a] [--pre-release] [--skip] [-k]
242
- [version ...]
240
+ [version ...]
243
241
 
244
242
  Update one, more, or all versions to another release.
245
243
 
@@ -249,7 +247,7 @@ positional arguments:
249
247
  options:
250
248
  -h, --help show this help message and exit
251
249
  -r, --release RELEASE
252
- update to specified YYYMMDD release (e.g. 20240415),
250
+ update to specified YYYMMDD release (e.g. 20260807),
253
251
  default is latest release
254
252
  -a, --all update ALL versions
255
253
  --pre-release update release version to a later superceding pre-
@@ -279,7 +277,7 @@ options:
279
277
  can be specified with --all)
280
278
  -r, --release RELEASE
281
279
  only remove versions if from specified YYYMMDD release
282
- (e.g. 20240415)
280
+ (e.g. 20260807)
283
281
 
284
282
  aliases: r, rm
285
283
  ```
@@ -299,7 +297,7 @@ options:
299
297
  -v, --verbose explicitly report why a version is not eligible for
300
298
  update
301
299
  -r, --release RELEASE
302
- use specified YYYYMMDD release (e.g. 20240415) for
300
+ use specified YYYYMMDD release (e.g. 20260807) for
303
301
  verbose compare, default is latest release
304
302
 
305
303
  aliases: l
@@ -323,7 +321,7 @@ options:
323
321
  -l, --list just list recent releases
324
322
  -r, --release RELEASE
325
323
  python-build-standalone YYYYMMDD release to show (e.g.
326
- 20240415), default is latest release
324
+ 20260807), default is latest release
327
325
  -a, --all show all available distributions for each version from
328
326
  the release
329
327
 
@@ -378,7 +376,7 @@ aliases: c
378
376
 
379
377
  ```
380
378
  usage: pystand uv [-h] [-p PYTHON]
381
- command [subcommand] [uv_args_for_command ...]
379
+ command [subcommand] [uv_args_for_command ...]
382
380
 
383
381
  Run a uv command using a version of python installed by pystand.
384
382
 
@@ -390,7 +388,7 @@ positional arguments:
390
388
 
391
389
  options:
392
390
  -h, --help show this help message and exit
393
- -p, --python PYTHON version of python to use, e.g. "3.12", default is
391
+ -p, --python PYTHON version of python to use, e.g. "3.14", default is
394
392
  latest release version
395
393
  ```
396
394
 
@@ -408,7 +406,7 @@ positional arguments:
408
406
 
409
407
  options:
410
408
  -h, --help show this help message and exit
411
- -p, --python PYTHON version of python to use, e.g. "3.12", default is
409
+ -p, --python PYTHON version of python to use, e.g. "3.14", default is
412
410
  latest release version
413
411
  ```
414
412
 
@@ -122,10 +122,10 @@ Type `pystand` or `pystand -h` to view the usage summary:
122
122
 
123
123
  ```
124
124
  usage: pystand [-h] [-D DISTRIBUTION] [-P PREFIX_DIR] [-C CACHE_DIR]
125
- [-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
126
- [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
127
- [--no-color] [--cert {system,certifi,none}] [-V]
128
- {install,i,update,u,up,remove,r,rm,list,l,show,s,path,p,cache,c,uv,uvx} ...
125
+ [-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
126
+ [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
127
+ [--no-color] [--cert {system,certifi,none}] [-V]
128
+ {install,i,update,u,up,remove,r,rm,list,l,show,s,path,p,cache,c,uv,uvx} ...
129
129
 
130
130
  Command line tool to download, install, and update pre-built Python versions
131
131
  from the python-build-standalone project at https://github.com/astral-
@@ -153,7 +153,7 @@ options:
153
153
  --purge-days PURGE_DAYS
154
154
  cache YYYYMMDD release file lists and downloads for
155
155
  this number of days after last version referencing
156
- that release is removed. Default is 90 days.
156
+ that release is removed. Default is 30 days.
157
157
  --github-access-token GITHUB_ACCESS_TOKEN
158
158
  optional Github access token. Can specify to reduce
159
159
  rate limiting.
@@ -184,25 +184,24 @@ Some commands offer aliases as shown in parentheses above. Note you can set
184
184
  default starting global options in ~/.config/pystand-flags.conf.
185
185
  ```
186
186
 
187
- Type `pystand <command> -h` to see specific help/usage for any
188
- individual command:
187
+ Type `pystand <command> -h` to see specific help/usage for any individual command:
189
188
 
190
189
  ### Command `install`
191
190
 
192
191
  ```
193
192
  usage: pystand install [-h] [-r RELEASE] [-a] [-A] [--skip] [-f] [-s]
194
- [version ...]
193
+ [version ...]
195
194
 
196
195
  Install one, more, or all versions from a python-build-standalone release.
197
196
 
198
197
  positional arguments:
199
- version version to install. E.g. 3.12 or 3.12.3
198
+ version version to install. E.g. 3.14 or 3.14.1
200
199
 
201
200
  options:
202
201
  -h, --help show this help message and exit
203
202
  -r, --release RELEASE
204
203
  install from specified python-build-standalone
205
- YYYYMMDD release (e.g. 20240415), default is latest
204
+ YYYYMMDD release (e.g. 20260807), default is latest
206
205
  release
207
206
  -a, --all install ALL versions from release
208
207
  -A, --all-prerelease install ALL versions from release, including pre-
@@ -220,7 +219,7 @@ aliases: i
220
219
 
221
220
  ```
222
221
  usage: pystand update [-h] [-r RELEASE] [-a] [--pre-release] [--skip] [-k]
223
- [version ...]
222
+ [version ...]
224
223
 
225
224
  Update one, more, or all versions to another release.
226
225
 
@@ -230,7 +229,7 @@ positional arguments:
230
229
  options:
231
230
  -h, --help show this help message and exit
232
231
  -r, --release RELEASE
233
- update to specified YYYMMDD release (e.g. 20240415),
232
+ update to specified YYYMMDD release (e.g. 20260807),
234
233
  default is latest release
235
234
  -a, --all update ALL versions
236
235
  --pre-release update release version to a later superceding pre-
@@ -260,7 +259,7 @@ options:
260
259
  can be specified with --all)
261
260
  -r, --release RELEASE
262
261
  only remove versions if from specified YYYMMDD release
263
- (e.g. 20240415)
262
+ (e.g. 20260807)
264
263
 
265
264
  aliases: r, rm
266
265
  ```
@@ -280,7 +279,7 @@ options:
280
279
  -v, --verbose explicitly report why a version is not eligible for
281
280
  update
282
281
  -r, --release RELEASE
283
- use specified YYYYMMDD release (e.g. 20240415) for
282
+ use specified YYYYMMDD release (e.g. 20260807) for
284
283
  verbose compare, default is latest release
285
284
 
286
285
  aliases: l
@@ -304,7 +303,7 @@ options:
304
303
  -l, --list just list recent releases
305
304
  -r, --release RELEASE
306
305
  python-build-standalone YYYYMMDD release to show (e.g.
307
- 20240415), default is latest release
306
+ 20260807), default is latest release
308
307
  -a, --all show all available distributions for each version from
309
308
  the release
310
309
 
@@ -359,7 +358,7 @@ aliases: c
359
358
 
360
359
  ```
361
360
  usage: pystand uv [-h] [-p PYTHON]
362
- command [subcommand] [uv_args_for_command ...]
361
+ command [subcommand] [uv_args_for_command ...]
363
362
 
364
363
  Run a uv command using a version of python installed by pystand.
365
364
 
@@ -371,7 +370,7 @@ positional arguments:
371
370
 
372
371
  options:
373
372
  -h, --help show this help message and exit
374
- -p, --python PYTHON version of python to use, e.g. "3.12", default is
373
+ -p, --python PYTHON version of python to use, e.g. "3.14", default is
375
374
  latest release version
376
375
  ```
377
376
 
@@ -389,7 +388,7 @@ positional arguments:
389
388
 
390
389
  options:
391
390
  -h, --help show this help message and exit
392
- -p, --python PYTHON version of python to use, e.g. "3.12", default is
391
+ -p, --python PYTHON version of python to use, e.g. "3.14", default is
393
392
  latest release version
394
393
  ```
395
394
 
@@ -1,4 +1,3 @@
1
- #!/usr/bin/python
2
1
  import re
3
2
  import subprocess
4
3
  import sys
@@ -7,7 +6,7 @@ import pystand
7
6
 
8
7
  cmd = 'pystand show --all'.split()
9
8
  out = subprocess.run(cmd, capture_output=True, text=True).stdout
10
- alldists = set(re.sub(r'.+"(.+)".*', r'\1', ln) for ln in out.splitlines())
9
+ alldists = {re.sub(r'.+"(.+)".*', r'\1', ln) for ln in out.splitlines()}
11
10
 
12
11
  error = 0
13
12
  for mach, dist in pystand.DISTRIBUTIONS.items():
@@ -15,7 +15,7 @@ upload: build
15
15
  uv-publish
16
16
 
17
17
  doc:
18
- update-readme-usage -A
18
+ update-readme-usage
19
19
 
20
20
  format:
21
21
  ruff check --select I --fix {{PYFILES}} && ruff format {{PYFILES}}
@@ -9,9 +9,6 @@ readme = "README.md"
9
9
  license = "GPL-3.0-or-later"
10
10
  requires-python = ">=3.8"
11
11
  keywords = ["python-build-standalone", "pyenv", "hatch", "pdm"]
12
- classifiers = [
13
- "Programming Language :: Python :: 3",
14
- ]
15
12
  dynamic = ["version"]
16
13
  dependencies = [
17
14
  "packaging",
@@ -20,7 +17,7 @@ dependencies = [
20
17
  "pygithub",
21
18
  "certifi",
22
19
  "filelock",
23
- "zstandard; python_version < '3.14'",
20
+ "backports.zstd; python_version < '3.14'",
24
21
  ]
25
22
 
26
23
  [[project.authors]]
@@ -36,22 +33,10 @@ pystand = "pystand:main"
36
33
  [tool.setuptools_scm]
37
34
  version_scheme = "post-release"
38
35
 
39
- [tool.mypy]
40
- implicit_optional = true
41
- warn_no_return = false
42
- allow_untyped_globals = true
43
- allow_redefinition = true
44
- ignore_missing_imports = true
45
-
46
36
  [tool.ruff.format]
47
37
  quote-style = "single"
48
- skip-magic-trailing-comma = true
49
38
 
50
- [tool.edit-lint]
51
- linters = [
52
- "ruff check",
53
- "mypy",
54
- "pyright",
55
- ]
39
+ [tool.ruff.lint]
40
+ ignore = ["SIM115", "SIM905", "BLE001", "PLW1510", "S110", "DTZ", "C408"]
56
41
 
57
42
  # vim:se sw=2:
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pystand
3
- Version: 2.33
3
+ Version: 2.35
4
4
  Summary: Install Python versions from python-build-standalone project
5
5
  Author-email: Mark Blakeney <mark.blakeney@bullet-systems.net>
6
6
  License-Expression: GPL-3.0-or-later
7
7
  Project-URL: Homepage, https://github.com/bulletmark/pystand
8
8
  Keywords: python-build-standalone,pyenv,hatch,pdm
9
- Classifier: Programming Language :: Python :: 3
10
9
  Requires-Python: >=3.8
11
10
  Description-Content-Type: text/markdown
12
11
  Requires-Dist: packaging
@@ -15,7 +14,7 @@ Requires-Dist: argparse-from-file
15
14
  Requires-Dist: pygithub
16
15
  Requires-Dist: certifi
17
16
  Requires-Dist: filelock
18
- Requires-Dist: zstandard; python_version < "3.14"
17
+ Requires-Dist: backports.zstd; python_version < "3.14"
19
18
 
20
19
  ## PYSTAND - Install Python Versions From The Python-Build-Standalone Project
21
20
  [![PyPi](https://img.shields.io/pypi/v/pystand)](https://pypi.org/project/pystand/)
@@ -141,10 +140,10 @@ Type `pystand` or `pystand -h` to view the usage summary:
141
140
 
142
141
  ```
143
142
  usage: pystand [-h] [-D DISTRIBUTION] [-P PREFIX_DIR] [-C CACHE_DIR]
144
- [-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
145
- [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
146
- [--no-color] [--cert {system,certifi,none}] [-V]
147
- {install,i,update,u,up,remove,r,rm,list,l,show,s,path,p,cache,c,uv,uvx} ...
143
+ [-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
144
+ [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
145
+ [--no-color] [--cert {system,certifi,none}] [-V]
146
+ {install,i,update,u,up,remove,r,rm,list,l,show,s,path,p,cache,c,uv,uvx} ...
148
147
 
149
148
  Command line tool to download, install, and update pre-built Python versions
150
149
  from the python-build-standalone project at https://github.com/astral-
@@ -172,7 +171,7 @@ options:
172
171
  --purge-days PURGE_DAYS
173
172
  cache YYYYMMDD release file lists and downloads for
174
173
  this number of days after last version referencing
175
- that release is removed. Default is 90 days.
174
+ that release is removed. Default is 30 days.
176
175
  --github-access-token GITHUB_ACCESS_TOKEN
177
176
  optional Github access token. Can specify to reduce
178
177
  rate limiting.
@@ -203,25 +202,24 @@ Some commands offer aliases as shown in parentheses above. Note you can set
203
202
  default starting global options in ~/.config/pystand-flags.conf.
204
203
  ```
205
204
 
206
- Type `pystand <command> -h` to see specific help/usage for any
207
- individual command:
205
+ Type `pystand <command> -h` to see specific help/usage for any individual command:
208
206
 
209
207
  ### Command `install`
210
208
 
211
209
  ```
212
210
  usage: pystand install [-h] [-r RELEASE] [-a] [-A] [--skip] [-f] [-s]
213
- [version ...]
211
+ [version ...]
214
212
 
215
213
  Install one, more, or all versions from a python-build-standalone release.
216
214
 
217
215
  positional arguments:
218
- version version to install. E.g. 3.12 or 3.12.3
216
+ version version to install. E.g. 3.14 or 3.14.1
219
217
 
220
218
  options:
221
219
  -h, --help show this help message and exit
222
220
  -r, --release RELEASE
223
221
  install from specified python-build-standalone
224
- YYYYMMDD release (e.g. 20240415), default is latest
222
+ YYYYMMDD release (e.g. 20260807), default is latest
225
223
  release
226
224
  -a, --all install ALL versions from release
227
225
  -A, --all-prerelease install ALL versions from release, including pre-
@@ -239,7 +237,7 @@ aliases: i
239
237
 
240
238
  ```
241
239
  usage: pystand update [-h] [-r RELEASE] [-a] [--pre-release] [--skip] [-k]
242
- [version ...]
240
+ [version ...]
243
241
 
244
242
  Update one, more, or all versions to another release.
245
243
 
@@ -249,7 +247,7 @@ positional arguments:
249
247
  options:
250
248
  -h, --help show this help message and exit
251
249
  -r, --release RELEASE
252
- update to specified YYYMMDD release (e.g. 20240415),
250
+ update to specified YYYMMDD release (e.g. 20260807),
253
251
  default is latest release
254
252
  -a, --all update ALL versions
255
253
  --pre-release update release version to a later superceding pre-
@@ -279,7 +277,7 @@ options:
279
277
  can be specified with --all)
280
278
  -r, --release RELEASE
281
279
  only remove versions if from specified YYYMMDD release
282
- (e.g. 20240415)
280
+ (e.g. 20260807)
283
281
 
284
282
  aliases: r, rm
285
283
  ```
@@ -299,7 +297,7 @@ options:
299
297
  -v, --verbose explicitly report why a version is not eligible for
300
298
  update
301
299
  -r, --release RELEASE
302
- use specified YYYYMMDD release (e.g. 20240415) for
300
+ use specified YYYYMMDD release (e.g. 20260807) for
303
301
  verbose compare, default is latest release
304
302
 
305
303
  aliases: l
@@ -323,7 +321,7 @@ options:
323
321
  -l, --list just list recent releases
324
322
  -r, --release RELEASE
325
323
  python-build-standalone YYYYMMDD release to show (e.g.
326
- 20240415), default is latest release
324
+ 20260807), default is latest release
327
325
  -a, --all show all available distributions for each version from
328
326
  the release
329
327
 
@@ -378,7 +376,7 @@ aliases: c
378
376
 
379
377
  ```
380
378
  usage: pystand uv [-h] [-p PYTHON]
381
- command [subcommand] [uv_args_for_command ...]
379
+ command [subcommand] [uv_args_for_command ...]
382
380
 
383
381
  Run a uv command using a version of python installed by pystand.
384
382
 
@@ -390,7 +388,7 @@ positional arguments:
390
388
 
391
389
  options:
392
390
  -h, --help show this help message and exit
393
- -p, --python PYTHON version of python to use, e.g. "3.12", default is
391
+ -p, --python PYTHON version of python to use, e.g. "3.14", default is
394
392
  latest release version
395
393
  ```
396
394
 
@@ -408,7 +406,7 @@ positional arguments:
408
406
 
409
407
  options:
410
408
  -h, --help show this help message and exit
411
- -p, --python PYTHON version of python to use, e.g. "3.12", default is
409
+ -p, --python PYTHON version of python to use, e.g. "3.14", default is
412
410
  latest release version
413
411
  ```
414
412
 
@@ -9,4 +9,6 @@ pystand.egg-info/SOURCES.txt
9
9
  pystand.egg-info/dependency_links.txt
10
10
  pystand.egg-info/entry_points.txt
11
11
  pystand.egg-info/requires.txt
12
+ pystand.egg-info/scm_file_list.json
13
+ pystand.egg-info/scm_version.json
12
14
  pystand.egg-info/top_level.txt
@@ -6,4 +6,4 @@ certifi
6
6
  filelock
7
7
 
8
8
  [:python_version < "3.14"]
9
- zstandard
9
+ backports.zstd
@@ -0,0 +1,10 @@
1
+ {
2
+ "files": [
3
+ ".gitignore",
4
+ "README.md",
5
+ "check-dist.py",
6
+ "justfile",
7
+ "pyproject.toml",
8
+ "pystand.py"
9
+ ]
10
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "2.35",
3
+ "distance": 0,
4
+ "node": "gf90437d7f2e68e78d07fcefc226c22bbc3c067be",
5
+ "dirty": false,
6
+ "branch": "main",
7
+ "node_date": "2026-09-04"
8
+ }
@@ -1,4 +1,3 @@
1
- #!/usr/bin/python3
2
1
  """
3
2
  Command line tool to download, install, and update pre-built Python
4
3
  versions from the python-build-standalone project at
@@ -35,11 +34,13 @@ GITHUB_SITE = f'https://github.com/{GITHUB_REPO}'
35
34
  LATEST_RELEASES = f'{GITHUB_SITE}/releases.atom'
36
35
  LATEST_RELEASE_TAG = f'{GITHUB_SITE}/releases/latest'
37
36
  DOC = 'https://gregoryszorc.com/docs/python-build-standalone/main/'
37
+ VER = f'{sys.version_info.major}.{sys.version_info.minor}'
38
38
 
39
39
  # Sample release tag for documentation/usage examples
40
- SAMPL_RELEASE = '20240415'
40
+ SAMPL_RELEASE = '20260807'
41
41
 
42
42
  PROG = Path(__file__).stem
43
+ HOME = Path.home()
43
44
 
44
45
  # Default distributions for various platforms
45
46
  DISTRIBUTIONS = {
@@ -114,10 +115,10 @@ class ColorDist(ColorTable):
114
115
  return text.split('-', 1)[0]
115
116
 
116
117
 
117
- def unexpanduser(path: Path, *, home=Path.home()) -> Path:
118
+ def unexpanduser(path: Path) -> Path:
118
119
  "Return path name, with $HOME replaced by ~ (opposite of Path.expanduser())"
119
- if path.parts[: len(home.parts)] == home.parts:
120
- return Path('~', *path.parts[len(home.parts) :])
120
+ if path.parts[: len(HOME.parts)] == HOME.parts:
121
+ return Path('~', *path.parts[len(HOME.parts) :])
121
122
 
122
123
  return path
123
124
 
@@ -215,23 +216,6 @@ def rm_path(path: Path) -> bool:
215
216
  return True
216
217
 
217
218
 
218
- def register_zst() -> None:
219
- "Register custom zstandard unpacker"
220
- import tarfile
221
-
222
- from zstandard import ZstdDecompressor
223
-
224
- def unpack_zst(filename: str, extract_dir: str) -> None:
225
- "Unpack a zstandard compressed tar"
226
- with open(filename, 'rb') as compressed:
227
- dctx = ZstdDecompressor()
228
- with dctx.stream_reader(compressed) as reader:
229
- with tarfile.open(fileobj=reader, mode='r|') as tar:
230
- tar.extractall(path=extract_dir)
231
-
232
- shutil.register_unpack_format('zst', ['.zst'], unpack_zst)
233
-
234
-
235
219
  def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
236
220
  "Fetch and unpack a release file"
237
221
  from urllib.parse import unquote, urlparse
@@ -256,9 +240,12 @@ def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
256
240
  if error:
257
241
  rm_path(cache_file)
258
242
  else:
259
- # Register custom zstandard handler (only if Python < 3.14 which has built-in support)
260
- if filename.endswith('.zst') and sys.version_info < (3, 14):
261
- register_zst()
243
+ # Register external zstd handler (only if Python < 3.14 which has
244
+ # built-in support)
245
+ if filename.lower().endswith('.zst') and sys.version_info < (3, 14):
246
+ from backports.zstd import register_shutil # type: ignore
247
+
248
+ register_shutil()
262
249
 
263
250
  try:
264
251
  shutil.unpack_archive(cache_file, tmpdir)
@@ -307,9 +294,9 @@ class VersionMatcher:
307
294
 
308
295
  # If no version specified, return the latest release version
309
296
  if not version:
310
- for version in self.seq:
311
- if is_release_version(version):
312
- return version
297
+ for ver in self.seq:
298
+ if is_release_version(ver):
299
+ return ver
313
300
  return None
314
301
 
315
302
  if version in self.seq:
@@ -329,9 +316,10 @@ class VersionMatcher:
329
316
  # Only allow upgrade of formal release to another formal
330
317
  # release.
331
318
  for full_version in self.seq:
332
- if full_version.startswith(version):
333
- if not upgrade or not is_release or is_release_version(full_version):
334
- return full_version
319
+ if full_version.startswith(version) and (
320
+ not upgrade or not is_release or is_release_version(full_version)
321
+ ):
322
+ return full_version
335
323
 
336
324
  return None
337
325
 
@@ -362,7 +350,7 @@ def get_version_names(args: Namespace) -> list[str]:
362
350
  if not args.version:
363
351
  args.parser.error('Must specify at least one version, or --all.')
364
352
 
365
- all_names = set(f.name for f in iter_versions(args))
353
+ all_names = {f.name for f in iter_versions(args)}
366
354
 
367
355
  # Upconvert all user specified partial version names to full version names
368
356
  matcher = VersionMatcher(all_names)
@@ -459,7 +447,10 @@ def add_file(files: dict[str, Any], tag: str, name: str, url: str) -> None:
459
447
  else:
460
448
  return
461
449
 
462
- impl, ver, arch = name.split('-', 2)
450
+ try:
451
+ impl, ver, arch = name.split('-', 2)
452
+ except ValueError:
453
+ return
463
454
 
464
455
  # Modern releases have a '+' in the name to separate the version
465
456
  if '+' in ver:
@@ -492,7 +483,7 @@ def get_release_files(args, tag) -> dict[str, Any]:
492
483
  try:
493
484
  release = gh.get_repo(GITHUB_REPO).get_release(tag)
494
485
  except Exception as e:
495
- print(f'Error: {str(e)}', file=sys.stderr)
486
+ print(f'Error: {e}', file=sys.stderr)
496
487
  return {}
497
488
 
498
489
  # Iterate over the release assets and store pertinent files in a
@@ -527,7 +518,7 @@ def update_version_symlinks(args: Namespace) -> None:
527
518
 
528
519
  # Create a map of all the new major version links
529
520
  newlinks_all = defaultdict(set)
530
- pre_releases = set(v for v in vers if not is_release_version(v))
521
+ pre_releases = {v for v in vers if not is_release_version(v)}
531
522
  for namevers in vers:
532
523
  while '.' in namevers[:-1]:
533
524
  namevers_major = namevers.rsplit('.', maxsplit=1)[0]
@@ -546,7 +537,7 @@ def update_version_symlinks(args: Namespace) -> None:
546
537
  if ver in pre_releases and (rels := (cands - pre_releases)):
547
538
  cands = rels
548
539
 
549
- newlinks[ver] = sorted(cands, key=parse_version)[-1]
540
+ newlinks[ver] = max(cands, key=parse_version)
550
541
 
551
542
  # Remove all old or invalid existing links
552
543
  for name, tgt in oldlinks.items():
@@ -601,9 +592,9 @@ def show_list(args: Namespace) -> None:
601
592
  "Show a list of available releases"
602
593
  latest = parse_version(args._release)
603
594
  releases = fetch_tags(args)
604
- cached = set(p.name for p in args._releases.iterdir())
595
+ cached = {p.name for p in args._releases.iterdir()}
605
596
  for release in sorted(cached.union(releases)):
606
- if args.re_match and not re.search(args.re_match, release):
597
+ if args._re_match and not args._re_match.search(release):
607
598
  continue
608
599
 
609
600
  # Ignore any bogus releases that don't parse as versions
@@ -633,16 +624,16 @@ def show_list(args: Namespace) -> None:
633
624
  print(f'{release} {dts}{app}{pre}')
634
625
 
635
626
 
636
- def get_title(desc: str) -> str:
637
- "Return single title line from description"
627
+ def get_title(desc: str, name: str) -> str:
628
+ "Return single title line from command description"
638
629
  res = []
639
630
  for line in desc.splitlines():
640
- line = line.strip()
641
- res.append(line)
642
- if line.endswith('.'):
643
- return ' '.join(res)
631
+ if line := line.strip():
632
+ res.append(line)
633
+ if line.endswith('.'):
634
+ return ' '.join(res)
644
635
 
645
- sys.exit('Must end description with a full stop.')
636
+ sys.exit(f'Must end {name} command description with a full stop.')
646
637
 
647
638
 
648
639
  def remove(args: Namespace, version: str) -> None:
@@ -654,7 +645,9 @@ def remove(args: Namespace, version: str) -> None:
654
645
  # Touch the associated release file to ensure it lives until the
655
646
  # full purge time has expired if this was the last version using it
656
647
  if release := get_json(vdir / args._data).get('release'):
657
- (args._releases / release).touch()
648
+ release_file = args._releases / release
649
+ if release_file.is_file():
650
+ release_file.touch()
658
651
 
659
652
  shutil.rmtree(vdir)
660
653
 
@@ -674,15 +667,42 @@ def strip_binaries(vdir: Path, distribution: str) -> bool:
674
667
  if not file.is_symlink() and file.is_file():
675
668
  cmd = f'strip -p --strip-unneeded {file}'.split()
676
669
  try:
677
- subprocess.run(cmd, stderr=subprocess.DEVNULL)
670
+ res = subprocess.run(cmd, stderr=subprocess.DEVNULL)
678
671
  except Exception:
679
672
  pass
680
673
  else:
681
- was_stripped = True
674
+ was_stripped |= res.returncode == 0
682
675
 
683
676
  return was_stripped
684
677
 
685
678
 
679
+ def find_python(vdir: Path) -> Path | None:
680
+ "Find the python executable in a version directory"
681
+ for path in (vdir / 'bin' / 'python', vdir / 'python.exe'):
682
+ if path.is_file():
683
+ return path
684
+
685
+ return None
686
+
687
+
688
+ def compile_bytecode(srcdir: Path, tgtdir: Path) -> str | None:
689
+ "Compile all Python bytecode in a version directory"
690
+ if not (python := find_python(srcdir)):
691
+ return f'Python executable not found in {srcdir}'
692
+
693
+ cmd = (python, '-m', 'compileall', '-qd', str(tgtdir), str(srcdir))
694
+
695
+ try:
696
+ res = subprocess.run(cmd)
697
+ except Exception as e:
698
+ return f'Compile error: {e}'
699
+
700
+ if res.returncode != 0:
701
+ return f'Compile error code: {res.returncode}'
702
+
703
+ return None
704
+
705
+
686
706
  def install(
687
707
  args: Namespace, vdir: Path, release: str, distribution: str, files: dict[str, Any]
688
708
  ) -> str | None:
@@ -706,6 +726,11 @@ def install(
706
726
 
707
727
  if error := set_json(tmpdir / args._data, data):
708
728
  error = f'Failed to write {version} data file: {error}'
729
+ elif c_error := compile_bytecode(tmpdir, vdir):
730
+ print(
731
+ f'Warning: Could not compile bytecode for {version}: {c_error}',
732
+ file=sys.stderr,
733
+ )
709
734
 
710
735
  if error:
711
736
  shutil.rmtree(tmpdir)
@@ -826,7 +851,7 @@ def main() -> str | None:
826
851
  )
827
852
  opt.add_argument(
828
853
  '--purge-days',
829
- default=90,
854
+ default=30,
830
855
  type=int,
831
856
  help='cache YYYYMMDD release file lists and downloads for '
832
857
  'this number of days after last version referencing that '
@@ -867,8 +892,8 @@ def main() -> str | None:
867
892
  else:
868
893
  return f'Must define a docstring for command class "{name}".'
869
894
 
870
- aliases = cls.aliases if hasattr(cls, 'aliases') else []
871
- title = get_title(desc)
895
+ aliases = cls.aliases if hasattr(cls, 'aliases') else ()
896
+ title = get_title(desc, name)
872
897
  cmdopt = cmd.add_parser(name, description=desc, help=title, aliases=aliases)
873
898
 
874
899
  # Set up this commands own arguments, if it has any
@@ -933,7 +958,7 @@ def main() -> str | None:
933
958
  # COMMAND
934
959
  class install_:
935
960
  doc = f'Install one, more, or all versions from a {REPO} release.'
936
- aliases = ['i']
961
+ aliases = ('i',)
937
962
 
938
963
  @staticmethod
939
964
  def init(parser: ArgumentParser) -> None:
@@ -972,7 +997,7 @@ class install_:
972
997
  help='also install source files if available in distribution download',
973
998
  )
974
999
  parser.add_argument(
975
- 'version', nargs='*', help='version to install. E.g. 3.12 or 3.12.3'
1000
+ 'version', nargs='*', help=f'version to install. E.g. {VER} or {VER}.1'
976
1001
  )
977
1002
 
978
1003
  @staticmethod
@@ -993,7 +1018,7 @@ class install_:
993
1018
  'Can not specify versions with --all unless also specifying --skip.'
994
1019
  )
995
1020
 
996
- skips = set(v for ver in args.version if (v := matcher.match(ver)))
1021
+ skips = {v for ver in args.version if (v := matcher.match(ver))}
997
1022
 
998
1023
  args.version = [
999
1024
  v
@@ -1033,7 +1058,7 @@ class install_:
1033
1058
  class update_:
1034
1059
  "Update one, more, or all versions to another release."
1035
1060
 
1036
- aliases = ['u', 'up']
1061
+ aliases = ('u', 'up')
1037
1062
 
1038
1063
  @staticmethod
1039
1064
  def init(parser: ArgumentParser) -> None:
@@ -1129,7 +1154,7 @@ class update_:
1129
1154
  class remove_:
1130
1155
  "Remove/uninstall one, more, or all versions."
1131
1156
 
1132
- aliases = ['r', 'rm']
1157
+ aliases = ('r', 'rm')
1133
1158
 
1134
1159
  @staticmethod
1135
1160
  def init(parser: ArgumentParser) -> None:
@@ -1170,7 +1195,7 @@ class remove_:
1170
1195
  class list_:
1171
1196
  "List installed versions and show which have an update available."
1172
1197
 
1173
- aliases = ['l']
1198
+ aliases = ('l',)
1174
1199
 
1175
1200
  @staticmethod
1176
1201
  def init(parser: ArgumentParser) -> None:
@@ -1260,7 +1285,7 @@ class show_:
1260
1285
  View available releases and their distributions at
1261
1286
  {GITHUB_SITE}/releases.
1262
1287
  """
1263
- aliases = ['s']
1288
+ aliases = ('s',)
1264
1289
 
1265
1290
  @staticmethod
1266
1291
  def init(parser: ArgumentParser) -> None:
@@ -1289,11 +1314,19 @@ class show_:
1289
1314
 
1290
1315
  @staticmethod
1291
1316
  def run(args: Namespace) -> str | None:
1317
+ re_match: re.Pattern | None = None
1318
+ if args.re_match:
1319
+ try:
1320
+ re_match = re.compile(args.re_match)
1321
+ except re.error as error:
1322
+ args.parser.error(f'Invalid regular expression: {error}')
1323
+
1292
1324
  if args.all and args.list:
1293
1325
  args.parser.error('Can not specify --all with --list.')
1294
1326
 
1295
1327
  if args.list:
1296
1328
  args.release = False
1329
+ args._re_match = re_match
1297
1330
  show_list(args)
1298
1331
  return None
1299
1332
 
@@ -1317,9 +1350,7 @@ class show_:
1317
1350
  if distribution == args._distribution:
1318
1351
  installable = True
1319
1352
 
1320
- if not args.re_match or re.search(
1321
- args.re_match, f'{version}+{distribution}'
1322
- ):
1353
+ if not re_match or re_match.search(f'{version}+{distribution}'):
1323
1354
  print(
1324
1355
  f'{args._fmtrel(version, release)} '
1325
1356
  f'{args._fmtdist(distribution)}{app}'
@@ -1335,7 +1366,7 @@ class show_:
1335
1366
  class path_:
1336
1367
  "Show path prefix to installed version base directory."
1337
1368
 
1338
- aliases = ['p']
1369
+ aliases = ('p',)
1339
1370
 
1340
1371
  @staticmethod
1341
1372
  def init(parser: ArgumentParser) -> None:
@@ -1373,13 +1404,8 @@ class path_:
1373
1404
  if args.resolve:
1374
1405
  path = path.resolve()
1375
1406
 
1376
- if args.python_path:
1377
- basepath = path
1378
- path = basepath / 'bin' / 'python'
1379
- if not path.exists():
1380
- path = basepath / 'python.exe'
1381
- if not path.exists():
1382
- return f'Error: Can not find python executable in "{basepath}"'
1407
+ if args.python_path and not (path := find_python(basepath := path)):
1408
+ return f'Error: Can not find python executable in "{basepath}"'
1383
1409
 
1384
1410
  print(path)
1385
1411
 
@@ -1388,7 +1414,7 @@ class path_:
1388
1414
  class cache_:
1389
1415
  "Show size of release download caches."
1390
1416
 
1391
- aliases = ['c']
1417
+ aliases = ('c',)
1392
1418
 
1393
1419
  @staticmethod
1394
1420
  def init(parser: ArgumentParser) -> None:
@@ -1436,9 +1462,7 @@ class cache_:
1436
1462
  )
1437
1463
 
1438
1464
  keep = keeplist(args)
1439
- releases = set(
1440
- p.name for p in args._downloads.iterdir() if p.name.isdigit()
1441
- )
1465
+ releases = {p.name for p in args._downloads.iterdir() if p.name.isdigit()}
1442
1466
  releases.update(
1443
1467
  p.name for p in args._releases.iterdir() if p.name.isdigit()
1444
1468
  )
@@ -1492,7 +1516,7 @@ class uv_:
1492
1516
  parser.add_argument(
1493
1517
  '-p',
1494
1518
  '--python',
1495
- help='version of python to use, e.g. "3.12", default is latest release version',
1519
+ help=f'version of python to use, e.g. "{VER}", default is latest release version',
1496
1520
  )
1497
1521
 
1498
1522
  parser.add_argument('command', help='uv command to run')
@@ -1522,7 +1546,7 @@ class uvx_:
1522
1546
  parser.add_argument(
1523
1547
  '-p',
1524
1548
  '--python',
1525
- help='version of python to use, e.g. "3.12", default is latest release version',
1549
+ help=f'version of python to use, e.g. "{VER}", default is latest release version',
1526
1550
  )
1527
1551
 
1528
1552
  parser.add_argument('program', help='uvx program to run')
File without changes
File without changes