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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pystand
3
- Version: 2.9
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 urllib.parse
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, timedelta
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
- LATEST_RELEASE = f'{GITHUB_SITE}/releases.atom'
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 json.load(fp)
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
- json.dump(data, fp, indent=2)
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
- auth = github.Auth.Token(args.github_access_token)
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
- get_gh_handle = github.Github(auth=auth) # type: ignore
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(urllib.parse.urlparse(url).path).name
144
- filename = urllib.parse.unquote(filename_q)
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
- urllib.request.urlretrieve(url, cache_file)
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 tag from the GitHub release atom feed'
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 urllib.request.urlopen(LATEST_RELEASE) as url:
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(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
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(args)):
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
- subprocess.run(cmd, stderr=subprocess.DEVNULL)
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
- tdiff = now - datetime.fromisoformat(datestr)
905
- if tdiff < args._min_release_time:
906
- print(title, '(pending)')
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
 
@@ -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