pystand 2.18__tar.gz → 2.20__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pystand
3
- Version: 2.18
3
+ Version: 2.20
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
@@ -15,6 +15,7 @@ Requires-Dist: platformdirs
15
15
  Requires-Dist: argparse-from-file
16
16
  Requires-Dist: pygithub
17
17
  Requires-Dist: certifi
18
+ Requires-Dist: filelock
18
19
  Requires-Dist: zstandard; python_version < "3.14"
19
20
 
20
21
  ## PYSTAND - Install Python Versions From The Python-Build-Standalone Project
@@ -145,7 +146,9 @@ options:
145
146
  python-build-standalone distribution. Default is
146
147
  "x86_64_v3-unknown-linux-gnu-install_only_stripped"
147
148
  for this host. Run "pystand show -a" to see all
148
- distributions.
149
+ distributions. See
150
+ https://gregoryszorc.com/docs/python-build-
151
+ standalone/main/
149
152
  -P, --prefix-dir PREFIX_DIR
150
153
  specify prefix dir for storing versions. Default is
151
154
  "$HOME/.local/share/pystand".
@@ -179,7 +182,7 @@ Commands:
179
182
  available.
180
183
  show Show versions available from a release.
181
184
  path Show path prefix to installed version base directory.
182
- cache Show release cache sizes.
185
+ cache Show size of release download caches.
183
186
 
184
187
  Some commands offer aliases as shown in parentheses above. Note you can set
185
188
  default starting global options in $HOME/.config/pystand-flags.conf.
@@ -317,9 +320,9 @@ options:
317
320
  ### Command `cache`
318
321
 
319
322
  ```
320
- usage: pystand cache [-h] [-T] [-H] [release ...]
323
+ usage: pystand cache [-h] [-T] [-H] [-r] [release ...]
321
324
 
322
- Show release cache sizes.
325
+ Show size of release download caches.
323
326
 
324
327
  positional arguments:
325
328
  release show cache size for given release[s] only
@@ -329,6 +332,7 @@ options:
329
332
  -T, --no-total do not show total cache size
330
333
  -H, --no-human-readable
331
334
  show sizes in bytes, not human readable format
335
+ -r, --remove remove download cache[s] instead of showing size
332
336
  ```
333
337
 
334
338
  ## Installation and Upgrade
@@ -126,7 +126,9 @@ options:
126
126
  python-build-standalone distribution. Default is
127
127
  "x86_64_v3-unknown-linux-gnu-install_only_stripped"
128
128
  for this host. Run "pystand show -a" to see all
129
- distributions.
129
+ distributions. See
130
+ https://gregoryszorc.com/docs/python-build-
131
+ standalone/main/
130
132
  -P, --prefix-dir PREFIX_DIR
131
133
  specify prefix dir for storing versions. Default is
132
134
  "$HOME/.local/share/pystand".
@@ -160,7 +162,7 @@ Commands:
160
162
  available.
161
163
  show Show versions available from a release.
162
164
  path Show path prefix to installed version base directory.
163
- cache Show release cache sizes.
165
+ cache Show size of release download caches.
164
166
 
165
167
  Some commands offer aliases as shown in parentheses above. Note you can set
166
168
  default starting global options in $HOME/.config/pystand-flags.conf.
@@ -298,9 +300,9 @@ options:
298
300
  ### Command `cache`
299
301
 
300
302
  ```
301
- usage: pystand cache [-h] [-T] [-H] [release ...]
303
+ usage: pystand cache [-h] [-T] [-H] [-r] [release ...]
302
304
 
303
- Show release cache sizes.
305
+ Show size of release download caches.
304
306
 
305
307
  positional arguments:
306
308
  release show cache size for given release[s] only
@@ -310,6 +312,7 @@ options:
310
312
  -T, --no-total do not show total cache size
311
313
  -H, --no-human-readable
312
314
  show sizes in bytes, not human readable format
315
+ -r, --remove remove download cache[s] instead of showing size
313
316
  ```
314
317
 
315
318
  ## Installation and Upgrade
@@ -20,6 +20,7 @@ dependencies = [
20
20
  "argparse-from-file",
21
21
  "pygithub",
22
22
  "certifi",
23
+ "filelock",
23
24
  "zstandard; python_version < '3.14'",
24
25
  ]
25
26
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pystand
3
- Version: 2.18
3
+ Version: 2.20
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
@@ -15,6 +15,7 @@ Requires-Dist: platformdirs
15
15
  Requires-Dist: argparse-from-file
16
16
  Requires-Dist: pygithub
17
17
  Requires-Dist: certifi
18
+ Requires-Dist: filelock
18
19
  Requires-Dist: zstandard; python_version < "3.14"
19
20
 
20
21
  ## PYSTAND - Install Python Versions From The Python-Build-Standalone Project
