pystand 2.6__tar.gz → 2.8__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.
- {pystand-2.6/pystand.egg-info → pystand-2.8}/PKG-INFO +5 -4
- {pystand-2.6 → pystand-2.8}/README.md +4 -3
- {pystand-2.6 → pystand-2.8/pystand.egg-info}/PKG-INFO +5 -4
- {pystand-2.6 → pystand-2.8}/pystand.py +89 -49
- {pystand-2.6 → pystand-2.8}/.flake8 +0 -0
- {pystand-2.6 → pystand-2.8}/.gitignore +0 -0
- {pystand-2.6 → pystand-2.8}/Makefile +0 -0
- {pystand-2.6 → pystand-2.8}/pyproject.toml +0 -0
- {pystand-2.6 → pystand-2.8}/pystand.egg-info/SOURCES.txt +0 -0
- {pystand-2.6 → pystand-2.8}/pystand.egg-info/dependency_links.txt +0 -0
- {pystand-2.6 → pystand-2.8}/pystand.egg-info/entry_points.txt +0 -0
- {pystand-2.6 → pystand-2.8}/pystand.egg-info/requires.txt +0 -0
- {pystand-2.6 → pystand-2.8}/pystand.egg-info/top_level.txt +0 -0
- {pystand-2.6 → pystand-2.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pystand
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.8
|
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: GPLv3
|
@@ -141,7 +141,7 @@ options:
|
|
141
141
|
-h, --help show this help message and exit
|
142
142
|
-D DISTRIBUTION, --distribution DISTRIBUTION
|
143
143
|
python-build-standalone distribution. Default is
|
144
|
-
"
|
144
|
+
"x86_64_v3-unknown-linux-gnu-install_only_stripped for
|
145
145
|
this host.
|
146
146
|
-P PREFIX_DIR, --prefix-dir PREFIX_DIR
|
147
147
|
specify prefix dir for storing versions. Default is
|
@@ -160,7 +160,7 @@ options:
|
|
160
160
|
--github-access-token GITHUB_ACCESS_TOKEN
|
161
161
|
optional Github access token. Can specify to reduce
|
162
162
|
rate limiting.
|
163
|
-
--no-strip do strip downloaded binaries
|
163
|
+
--no-strip do not strip downloaded binaries
|
164
164
|
-V, --version just show pystand version
|
165
165
|
|
166
166
|
Commands:
|
@@ -270,7 +270,7 @@ options:
|
|
270
270
|
### Command `show`
|
271
271
|
|
272
272
|
```
|
273
|
-
usage: pystand show [-h] [-r RELEASE] [-a] [re_match]
|
273
|
+
usage: pystand show [-h] [-l | -r RELEASE] [-a] [re_match]
|
274
274
|
|
275
275
|
Show versions available from a release. View available releases and their
|
276
276
|
distributions at https://github.com/indygreg/python-build-standalone/releases.
|
@@ -281,6 +281,7 @@ positional arguments:
|
|
281
281
|
|
282
282
|
options:
|
283
283
|
-h, --help show this help message and exit
|
284
|
+
-l, --list just list recent releases
|
284
285
|
-r RELEASE, --release RELEASE
|
285
286
|
python-build-standalone YYYYMMDD release to show (e.g.
|
286
287
|
20240415), default is latest release
|
@@ -124,7 +124,7 @@ options:
|
|
124
124
|
-h, --help show this help message and exit
|
125
125
|
-D DISTRIBUTION, --distribution DISTRIBUTION
|
126
126
|
python-build-standalone distribution. Default is
|
127
|
-
"
|
127
|
+
"x86_64_v3-unknown-linux-gnu-install_only_stripped for
|
128
128
|
this host.
|
129
129
|
-P PREFIX_DIR, --prefix-dir PREFIX_DIR
|
130
130
|
specify prefix dir for storing versions. Default is
|
@@ -143,7 +143,7 @@ options:
|
|
143
143
|
--github-access-token GITHUB_ACCESS_TOKEN
|
144
144
|
optional Github access token. Can specify to reduce
|
145
145
|
rate limiting.
|
146
|
-
--no-strip do strip downloaded binaries
|
146
|
+
--no-strip do not strip downloaded binaries
|
147
147
|
-V, --version just show pystand version
|
148
148
|
|
149
149
|
Commands:
|
@@ -253,7 +253,7 @@ options:
|
|
253
253
|
### Command `show`
|
254
254
|
|
255
255
|
```
|
256
|
-
usage: pystand show [-h] [-r RELEASE] [-a] [re_match]
|
256
|
+
usage: pystand show [-h] [-l | -r RELEASE] [-a] [re_match]
|
257
257
|
|
258
258
|
Show versions available from a release. View available releases and their
|
259
259
|
distributions at https://github.com/indygreg/python-build-standalone/releases.
|
@@ -264,6 +264,7 @@ positional arguments:
|
|
264
264
|
|
265
265
|
options:
|
266
266
|
-h, --help show this help message and exit
|
267
|
+
-l, --list just list recent releases
|
267
268
|
-r RELEASE, --release RELEASE
|
268
269
|
python-build-standalone YYYYMMDD release to show (e.g.
|
269
270
|
20240415), default is latest release
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pystand
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.8
|
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: GPLv3
|
@@ -141,7 +141,7 @@ options:
|
|
141
141
|
-h, --help show this help message and exit
|
142
142
|
-D DISTRIBUTION, --distribution DISTRIBUTION
|
143
143
|
python-build-standalone distribution. Default is
|
144
|
-
"
|
144
|
+
"x86_64_v3-unknown-linux-gnu-install_only_stripped for
|
145
145
|
this host.
|
146
146
|
-P PREFIX_DIR, --prefix-dir PREFIX_DIR
|
147
147
|
specify prefix dir for storing versions. Default is
|
@@ -160,7 +160,7 @@ options:
|
|
160
160
|
--github-access-token GITHUB_ACCESS_TOKEN
|
161
161
|
optional Github access token. Can specify to reduce
|
162
162
|
rate limiting.
|
163
|
-
--no-strip do strip downloaded binaries
|
163
|
+
--no-strip do not strip downloaded binaries
|
164
164
|
-V, --version just show pystand version
|
165
165
|
|
166
166
|
Commands:
|
@@ -270,7 +270,7 @@ options:
|
|
270
270
|
### Command `show`
|
271
271
|
|
272
272
|
```
|
273
|
-
usage: pystand show [-h] [-r RELEASE] [-a] [re_match]
|
273
|
+
usage: pystand show [-h] [-l | -r RELEASE] [-a] [re_match]
|
274
274
|
|
275
275
|
Show versions available from a release. View available releases and their
|
276
276
|
distributions at https://github.com/indygreg/python-build-standalone/releases.
|
@@ -281,6 +281,7 @@ positional arguments:
|
|
281
281
|
|
282
282
|
options:
|
283
283
|
-h, --help show this help message and exit
|
284
|
+
-l, --list just list recent releases
|
284
285
|
-r RELEASE, --release RELEASE
|
285
286
|
python-build-standalone YYYYMMDD release to show (e.g.
|
286
287
|
20240415), default is latest release
|
@@ -19,22 +19,22 @@ import tarfile
|
|
19
19
|
import time
|
20
20
|
import urllib.parse
|
21
21
|
import urllib.request
|
22
|
-
from argparse import ArgumentParser, Namespace
|
22
|
+
from argparse import SUPPRESS, ArgumentParser, Namespace
|
23
23
|
from collections import defaultdict
|
24
|
-
from datetime import date
|
24
|
+
from datetime import date, datetime, timedelta
|
25
25
|
from pathlib import Path
|
26
26
|
from typing import Any, Iterable, Iterator
|
27
27
|
|
28
28
|
import argcomplete
|
29
|
+
import github
|
29
30
|
import platformdirs
|
30
31
|
import zstandard
|
31
32
|
from packaging.version import parse as parse_version
|
32
33
|
|
33
|
-
REPO_OWNER = 'indygreg'
|
34
34
|
REPO = 'python-build-standalone'
|
35
|
-
GITHUB_REPO = f'
|
36
|
-
|
37
|
-
|
35
|
+
GITHUB_REPO = f'indygreg/{REPO}'
|
36
|
+
GITHUB_SITE = f'https://github.com/{GITHUB_REPO}'
|
37
|
+
LATEST_RELEASE = f'{GITHUB_SITE}/releases.atom'
|
38
38
|
|
39
39
|
# Sample release tag for documentation/usage examples
|
40
40
|
SAMPL_RELEASE = '20240415'
|
@@ -50,7 +50,8 @@ DISTRIBUTIONS = {
|
|
50
50
|
('Linux', 'armv8l'): 'armv7-unknown-linux-gnueabihf-install_only_stripped',
|
51
51
|
('Darwin', 'x86_64'): 'x86_64-apple-darwin-install_only_stripped',
|
52
52
|
('Darwin', 'aarch64'): 'aarch64-apple-darwin-install_only_stripped',
|
53
|
-
('Windows', 'x86_64'):
|
53
|
+
('Windows', 'x86_64'):
|
54
|
+
'x86_64-pc-windows-msvc-shared-install_only_stripped',
|
54
55
|
('Windows', 'i686'): 'i686-pc-windows-msvc-shared-install_only_stripped',
|
55
56
|
}
|
56
57
|
|
@@ -106,15 +107,13 @@ def get_gh(args: Namespace) -> Any:
|
|
106
107
|
if get_gh_handle:
|
107
108
|
return get_gh_handle
|
108
109
|
|
109
|
-
from github import Github
|
110
110
|
if args.github_access_token:
|
111
|
-
|
112
|
-
auth = Auth.Token(args.github_access_token)
|
111
|
+
auth = github.Auth.Token(args.github_access_token)
|
113
112
|
else:
|
114
113
|
auth = None
|
115
114
|
|
116
115
|
# Save this handle globally for future use
|
117
|
-
get_gh_handle = Github(auth=auth) # type: ignore
|
116
|
+
get_gh_handle = github.Github(auth=auth) # type: ignore
|
118
117
|
return get_gh_handle
|
119
118
|
|
120
119
|
def rm_path(path: Path) -> None:
|
@@ -274,6 +273,34 @@ def check_release_tag(release: str) -> str | None:
|
|
274
273
|
|
275
274
|
return None
|
276
275
|
|
276
|
+
# Note we use a simple direct URL fetch to get the latest tag info
|
277
|
+
# because it is much faster than using the GitHub API, and has no
|
278
|
+
# rate-limits.
|
279
|
+
def fetch_tags() -> Iterator[tuple[str, str]]:
|
280
|
+
'Fetch the latest release tag from the GitHub release atom feed'
|
281
|
+
import xml.etree.ElementTree as et
|
282
|
+
try:
|
283
|
+
with urllib.request.urlopen(LATEST_RELEASE) as url:
|
284
|
+
data = et.parse(url).getroot()
|
285
|
+
except Exception:
|
286
|
+
sys.exit('Failed to fetch latest YYYYMMDD release atom file.')
|
287
|
+
|
288
|
+
for child in data.iter():
|
289
|
+
for entry in child.findall('{http://www.w3.org/2005/Atom}entry'):
|
290
|
+
tl = entry.findtext('{http://www.w3.org/2005/Atom}title')
|
291
|
+
dt = entry.findtext('{http://www.w3.org/2005/Atom}updated')
|
292
|
+
if tl and dt:
|
293
|
+
yield tl, dt
|
294
|
+
|
295
|
+
def fetch_tag_latest(args: Namespace) -> str:
|
296
|
+
now = datetime.now().astimezone()
|
297
|
+
for title, datestr in fetch_tags():
|
298
|
+
timestamp = datetime.fromisoformat(datestr)
|
299
|
+
if now - timestamp >= args._min_release_time:
|
300
|
+
return title
|
301
|
+
|
302
|
+
return ''
|
303
|
+
|
277
304
|
def get_release_tag(args: Namespace) -> str:
|
278
305
|
'Return the release tag, or latest if not specified'
|
279
306
|
if release := args.release:
|
@@ -287,19 +314,8 @@ def get_release_tag(args: Namespace) -> str:
|
|
287
314
|
if time.time() < (stat.st_mtime + int(args.cache_minutes * 60)):
|
288
315
|
return args._latest_release.read_text().strip()
|
289
316
|
|
290
|
-
|
291
|
-
|
292
|
-
# release tag.
|
293
|
-
try:
|
294
|
-
with urllib.request.urlopen(LATEST_RELEASE_URL) as url:
|
295
|
-
data = json.load(url)
|
296
|
-
except Exception:
|
297
|
-
sys.exit('Failed to fetch latest YYYYMMDD release tag.')
|
298
|
-
|
299
|
-
tag = data.get('tag')
|
300
|
-
|
301
|
-
if not tag:
|
302
|
-
sys.exit('Latest YYYYMMDD release tag timestamp file is corrupted.')
|
317
|
+
if not (tag := fetch_tag_latest(args)):
|
318
|
+
sys.exit('Latest YYYYMMDD release tag timestamp file is unavailable.')
|
303
319
|
|
304
320
|
args._latest_release.write_text(tag + '\n')
|
305
321
|
return tag
|
@@ -333,6 +349,7 @@ def add_file(files: dict, tag: str, name: str, url: str) -> None:
|
|
333
349
|
|
334
350
|
def get_release_files(args, tag, implementation: str | None = None) -> dict:
|
335
351
|
'Return the release files for the given tag'
|
352
|
+
from github.GithubException import UnknownObjectException
|
336
353
|
# Look for tag data in our release cache
|
337
354
|
jfile = args._releases / tag
|
338
355
|
if not (files := get_json(jfile)):
|
@@ -345,7 +362,7 @@ def get_release_files(args, tag, implementation: str | None = None) -> dict:
|
|
345
362
|
gh = get_gh(args)
|
346
363
|
try:
|
347
364
|
release = gh.get_repo(GITHUB_REPO).get_release(tag)
|
348
|
-
except
|
365
|
+
except UnknownObjectException:
|
349
366
|
return {}
|
350
367
|
|
351
368
|
# Iterate over the release assets and store pertinent files in a
|
@@ -414,28 +431,28 @@ def update_version_symlinks(args: Namespace) -> None:
|
|
414
431
|
|
415
432
|
def purge_unused_releases(args: Namespace) -> None:
|
416
433
|
'Purge old releases that are no longer needed and have expired'
|
417
|
-
|
418
|
-
keep =
|
434
|
+
# Want to keep releases for versions that we currently have installed
|
435
|
+
keep = {r for v in iter_versions(args)
|
436
|
+
if (r := get_json(v / args._data).get('release'))}
|
437
|
+
|
438
|
+
# Add current release to keep list (even if not currently installed)
|
419
439
|
if args._latest_release.exists():
|
420
440
|
keep.add(args._latest_release.read_text().strip())
|
421
441
|
|
422
|
-
|
423
|
-
if (release := get_json(version / args._data).get('release')):
|
424
|
-
keep.add(release)
|
425
|
-
|
442
|
+
# Purge any release lists that are no longer used and have expired
|
426
443
|
now_secs = time.time()
|
427
444
|
end_secs = args.purge_days * 86400
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
keep.add(release)
|
445
|
+
for path in args._releases.iterdir():
|
446
|
+
if path.name not in keep:
|
447
|
+
if (path.stat().st_mtime + end_secs) < now_secs:
|
448
|
+
path.unlink()
|
449
|
+
else:
|
450
|
+
keep.add(path.name)
|
435
451
|
|
436
|
-
|
437
|
-
for
|
438
|
-
|
452
|
+
# Purge any downloads for releases that have expired
|
453
|
+
for path in args._downloads.iterdir():
|
454
|
+
if path.name not in keep:
|
455
|
+
rm_path(path)
|
439
456
|
|
440
457
|
class COMMAND:
|
441
458
|
'Base class for all commands'
|
@@ -540,8 +557,7 @@ def main() -> str | None:
|
|
540
557
|
# Set up main/global arguments
|
541
558
|
opt.add_argument('-D', '--distribution',
|
542
559
|
help=f'{REPO} distribution. '
|
543
|
-
f'Default is "{distro_help}
|
544
|
-
'for this host.')
|
560
|
+
f'Default is "{distro_help} for this host.')
|
545
561
|
opt.add_argument('-P', '--prefix-dir', default=prefix_dir,
|
546
562
|
help='specify prefix dir for storing '
|
547
563
|
'versions. Default is "%(default)s"')
|
@@ -560,9 +576,11 @@ def main() -> str | None:
|
|
560
576
|
help='optional Github access token. Can specify to reduce '
|
561
577
|
'rate limiting.')
|
562
578
|
opt.add_argument('--no-strip', action='store_true',
|
563
|
-
help='do strip downloaded binaries')
|
579
|
+
help='do not strip downloaded binaries')
|
564
580
|
opt.add_argument('-V', '--version', action='store_true',
|
565
581
|
help=f'just show {PROG} version')
|
582
|
+
opt.add_argument('--min-release-hours', default=6, type=int,
|
583
|
+
help=SUPPRESS)
|
566
584
|
cmd = opt.add_subparsers(title='Commands', dest='cmdname')
|
567
585
|
|
568
586
|
# Add each command ..
|
@@ -631,6 +649,7 @@ def main() -> str | None:
|
|
631
649
|
args._releases = cache_dir / 'releases'
|
632
650
|
args._releases.mkdir(parents=True, exist_ok=True)
|
633
651
|
args._latest_release = cache_dir / 'latest_release'
|
652
|
+
args._min_release_time = timedelta(hours=args.min_release_hours)
|
634
653
|
|
635
654
|
result = args.func(args)
|
636
655
|
purge_unused_releases(args)
|
@@ -849,17 +868,21 @@ class _list(COMMAND):
|
|
849
868
|
|
850
869
|
@COMMAND.add
|
851
870
|
class _show(COMMAND):
|
852
|
-
'''
|
871
|
+
doc = f'''
|
853
872
|
Show versions available from a release.
|
854
873
|
|
855
874
|
View available releases and their distributions at
|
856
|
-
|
875
|
+
{GITHUB_SITE}/releases.
|
857
876
|
'''
|
877
|
+
|
858
878
|
@staticmethod
|
859
879
|
def init(parser: ArgumentParser) -> None:
|
860
|
-
parser.
|
861
|
-
|
862
|
-
|
880
|
+
group = parser.add_mutually_exclusive_group()
|
881
|
+
group.add_argument('-l', '--list', action='store_true',
|
882
|
+
help='just list recent releases')
|
883
|
+
group.add_argument('-r', '--release',
|
884
|
+
help=f'{REPO} YYYYMMDD release to show (e.g. '
|
885
|
+
f'{SAMPL_RELEASE}), default is latest release')
|
863
886
|
parser.add_argument('-a', '--all', action='store_true',
|
864
887
|
help='show all available distributions for '
|
865
888
|
'each version from the release')
|
@@ -869,6 +892,23 @@ class _show(COMMAND):
|
|
869
892
|
|
870
893
|
@staticmethod
|
871
894
|
def run(args: Namespace) -> None:
|
895
|
+
if args.all and args.list:
|
896
|
+
args.parser.error('Can not specify --all with --list.')
|
897
|
+
|
898
|
+
if args.list:
|
899
|
+
now = datetime.now().astimezone()
|
900
|
+
for title, datestr in fetch_tags():
|
901
|
+
if args.re_match and not re.search(args.re_match, title):
|
902
|
+
continue
|
903
|
+
|
904
|
+
tdiff = now - datetime.fromisoformat(datestr)
|
905
|
+
if tdiff < args._min_release_time:
|
906
|
+
print(title, '(pending)')
|
907
|
+
else:
|
908
|
+
print(title)
|
909
|
+
|
910
|
+
return
|
911
|
+
|
872
912
|
release = get_release_tag(args)
|
873
913
|
files = get_release_files(args, release, 'cpython')
|
874
914
|
if not files:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|