pystand 2.9__py3-none-any.whl → 2.11__py3-none-any.whl
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.9.dist-info → pystand-2.11.dist-info}/METADATA +2 -2
- pystand-2.11.dist-info/RECORD +6 -0
- pystand.py +41 -39
- pystand-2.9.dist-info/RECORD +0 -6
- {pystand-2.9.dist-info → pystand-2.11.dist-info}/WHEEL +0 -0
- {pystand-2.9.dist-info → pystand-2.11.dist-info}/entry_points.txt +0 -0
- {pystand-2.9.dist-info → pystand-2.11.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pystand
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.11
|
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
|
@@ -142,7 +142,7 @@ options:
|
|
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
|
-
this host
|
145
|
+
this host
|
146
146
|
-P PREFIX_DIR, --prefix-dir PREFIX_DIR
|
147
147
|
specify prefix dir for storing versions. Default is
|
148
148
|
"$HOME/.local/share/pystand"
|
@@ -0,0 +1,6 @@
|
|
1
|
+
pystand.py,sha256=6yFhPrfS19fvrqxZKujgfWvbE0BxlKyexaX2-uXVD8E,36230
|
2
|
+
pystand-2.11.dist-info/METADATA,sha256=Be4WcqhlUAXtn_PbXtG1hAlklpEQ3bspNTvhuKuOTJg,24843
|
3
|
+
pystand-2.11.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
4
|
+
pystand-2.11.dist-info/entry_points.txt,sha256=DG4ps3I3nni1bubV1tXs6u8FARgkdbAYaEAzZD4RAo8,41
|
5
|
+
pystand-2.11.dist-info/top_level.txt,sha256=NoWUh19UQymAJLHTCdxMnVwV6Teftef5fzyF3OWLyNY,8
|
6
|
+
pystand-2.11.dist-info/RECORD,,
|
pystand.py
CHANGED
@@ -7,34 +7,28 @@ https://github.com/astral-sh/python-build-standalone.
|
|
7
7
|
'''
|
8
8
|
from __future__ import annotations
|
9
9
|
|
10
|
-
import json
|
11
10
|
import os
|
12
11
|
import platform
|
13
12
|
import re
|
14
13
|
import shlex
|
15
14
|
import shutil
|
16
|
-
import subprocess
|
17
15
|
import sys
|
18
|
-
import tarfile
|
19
16
|
import time
|
20
|
-
import
|
21
|
-
import urllib.request
|
22
|
-
from argparse import SUPPRESS, ArgumentParser, Namespace
|
17
|
+
from argparse import ArgumentParser, Namespace
|
23
18
|
from collections import defaultdict
|
24
|
-
from datetime import date, datetime
|
19
|
+
from datetime import date, datetime
|
25
20
|
from pathlib import Path
|
26
21
|
from typing import Any, Iterable, Iterator
|
27
22
|
|
28
23
|
import argcomplete
|
29
|
-
import github
|
30
24
|
import platformdirs
|
31
|
-
import zstandard
|
32
25
|
from packaging.version import parse as parse_version
|
33
26
|
|
34
27
|
REPO = 'python-build-standalone'
|
35
28
|
GITHUB_REPO = f'astral-sh/{REPO}'
|
36
29
|
GITHUB_SITE = f'https://github.com/{GITHUB_REPO}'
|
37
|
-
|
30
|
+
LATEST_RELEASES = f'{GITHUB_SITE}/releases.atom'
|
31
|
+
LATEST_RELEASE_TAG = f'{GITHUB_SITE}/releases/latest'
|
38
32
|
|
39
33
|
# Sample release tag for documentation/usage examples
|
40
34
|
SAMPL_RELEASE = '20240415'
|
@@ -78,10 +72,11 @@ def fmt(version, release) -> str:
|
|
78
72
|
return f'{version} @ {release}'
|
79
73
|
|
80
74
|
def get_json(file: Path) -> dict:
|
75
|
+
from json import load
|
81
76
|
'Get JSON data from given file'
|
82
77
|
try:
|
83
78
|
with file.open() as fp:
|
84
|
-
return
|
79
|
+
return load(fp)
|
85
80
|
except Exception:
|
86
81
|
pass
|
87
82
|
|
@@ -89,9 +84,10 @@ def get_json(file: Path) -> dict:
|
|
89
84
|
|
90
85
|
def set_json(file: Path, data: dict) -> str | None:
|
91
86
|
'Set JSON data to given file'
|
87
|
+
from json import dump
|
92
88
|
try:
|
93
89
|
with file.open('w') as fp:
|
94
|
-
|
90
|
+
dump(data, fp, indent=2)
|
95
91
|
except Exception as e:
|
96
92
|
return str(e)
|
97
93
|
|
@@ -108,12 +104,14 @@ def get_gh(args: Namespace) -> Any:
|
|
108
104
|
return get_gh_handle
|
109
105
|
|
110
106
|
if args.github_access_token:
|
111
|
-
|
107
|
+
from github.Auth import Token
|
108
|
+
auth = Token(args.github_access_token)
|
112
109
|
else:
|
113
110
|
auth = None
|
114
111
|
|
115
112
|
# Save this handle globally for future use
|
116
|
-
|
113
|
+
from github import Github
|
114
|
+
get_gh_handle = Github(auth=auth) # type: ignore
|
117
115
|
return get_gh_handle
|
118
116
|
|
119
117
|
def rm_path(path: Path) -> None:
|
@@ -127,6 +125,9 @@ def rm_path(path: Path) -> None:
|
|
127
125
|
|
128
126
|
def unpack_zst(filename: str, extract_dir: str) -> None:
|
129
127
|
'Unpack a zstandard compressed tar'
|
128
|
+
import tarfile
|
129
|
+
|
130
|
+
import zstandard
|
130
131
|
with open(filename, 'rb') as compressed:
|
131
132
|
dctx = zstandard.ZstdDecompressor()
|
132
133
|
with dctx.stream_reader(compressed) as reader:
|
@@ -135,19 +136,21 @@ def unpack_zst(filename: str, extract_dir: str) -> None:
|
|
135
136
|
|
136
137
|
def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
|
137
138
|
'Fetch and unpack a release file'
|
139
|
+
from urllib.parse import unquote, urlparse
|
140
|
+
from urllib.request import urlretrieve
|
138
141
|
error = None
|
139
142
|
tmpdir = tdir.with_name(f'{tdir.name}-tmp')
|
140
143
|
rm_path(tmpdir)
|
141
144
|
tmpdir.mkdir(parents=True)
|
142
145
|
|
143
|
-
filename_q = Path(
|
144
|
-
filename =
|
146
|
+
filename_q = Path(urlparse(url).path).name
|
147
|
+
filename = unquote(filename_q)
|
145
148
|
cache_file = args._downloads / release / filename
|
146
149
|
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
147
150
|
|
148
151
|
if not cache_file.exists():
|
149
152
|
try:
|
150
|
-
|
153
|
+
urlretrieve(url, cache_file)
|
151
154
|
except Exception as e:
|
152
155
|
error = f'Failed to fetch "{url}": {e}'
|
153
156
|
|
@@ -277,10 +280,11 @@ def check_release_tag(release: str) -> str | None:
|
|
277
280
|
# because it is much faster than using the GitHub API, and has no
|
278
281
|
# rate-limits.
|
279
282
|
def fetch_tags() -> Iterator[tuple[str, str]]:
|
280
|
-
'Fetch the latest release
|
283
|
+
'Fetch the latest release tags from the GitHub release atom feed'
|
281
284
|
import xml.etree.ElementTree as et
|
285
|
+
from urllib.request import urlopen
|
282
286
|
try:
|
283
|
-
with
|
287
|
+
with urlopen(LATEST_RELEASES) as url:
|
284
288
|
data = et.parse(url).getroot()
|
285
289
|
except Exception:
|
286
290
|
sys.exit('Failed to fetch latest YYYYMMDD release atom file.')
|
@@ -292,14 +296,16 @@ def fetch_tags() -> Iterator[tuple[str, str]]:
|
|
292
296
|
if tl and dt:
|
293
297
|
yield tl, dt
|
294
298
|
|
295
|
-
def fetch_tag_latest(
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
299
|
+
def fetch_tag_latest() -> str:
|
300
|
+
'Fetch the latest release tag from the GitHub'
|
301
|
+
from urllib.request import urlopen
|
302
|
+
try:
|
303
|
+
with urlopen(LATEST_RELEASE_TAG) as url:
|
304
|
+
data = url.geturl()
|
305
|
+
except Exception:
|
306
|
+
sys.exit('Failed to fetch latest YYYYMMDD release tag.')
|
301
307
|
|
302
|
-
return ''
|
308
|
+
return data.split('/')[-1]
|
303
309
|
|
304
310
|
def get_release_tag(args: Namespace) -> str:
|
305
311
|
'Return the release tag, or latest if not specified'
|
@@ -314,7 +320,7 @@ def get_release_tag(args: Namespace) -> str:
|
|
314
320
|
if time.time() < (stat.st_mtime + int(args.cache_minutes * 60)):
|
315
321
|
return args._latest_release.read_text().strip()
|
316
322
|
|
317
|
-
if not (tag := fetch_tag_latest(
|
323
|
+
if not (tag := fetch_tag_latest()):
|
318
324
|
sys.exit('Latest YYYYMMDD release tag timestamp file is unavailable.')
|
319
325
|
|
320
326
|
args._latest_release.write_text(tag + '\n')
|
@@ -349,7 +355,6 @@ def add_file(files: dict, tag: str, name: str, url: str) -> None:
|
|
349
355
|
|
350
356
|
def get_release_files(args, tag, implementation: str | None = None) -> dict:
|
351
357
|
'Return the release files for the given tag'
|
352
|
-
from github.GithubException import UnknownObjectException
|
353
358
|
# Look for tag data in our release cache
|
354
359
|
jfile = args._releases / tag
|
355
360
|
if not (files := get_json(jfile)):
|
@@ -359,6 +364,7 @@ def get_release_files(args, tag, implementation: str | None = None) -> dict:
|
|
359
364
|
return {}
|
360
365
|
|
361
366
|
# Not in cache so fetch it (and also store in cache)
|
367
|
+
from github.GithubException import UnknownObjectException
|
362
368
|
gh = get_gh(args)
|
363
369
|
try:
|
364
370
|
release = gh.get_repo(GITHUB_REPO).get_release(tag)
|
@@ -489,6 +495,8 @@ def remove(args: Namespace, version: str) -> None:
|
|
489
495
|
|
490
496
|
def strip_binaries(vdir: Path, distribution: str) -> bool:
|
491
497
|
'Strip binaries from files in a version directory'
|
498
|
+
from subprocess import DEVNULL, run
|
499
|
+
|
492
500
|
# Only run the strip command on Linux hosts and for Linux distributions
|
493
501
|
was_stripped = False
|
494
502
|
if platform.system() == 'Linux' and '-linux-' in distribution:
|
@@ -501,7 +509,7 @@ def strip_binaries(vdir: Path, distribution: str) -> bool:
|
|
501
509
|
if not file.is_symlink() and file.is_file():
|
502
510
|
cmd = f'strip -p --strip-unneeded {file}'.split()
|
503
511
|
try:
|
504
|
-
|
512
|
+
run(cmd, stderr=DEVNULL)
|
505
513
|
except Exception:
|
506
514
|
pass
|
507
515
|
else:
|
@@ -557,7 +565,7 @@ def main() -> str | None:
|
|
557
565
|
# Set up main/global arguments
|
558
566
|
opt.add_argument('-D', '--distribution',
|
559
567
|
help=f'{REPO} distribution. '
|
560
|
-
f'Default is "{distro_help} for this host
|
568
|
+
f'Default is "{distro_help} for this host')
|
561
569
|
opt.add_argument('-P', '--prefix-dir', default=prefix_dir,
|
562
570
|
help='specify prefix dir for storing '
|
563
571
|
'versions. Default is "%(default)s"')
|
@@ -579,8 +587,6 @@ def main() -> str | None:
|
|
579
587
|
help='do not strip downloaded binaries')
|
580
588
|
opt.add_argument('-V', '--version', action='store_true',
|
581
589
|
help=f'just show {PROG} version')
|
582
|
-
opt.add_argument('--min-release-hours', default=6, type=int,
|
583
|
-
help=SUPPRESS)
|
584
590
|
cmd = opt.add_subparsers(title='Commands', dest='cmdname')
|
585
591
|
|
586
592
|
# Add each command ..
|
@@ -649,7 +655,6 @@ def main() -> str | None:
|
|
649
655
|
args._releases = cache_dir / 'releases'
|
650
656
|
args._releases.mkdir(parents=True, exist_ok=True)
|
651
657
|
args._latest_release = cache_dir / 'latest_release'
|
652
|
-
args._min_release_time = timedelta(hours=args.min_release_hours)
|
653
658
|
|
654
659
|
result = args.func(args)
|
655
660
|
purge_unused_releases(args)
|
@@ -896,16 +901,13 @@ class _show(COMMAND):
|
|
896
901
|
args.parser.error('Can not specify --all with --list.')
|
897
902
|
|
898
903
|
if args.list:
|
899
|
-
now = datetime.now().astimezone()
|
900
904
|
for title, datestr in fetch_tags():
|
901
905
|
if args.re_match and not re.search(args.re_match, title):
|
902
906
|
continue
|
903
907
|
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
else:
|
908
|
-
print(title)
|
908
|
+
dts = datetime.fromisoformat(datestr).astimezone().isoformat(
|
909
|
+
sep='_', timespec='minutes')
|
910
|
+
print(title, dts)
|
909
911
|
|
910
912
|
return
|
911
913
|
|
pystand-2.9.dist-info/RECORD
DELETED
@@ -1,6 +0,0 @@
|
|
1
|
-
pystand.py,sha256=bvWRxN3JrIodS5p1pvBy6Lf2evbpACtNp6LPl3XO2xI,36294
|
2
|
-
pystand-2.9.dist-info/METADATA,sha256=y5bHXt3xX-XT8Ow7dxQyYI2GhmUlM29V-Eawq7c3uqk,24843
|
3
|
-
pystand-2.9.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
4
|
-
pystand-2.9.dist-info/entry_points.txt,sha256=DG4ps3I3nni1bubV1tXs6u8FARgkdbAYaEAzZD4RAo8,41
|
5
|
-
pystand-2.9.dist-info/top_level.txt,sha256=NoWUh19UQymAJLHTCdxMnVwV6Teftef5fzyF3OWLyNY,8
|
6
|
-
pystand-2.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|