pystand 2.6__py3-none-any.whl → 2.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pystand
3
- Version: 2.6
3
+ Version: 2.7
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
- "x86_64-unknown-linux-gnu-install_only_stripped" for
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
@@ -0,0 +1,6 @@
1
+ pystand.py,sha256=mTEw8JKSQrSBKR8ZOR0YjzUGj6kyONz0XHgXXfevyIQ,36251
2
+ pystand-2.7.dist-info/METADATA,sha256=7SHzF64a1egBqA-pbvIPbLwhbR7VObS46pbyko3JiuA,24774
3
+ pystand-2.7.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
4
+ pystand-2.7.dist-info/entry_points.txt,sha256=DG4ps3I3nni1bubV1tXs6u8FARgkdbAYaEAzZD4RAo8,41
5
+ pystand-2.7.dist-info/top_level.txt,sha256=NoWUh19UQymAJLHTCdxMnVwV6Teftef5fzyF3OWLyNY,8
6
+ pystand-2.7.dist-info/RECORD,,
pystand.py CHANGED
@@ -19,9 +19,9 @@ 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 ArgumentParser, Namespace, SUPPRESS
23
23
  from collections import defaultdict
24
- from datetime import date
24
+ from datetime import datetime, date, timedelta
25
25
  from pathlib import Path
26
26
  from typing import Any, Iterable, Iterator
27
27
 
@@ -30,11 +30,10 @@ import platformdirs
30
30
  import zstandard
31
31
  from packaging.version import parse as parse_version
32
32
 
33
- REPO_OWNER = 'indygreg'
34
33
  REPO = 'python-build-standalone'
35
- GITHUB_REPO = f'{REPO_OWNER}/{REPO}'
36
- LATEST_RELEASE_URL = f'https://raw.githubusercontent.com/{GITHUB_REPO}'\
37
- '/latest-release/latest-release.json'
34
+ GITHUB_REPO = f'indygreg/{REPO}'
35
+ GITHUB_SITE = f'https://github.com/{GITHUB_REPO}'
36
+ LATEST_RELEASE = f'{GITHUB_SITE}/releases.atom'
38
37
 
39
38
  # Sample release tag for documentation/usage examples
40
39
  SAMPL_RELEASE = '20240415'
@@ -50,7 +49,8 @@ DISTRIBUTIONS = {
50
49
  ('Linux', 'armv8l'): 'armv7-unknown-linux-gnueabihf-install_only_stripped',
51
50
  ('Darwin', 'x86_64'): 'x86_64-apple-darwin-install_only_stripped',
52
51
  ('Darwin', 'aarch64'): 'aarch64-apple-darwin-install_only_stripped',
53
- ('Windows', 'x86_64'): 'x86_64-pc-windows-msvc-shared-install_only_stripped',
52
+ ('Windows', 'x86_64'):
53
+ 'x86_64-pc-windows-msvc-shared-install_only_stripped',
54
54
  ('Windows', 'i686'): 'i686-pc-windows-msvc-shared-install_only_stripped',
55
55
  }
56
56
 
@@ -274,6 +274,34 @@ def check_release_tag(release: str) -> str | None:
274
274
 
275
275
  return None
276
276
 
277
+ # Note we use a simple direct URL fetch to get the latest tag info
278
+ # because it is much faster than using the GitHub API, and has no
279
+ # rate-limits.
280
+ def fetch_tags() -> Iterator[tuple[str, str]]:
281
+ 'Fetch the latest release tag from the GitHub release atom feed'
282
+ import xml.etree.ElementTree as et
283
+ try:
284
+ with urllib.request.urlopen(LATEST_RELEASE) as url:
285
+ data = et.parse(url).getroot()
286
+ except Exception:
287
+ sys.exit('Failed to fetch latest YYYYMMDD release atom file.')
288
+
289
+ for child in data.iter():
290
+ for entry in child.findall('{http://www.w3.org/2005/Atom}entry'):
291
+ tl = entry.findtext('{http://www.w3.org/2005/Atom}title')
292
+ dt = entry.findtext('{http://www.w3.org/2005/Atom}updated')
293
+ if tl and dt:
294
+ yield tl, dt
295
+
296
+ def fetch_tag_latest(args: Namespace) -> str:
297
+ now = datetime.now().astimezone()
298
+ for title, datestr in fetch_tags():
299
+ timestamp = datetime.fromisoformat(datestr)
300
+ if now - timestamp >= args._min_release_time:
301
+ return title
302
+
303
+ return ''
304
+
277
305
  def get_release_tag(args: Namespace) -> str:
278
306
  'Return the release tag, or latest if not specified'
279
307
  if release := args.release:
@@ -287,19 +315,8 @@ def get_release_tag(args: Namespace) -> str:
287
315
  if time.time() < (stat.st_mtime + int(args.cache_minutes * 60)):
288
316
  return args._latest_release.read_text().strip()
289
317
 
290
- # Note this simple URL fetch is much faster than using the GitHub
291
- # API, and has no rate-limits, so we use it to get the latest
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.')
318
+ if not (tag := fetch_tag_latest(args)):
319
+ sys.exit('Latest YYYYMMDD release tag timestamp file is unavailable.')
303
320
 
304
321
  args._latest_release.write_text(tag + '\n')
305
322
  return tag
@@ -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
- releases = set(f.name for f in args._releases.iterdir())
418
- keep = set()
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
- for version in iter_versions(args):
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
- releases -= keep
429
- for release in releases:
430
- rdir = args._releases / release
431
- if (rdir.stat().st_mtime + end_secs) < now_secs:
432
- rdir.unlink()
433
- else:
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
- downloads = set(f.name for f in args._downloads.iterdir())
437
- for release in (downloads - keep):
438
- rm_path(args._downloads / release)
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
- https://github.com/indygreg/python-build-standalone/releases.
875
+ {GITHUB_SITE}/releases.
857
876
  '''
877
+
858
878
  @staticmethod
859
879
  def init(parser: ArgumentParser) -> None:
860
- parser.add_argument('-r', '--release',
861
- help=f'{REPO} YYYYMMDD release to show (e.g. '
862
- f'{SAMPL_RELEASE}), default is latest release')
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:
@@ -1,6 +0,0 @@
1
- pystand.py,sha256=dYDDeqM3pNPeurMpilFJyCVuIErvMDBZZdH3hs2uj54,34548
2
- pystand-2.6.dist-info/METADATA,sha256=qsQOtbkY7hOhwHlwyP-rvZvAGm9e53EKF-kewHg5MWw,24713
3
- pystand-2.6.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
4
- pystand-2.6.dist-info/entry_points.txt,sha256=DG4ps3I3nni1bubV1tXs6u8FARgkdbAYaEAzZD4RAo8,41
5
- pystand-2.6.dist-info/top_level.txt,sha256=NoWUh19UQymAJLHTCdxMnVwV6Teftef5fzyF3OWLyNY,8
6
- pystand-2.6.dist-info/RECORD,,
File without changes