@@ -145,7 +146,9 @@ options:
145
146
  python-build-standalone distribution. Default is
146
147
  "x86_64_v3-unknown-linux-gnu-install_only_stripped"
147
148
  for this host. Run "pystand show -a" to see all
148
- distributions.
149
+ distributions. See
150
+ https://gregoryszorc.com/docs/python-build-
151
+ standalone/main/
149
152
  -P, --prefix-dir PREFIX_DIR
150
153
  specify prefix dir for storing versions. Default is
151
154
  "$HOME/.local/share/pystand".
@@ -179,7 +182,7 @@ Commands:
179
182
  available.
180
183
  show Show versions available from a release.
181
184
  path Show path prefix to installed version base directory.
182
- cache Show release cache sizes.
185
+ cache Show size of release download caches.
183
186
 
184
187
  Some commands offer aliases as shown in parentheses above. Note you can set
185
188
  default starting global options in $HOME/.config/pystand-flags.conf.
@@ -317,9 +320,9 @@ options:
317
320
  ### Command `cache`
318
321
 
319
322
  ```
320
- usage: pystand cache [-h] [-T] [-H] [release ...]
323
+ usage: pystand cache [-h] [-T] [-H] [-r] [release ...]
321
324
 
322
- Show release cache sizes.
325
+ Show size of release download caches.
323
326
 
324
327
  positional arguments:
325
328
  release show cache size for given release[s] only
@@ -329,6 +332,7 @@ options:
329
332
  -T, --no-total do not show total cache size
330
333
  -H, --no-human-readable
331
334
  show sizes in bytes, not human readable format
335
+ -r, --remove remove download cache[s] instead of showing size
332
336
  ```
333
337
 
334
338
  ## Installation and Upgrade
@@ -4,6 +4,7 @@ platformdirs
4
4
  argparse-from-file
5
5
  pygithub
6
6
  certifi
7
+ filelock
7
8
 
8
9
  [:python_version < "3.14"]
9
10
  zstandard
@@ -23,8 +23,9 @@ from typing import Any
23
23
  from urllib.request import urlopen
24
24
 
25
25
  import argcomplete
26
+ import filelock
26
27
  import platformdirs
27
- from argparse_from_file import ArgumentParser, Namespace # type: ignore
28
+ from argparse_from_file import ArgumentParser, Namespace
28
29
  from packaging.version import parse as parse_version
29
30
 
30
31
  REPO = 'python-build-standalone'
@@ -32,6 +33,7 @@ GITHUB_REPO = f'astral-sh/{REPO}'
32
33
  GITHUB_SITE = f'https://github.com/{GITHUB_REPO}'
33
34
  LATEST_RELEASES = f'{GITHUB_SITE}/releases.atom'
34
35
  LATEST_RELEASE_TAG = f'{GITHUB_SITE}/releases/latest'
36
+ DOC = 'https://gregoryszorc.com/docs/python-build-standalone/main/'
35
37
 
36
38
  # Sample release tag for documentation/usage examples
37
39
  SAMPL_RELEASE = '20240415'
@@ -144,23 +146,19 @@ def rm_path(path: Path) -> None:
144
146
 
145
147
  def register_zst() -> None:
146
148
  "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):
149
+ def unpack_zst(filename: str, extract_dir: str) -> None:
150
+ "Unpack a zstandard compressed tar"
151
+ import tarfile
150
152
 
151
- def unpack_zst(filename: str, extract_dir: str) -> None:
152
- "Unpack a zstandard compressed tar"
153
- import tarfile
153
+ import zstandard
154
154
 
155
- import zstandard
155
+ with open(filename, 'rb') as compressed:
156
+ dctx = zstandard.ZstdDecompressor()
157
+ with dctx.stream_reader(compressed) as reader:
158
+ with tarfile.open(fileobj=reader, mode='r|') as tar:
159
+ tar.extractall(path=extract_dir)
156
160
 
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)
161
+ shutil.register_unpack_format('zst', ['.zst'], unpack_zst)
164
162
 
165
163
 
166
164
  def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
@@ -187,7 +185,8 @@ def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
187
185
  if error:
188
186
  rm_path(cache_file)
189
187
  else:
190
- if filename.endswith('.zst'):
188
+ # Register custom zstandard handler (only if Python < 3.14 which has built-in support)
189
+ if filename.endswith('.zst') and sys.version_info < (3, 14):
191
190
  register_zst()
192
191
 
193
192
  try:
@@ -500,9 +499,10 @@ def purge_unused_releases(args: Namespace) -> None:
500
499
  keep.add(path.name)
501
500
 
502
501
  # Purge any downloads for releases that have expired
503
- for path in args._downloads.iterdir():
504
- if path.name not in keep:
505
- rm_path(path)
502
+ if args._downloads.is_dir():
503
+ for path in args._downloads.iterdir():
504
+ if path.name not in keep:
505
+ rm_path(path)
506
506
 
