pystand 1.8__tar.gz → 1.9__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.1
2
2
  Name: pystand
3
- Version: 1.8
3
+ Version: 1.9
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
@@ -255,18 +255,18 @@ options:
255
255
  ### Command `show`
256
256
 
257
257
  ```
258
- usage: pystand show [-h] [-d] [release]
258
+ usage: pystand show [-h] [-D] [release]
259
259
 
260
260
  Show versions available from a release.
261
261
 
262
262
  positional arguments:
263
- release python-build-standalone YYYYMMDD release to show (e.g.
264
- 20240415), default is latest release
263
+ release python-build-standalone YYYYMMDD release to show (e.g.
264
+ 20240415), default is latest release
265
265
 
266
266
  options:
267
- -h, --help show this help message and exit
268
- -d, --distributions also show all available distributions for each version
269
- from the release
267
+ -h, --help show this help message and exit
268
+ -D, --distribution also show all available distributions for each version
269
+ from the release
270
270
  ```
271
271
 
272
272
  ### Command `path`
@@ -291,7 +291,7 @@ from the AUR](https://aur.archlinux.org/packages/pystand) and skip this
291
291
  section.
292
292
 
293
293
  The easiest way to install [`pystand`][pystand] is to use [`pipx`][pipx]
294
- (or [`pipxu`][pipxu]).
294
+ (or [`pipxu`][pipxu], or [`uv tool`][uvtool]).
295
295
 
296
296
  ```sh
297
297
  $ pipx install pystand
@@ -375,6 +375,7 @@ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
375
375
  [pbs-rel]: https://github.com/indygreg/python-build-standalone/releases
376
376
  [pipx]: https://github.com/pypa/pipx
377
377
  [pipxu]: https://github.com/bulletmark/pipxu
378
+ [uvtool]: https://docs.astral.sh/uv/guides/tools/#installing-tools
378
379
  [pyenv]: https://github.com/pyenv/pyenv
379
380
  [pdm]: https://pdm-project.org/
380
381
  [pdmpy]: https://pdm-project.org/en/latest/usage/project/#install-python-interpreters-with-pdm
@@ -239,18 +239,18 @@ options:
239
239
  ### Command `show`
240
240
 
241
241
  ```
242
- usage: pystand show [-h] [-d] [release]
242
+ usage: pystand show [-h] [-D] [release]
243
243
 
244
244
  Show versions available from a release.
245
245
 
246
246
  positional arguments:
247
- release python-build-standalone YYYYMMDD release to show (e.g.
248
- 20240415), default is latest release
247
+ release python-build-standalone YYYYMMDD release to show (e.g.
248
+ 20240415), default is latest release
249
249
 
250
250
  options:
251
- -h, --help show this help message and exit
252
- -d, --distributions also show all available distributions for each version
253
- from the release
251
+ -h, --help show this help message and exit
252
+ -D, --distribution also show all available distributions for each version
253
+ from the release
254
254
  ```
255
255
 
256
256
  ### Command `path`
@@ -275,7 +275,7 @@ from the AUR](https://aur.archlinux.org/packages/pystand) and skip this
275
275
  section.
276
276
 
277
277
  The easiest way to install [`pystand`][pystand] is to use [`pipx`][pipx]
278
- (or [`pipxu`][pipxu]).
278
+ (or [`pipxu`][pipxu], or [`uv tool`][uvtool]).
279
279
 
280
280
  ```sh
281
281
  $ pipx install pystand
@@ -359,6 +359,7 @@ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
359
359
  [pbs-rel]: https://github.com/indygreg/python-build-standalone/releases
360
360
  [pipx]: https://github.com/pypa/pipx
361
361
  [pipxu]: https://github.com/bulletmark/pipxu
362
+ [uvtool]: https://docs.astral.sh/uv/guides/tools/#installing-tools
362
363
  [pyenv]: https://github.com/pyenv/pyenv
363
364
  [pdm]: https://pdm-project.org/
364
365
  [pdmpy]: https://pdm-project.org/en/latest/usage/project/#install-python-interpreters-with-pdm
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pystand
3
- Version: 1.8
3
+ Version: 1.9
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
@@ -255,18 +255,18 @@ options:
255
255
  ### Command `show`
256
256
 
257
257
  ```
258
- usage: pystand show [-h] [-d] [release]
258
+ usage: pystand show [-h] [-D] [release]
259
259
 
260
260
  Show versions available from a release.
261
261
 
262
262
  positional arguments:
263
- release python-build-standalone YYYYMMDD release to show (e.g.
264
- 20240415), default is latest release
263
+ release python-build-standalone YYYYMMDD release to show (e.g.
264
+ 20240415), default is latest release
265
265
 
266
266
  options:
267
- -h, --help show this help message and exit
268
- -d, --distributions also show all available distributions for each version
269
- from the release
267
+ -h, --help show this help message and exit
268
+ -D, --distribution also show all available distributions for each version
269
+ from the release
270
270
  ```
271
271
 
272
272
  ### Command `path`
@@ -291,7 +291,7 @@ from the AUR](https://aur.archlinux.org/packages/pystand) and skip this
291
291
  section.
292
292
 
293
293
  The easiest way to install [`pystand`][pystand] is to use [`pipx`][pipx]
294
- (or [`pipxu`][pipxu]).
294
+ (or [`pipxu`][pipxu], or [`uv tool`][uvtool]).
295
295
 
296
296
  ```sh
297
297
  $ pipx install pystand
@@ -375,6 +375,7 @@ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at
375
375
  [pbs-rel]: https://github.com/indygreg/python-build-standalone/releases
376
376
  [pipx]: https://github.com/pypa/pipx
377
377
  [pipxu]: https://github.com/bulletmark/pipxu
378
+ [uvtool]: https://docs.astral.sh/uv/guides/tools/#installing-tools
378
379
  [pyenv]: https://github.com/pyenv/pyenv
379
380
  [pdm]: https://pdm-project.org/
380
381
  [pdmpy]: https://pdm-project.org/en/latest/usage/project/#install-python-interpreters-with-pdm
@@ -21,7 +21,7 @@ from argparse import ArgumentParser, Namespace
21
21
  from collections import defaultdict
22
22
  from datetime import date
23
23
  from pathlib import Path
24
- from typing import Any, Iterable, Iterator, Optional
24
+ from typing import Any, Iterable, Iterator
25
25
 
26
26
  import argcomplete
27
27
  import platformdirs
@@ -92,7 +92,7 @@ def get_json(file: Path) -> dict:
92
92
 
93
93
  return {}
94
94
 
95
- def set_json(file: Path, data: dict) -> Optional[str]:
95
+ def set_json(file: Path, data: dict) -> str | None:
96
96
  'Set JSON data to given file'
97
97
  try:
98
98
  with file.open('w') as fp:
@@ -132,26 +132,38 @@ def rm_path(path: Path) -> None:
132
132
  elif path.exists():
133
133
  path.unlink()
134
134
 
135
+ def is_release_version(version: str) -> bool:
136
+ 'Check if a string is a formal Python release tag'
137
+ return version.replace('.', '').isdigit()
138
+
135
139
  class VersionMatcher:
136
140
  'Match a version string to a list of versions'
137
141
  def __init__(self, seq: Iterable[str]) -> None:
138
142
  self.seq = sorted(seq, key=parse_version, reverse=True)
139
143
 
140
- def match(self, version: str, *,
141
- upconvert_minor: bool = False) -> Optional[str]:
144
+ def match(self, version: str, *, upgrade: bool = False) -> str | None:
142
145
  'Return full version string given a [possibly] part version prefix'
143
146
  if version in self.seq:
144
147
  return version
145
148
 
146
- if upconvert_minor:
149
+ is_release = is_release_version(version)
150
+ major_only = '.' not in version
151
+
152
+ if major_only:
153
+ upgrade = True
154
+ elif upgrade:
147
155
  version = version.rsplit('.', 1)[0]
148
156
 
149
157
  if not version.endswith('.'):
150
158
  version += '.'
151
159
 
160
+ # Only allow upgrade of formal release to another formal
161
+ # release, or alpha/beta release to another alpha/beta release.
152
162
  for full_version in self.seq:
153
163
  if full_version.startswith(version):
154
- return full_version
164
+ if not upgrade \
165
+ or is_release_version(full_version) == is_release:
166
+ return full_version
155
167
 
156
168
  return None
157
169
 
@@ -191,7 +203,7 @@ def get_version_names(args: Namespace) -> list[str]:
191
203
  if args.all else versions
192
204
 
193
205
  def check_release_tag(release: str, *,
194
- check_first: bool = True) -> Optional[str]:
206
+ check_first: bool = True) -> str | None:
195
207
  'Check the specified release tag is valid'
196
208
  if not release.isdigit() or len(release) != len(FIRST_RELEASE):
197
209
  return 'Release must be a YYYYMMDD string.'
@@ -253,7 +265,7 @@ def merge(files: dict, name: str, url: str, stripped: bool) -> None:
253
265
  else:
254
266
  files[impl][ver][distrib] = ('', url) if stripped else url
255
267
 
256
- def get_release_files(args, tag, implementation: Optional[str] = None) -> dict:
268
+ def get_release_files(args, tag, implementation: str | None = None) -> dict:
257
269
  'Return the release files for the given tag'
258
270
  # Look for tag data in our release cache
259
271
  jfile = args._releases / tag
@@ -295,7 +307,7 @@ def update_version_symlinks(args: Namespace) -> None:
295
307
  if not base.exists():
296
308
  return
297
309
 
298
- # Record of all the existing symlinks and version dirs
310
+ # Record all the existing symlinks and version dirs
299
311
  oldlinks = {}
300
312
  vers = []
301
313
  for path in base.iterdir():
@@ -303,19 +315,30 @@ def update_version_symlinks(args: Namespace) -> None:
303
315
  if path.is_symlink():
304
316
  oldlinks[path.name] = os.readlink(str(path))
305
317
  else:
306
- vers.append(path)
318
+ vers.append(path.name)
307
319
 
308
320
  # Create a map of all the new major version links
309
- newlinks_all = defaultdict(list)
310
- for path in vers:
311
- namevers = path.name
321
+ newlinks_all = defaultdict(set)
322
+ pre_releases = set(v for v in vers if not is_release_version(v))
323
+ for namevers in vers:
312
324
  while '.' in namevers[:-1]:
313
325
  namevers_major = namevers.rsplit('.', maxsplit=1)[0]
314
- newlinks_all[namevers_major].append(namevers)
326
+ newlinks_all[namevers_major].add(namevers)
327
+
328
+ if namevers in pre_releases:
329
+ pre_releases.add(namevers_major)
330
+
315
331
  namevers = namevers_major
316
332
 
317
- newlinks = {k: sorted(v, key=parse_version)[-1] for k, v in
318
- newlinks_all.items()}
333
+ # Find the latest version for each major version, but ensure we
334
+ # don't link a major release to pre-released version, if it also can
335
+ # point to released versions.
336
+ newlinks = {}
337
+ for ver, cands in newlinks_all.items():
338
+ if ver in pre_releases and (rels := (cands - pre_releases)):
339
+ cands = rels
340
+
341
+ newlinks[ver] = sorted(cands, key=parse_version)[-1]
319
342
 
320
343
  # Remove all old or invalid existing links
321
344
  for name, tgt in oldlinks.items():
@@ -403,7 +426,7 @@ def strip_binaries(vdir: Path, distribution: str) -> bool:
403
426
  return was_stripped
404
427
 
405
428
  def install(args: Namespace, vdir: Path, release: str, distribution: str,
406
- files: dict) -> Optional[str]:
429
+ files: dict) -> str | None:
407
430
  'Install a version'
408
431
  version = vdir.name
409
432
 
@@ -460,7 +483,7 @@ def install(args: Namespace, vdir: Path, release: str, distribution: str,
460
483
  shutil.rmtree(tmpdir)
461
484
  return error
462
485
 
463
- def main() -> Optional[str]:
486
+ def main() -> str | None:
464
487
  'Main code'
465
488
  distro_default = DISTRIBUTIONS.get((platform.system(), platform.machine()))
466
489
  distro_help = distro_default or '?unknown?'
@@ -583,7 +606,7 @@ class _install(COMMAND):
583
606
  help='version to install. E.g. 3.12 or 3.12.3')
584
607
 
585
608
  @staticmethod
586
- def run(args: Namespace) -> Optional[str]:
609
+ def run(args: Namespace) -> str | None:
587
610
  release = get_release_tag(args)
588
611
  files = get_release_files(args, release, 'cpython')
589
612
  if not files:
@@ -627,7 +650,7 @@ class _update(COMMAND):
627
650
  '--all --skip)')
628
651
 
629
652
  @staticmethod
630
- def run(args: Namespace) -> Optional[str]:
653
+ def run(args: Namespace) -> str | None:
631
654
  release_target = get_release_tag(args)
632
655
  files = get_release_files(args, release_target, 'cpython')
633
656
  if not files:
@@ -642,7 +665,7 @@ class _update(COMMAND):
642
665
  if release == release_target:
643
666
  continue
644
667
 
645
- nextver = matcher.match(version, upconvert_minor=True)
668
+ nextver = matcher.match(version, upgrade=True)
646
669
 
647
670
  distribution = data.get('distribution')
648
671
  if not distribution or distribution not in files.get(nextver, {}):
@@ -687,7 +710,7 @@ class _remove(COMMAND):
687
710
  '--all --skip)')
688
711
 
689
712
  @staticmethod
690
- def run(args: Namespace) -> Optional[str]:
713
+ def run(args: Namespace) -> str | None:
691
714
  release_del = args.release
692
715
  if release_del and \
693
716
  (err := check_release_tag(release_del, check_first=False)):
@@ -716,7 +739,7 @@ class _list(COMMAND):
716
739
  help='only list specified version, else all')
717
740
 
718
741
  @staticmethod
719
- def run(args: Namespace) -> Optional[str]:
742
+ def run(args: Namespace) -> str | None:
720
743
  release_target = get_release_tag(args)
721
744
  files = get_release_files(args, release_target, 'cpython')
722
745
  if not files:
@@ -735,7 +758,7 @@ class _list(COMMAND):
735
758
  upd = ''
736
759
  app = ''
737
760
  if release_target and release != release_target:
738
- nextver = matcher.match(version, upconvert_minor=True)
761
+ nextver = matcher.match(version, upgrade=True)
739
762
  new_vdir = args._versions / nextver
740
763
  if nextver != version and new_vdir.exists():
741
764
  if args.verbose:
@@ -762,7 +785,7 @@ class _show(COMMAND):
762
785
  'Show versions available from a release.'
763
786
  @staticmethod
764
787
  def init(parser: ArgumentParser) -> None:
765
- parser.add_argument('-d', '--distributions', action='store_true',
788
+ parser.add_argument('-D', '--distribution', action='store_true',
766
789
  help='also show all available distributions for '
767
790
  'each version from the release')
768
791
  parser.add_argument('release', nargs='?',
@@ -789,7 +812,7 @@ class _show(COMMAND):
789
812
  for distribution in files[version]:
790
813
  app = ' (installed)' \
791
814
  if distribution == installed_distribution else ''
792
- if args.distributions or app \
815
+ if args.distribution or app \
793
816
  or distribution == args._distribution:
794
817
  if distribution == args._distribution:
795
818
  installable = True
@@ -810,7 +833,7 @@ class _path(COMMAND):
810
833
  parser.add_argument('version', help='version to return path for')
811
834
 
812
835
  @staticmethod
813
- def run(args: Namespace) -> Optional[str]:
836
+ def run(args: Namespace) -> str | None:
814
837
  matcher = VersionMatcher([f.name for f in iter_versions(args)])
815
838
  version = matcher.match(args.version) or args.version
816
839
  path = args._versions / version
File without changes
File without changes
File without changes
File without changes
File without changes