pystand 2.15__tar.gz → 2.17__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.
@@ -3,6 +3,7 @@ check:
3
3
  ruff check $(PYFILES)
4
4
  mypy $(PYFILES)
5
5
  pyright $(PYFILES)
6
+ md-link-checker
6
7
 
7
8
  build:
8
9
  rm -rf dist
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pystand
3
- Version: 2.15
3
+ Version: 2.17
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
@@ -13,7 +13,8 @@ Requires-Dist: argcomplete
13
13
  Requires-Dist: packaging
14
14
  Requires-Dist: platformdirs
15
15
  Requires-Dist: pygithub
16
- Requires-Dist: zstandard
16
+ Requires-Dist: certifi
17
+ Requires-Dist: zstandard; python_version < "3.14"
17
18
 
18
19
  ## PYSTAND - Install Python Versions From The Python-Build-Standalone Project
19
20
  [![PyPi](https://img.shields.io/pypi/v/pystand)](https://pypi.org/project/pystand/)
@@ -41,7 +42,7 @@ required
41
42
  for your machine architecture is normally auto-detected. By default, the
42
43
  _`install_only_stripped`_ build of the distribution is installed but you
43
44
  can choose to [install any other
44
- build/distribution](#installing-other-builds/distributions) instead, or
45
+ build/distribution](#installing-other-buildsdistributions) instead, or
45
46
  in parallel.
46
47
 
47
48
  Some simple usage examples are:
@@ -130,8 +131,8 @@ Type `pystand` or `pystand -h` to view the usage summary:
130
131
  usage: pystand [-h] [-D DISTRIBUTION] [-P PREFIX_DIR] [-C CACHE_DIR]
131
132
  [-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
132
133
  [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
133
- [-V]
134
- {install,update,upgrade,remove,uninstall,list,show,path} ...
134
+ [--cert {system,certifi,none}] [-V]
135
+ {install,update,upgrade,remove,uninstall,list,show,path,cache} ...
135
136
 
136
137
  Command line tool to download, install, and update pre-built Python versions
137
138
  from the python-build-standalone project at https://github.com/astral-
@@ -141,8 +142,9 @@ options:
141
142
  -h, --help show this help message and exit
142
143
  -D, --distribution DISTRIBUTION
143
144
  python-build-standalone distribution. Default is
144
- "x86_64_v3-unknown-linux-gnu-install_only_stripped for
145
- this host
145
+ "x86_64_v3-unknown-linux-gnu-install_only_stripped"
146
+ for this host. Run "pystand show -a" to see all
147
+ distributions.
146
148
  -P, --prefix-dir PREFIX_DIR
147
149
  specify prefix dir for storing versions. Default is
148
150
  "$HOME/.local/share/pystand"
@@ -161,10 +163,13 @@ options:
161
163
  optional Github access token. Can specify to reduce
162
164
  rate limiting.
163
165
  --no-strip do not strip downloaded binaries
166
+ --cert {system,certifi,none}
167
+ specify which SSL certificates to use for HTTPS
168
+ requests. Default="system"
164
169
  -V, --version just show pystand version
165
170
 
166
171
  Commands:
167
- {install,update,upgrade,remove,uninstall,list,show,path}
172
+ {install,update,upgrade,remove,uninstall,list,show,path,cache}
168
173
  install Install one or more versions from a python-build-
169
174
  standalone release.
170
175
  update (upgrade) Update one, more, or all versions to another release.
@@ -173,6 +178,7 @@ Commands:
173
178
  available.
174
179
  show Show versions available from a release.
175
180
  path Show path prefix to installed version base directory.
181
+ cache Show release cache sizes.
176
182
 
177
183
  Some commands offer aliases as shown in parentheses above. Note you can set
178
184
  default starting global options in $HOME/.config/pystand-flags.conf.
@@ -307,6 +313,23 @@ options:
307
313
  -c, --cache-path just show path to cache dir
308
314
  ```
309
315
 
316
+ ### Command `cache`
317
+
318
+ ```
319
+ usage: pystand cache [-h] [-T] [-H] [release ...]
320
+
321
+ Show release cache sizes.
322
+
323
+ positional arguments:
324
+ release show cache size for given release[s] only
325
+
326
+ options:
327
+ -h, --help show this help message and exit
328
+ -T, --no-total do not show total cache size
329
+ -H, --no-human-readable
330
+ show sizes in bytes, not human readable format
331
+ ```
332
+
310
333
  ## Installation and Upgrade
311
334
 
312
335
  Python 3.8 or later is required. Arch Linux users can install [`pystand`
@@ -579,7 +602,7 @@ either version 3 of the License, or any later version. This program is
579
602
  distributed in the hope that it will be useful, but WITHOUT ANY
580
603
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
581
604
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
582
- <http://www.gnu.org/licenses/> for more details.
605
+ <https://en.wikipedia.org/wiki/GNU_General_Public_License> for more details.
583
606
 
584
607
  [pystand]: https://github.com/bulletmark/pystand
585
608
  [pbs]: https://github.com/astral-sh/python-build-standalone
@@ -1,20 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: pystand
3
- Version: 2.15
4
- Summary: Install Python versions from python-build-standalone project
5
- Author-email: Mark Blakeney <mark.blakeney@bullet-systems.net>
6
- License-Expression: GPL-3.0-or-later
7
- Project-URL: Homepage, https://github.com/bulletmark/pystand
8
- Keywords: python-build-standalone,pyenv,hatch,pdm
9
- Classifier: Programming Language :: Python :: 3
10
- Requires-Python: >=3.8
11
- Description-Content-Type: text/markdown
12
- Requires-Dist: argcomplete
13
- Requires-Dist: packaging
14
- Requires-Dist: platformdirs
15
- Requires-Dist: pygithub
16
- Requires-Dist: zstandard
17
-
18
1
  ## PYSTAND - Install Python Versions From The Python-Build-Standalone Project
19
2
  [![PyPi](https://img.shields.io/pypi/v/pystand)](https://pypi.org/project/pystand/)
20
3
  [![AUR](https://img.shields.io/aur/version/pystand)](https://aur.archlinux.org/packages/pystand/)
@@ -41,7 +24,7 @@ required
41
24
  for your machine architecture is normally auto-detected. By default, the
42
25
  _`install_only_stripped`_ build of the distribution is installed but you
43
26
  can choose to [install any other
44
- build/distribution](#installing-other-builds/distributions) instead, or
27
+ build/distribution](#installing-other-buildsdistributions) instead, or
45
28
  in parallel.
46
29
 
47
30
  Some simple usage examples are:
@@ -130,8 +113,8 @@ Type `pystand` or `pystand -h` to view the usage summary:
130
113
  usage: pystand [-h] [-D DISTRIBUTION] [-P PREFIX_DIR] [-C CACHE_DIR]
131
114
  [-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
132
115
  [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
133
- [-V]
134
- {install,update,upgrade,remove,uninstall,list,show,path} ...
116
+ [--cert {system,certifi,none}] [-V]
117
+ {install,update,upgrade,remove,uninstall,list,show,path,cache} ...
135
118
 
136
119
  Command line tool to download, install, and update pre-built Python versions
137
120
  from the python-build-standalone project at https://github.com/astral-
@@ -141,8 +124,9 @@ options:
141
124
  -h, --help show this help message and exit
142
125
  -D, --distribution DISTRIBUTION
143
126
  python-build-standalone distribution. Default is
144
- "x86_64_v3-unknown-linux-gnu-install_only_stripped for
145
- this host
127
+ "x86_64_v3-unknown-linux-gnu-install_only_stripped"
128
+ for this host. Run "pystand show -a" to see all
129
+ distributions.
146
130
  -P, --prefix-dir PREFIX_DIR
147
131
  specify prefix dir for storing versions. Default is
148
132
  "$HOME/.local/share/pystand"
@@ -161,10 +145,13 @@ options:
161
145
  optional Github access token. Can specify to reduce
162
146
  rate limiting.
163
147
  --no-strip do not strip downloaded binaries
148
+ --cert {system,certifi,none}
149
+ specify which SSL certificates to use for HTTPS
150
+ requests. Default="system"
164
151
  -V, --version just show pystand version
165
152
 
166
153
  Commands:
167
- {install,update,upgrade,remove,uninstall,list,show,path}
154
+ {install,update,upgrade,remove,uninstall,list,show,path,cache}
168
155
  install Install one or more versions from a python-build-
169
156
  standalone release.
170
157
  update (upgrade) Update one, more, or all versions to another release.
@@ -173,6 +160,7 @@ Commands:
173
160
  available.
174
161
  show Show versions available from a release.
175
162
  path Show path prefix to installed version base directory.
163
+ cache Show release cache sizes.
176
164
 
177
165
  Some commands offer aliases as shown in parentheses above. Note you can set
178
166
  default starting global options in $HOME/.config/pystand-flags.conf.
@@ -307,6 +295,23 @@ options:
307
295
  -c, --cache-path just show path to cache dir
308
296
  ```
309
297
 
298
+ ### Command `cache`
299
+
300
+ ```
301
+ usage: pystand cache [-h] [-T] [-H] [release ...]
302
+
303
+ Show release cache sizes.
304
+
305
+ positional arguments:
306
+ release show cache size for given release[s] only
307
+
308
+ options:
309
+ -h, --help show this help message and exit
310
+ -T, --no-total do not show total cache size
311
+ -H, --no-human-readable
312
+ show sizes in bytes, not human readable format
313
+ ```
314
+
310
315
  ## Installation and Upgrade
311
316
 
312
317
  Python 3.8 or later is required. Arch Linux users can install [`pystand`
@@ -579,7 +584,7 @@ either version 3 of the License, or any later version. This program is
579
584
  distributed in the hope that it will be useful, but WITHOUT ANY
580
585
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
581
586
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
582
- <http://www.gnu.org/licenses/> for more details.
587
+ <https://en.wikipedia.org/wiki/GNU_General_Public_License> for more details.
583
588
 
584
589
  [pystand]: https://github.com/bulletmark/pystand
585
590
  [pbs]: https://github.com/astral-sh/python-build-standalone
@@ -18,7 +18,8 @@ dependencies = [
18
18
  "packaging",
19
19
  "platformdirs",
20
20
  "pygithub",
21
- "zstandard",
21
+ "certifi",
22
+ "zstandard; python_version < '3.14'",
22
23
  ]
23
24
 
24
25
  [[project.authors]]
@@ -1,3 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: pystand
3
+ Version: 2.17
4
+ Summary: Install Python versions from python-build-standalone project
5
+ Author-email: Mark Blakeney <mark.blakeney@bullet-systems.net>
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/bulletmark/pystand
8
+ Keywords: python-build-standalone,pyenv,hatch,pdm
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: argcomplete
13
+ Requires-Dist: packaging
14
+ Requires-Dist: platformdirs
15
+ Requires-Dist: pygithub
16
+ Requires-Dist: certifi
17
+ Requires-Dist: zstandard; python_version < "3.14"
18
+
1
19
  ## PYSTAND - Install Python Versions From The Python-Build-Standalone Project
2
20
  [![PyPi](https://img.shields.io/pypi/v/pystand)](https://pypi.org/project/pystand/)
3
21
  [![AUR](https://img.shields.io/aur/version/pystand)](https://aur.archlinux.org/packages/pystand/)
@@ -24,7 +42,7 @@ required
24
42
  for your machine architecture is normally auto-detected. By default, the
25
43
  _`install_only_stripped`_ build of the distribution is installed but you
26
44
  can choose to [install any other
27
- build/distribution](#installing-other-builds/distributions) instead, or
45
+ build/distribution](#installing-other-buildsdistributions) instead, or
28
46
  in parallel.
29
47
 
30
48
  Some simple usage examples are:
@@ -113,8 +131,8 @@ Type `pystand` or `pystand -h` to view the usage summary:
113
131
  usage: pystand [-h] [-D DISTRIBUTION] [-P PREFIX_DIR] [-C CACHE_DIR]
114
132
  [-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
115
133
  [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
116
- [-V]
117
- {install,update,upgrade,remove,uninstall,list,show,path} ...
134
+ [--cert {system,certifi,none}] [-V]
135
+ {install,update,upgrade,remove,uninstall,list,show,path,cache} ...
118
136
 
119
137
  Command line tool to download, install, and update pre-built Python versions
120
138
  from the python-build-standalone project at https://github.com/astral-
@@ -124,8 +142,9 @@ options:
124
142
  -h, --help show this help message and exit
125
143
  -D, --distribution DISTRIBUTION
126
144
  python-build-standalone distribution. Default is
127
- "x86_64_v3-unknown-linux-gnu-install_only_stripped for
128
- this host
145
+ "x86_64_v3-unknown-linux-gnu-install_only_stripped"
146
+ for this host. Run "pystand show -a" to see all
147
+ distributions.
129
148
  -P, --prefix-dir PREFIX_DIR
130
149
  specify prefix dir for storing versions. Default is
131
150
  "$HOME/.local/share/pystand"
@@ -144,10 +163,13 @@ options:
144
163
  optional Github access token. Can specify to reduce
145
164
  rate limiting.
146
165
  --no-strip do not strip downloaded binaries
166
+ --cert {system,certifi,none}
167
+ specify which SSL certificates to use for HTTPS
168
+ requests. Default="system"
147
169
  -V, --version just show pystand version
148
170
 
149
171
  Commands:
150
- {install,update,upgrade,remove,uninstall,list,show,path}
172
+ {install,update,upgrade,remove,uninstall,list,show,path,cache}
151
173
  install Install one or more versions from a python-build-
152
174
  standalone release.
153
175
  update (upgrade) Update one, more, or all versions to another release.
@@ -156,6 +178,7 @@ Commands:
156
178
  available.
157
179
  show Show versions available from a release.
158
180
  path Show path prefix to installed version base directory.
181
+ cache Show release cache sizes.
159
182
 
160
183
  Some commands offer aliases as shown in parentheses above. Note you can set
161
184
  default starting global options in $HOME/.config/pystand-flags.conf.
@@ -290,6 +313,23 @@ options:
290
313
  -c, --cache-path just show path to cache dir
291
314
  ```
292
315
 
316
+ ### Command `cache`
317
+
318
+ ```
319
+ usage: pystand cache [-h] [-T] [-H] [release ...]
320
+
321
+ Show release cache sizes.
322
+
323
+ positional arguments:
324
+ release show cache size for given release[s] only
325
+
326
+ options:
327
+ -h, --help show this help message and exit
328
+ -T, --no-total do not show total cache size
329
+ -H, --no-human-readable
330
+ show sizes in bytes, not human readable format
331
+ ```
332
+
293
333
  ## Installation and Upgrade
294
334
 
295
335
  Python 3.8 or later is required. Arch Linux users can install [`pystand`
@@ -562,7 +602,7 @@ either version 3 of the License, or any later version. This program is
562
602
  distributed in the hope that it will be useful, but WITHOUT ANY
563
603
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
564
604
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
565
- <http://www.gnu.org/licenses/> for more details.
605
+ <https://en.wikipedia.org/wiki/GNU_General_Public_License> for more details.
566
606
 
567
607
  [pystand]: https://github.com/bulletmark/pystand
568
608
  [pbs]: https://github.com/astral-sh/python-build-standalone
@@ -2,4 +2,7 @@ argcomplete
2
2
  packaging
3
3
  platformdirs
4
4
  pygithub
5
+ certifi
6
+
7
+ [:python_version < "3.14"]
5
8
  zstandard
@@ -13,6 +13,7 @@ import platform
13
13
  import re
14
14
  import shlex
15
15
  import shutil
16
+ import ssl
16
17
  import sys
17
18
  import time
18
19
  from argparse import ArgumentParser, Namespace
@@ -20,6 +21,7 @@ from collections import defaultdict
20
21
  from datetime import date, datetime
21
22
  from pathlib import Path
22
23
  from typing import Any, Iterable, Iterator
24
+ from urllib.request import urlopen
23
25
 
24
26
  import argcomplete
25
27
  import platformdirs
@@ -45,10 +47,13 @@ DISTRIBUTIONS = {
45
47
  ('Linux', 'armv8l'): 'armv7-unknown-linux-gnueabihf-install_only_stripped',
46
48
  ('Darwin', 'x86_64'): 'x86_64-apple-darwin-install_only_stripped',
47
49
  ('Darwin', 'aarch64'): 'aarch64-apple-darwin-install_only_stripped',
50
+ ('Darwin', 'arm64'): 'aarch64-apple-darwin-install_only_stripped',
48
51
  ('Windows', 'x86_64'): 'x86_64-pc-windows-msvc-shared-install_only_stripped',
49
52
  ('Windows', 'i686'): 'i686-pc-windows-msvc-shared-install_only_stripped',
50
53
  }
51
54
 
55
+ CERTS = ('system', 'certifi', 'none')
56
+
52
57
 
53
58
  def is_admin() -> bool:
54
59
  "Check if we are running as root"
@@ -77,7 +82,7 @@ def fmt(version, release) -> str:
77
82
  return f'{version} @ {release}'
78
83
 
79
84
 
80
- def get_json(file: Path) -> dict:
85
+ def get_json(file: Path) -> dict[str, Any]:
81
86
  from json import load
82
87
 
83
88
  'Get JSON data from given file'
@@ -90,7 +95,7 @@ def get_json(file: Path) -> dict:
90
95
  return {}
91
96
 
92
97
 
93
- def set_json(file: Path, data: dict) -> str | None:
98
+ def set_json(file: Path, data: dict[str, Any]) -> str | None:
94
99
  "Set JSON data to given file"
95
100
  from json import dump
96
101
 
@@ -138,23 +143,30 @@ def rm_path(path: Path) -> None:
138
143
  path.unlink()
139
144
 
140
145
 
141
- def unpack_zst(filename: str, extract_dir: str) -> None:
142
- "Unpack a zstandard compressed tar"
143
- import tarfile
146
+ def register_zst() -> None:
147
+ "Register custom zstandard unpacker"
148
+ # Python 3.14+ has built-in support for zstandard so only register custom
149
+ # handler for earlier versions
150
+ if sys.version_info < (3, 14):
151
+
152
+ def unpack_zst(filename: str, extract_dir: str) -> None:
153
+ "Unpack a zstandard compressed tar"
154
+ import tarfile
155
+
156
+ import zstandard
144
157
 
145
- import zstandard
158
+ with open(filename, 'rb') as compressed:
159
+ dctx = zstandard.ZstdDecompressor()
160
+ with dctx.stream_reader(compressed) as reader:
161
+ with tarfile.open(fileobj=reader, mode='r|') as tar:
162
+ tar.extractall(path=extract_dir)
146
163
 
147
- with open(filename, 'rb') as compressed:
148
- dctx = zstandard.ZstdDecompressor()
149
- with dctx.stream_reader(compressed) as reader:
150
- with tarfile.open(fileobj=reader, mode='r|') as tar:
151
- tar.extractall(path=extract_dir)
164
+ shutil.register_unpack_format('zst', ['.zst'], unpack_zst)
152
165
 
153
166
 
154
167
  def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
155
168
  "Fetch and unpack a release file"
156
169
  from urllib.parse import unquote, urlparse
157
- from urllib.request import urlretrieve
158
170
 
159
171
  error = None
160
172
  tmpdir = tdir.with_name(f'{tdir.name}-tmp')
@@ -168,7 +180,8 @@ def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
168
180
 
169
181
  if not cache_file.exists():
170
182
  try:
171
- urlretrieve(url, cache_file)
183
+ with urlopen(url, context=args._cert) as urlp, cache_file.open('wb') as fp:
184
+ shutil.copyfileobj(urlp, fp)
172
185
  except Exception as e:
173
186
  error = f'Failed to fetch "{url}": {e}'
174
187
 
@@ -176,7 +189,7 @@ def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
176
189
  rm_path(cache_file)
177
190
  else:
178
191
  if filename.endswith('.zst'):
179
- shutil.register_unpack_format('zst', ['.zst'], unpack_zst)
192
+ register_zst()
180
193
 
181
194
  try:
182
195
  shutil.unpack_archive(cache_file, tmpdir)
@@ -306,13 +319,12 @@ def check_release_tag(release: str) -> str | None:
306
319
  # Note we use a simple direct URL fetch to get the latest tag info
307
320
  # because it is much faster than using the GitHub API, and has no
308
321
  # rate-limits.
309
- def fetch_tags() -> Iterator[tuple[str, str]]:
322
+ def fetch_tags(args: Namespace) -> Iterator[tuple[str, str]]:
310
323
  "Fetch the latest release tags from the GitHub release atom feed"
311
324
  import xml.etree.ElementTree as et
312
- from urllib.request import urlopen
313
325
 
314
326
  try:
315
- with urlopen(LATEST_RELEASES) as url:
327
+ with urlopen(LATEST_RELEASES, context=args._cert) as url:
316
328
  data = et.parse(url).getroot()
317
329
  except Exception:
318
330
  sys.exit('Failed to fetch latest YYYYMMDD release atom file.')
@@ -325,12 +337,10 @@ def fetch_tags() -> Iterator[tuple[str, str]]:
325
337
  yield tl, dt
326
338
 
327
339
 
328
- def fetch_tag_latest() -> str:
340
+ def fetch_tag_latest(args: Namespace) -> str:
329
341
  "Fetch the latest release tag from the GitHub"
330
- from urllib.request import urlopen
331
-
332
342
  try:
333
- with urlopen(LATEST_RELEASE_TAG) as url:
343
+ with urlopen(LATEST_RELEASE_TAG, context=args._cert) as url:
334
344
  data = url.geturl()
335
345
  except Exception:
336
346
  sys.exit('Failed to fetch latest YYYYMMDD release tag.')
@@ -351,14 +361,14 @@ def get_release_tag(args: Namespace) -> str:
351
361
  if time.time() < (stat.st_mtime + int(args.cache_minutes * 60)):
352
362
  return args._latest_release.read_text().strip()
353
363
 
354
- if not (tag := fetch_tag_latest()):
364
+ if not (tag := fetch_tag_latest(args)):
355
365
  sys.exit('Latest YYYYMMDD release tag timestamp file is unavailable.')
356
366
 
357
367
  args._latest_release.write_text(tag + '\n')
358
368
  return tag
359
369
 
360
370
 
361
- def add_file(files: dict, tag: str, name: str, url: str) -> None:
371
+ def add_file(files: dict[str, Any], tag: str, name: str, url: str) -> None:
362
372
  "Extract the implementation, version, and architecture from a filename"
363
373
  if name.endswith('.tar.zst'):
364
374
  name = name[:-8]
@@ -386,7 +396,7 @@ def add_file(files: dict, tag: str, name: str, url: str) -> None:
386
396
  vers[ver][arch] = url
387
397
 
388
398
 
389
- def get_release_files(args, tag, implementation: str | None = None) -> dict:
399
+ def get_release_files(args, tag) -> dict[str, Any]:
390
400
  "Return the release files for the given tag"
391
401
  # Look for tag data in our release cache
392
402
  jfile = args._releases / tag
@@ -414,7 +424,7 @@ def get_release_files(args, tag, implementation: str | None = None) -> dict:
414
424
  if error := set_json(jfile, files):
415
425
  sys.exit(f'Failed to write release {tag} file {jfile}: {error}')
416
426
 
417
- return files.get(implementation, {}) if implementation else files
427
+ return files.get(args._implementation, {})
418
428
 
419
429
 
420
430
  def update_version_symlinks(args: Namespace) -> None:
@@ -499,7 +509,7 @@ def purge_unused_releases(args: Namespace) -> None:
499
509
  def show_list(args: Namespace) -> None:
500
510
  "Show a list of available releases"
501
511
  latest = parse_version(get_release_tag(args))
502
- releases = {r: d for r, d in fetch_tags()}
512
+ releases = {r: d for r, d in fetch_tags(args)}
503
513
  cached = set(p.name for p in args._releases.iterdir())
504
514
  for release in sorted(cached.union(releases)):
505
515
  if args.re_match and not re.search(args.re_match, release):
@@ -578,7 +588,7 @@ def strip_binaries(vdir: Path, distribution: str) -> bool:
578
588
 
579
589
 
580
590
  def install(
581
- args: Namespace, vdir: Path, release: str, distribution: str, files: dict
591
+ args: Namespace, vdir: Path, release: str, distribution: str, files: dict[str, Any]
582
592
  ) -> str | None:
583
593
  "Install a version"
584
594
  version = vdir.name
@@ -610,6 +620,51 @@ def install(
610
620
  return error
611
621
 
612
622
 
623
+ def to_human(num, prec: int | None = None) -> str:
624
+ "Convert a number of bytes to a human-readable format"
625
+ units = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
626
+ for unit in units:
627
+ if abs(num) < 1024.0 or unit == units[-1]:
628
+ return f'{round(num, prec)}{unit}'
629
+ num /= 1024.0
630
+
631
+ return ''
632
+
633
+
634
+ def show_cache_size(path: Path, args: Namespace) -> None:
635
+ "Show the size of the cache directory"
636
+ total = 0
637
+ for spath in sorted(path.iterdir()):
638
+ size = (
639
+ sum(p.stat().st_size for p in spath.iterdir())
640
+ if spath.is_dir()
641
+ else spath.stat().st_size
642
+ )
643
+ total += size
644
+ size_str = f'{size}B' if args.no_human_readable else to_human(size)
645
+ name = str(spath) if spath.is_dir() else spath.name
646
+ print(f'{size_str}\t{name}')
647
+
648
+ if not args.no_total:
649
+ size_str = f'{total}B' if args.no_human_readable else to_human(total, 2)
650
+ print(f'{size_str}\tTOTAL')
651
+
652
+
653
+ def create_cert(option: str) -> ssl.SSLContext | None:
654
+ "Create an SSL context for HTTPS connections based on the specified certificate store"
655
+
656
+ if not option or option == 'system':
657
+ return None
658
+
659
+ if option == 'certifi':
660
+ import certifi
661
+
662
+ return ssl.create_default_context(cafile=certifi.where())
663
+
664
+ assert option == 'none'
665
+ return ssl._create_unverified_context()
666
+
667
+
613
668
  def main() -> str | None:
614
669
  "Main code"
615
670
  distro_default = DISTRIBUTIONS.get((platform.system(), platform.machine()))
@@ -631,7 +686,8 @@ def main() -> str | None:
631
686
  opt.add_argument(
632
687
  '-D',
633
688
  '--distribution',
634
- help=f'{REPO} distribution. Default is "{distro_help} for this host',
689
+ help=f'{REPO} distribution. Default is "{distro_help}" for this host. '
690
+ f'Run "{PROG} show -a" to see all distributions.',
635
691
  )
636
692
  opt.add_argument(
637
693
  '-P',
@@ -669,6 +725,11 @@ def main() -> str | None:
669
725
  opt.add_argument(
670
726
  '--no-strip', action='store_true', help='do not strip downloaded binaries'
671
727
  )
728
+ opt.add_argument(
729
+ '--cert',
730
+ choices=CERTS,
731
+ help=f'specify which SSL certificates to use for HTTPS requests. Default="{CERTS[0]}"',
732
+ )
672
733
  opt.add_argument(
673
734
  '-V', '--version', action='store_true', help=f'just show {PROG} version'
674
735
  )
@@ -734,6 +795,7 @@ def main() -> str | None:
734
795
  prefix_dir = Path(args.prefix_dir).expanduser().resolve()
735
796
  cache_dir = Path(args.cache_dir).expanduser().resolve()
736
797
 
798
+ args._implementation = 'cpython' # at the moment, only support CPython
737
799
  args._distribution = distribution
738
800
  args._data = f'{PROG}.json'
739
801
 
@@ -745,6 +807,7 @@ def main() -> str | None:
745
807
  args._releases = cache_dir / 'releases'
746
808
  args._releases.mkdir(parents=True, exist_ok=True)
747
809
  args._latest_release = cache_dir / 'latest_release'
810
+ args._cert = create_cert(args.cert)
748
811
 
749
812
  result = args.func(args)
750
813
  purge_unused_releases(args)
@@ -784,7 +847,7 @@ class install_:
784
847
  @staticmethod
785
848
  def run(args: Namespace) -> str | None:
786
849
  release = get_release_tag(args)
787
- files = get_release_files(args, release, 'cpython')
850
+ files = get_release_files(args, release)
788
851
  if not files:
789
852
  return f'Release "{release}" not found, or has no compatible files.'
790
853
 
@@ -843,7 +906,7 @@ class update_:
843
906
  @staticmethod
844
907
  def run(args: Namespace) -> str | None:
845
908
  release_target = get_release_tag(args)
846
- files = get_release_files(args, release_target, 'cpython')
909
+ files = get_release_files(args, release_target)
847
910
  if not files:
848
911
  return f'Release "{release_target}" not found.'
849
912
 
@@ -960,7 +1023,7 @@ class list_:
960
1023
  @staticmethod
961
1024
  def run(args: Namespace) -> str | None:
962
1025
  release_target = get_release_tag(args)
963
- files = get_release_files(args, release_target, 'cpython')
1026
+ files = get_release_files(args, release_target)
964
1027
  if not files:
965
1028
  return f'Release "{release_target}" not found.'
966
1029
 
@@ -1058,7 +1121,7 @@ class show_:
1058
1121
  return None
1059
1122
 
1060
1123
  release = get_release_tag(args)
1061
- files = get_release_files(args, release, 'cpython')
1124
+ files = get_release_files(args, release)
1062
1125
  if not files:
1063
1126
  return f'Error: release "{release}" not found.'
1064
1127
 
@@ -1143,5 +1206,33 @@ class path_:
1143
1206
  print(path)
1144
1207
 
1145
1208
 
1209
+ # COMMAND
1210
+ class cache_:
1211
+ "Show release cache sizes."
1212
+
1213
+ @staticmethod
1214
+ def init(parser: ArgumentParser) -> None:
1215
+ parser.add_argument(
1216
+ '-T', '--no-total', action='store_true', help='do not show total cache size'
1217
+ )
1218
+ parser.add_argument(
1219
+ '-H',
1220
+ '--no-human-readable',
1221
+ action='store_true',
1222
+ help='show sizes in bytes, not human readable format',
1223
+ )
1224
+ parser.add_argument(
1225
+ 'release', nargs='*', help='show cache size for given release[s] only'
1226
+ )
1227
+
1228
+ @staticmethod
1229
+ def run(args: Namespace) -> str | None:
1230
+ if args.release:
1231
+ for release in args.release:
1232
+ show_cache_size(args._downloads / release, args)
1233
+ else:
1234
+ show_cache_size(args._downloads, args)
1235
+
1236
+
1146
1237
  if __name__ == '__main__':
1147
1238
  sys.exit(main())
File without changes
File without changes