507
507
 
508
508
  def show_list(args: Namespace) -> None:
@@ -677,7 +677,7 @@ def main() -> str | None:
677
677
  opt = ArgumentParser(
678
678
  description=__doc__,
679
679
  epilog='Some commands offer aliases as shown in parentheses above. '
680
- 'Note you can set default starting global options in #FROM_FILE_PATH#',
680
+ 'Note you can set default starting global options in #FROM_FILE_PATH#.',
681
681
  )
682
682
 
683
683
  # Set up main/global arguments
@@ -685,7 +685,7 @@ def main() -> str | None:
685
685
  '-D',
686
686
  '--distribution',
687
687
  help=f'{REPO} distribution. Default is "{distro_help}" for this host. '
688
- f'Run "{PROG} show -a" to see all distributions.',
688
+ f'Run "{PROG} show -a" to see all distributions. See {DOC}',
689
689
  )
690
690
  opt.add_argument(
691
691
  '-P',
@@ -773,10 +773,7 @@ def main() -> str | None:
773
773
 
774
774
  distribution = args.distribution or distro_default
775
775
  if not distribution:
776
- sys.exit(
777
- 'Unknown system + machine distribution. Please specify '
778
- 'using -D/--distribution option.'
779
- )
776
+ return 'Unknown system + machine distribution. Please specify using -D/--distribution option.'
780
777
 
781
778
  # Keep some useful info in the namespace passed to the command
782
779
  prefix_dir = Path(args.prefix_dir).expanduser().resolve()
@@ -788,7 +785,6 @@ def main() -> str | None:
788
785
 
789
786
  args._versions = prefix_dir
790
787
  args._versions.mkdir(parents=True, exist_ok=True)
791
-
792
788
  args._downloads = cache_dir / 'downloads'
793
789
  args._downloads.mkdir(parents=True, exist_ok=True)
794
790
  args._releases = cache_dir / 'releases'
@@ -796,9 +792,18 @@ def main() -> str | None:
796
792
  args._latest_release = cache_dir / 'latest_release'
797
793
  args._cert = create_cert(args.cert)
798
794
 
799
- result = args.func(args)
800
- purge_unused_releases(args)
801
- update_version_symlinks(args)
795
+ # Only allow one instance of this program to run (to read/write prefix and cache dirs)
796
+ p_lock = filelock.FileLock(prefix_dir / '.lock')
797
+ c_lock = filelock.FileLock(cache_dir / '.lock')
798
+
799
+ try:
800
+ with p_lock.acquire(blocking=False), c_lock.acquire(blocking=False):
801
+ result = args.func(args)
802
+ purge_unused_releases(args)
803
+ update_version_symlinks(args)
804
+ except filelock.Timeout:
805
+ return f'ERROR: Another instance of {PROG} is already running.'
806
+
802
807
  return result
803
808
 
804
809
 
@@ -1176,7 +1181,7 @@ class path_:
1176
1181
  print(args._downloads.parent if args.cache_path else args._versions)
1177
1182
  else:
1178
1183
  path = args._versions / version
1179
- if not path.is_symlink() or not path.exists():
1184
+ if not path.exists():
1180
1185
  return f'Version "{version}" is not installed.'
1181
1186
 
1182
1187
  if args.resolve:
@@ -1195,7 +1200,7 @@ class path_:
1195
1200
 
1196
1201
  # COMMAND
1197
1202
  class cache_:
1198
- "Show release cache sizes."
1203
+ "Show size of release download caches."
1199
1204
 
1200
1205
  @staticmethod
1201
1206
  def init(parser: ArgumentParser) -> None:
@@ -1208,13 +1213,28 @@ class cache_:
1208
1213
  action='store_true',
1209
1214
  help='show sizes in bytes, not human readable format',
1210
1215
  )
1216
+ parser.add_argument(
1217
+ '-r',
1218
+ '--remove',
1219
+ action='store_true',
1220
+ help='remove download cache[s] instead of showing size',
1221
+ )
1211
1222
  parser.add_argument(
1212
1223
  'release', nargs='*', help='show cache size for given release[s] only'
1213
1224
  )
1214
1225
 
1215
1226
  @staticmethod
1216
1227
  def run(args: Namespace) -> str | None:
1217
- if args.release:
1228
+ if args.remove:
1229
+ if args.release:
1230
+ for release in args.release:
1231
+ rm_path(args._downloads / release)
1232
+ else:
1233
+ rm_path(args._downloads)
1234
+
1235
+ print('Cache removed.')
1236
+
1237
+ elif args.release:
1218
1238
  for release in args.release:
1219
1239
  show_cache_size(args._downloads / release, args)
1220
1240
  else:
File without changes
File without changes
File without changes