pystand 2.16__tar.gz → 2.18__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
+ vermin -vv --no-tips -i $(PYFILES)
6
7
  md-link-checker
7
8
 
8
9
  build:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pystand
3
- Version: 2.16
3
+ Version: 2.18
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
@@ -12,8 +12,10 @@ Description-Content-Type: text/markdown
12
12
  Requires-Dist: argcomplete
13
13
  Requires-Dist: packaging
14
14
  Requires-Dist: platformdirs
15
+ Requires-Dist: argparse-from-file
15
16
  Requires-Dist: pygithub
16
- Requires-Dist: zstandard
17
+ Requires-Dist: certifi
18
+ Requires-Dist: zstandard; python_version < "3.14"
17
19
 
18
20
  ## PYSTAND - Install Python Versions From The Python-Build-Standalone Project
19
21
  [![PyPi](https://img.shields.io/pypi/v/pystand)](https://pypi.org/project/pystand/)
@@ -24,14 +26,14 @@ installation, and update of pre-built Python versions from the
24
26
  [`python-build-standalone`][pbs] project. The following commands are
25
27
  provided:
26
28
 
27
- |Command |Description |
28
- |---------|----------------------------------------------------------------------|
29
- |`install`|Install one or more versions from a python-build-standalone release |
30
- |`update` (or `upgrade`) |Update one, more, or all versions to another release |
31
- |`remove` (or `uninstall`) |Remove/uninstall one, more, or all versions |
32
- |`list` |List installed versions and show which have an update available |
33
- |`show` |Show versions available from a release |
34
- |`path` |Show path prefix to installed version base directory |
29
+ |Command |Description |
30
+ |---------|--------------------------------------------------------------------|
31
+ |`install`|Install one or more versions from a python-build-standalone release.|
32
+ |`update` (or `upgrade`) |Update one, more, or all versions to another release.|
33
+ |`remove` (or `uninstall`) |Remove/uninstall one, more, or all versions.|
34
+ |`list` |List installed versions and show which have an update available.|
35
+ |`show` |Show versions available from a release.|
36
+ |`path` |Show path prefix to installed version base directory.|
35
37
 
36
38
  By default, Python versions are sourced from the latest
37
39
  `python-build-standalone` [release][pbs-rel] available (e.g.
@@ -130,7 +132,7 @@ Type `pystand` or `pystand -h` to view the usage summary:
130
132
  usage: pystand [-h] [-D DISTRIBUTION] [-P PREFIX_DIR] [-C CACHE_DIR]
131
133
  [-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
132
134
  [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
133
- [-V]
135
+ [--cert {system,certifi,none}] [-V]
134
136
  {install,update,upgrade,remove,uninstall,list,show,path,cache} ...
135
137
 
136
138
  Command line tool to download, install, and update pre-built Python versions
@@ -146,22 +148,25 @@ options:
146
148
  distributions.
147
149
  -P, --prefix-dir PREFIX_DIR
148
150
  specify prefix dir for storing versions. Default is
149
- "$HOME/.local/share/pystand"
151
+ "$HOME/.local/share/pystand".
150
152
  -C, --cache-dir CACHE_DIR
151
153
  specify cache dir for downloads. Default is
152
- "$HOME/.cache/pystand"
154
+ "$HOME/.cache/pystand".
153
155
  -M, --cache-minutes CACHE_MINUTES
154
156
  cache latest YYYYMMDD release tag fetch for this many
155
157
  minutes, before rechecking for latest. Default is 60
156
- minutes
158
+ minutes.
157
159
  --purge-days PURGE_DAYS
158
160
  cache YYYYMMDD release file lists and downloads for
159
161
  this number of days after last version referencing
160
- that release is removed. Default is 90 days
162
+ that release is removed. Default is 90 days.
161
163
  --github-access-token GITHUB_ACCESS_TOKEN
162
164
  optional Github access token. Can specify to reduce
163
165
  rate limiting.
164
166
  --no-strip do not strip downloaded binaries
167
+ --cert {system,certifi,none}
168
+ specify which SSL certificates to use for HTTPS
169
+ requests. Default="system".
165
170
  -V, --version just show pystand version
166
171
 
167
172
  Commands:
@@ -562,9 +567,8 @@ anything after on a line) are ignored. Type `pystand` to see all
562
567
  supported options.
563
568
 
564
569
  The global options: `--distribution`, `--prefix-dir`, `--cache-dir`,
565
- `--cache-minutes`, `--purge-days`, `--github-access-token`,
566
- `--no-strip`, are the only sensible candidates to consider setting
567
- as defaults.
570
+ `--cache-minutes`, `--purge-days`, `--github-access-token`, `--no-strip`,
571
+ `--cert` are the only sensible candidates to consider setting as defaults.
568
572
 
569
573
  ## Github API Rate Limiting
570
574
 
@@ -579,6 +583,24 @@ either a Github "fine-grained" or "classic" token. Specify the token on
579
583
  the command line with `--github-access-token`, or set that as a [default
580
584
  option](#command-default-options).
581
585
 
586
+ ## HTTPS Certificate Verification
587
+
588
+ A global option `--cert` is provided to specify which SSL certificates to use
589
+ for HTTPS requests. This is useful if you are running `pystand` in an
590
+ environment where the system certificates are not available, or you want to use
591
+ the bundled [`certifi`][certifi] certificates instead.
592
+
593
+ The available options are:
594
+
595
+ |Option |Description
596
+ |-------- |------------
597
+ |`system` |Use system certificates (as used normally by Python). This is the default.|
598
+ |`certifi`|Use the [`certifi`][certifi] package, i.e. use the certificates bundled within the application.|
599
+ |`none` |Perform unverified https requests (best to avoid using this).|
600
+
601
+ Specify the option on the command line with `--cert`, or set that as a [default
602
+ option](#command-default-options).
603
+
582
604
  ## Command Line Tab Completion
583
605
 
584
606
  Command line shell [tab
@@ -598,7 +620,7 @@ either version 3 of the License, or any later version. This program is
598
620
  distributed in the hope that it will be useful, but WITHOUT ANY
599
621
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
600
622
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
601
- <http://www.gnu.org/licenses/> for more details.
623
+ <https://opensource.org/license/gpl-3-0> for more details.
602
624
 
603
625
  [pystand]: https://github.com/bulletmark/pystand
604
626
  [pbs]: https://github.com/astral-sh/python-build-standalone
@@ -612,5 +634,6 @@ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
612
634
  [hatch]: https://hatch.pypa.io/
613
635
  [hatchpy]: https://hatch.pypa.io/latest/tutorials/python/manage/
614
636
  [ryepy]: https://rye.astral.sh/guide/toolchains/#fetching-toolchains
637
+ [certifi]: https://pypi.org/project/certifi/
615
638
 
616
639
  <!-- vim: se ai syn=markdown: -->
@@ -1,20 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: pystand
3
- Version: 2.16
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/)
@@ -24,14 +7,14 @@ installation, and update of pre-built Python versions from the
24
7
  [`python-build-standalone`][pbs] project. The following commands are
25
8
  provided:
26
9
 
27
- |Command |Description |
28
- |---------|----------------------------------------------------------------------|
29
- |`install`|Install one or more versions from a python-build-standalone release |
30
- |`update` (or `upgrade`) |Update one, more, or all versions to another release |
31
- |`remove` (or `uninstall`) |Remove/uninstall one, more, or all versions |
32
- |`list` |List installed versions and show which have an update available |
33
- |`show` |Show versions available from a release |
34
- |`path` |Show path prefix to installed version base directory |
10
+ |Command |Description |
11
+ |---------|--------------------------------------------------------------------|
12
+ |`install`|Install one or more versions from a python-build-standalone release.|
13
+ |`update` (or `upgrade`) |Update one, more, or all versions to another release.|
14
+ |`remove` (or `uninstall`) |Remove/uninstall one, more, or all versions.|
15
+ |`list` |List installed versions and show which have an update available.|
16
+ |`show` |Show versions available from a release.|
17
+ |`path` |Show path prefix to installed version base directory.|
35
18
 
36
19
  By default, Python versions are sourced from the latest
37
20
  `python-build-standalone` [release][pbs-rel] available (e.g.
@@ -130,7 +113,7 @@ 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]
116
+ [--cert {system,certifi,none}] [-V]
134
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
@@ -146,22 +129,25 @@ options:
146
129
  distributions.
147
130
  -P, --prefix-dir PREFIX_DIR
148
131
  specify prefix dir for storing versions. Default is
149
- "$HOME/.local/share/pystand"
132
+ "$HOME/.local/share/pystand".
150
133
  -C, --cache-dir CACHE_DIR
151
134
  specify cache dir for downloads. Default is
152
- "$HOME/.cache/pystand"
135
+ "$HOME/.cache/pystand".
153
136
  -M, --cache-minutes CACHE_MINUTES
154
137
  cache latest YYYYMMDD release tag fetch for this many
155
138
  minutes, before rechecking for latest. Default is 60
156
- minutes
139
+ minutes.
157
140
  --purge-days PURGE_DAYS
158
141
  cache YYYYMMDD release file lists and downloads for
159
142
  this number of days after last version referencing
160
- that release is removed. Default is 90 days
143
+ that release is removed. Default is 90 days.
161
144
  --github-access-token GITHUB_ACCESS_TOKEN
162
145
  optional Github access token. Can specify to reduce
163
146
  rate limiting.
164
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".
165
151
  -V, --version just show pystand version
166
152
 
167
153
  Commands:
@@ -562,9 +548,8 @@ anything after on a line) are ignored. Type `pystand` to see all
562
548
  supported options.
563
549
 
564
550
  The global options: `--distribution`, `--prefix-dir`, `--cache-dir`,
565
- `--cache-minutes`, `--purge-days`, `--github-access-token`,
566
- `--no-strip`, are the only sensible candidates to consider setting
567
- as defaults.
551
+ `--cache-minutes`, `--purge-days`, `--github-access-token`, `--no-strip`,
552
+ `--cert` are the only sensible candidates to consider setting as defaults.
568
553
 
569
554
  ## Github API Rate Limiting
570
555
 
@@ -579,6 +564,24 @@ either a Github "fine-grained" or "classic" token. Specify the token on
579
564
  the command line with `--github-access-token`, or set that as a [default
580
565
  option](#command-default-options).
581
566
 
567
+ ## HTTPS Certificate Verification
568
+
569
+ A global option `--cert` is provided to specify which SSL certificates to use
570
+ for HTTPS requests. This is useful if you are running `pystand` in an
571
+ environment where the system certificates are not available, or you want to use
572
+ the bundled [`certifi`][certifi] certificates instead.
573
+
574
+ The available options are:
575
+
576
+ |Option |Description
577
+ |-------- |------------
578
+ |`system` |Use system certificates (as used normally by Python). This is the default.|
579
+ |`certifi`|Use the [`certifi`][certifi] package, i.e. use the certificates bundled within the application.|
580
+ |`none` |Perform unverified https requests (best to avoid using this).|
581
+
582
+ Specify the option on the command line with `--cert`, or set that as a [default
583
+ option](#command-default-options).
584
+
582
585
  ## Command Line Tab Completion
583
586
 
584
587
  Command line shell [tab
@@ -598,7 +601,7 @@ either version 3 of the License, or any later version. This program is
598
601
  distributed in the hope that it will be useful, but WITHOUT ANY
599
602
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
600
603
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
601
- <http://www.gnu.org/licenses/> for more details.
604
+ <https://opensource.org/license/gpl-3-0> for more details.
602
605
 
603
606
  [pystand]: https://github.com/bulletmark/pystand
604
607
  [pbs]: https://github.com/astral-sh/python-build-standalone
@@ -612,5 +615,6 @@ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
612
615
  [hatch]: https://hatch.pypa.io/
613
616
  [hatchpy]: https://hatch.pypa.io/latest/tutorials/python/manage/
614
617
  [ryepy]: https://rye.astral.sh/guide/toolchains/#fetching-toolchains
618
+ [certifi]: https://pypi.org/project/certifi/
615
619
 
616
620
  <!-- vim: se ai syn=markdown: -->
@@ -17,8 +17,10 @@ dependencies = [
17
17
  "argcomplete",
18
18
  "packaging",
19
19
  "platformdirs",
20
+ "argparse-from-file",
20
21
  "pygithub",
21
- "zstandard",
22
+ "certifi",
23
+ "zstandard; python_version < '3.14'",
22
24
  ]
23
25
 
24
26
  [[project.authors]]
@@ -1,3 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: pystand
3
+ Version: 2.18
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: argparse-from-file
16
+ Requires-Dist: pygithub
17
+ Requires-Dist: certifi
18
+ Requires-Dist: zstandard; python_version < "3.14"
19
+
1
20
  ## PYSTAND - Install Python Versions From The Python-Build-Standalone Project
2
21
  [![PyPi](https://img.shields.io/pypi/v/pystand)](https://pypi.org/project/pystand/)
3
22
  [![AUR](https://img.shields.io/aur/version/pystand)](https://aur.archlinux.org/packages/pystand/)
@@ -7,14 +26,14 @@ installation, and update of pre-built Python versions from the
7
26
  [`python-build-standalone`][pbs] project. The following commands are
8
27
  provided:
9
28
 
10
- |Command |Description |
11
- |---------|----------------------------------------------------------------------|
12
- |`install`|Install one or more versions from a python-build-standalone release |
13
- |`update` (or `upgrade`) |Update one, more, or all versions to another release |
14
- |`remove` (or `uninstall`) |Remove/uninstall one, more, or all versions |
15
- |`list` |List installed versions and show which have an update available |
16
- |`show` |Show versions available from a release |
17
- |`path` |Show path prefix to installed version base directory |
29
+ |Command |Description |
30
+ |---------|--------------------------------------------------------------------|
31
+ |`install`|Install one or more versions from a python-build-standalone release.|
32
+ |`update` (or `upgrade`) |Update one, more, or all versions to another release.|
33
+ |`remove` (or `uninstall`) |Remove/uninstall one, more, or all versions.|
34
+ |`list` |List installed versions and show which have an update available.|
35
+ |`show` |Show versions available from a release.|
36
+ |`path` |Show path prefix to installed version base directory.|
18
37
 
19
38
  By default, Python versions are sourced from the latest
20
39
  `python-build-standalone` [release][pbs-rel] available (e.g.
@@ -113,7 +132,7 @@ Type `pystand` or `pystand -h` to view the usage summary:
113
132
  usage: pystand [-h] [-D DISTRIBUTION] [-P PREFIX_DIR] [-C CACHE_DIR]
114
133
  [-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
115
134
  [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
116
- [-V]
135
+ [--cert {system,certifi,none}] [-V]
117
136
  {install,update,upgrade,remove,uninstall,list,show,path,cache} ...
118
137
 
119
138
  Command line tool to download, install, and update pre-built Python versions
@@ -129,22 +148,25 @@ options:
129
148
  distributions.
130
149
  -P, --prefix-dir PREFIX_DIR
131
150
  specify prefix dir for storing versions. Default is
132
- "$HOME/.local/share/pystand"
151
+ "$HOME/.local/share/pystand".
133
152
  -C, --cache-dir CACHE_DIR
134
153
  specify cache dir for downloads. Default is
135
- "$HOME/.cache/pystand"
154
+ "$HOME/.cache/pystand".
136
155
  -M, --cache-minutes CACHE_MINUTES
137
156
  cache latest YYYYMMDD release tag fetch for this many
138
157
  minutes, before rechecking for latest. Default is 60
139
- minutes
158
+ minutes.
140
159
  --purge-days PURGE_DAYS
141
160
  cache YYYYMMDD release file lists and downloads for
142
161
  this number of days after last version referencing
143
- that release is removed. Default is 90 days
162
+ that release is removed. Default is 90 days.
144
163
  --github-access-token GITHUB_ACCESS_TOKEN
145
164
  optional Github access token. Can specify to reduce
146
165
  rate limiting.
147
166
  --no-strip do not strip downloaded binaries
167
+ --cert {system,certifi,none}
168
+ specify which SSL certificates to use for HTTPS
169
+ requests. Default="system".
148
170
  -V, --version just show pystand version
149
171
 
150
172
  Commands:
@@ -545,9 +567,8 @@ anything after on a line) are ignored. Type `pystand` to see all
545
567
  supported options.
546
568
 
547
569
  The global options: `--distribution`, `--prefix-dir`, `--cache-dir`,
548
- `--cache-minutes`, `--purge-days`, `--github-access-token`,
549
- `--no-strip`, are the only sensible candidates to consider setting
550
- as defaults.
570
+ `--cache-minutes`, `--purge-days`, `--github-access-token`, `--no-strip`,
571
+ `--cert` are the only sensible candidates to consider setting as defaults.
551
572
 
552
573
  ## Github API Rate Limiting
553
574
 
@@ -562,6 +583,24 @@ either a Github "fine-grained" or "classic" token. Specify the token on
562
583
  the command line with `--github-access-token`, or set that as a [default
563
584
  option](#command-default-options).
564
585
 
586
+ ## HTTPS Certificate Verification
587
+
588
+ A global option `--cert` is provided to specify which SSL certificates to use
589
+ for HTTPS requests. This is useful if you are running `pystand` in an
590
+ environment where the system certificates are not available, or you want to use
591
+ the bundled [`certifi`][certifi] certificates instead.
592
+
593
+ The available options are:
594
+
595
+ |Option |Description
596
+ |-------- |------------
597
+ |`system` |Use system certificates (as used normally by Python). This is the default.|
598
+ |`certifi`|Use the [`certifi`][certifi] package, i.e. use the certificates bundled within the application.|
599
+ |`none` |Perform unverified https requests (best to avoid using this).|
600
+
601
+ Specify the option on the command line with `--cert`, or set that as a [default
602
+ option](#command-default-options).
603
+
565
604
  ## Command Line Tab Completion
566
605
 
567
606
  Command line shell [tab
@@ -581,7 +620,7 @@ either version 3 of the License, or any later version. This program is
581
620
  distributed in the hope that it will be useful, but WITHOUT ANY
582
621
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
583
622
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
584
- <http://www.gnu.org/licenses/> for more details.
623
+ <https://opensource.org/license/gpl-3-0> for more details.
585
624
 
586
625
  [pystand]: https://github.com/bulletmark/pystand
587
626
  [pbs]: https://github.com/astral-sh/python-build-standalone
@@ -595,5 +634,6 @@ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
595
634
  [hatch]: https://hatch.pypa.io/
596
635
  [hatchpy]: https://hatch.pypa.io/latest/tutorials/python/manage/
597
636
  [ryepy]: https://rye.astral.sh/guide/toolchains/#fetching-toolchains
637
+ [certifi]: https://pypi.org/project/certifi/
598
638
 
599
639
  <!-- vim: se ai syn=markdown: -->
@@ -0,0 +1,9 @@
1
+ argcomplete
2
+ packaging
3
+ platformdirs
4
+ argparse-from-file
5
+ pygithub
6
+ certifi
7
+
8
+ [:python_version < "3.14"]
9
+ zstandard
@@ -11,18 +11,20 @@ from __future__ import annotations
11
11
  import os
12
12
  import platform
13
13
  import re
14
- import shlex
15
14
  import shutil
15
+ import ssl
16
16
  import sys
17
17
  import time
18
- from argparse import ArgumentParser, Namespace
19
18
  from collections import defaultdict
19
+ from collections.abc import Iterable, Iterator
20
20
  from datetime import date, datetime
21
21
  from pathlib import Path
22
- from typing import Any, Iterable, Iterator
22
+ from typing import Any
23
+ from urllib.request import urlopen
23
24
 
24
25
  import argcomplete
25
26
  import platformdirs
27
+ from argparse_from_file import ArgumentParser, Namespace # type: ignore
26
28
  from packaging.version import parse as parse_version
27
29
 
28
30
  REPO = 'python-build-standalone'
@@ -35,7 +37,6 @@ LATEST_RELEASE_TAG = f'{GITHUB_SITE}/releases/latest'
35
37
  SAMPL_RELEASE = '20240415'
36
38
 
37
39
  PROG = Path(__file__).stem
38
- CNFFILE = platformdirs.user_config_path(f'{PROG}-flags.conf')
39
40
 
40
41
  # Default distributions for various platforms
41
42
  DISTRIBUTIONS = {
@@ -45,10 +46,13 @@ DISTRIBUTIONS = {
45
46
  ('Linux', 'armv8l'): 'armv7-unknown-linux-gnueabihf-install_only_stripped',
46
47
  ('Darwin', 'x86_64'): 'x86_64-apple-darwin-install_only_stripped',
47
48
  ('Darwin', 'aarch64'): 'aarch64-apple-darwin-install_only_stripped',
49
+ ('Darwin', 'arm64'): 'aarch64-apple-darwin-install_only_stripped',
48
50
  ('Windows', 'x86_64'): 'x86_64-pc-windows-msvc-shared-install_only_stripped',
49
51
  ('Windows', 'i686'): 'i686-pc-windows-msvc-shared-install_only_stripped',
50
52
  }
51
53
 
54
+ CERTS = ('system', 'certifi', 'none')
55
+
52
56
 
53
57
  def is_admin() -> bool:
54
58
  "Check if we are running as root"
@@ -77,7 +81,7 @@ def fmt(version, release) -> str:
77
81
  return f'{version} @ {release}'
78
82
 
79
83
 
80
- def get_json(file: Path) -> dict:
84
+ def get_json(file: Path) -> dict[str, Any]:
81
85
  from json import load
82
86
 
83
87
  'Get JSON data from given file'
@@ -90,7 +94,7 @@ def get_json(file: Path) -> dict:
90
94
  return {}
91
95
 
92
96
 
93
- def set_json(file: Path, data: dict) -> str | None:
97
+ def set_json(file: Path, data: dict[str, Any]) -> str | None:
94
98
  "Set JSON data to given file"
95
99
  from json import dump
96
100
 
@@ -138,23 +142,30 @@ def rm_path(path: Path) -> None:
138
142
  path.unlink()
139
143
 
140
144
 
141
- def unpack_zst(filename: str, extract_dir: str) -> None:
142
- "Unpack a zstandard compressed tar"
143
- import tarfile
145
+ def register_zst() -> None:
146
+ "Register custom zstandard unpacker"
147
+ # Python 3.14+ has built-in support for zstandard so only register custom
148
+ # handler for earlier versions
149
+ if sys.version_info < (3, 14):
150
+
151
+ def unpack_zst(filename: str, extract_dir: str) -> None:
152
+ "Unpack a zstandard compressed tar"
153
+ import tarfile
144
154
 
145
- import zstandard
155
+ import zstandard
146
156
 
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)
157
+ with open(filename, 'rb') as compressed:
158
+ dctx = zstandard.ZstdDecompressor()
159
+ with dctx.stream_reader(compressed) as reader:
160
+ with tarfile.open(fileobj=reader, mode='r|') as tar:
161
+ tar.extractall(path=extract_dir)
162
+
163
+ shutil.register_unpack_format('zst', ['.zst'], unpack_zst)
152
164
 
153
165
 
154
166
  def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
155
167
  "Fetch and unpack a release file"
156
168
  from urllib.parse import unquote, urlparse
157
- from urllib.request import urlretrieve
158
169
 
159
170
  error = None
160
171
  tmpdir = tdir.with_name(f'{tdir.name}-tmp')
@@ -168,7 +179,8 @@ def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
168
179
 
169
180
  if not cache_file.exists():
170
181
  try:
171
- urlretrieve(url, cache_file)
182
+ with urlopen(url, context=args._cert) as urlp, cache_file.open('wb') as fp:
183
+ shutil.copyfileobj(urlp, fp)
172
184
  except Exception as e:
173
185
  error = f'Failed to fetch "{url}": {e}'
174
186
 
@@ -176,7 +188,7 @@ def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
176
188
  rm_path(cache_file)
177
189
  else:
178
190
  if filename.endswith('.zst'):
179
- shutil.register_unpack_format('zst', ['.zst'], unpack_zst)
191
+ register_zst()
180
192
 
181
193
  try:
182
194
  shutil.unpack_archive(cache_file, tmpdir)
@@ -306,13 +318,12 @@ def check_release_tag(release: str) -> str | None:
306
318
  # Note we use a simple direct URL fetch to get the latest tag info
307
319
  # because it is much faster than using the GitHub API, and has no
308
320
  # rate-limits.
309
- def fetch_tags() -> Iterator[tuple[str, str]]:
321
+ def fetch_tags(args: Namespace) -> Iterator[tuple[str, str]]:
310
322
  "Fetch the latest release tags from the GitHub release atom feed"
311
323
  import xml.etree.ElementTree as et
312
- from urllib.request import urlopen
313
324
 
314
325
  try:
315
- with urlopen(LATEST_RELEASES) as url:
326
+ with urlopen(LATEST_RELEASES, context=args._cert) as url:
316
327
  data = et.parse(url).getroot()
317
328
  except Exception:
318
329
  sys.exit('Failed to fetch latest YYYYMMDD release atom file.')
@@ -325,12 +336,10 @@ def fetch_tags() -> Iterator[tuple[str, str]]:
325
336
  yield tl, dt
326
337
 
327
338
 
328
- def fetch_tag_latest() -> str:
339
+ def fetch_tag_latest(args: Namespace) -> str:
329
340
  "Fetch the latest release tag from the GitHub"
330
- from urllib.request import urlopen
331
-
332
341
  try:
333
- with urlopen(LATEST_RELEASE_TAG) as url:
342
+ with urlopen(LATEST_RELEASE_TAG, context=args._cert) as url:
334
343
  data = url.geturl()
335
344
  except Exception:
336
345
  sys.exit('Failed to fetch latest YYYYMMDD release tag.')
@@ -351,14 +360,14 @@ def get_release_tag(args: Namespace) -> str:
351
360
  if time.time() < (stat.st_mtime + int(args.cache_minutes * 60)):
352
361
  return args._latest_release.read_text().strip()
353
362
 
354
- if not (tag := fetch_tag_latest()):
363
+ if not (tag := fetch_tag_latest(args)):
355
364
  sys.exit('Latest YYYYMMDD release tag timestamp file is unavailable.')
356
365
 
357
366
  args._latest_release.write_text(tag + '\n')
358
367
  return tag
359
368
 
360
369
 
361
- def add_file(files: dict, tag: str, name: str, url: str) -> None:
370
+ def add_file(files: dict[str, Any], tag: str, name: str, url: str) -> None:
362
371
  "Extract the implementation, version, and architecture from a filename"
363
372
  if name.endswith('.tar.zst'):
364
373
  name = name[:-8]
@@ -386,7 +395,7 @@ def add_file(files: dict, tag: str, name: str, url: str) -> None:
386
395
  vers[ver][arch] = url
387
396
 
388
397
 
389
- def get_release_files(args, tag, implementation: str | None = None) -> dict:
398
+ def get_release_files(args, tag) -> dict[str, Any]:
390
399
  "Return the release files for the given tag"
391
400
  # Look for tag data in our release cache
392
401
  jfile = args._releases / tag
@@ -414,7 +423,7 @@ def get_release_files(args, tag, implementation: str | None = None) -> dict:
414
423
  if error := set_json(jfile, files):
415
424
  sys.exit(f'Failed to write release {tag} file {jfile}: {error}')
416
425
 
417
- return files.get(implementation, {}) if implementation else files
426
+ return files.get(args._implementation, {})
418
427
 
419
428
 
420
429
  def update_version_symlinks(args: Namespace) -> None:
@@ -499,7 +508,7 @@ def purge_unused_releases(args: Namespace) -> None:
499
508
  def show_list(args: Namespace) -> None:
500
509
  "Show a list of available releases"
501
510
  latest = parse_version(get_release_tag(args))
502
- releases = {r: d for r, d in fetch_tags()}
511
+ releases = {r: d for r, d in fetch_tags(args)}
503
512
  cached = set(p.name for p in args._releases.iterdir())
504
513
  for release in sorted(cached.union(releases)):
505
514
  if args.re_match and not re.search(args.re_match, release):
@@ -578,7 +587,7 @@ def strip_binaries(vdir: Path, distribution: str) -> bool:
578
587
 
579
588
 
580
589
  def install(
581
- args: Namespace, vdir: Path, release: str, distribution: str, files: dict
590
+ args: Namespace, vdir: Path, release: str, distribution: str, files: dict[str, Any]
582
591
  ) -> str | None:
583
592
  "Install a version"
584
593
  version = vdir.name
@@ -640,6 +649,21 @@ def show_cache_size(path: Path, args: Namespace) -> None:
640
649
  print(f'{size_str}\tTOTAL')
641
650
 
642
651
 
652
+ def create_cert(option: str) -> ssl.SSLContext | None:
653
+ "Create an SSL context for HTTPS connections based on the specified certificate store"
654
+
655
+ if not option or option == 'system':
656
+ return None
657
+
658
+ if option == 'certifi':
659
+ import certifi
660
+
661
+ return ssl.create_default_context(cafile=certifi.where())
662
+
663
+ assert option == 'none'
664
+ return ssl._create_unverified_context()
665
+
666
+
643
667
  def main() -> str | None:
644
668
  "Main code"
645
669
  distro_default = DISTRIBUTIONS.get((platform.system(), platform.machine()))
@@ -653,8 +677,7 @@ def main() -> str | None:
653
677
  opt = ArgumentParser(
654
678
  description=__doc__,
655
679
  epilog='Some commands offer aliases as shown in parentheses above. '
656
- 'Note you can set default starting global options in '
657
- f'{CNFFILE}.',
680
+ 'Note you can set default starting global options in #FROM_FILE_PATH#',
658
681
  )
659
682
 
660
683
  # Set up main/global arguments
@@ -668,13 +691,13 @@ def main() -> str | None:
668
691
  '-P',
669
692
  '--prefix-dir',
670
693
  default=prefix_dir,
671
- help='specify prefix dir for storing versions. Default is "%(default)s"',
694
+ help='specify prefix dir for storing versions. Default is "%(default)s".',
672
695
  )
673
696
  opt.add_argument(
674
697
  '-C',
675
698
  '--cache-dir',
676
699
  default=str(cache_dir),
677
- help='specify cache dir for downloads. Default is "%(default)s"',
700
+ help='specify cache dir for downloads. Default is "%(default)s".',
678
701
  )
679
702
  opt.add_argument(
680
703
  '-M',
@@ -683,7 +706,7 @@ def main() -> str | None:
683
706
  type=float,
684
707
  help='cache latest YYYYMMDD release tag fetch for this '
685
708
  'many minutes, before rechecking for latest. '
686
- 'Default is %(default)d minutes',
709
+ 'Default is %(default)d minutes.',
687
710
  )
688
711
  opt.add_argument(
689
712
  '--purge-days',
@@ -691,7 +714,7 @@ def main() -> str | None:
691
714
  type=int,
692
715
  help='cache YYYYMMDD release file lists and downloads for '
693
716
  'this number of days after last version referencing that '
694
- 'release is removed. Default is %(default)d days',
717
+ 'release is removed. Default is %(default)d days.',
695
718
  )
696
719
  opt.add_argument(
697
720
  '--github-access-token',
@@ -700,6 +723,11 @@ def main() -> str | None:
700
723
  opt.add_argument(
701
724
  '--no-strip', action='store_true', help='do not strip downloaded binaries'
702
725
  )
726
+ opt.add_argument(
727
+ '--cert',
728
+ choices=CERTS,
729
+ help=f'specify which SSL certificates to use for HTTPS requests. Default="{CERTS[0]}".',
730
+ )
703
731
  opt.add_argument(
704
732
  '-V', '--version', action='store_true', help=f'just show {PROG} version'
705
733
  )
@@ -733,18 +761,7 @@ def main() -> str | None:
733
761
 
734
762
  # Command arguments are now defined, so we can set up argcomplete
735
763
  argcomplete.autocomplete(opt)
736
-
737
- # Merge in default args from user config file. Then parse the
738
- # command line.
739
- cnffile = CNFFILE.expanduser()
740
- if cnffile.is_file():
741
- with cnffile.open() as fp:
742
- lines = [re.sub(r'#.*$', '', line).strip() for line in fp]
743
- cnflines = ' '.join(lines).strip()
744
- else:
745
- cnflines = ''
746
-
747
- args = opt.parse_args(shlex.split(cnflines) + sys.argv[1:])
764
+ args = opt.parse_args()
748
765
 
749
766
  if 'func' not in args:
750
767
  if args.version:
@@ -765,6 +782,7 @@ def main() -> str | None:
765
782
  prefix_dir = Path(args.prefix_dir).expanduser().resolve()
766
783
  cache_dir = Path(args.cache_dir).expanduser().resolve()
767
784
 
785
+ args._implementation = 'cpython' # at the moment, only support CPython
768
786
  args._distribution = distribution
769
787
  args._data = f'{PROG}.json'
770
788
 
@@ -776,6 +794,7 @@ def main() -> str | None:
776
794
  args._releases = cache_dir / 'releases'
777
795
  args._releases.mkdir(parents=True, exist_ok=True)
778
796
  args._latest_release = cache_dir / 'latest_release'
797
+ args._cert = create_cert(args.cert)
779
798
 
780
799
  result = args.func(args)
781
800
  purge_unused_releases(args)
@@ -815,7 +834,7 @@ class install_:
815
834
  @staticmethod
816
835
  def run(args: Namespace) -> str | None:
817
836
  release = get_release_tag(args)
818
- files = get_release_files(args, release, 'cpython')
837
+ files = get_release_files(args, release)
819
838
  if not files:
820
839
  return f'Release "{release}" not found, or has no compatible files.'
821
840
 
@@ -874,7 +893,7 @@ class update_:
874
893
  @staticmethod
875
894
  def run(args: Namespace) -> str | None:
876
895
  release_target = get_release_tag(args)
877
- files = get_release_files(args, release_target, 'cpython')
896
+ files = get_release_files(args, release_target)
878
897
  if not files:
879
898
  return f'Release "{release_target}" not found.'
880
899
 
@@ -991,7 +1010,7 @@ class list_:
991
1010
  @staticmethod
992
1011
  def run(args: Namespace) -> str | None:
993
1012
  release_target = get_release_tag(args)
994
- files = get_release_files(args, release_target, 'cpython')
1013
+ files = get_release_files(args, release_target)
995
1014
  if not files:
996
1015
  return f'Release "{release_target}" not found.'
997
1016
 
@@ -1089,7 +1108,7 @@ class show_:
1089
1108
  return None
1090
1109
 
1091
1110
  release = get_release_tag(args)
1092
- files = get_release_files(args, release, 'cpython')
1111
+ files = get_release_files(args, release)
1093
1112
  if not files:
1094
1113
  return f'Error: release "{release}" not found.'
1095
1114
 
@@ -1,5 +0,0 @@
1
- argcomplete
2
- packaging
3
- platformdirs
4
- pygithub
5
- zstandard
File without changes
File without changes