pystand 2.22__tar.gz → 2.24__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.22
3
+ Version: 2.24
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
@@ -27,14 +27,14 @@ installation, and update of pre-built Python versions from the
27
27
  [`python-build-standalone`][pbs] project. The following commands are
28
28
  provided:
29
29
 
30
- |Command |Description |
31
- |---------|--------------------------------------------------------------------|
32
- |`install`|Install one or more versions from a python-build-standalone release.|
33
- |`update` (or `upgrade`) |Update one, more, or all versions to another release.|
34
- |`remove` (or `uninstall`) |Remove/uninstall one, more, or all versions.|
35
- |`list` |List installed versions and show which have an update available.|
36
- |`show` |Show versions available from a release.|
37
- |`path` |Show path prefix to installed version base directory.|
30
+ |Command|Description|
31
+ |---------|--------------------------------------------------------------------|
32
+ |`install`|Install one, more, or all versions from a python-build-standalone release.|
33
+ |`update` (or `upgrade`)|Update one, more, or all versions to another release.|
34
+ |`remove` (or `uninstall`)|Remove/uninstall one, more, or all versions.|
35
+ |`list`|List installed versions and show which have an update available.|
36
+ |`show`|Show versions available from a release.|
37
+ |`path`|Show path prefix to installed version base directory.|
38
38
 
39
39
  By default, Python versions are sourced from the latest
40
40
  `python-build-standalone` [release][pbs-rel] available (e.g.
@@ -89,6 +89,14 @@ Version 3.10.14 @ 20240415 removed.
89
89
 
90
90
  $ pystand list
91
91
  3.12.3 @ 20240415 distribution="x86_64-unknown-linux-gnu-install_only_stripped"
92
+
93
+ # Install all available versions from latest release:
94
+ $ pystand install -a
95
+ Version 3.8.19 @ 20240415 installed.
96
+ Version 3.9.19 @ 20240415 installed.
97
+ Version 3.10.14 @ 20240415 installed.
98
+ Version 3.11.9 @ 20240415 installed.
99
+ Version 3.12.3 is already installed.
92
100
  ```
93
101
 
94
102
  Here are some examples showing how to use an installed version ..
@@ -175,8 +183,8 @@ options:
175
183
 
176
184
  Commands:
177
185
  {install,update,upgrade,remove,uninstall,list,show,path,cache}
178
- install Install one or more versions from a python-build-
179
- standalone release.
186
+ install Install one, more, or all versions from a python-
187
+ build-standalone release.
180
188
  update (upgrade) Update one, more, or all versions to another release.
181
189
  remove (uninstall) Remove/uninstall one, more, or all versions.
182
190
  list List installed versions and show which have an update
@@ -195,9 +203,10 @@ individual command:
195
203
  ### Command `install`
196
204
 
197
205
  ```
198
- usage: pystand install [-h] [-r RELEASE] [-f] [-s] version [version ...]
206
+ usage: pystand install [-h] [-r RELEASE] [-a] [-A] [--skip] [-f] [-s]
207
+ [version ...]
199
208
 
200
- Install one or more versions from a python-build-standalone release.
209
+ Install one, more, or all versions from a python-build-standalone release.
201
210
 
202
211
  positional arguments:
203
212
  version version to install. E.g. 3.12 or 3.12.3
@@ -208,6 +217,11 @@ options:
208
217
  install from specified python-build-standalone
209
218
  YYYYMMDD release (e.g. 20240415), default is latest
210
219
  release
220
+ -a, --all install ALL versions from release
221
+ -A, --all-prerelease install ALL versions from release, including pre-
222
+ releases
223
+ --skip skip the specified versions when installing all (only
224
+ can be specified with --all)
211
225
  -f, --force force install even if already installed
212
226
  -s, --include-source also install source files if available in distribution
213
227
  download
@@ -7,14 +7,14 @@ installation, and update of pre-built Python versions from the
7
7
  [`python-build-standalone`][pbs] project. The following commands are
8
8
  provided:
9
9
 
10
- |Command |Description |
11
- |---------|--------------------------------------------------------------------|
12
- |`install`|Install one or more versions from a python-build-standalone release.|
13
- |`update` (or `upgrade`) |Update one, more, or all versions to another release.|
14
- |`remove` (or `uninstall`) |Remove/uninstall one, more, or all versions.|
15
- |`list` |List installed versions and show which have an update available.|
16
- |`show` |Show versions available from a release.|
17
- |`path` |Show path prefix to installed version base directory.|
10
+ |Command|Description|
11
+ |---------|--------------------------------------------------------------------|
12
+ |`install`|Install one, more, or all versions from a python-build-standalone release.|
13
+ |`update` (or `upgrade`)|Update one, more, or all versions to another release.|
14
+ |`remove` (or `uninstall`)|Remove/uninstall one, more, or all versions.|
15
+ |`list`|List installed versions and show which have an update available.|
16
+ |`show`|Show versions available from a release.|
17
+ |`path`|Show path prefix to installed version base directory.|
18
18
 
19
19
  By default, Python versions are sourced from the latest
20
20
  `python-build-standalone` [release][pbs-rel] available (e.g.
@@ -69,6 +69,14 @@ Version 3.10.14 @ 20240415 removed.
69
69
 
70
70
  $ pystand list
71
71
  3.12.3 @ 20240415 distribution="x86_64-unknown-linux-gnu-install_only_stripped"
72
+
73
+ # Install all available versions from latest release:
74
+ $ pystand install -a
75
+ Version 3.8.19 @ 20240415 installed.
76
+ Version 3.9.19 @ 20240415 installed.
77
+ Version 3.10.14 @ 20240415 installed.
78
+ Version 3.11.9 @ 20240415 installed.
79
+ Version 3.12.3 is already installed.
72
80
  ```
73
81
 
74
82
  Here are some examples showing how to use an installed version ..
@@ -155,8 +163,8 @@ options:
155
163
 
156
164
  Commands:
157
165
  {install,update,upgrade,remove,uninstall,list,show,path,cache}
158
- install Install one or more versions from a python-build-
159
- standalone release.
166
+ install Install one, more, or all versions from a python-
167
+ build-standalone release.
160
168
  update (upgrade) Update one, more, or all versions to another release.
161
169
  remove (uninstall) Remove/uninstall one, more, or all versions.
162
170
  list List installed versions and show which have an update
@@ -175,9 +183,10 @@ individual command:
175
183
  ### Command `install`
176
184
 
177
185
  ```
178
- usage: pystand install [-h] [-r RELEASE] [-f] [-s] version [version ...]
186
+ usage: pystand install [-h] [-r RELEASE] [-a] [-A] [--skip] [-f] [-s]
187
+ [version ...]
179
188
 
180
- Install one or more versions from a python-build-standalone release.
189
+ Install one, more, or all versions from a python-build-standalone release.
181
190
 
182
191
  positional arguments:
183
192
  version version to install. E.g. 3.12 or 3.12.3
@@ -188,6 +197,11 @@ options:
188
197
  install from specified python-build-standalone
189
198
  YYYYMMDD release (e.g. 20240415), default is latest
190
199
  release
200
+ -a, --all install ALL versions from release
201
+ -A, --all-prerelease install ALL versions from release, including pre-
202
+ releases
203
+ --skip skip the specified versions when installing all (only
204
+ can be specified with --all)
191
205
  -f, --force force install even if already installed
192
206
  -s, --include-source also install source files if available in distribution
193
207
  download
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pystand
3
- Version: 2.22
3
+ Version: 2.24
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
@@ -27,14 +27,14 @@ installation, and update of pre-built Python versions from the
27
27
  [`python-build-standalone`][pbs] project. The following commands are
28
28
  provided:
29
29
 
30
- |Command |Description |
31
- |---------|--------------------------------------------------------------------|
32
- |`install`|Install one or more versions from a python-build-standalone release.|
33
- |`update` (or `upgrade`) |Update one, more, or all versions to another release.|
34
- |`remove` (or `uninstall`) |Remove/uninstall one, more, or all versions.|
35
- |`list` |List installed versions and show which have an update available.|
36
- |`show` |Show versions available from a release.|
37
- |`path` |Show path prefix to installed version base directory.|
30
+ |Command|Description|
31
+ |---------|--------------------------------------------------------------------|
32
+ |`install`|Install one, more, or all versions from a python-build-standalone release.|
33
+ |`update` (or `upgrade`)|Update one, more, or all versions to another release.|
34
+ |`remove` (or `uninstall`)|Remove/uninstall one, more, or all versions.|
35
+ |`list`|List installed versions and show which have an update available.|
36
+ |`show`|Show versions available from a release.|
37
+ |`path`|Show path prefix to installed version base directory.|
38
38
 
39
39
  By default, Python versions are sourced from the latest
40
40
  `python-build-standalone` [release][pbs-rel] available (e.g.
@@ -89,6 +89,14 @@ Version 3.10.14 @ 20240415 removed.
89
89
 
90
90
  $ pystand list
91
91
  3.12.3 @ 20240415 distribution="x86_64-unknown-linux-gnu-install_only_stripped"
92
+
93
+ # Install all available versions from latest release:
94
+ $ pystand install -a
95
+ Version 3.8.19 @ 20240415 installed.
96
+ Version 3.9.19 @ 20240415 installed.
97
+ Version 3.10.14 @ 20240415 installed.
98
+ Version 3.11.9 @ 20240415 installed.
99
+ Version 3.12.3 is already installed.
92
100
  ```
93
101
 
94
102
  Here are some examples showing how to use an installed version ..
@@ -175,8 +183,8 @@ options:
175
183
 
176
184
  Commands:
177
185
  {install,update,upgrade,remove,uninstall,list,show,path,cache}
178
- install Install one or more versions from a python-build-
179
- standalone release.
186
+ install Install one, more, or all versions from a python-
187
+ build-standalone release.
180
188
  update (upgrade) Update one, more, or all versions to another release.
181
189
  remove (uninstall) Remove/uninstall one, more, or all versions.
182
190
  list List installed versions and show which have an update
@@ -195,9 +203,10 @@ individual command:
195
203
  ### Command `install`
196
204
 
197
205
  ```
198
- usage: pystand install [-h] [-r RELEASE] [-f] [-s] version [version ...]
206
+ usage: pystand install [-h] [-r RELEASE] [-a] [-A] [--skip] [-f] [-s]
207
+ [version ...]
199
208
 
200
- Install one or more versions from a python-build-standalone release.
209
+ Install one, more, or all versions from a python-build-standalone release.
201
210
 
202
211
  positional arguments:
203
212
  version version to install. E.g. 3.12 or 3.12.3
@@ -208,6 +217,11 @@ options:
208
217
  install from specified python-build-standalone
209
218
  YYYYMMDD release (e.g. 20240415), default is latest
210
219
  release
220
+ -a, --all install ALL versions from release
221
+ -A, --all-prerelease install ALL versions from release, including pre-
222
+ releases
223
+ --skip skip the specified versions when installing all (only
224
+ can be specified with --all)
211
225
  -f, --force force install even if already installed
212
226
  -s, --include-source also install source files if available in distribution
213
227
  download
@@ -18,7 +18,6 @@ import sys
18
18
  import time
19
19
  from collections import defaultdict
20
20
  from collections.abc import Iterable, Iterator
21
- from dataclasses import dataclass, field
22
21
  from datetime import date, datetime
23
22
  from pathlib import Path
24
23
  from typing import Any
@@ -71,14 +70,45 @@ COLORS = [
71
70
  ]
72
71
 
73
72
 
74
- @dataclass
75
73
  class ColorTable:
76
- next: Iterator[str] = field(default_factory=lambda: itertools.cycle(COLORS[1:-1]))
77
- table: dict[str, str] = field(default_factory=dict)
74
+ "Base class for color tables"
78
75
 
76
+ def __init__(self, initval: str, no_color: bool) -> None:
77
+ self.table = None if no_color else {self.parse_key(initval): COLORS[0]}
78
+ self.next = itertools.cycle(COLORS[1:-1])
79
79
 
80
- colors_rel = ColorTable()
81
- colors_dist = ColorTable()
80
+ def get_color(self, text: str) -> str:
81
+ "Assign a new color by cycling through the available colors"
82
+ if not self.table:
83
+ return text
84
+
85
+ if not (color := self.table.get(key := self.parse_key(text))):
86
+ self.table[key] = color = next(self.next)
87
+
88
+ return f'{color}{text}{COLORS[-1]}'
89
+
90
+ def parse_key(self, text: str) -> str:
91
+ "Parse text for color table key, default is full text"
92
+ return text
93
+
94
+
95
+ class ColorRel(ColorTable):
96
+ "Return colored release version string"
97
+
98
+ def format(self, version: str, release: str) -> str:
99
+ "Return a formatted release version string"
100
+ return f'{version} @ {self.get_color(release)}'
101
+
102
+
103
+ class ColorDist(ColorTable):
104
+ "Return colored distribution string"
105
+
106
+ def format(self, dist: str) -> str:
107
+ return f'distribution="{self.get_color(dist)}"'
108
+
109
+ def parse_key(self, text: str) -> str:
110
+ "Extract key for distribution color"
111
+ return text.split('-', 1)[0]
82
112
 
83
113
 
84
114
  def is_admin() -> bool:
@@ -103,36 +133,6 @@ def get_version() -> str:
103
133
  return ver
104
134
 
105
135
 
106
- def fmtrel(version: str, release: str, args: Namespace) -> str:
107
- "Return a formatted release version string"
108
- if not args.no_color:
109
- if release == args._release:
110
- color = COLORS[0]
111
- elif not (color := colors_rel.table.get(release)):
112
- # Assign a new color for this release by cycling through the
113
- # available colors.
114
- colors_rel.table[release] = color = next(colors_rel.next)
115
-
116
- release = f'{color}{release}{COLORS[-1]}'
117
-
118
- return f'{version} @ {release}'
119
-
120
-
121
- def fmtdist(dist: str, args: Namespace) -> str:
122
- "Return a formatted distribution string"
123
- if not args.no_color:
124
- if dist == args._distribution:
125
- color = COLORS[0]
126
- elif not (color := colors_dist.table.get(dist)):
127
- # Assign a new color for this distribution by cycling through the
128
- # available colors.
129
- colors_dist.table[dist] = color = next(colors_dist.next)
130
-
131
- dist = f'{color}{dist}{COLORS[-1]}'
132
-
133
- return f'distribution="{dist}"'
134
-
135
-
136
136
  def get_json(file: Path) -> dict[str, Any]:
137
137
  from json import load
138
138
 
@@ -838,6 +838,7 @@ def main() -> str | None:
838
838
 
839
839
  args._implementation = 'cpython' # at the moment, only support CPython
840
840
  args._distribution = distribution
841
+ args._fmtdist = ColorDist(distribution, args.no_color).format
841
842
  args._data = f'{PROG}.json'
842
843
 
843
844
  args._versions = prefix_dir
@@ -850,12 +851,12 @@ def main() -> str | None:
850
851
  args._cert = create_cert(args.cert)
851
852
 
852
853
  # Only allow one instance of this program to run (to read/write prefix and cache dirs)
853
- p_lock = filelock.FileLock(prefix_dir / '.lock')
854
- c_lock = filelock.FileLock(cache_dir / '.lock')
854
+ locks = [filelock.FileLock(d / '.lock') for d in (prefix_dir, cache_dir)]
855
855
 
856
856
  try:
857
- with p_lock.acquire(blocking=False), c_lock.acquire(blocking=False):
857
+ with locks[0].acquire(blocking=False), locks[1].acquire(blocking=False):
858
858
  args._release = get_release_tag(args)
859
+ args._fmtrel = ColorRel(args._release, args.no_color).format
859
860
  result = args.func(args)
860
861
  purge_unused_releases(args)
861
862
  update_version_symlinks(args)
@@ -867,7 +868,7 @@ def main() -> str | None:
867
868
 
868
869
  # COMMAND
869
870
  class install_:
870
- doc = f'Install one or more versions from a {REPO} release.'
871
+ doc = f'Install one, more, or all versions from a {REPO} release.'
871
872
 
872
873
  @staticmethod
873
874
  def init(parser: ArgumentParser) -> None:
@@ -878,6 +879,21 @@ class install_:
878
879
  f'YYYYMMDD release (e.g. {SAMPL_RELEASE}), '
879
880
  'default is latest release',
880
881
  )
882
+ parser.add_argument(
883
+ '-a', '--all', action='store_true', help='install ALL versions from release'
884
+ )
885
+ parser.add_argument(
886
+ '-A',
887
+ '--all-prerelease',
888
+ action='store_true',
889
+ help='install ALL versions from release, including pre-releases',
890
+ )
891
+ parser.add_argument(
892
+ '--skip',
893
+ action='store_true',
894
+ help='skip the specified versions when '
895
+ 'installing all (only can be specified with --all)',
896
+ )
881
897
  parser.add_argument(
882
898
  '-f',
883
899
  '--force',
@@ -891,7 +907,7 @@ class install_:
891
907
  help='also install source files if available in distribution download',
892
908
  )
893
909
  parser.add_argument(
894
- 'version', nargs='+', help='version to install. E.g. 3.12 or 3.12.3'
910
+ 'version', nargs='*', help='version to install. E.g. 3.12 or 3.12.3'
895
911
  )
896
912
 
897
913
  @staticmethod
@@ -902,21 +918,47 @@ class install_:
902
918
  return f'Release "{release}" not found, or has no compatible files.'
903
919
 
904
920
  matcher = VersionMatcher(files)
921
+
922
+ if args.all_prerelease:
923
+ args.all = True
924
+
925
+ if args.all:
926
+ if not args.skip and args.version:
927
+ args.parser.error(
928
+ 'Can not specify versions with --all unless also specifying --skip.'
929
+ )
930
+
931
+ skips = set(v for ver in args.version if (v := matcher.match(ver)))
932
+
933
+ args.version = [
934
+ v
935
+ for v in files
936
+ if (args.all_prerelease or is_release_version(v)) and v not in skips
937
+ ]
938
+
939
+ else:
940
+ if args.skip:
941
+ args.parser.error('--skip can only be specified with --all.')
942
+
943
+ if not args.version:
944
+ args.parser.error('Must specify at least one version, or --all.')
945
+
905
946
  for version in args.version:
906
947
  full_version = matcher.match(version)
907
948
  if not full_version:
908
- return f'Version {fmtrel(version, release, args)} not found.'
949
+ return f'Version {args._fmtrel(version, release)} not found.'
909
950
 
910
951
  version = full_version
911
952
  vdir = args._versions / version
912
953
 
913
954
  if vdir.exists() and not args.force:
914
- return f'Version "{version}" is already installed.'
955
+ print(f'Version {version} is already installed.', file=sys.stderr)
956
+ continue
915
957
 
916
958
  if error := install(args, vdir, release, args._distribution, files):
917
959
  return error
918
960
 
919
- print(f'Version {fmtrel(version, release, args)} installed.')
961
+ print(f'Version {args._fmtrel(version, release)} installed.')
920
962
 
921
963
 
922
964
  # COMMAND
@@ -978,9 +1020,9 @@ class update_:
978
1020
 
979
1021
  if nextver == version and args.keep and release:
980
1022
  print(
981
- f'Error: {fmtrel(version, release, args)} would not be kept '
982
- f'if update to {fmtrel(nextver, release_target, args)} '
983
- f'{fmtdist(distribution, args)}',
1023
+ f'Error: {args._fmtrel(version, release)} would not be kept '
1024
+ f'if update to {args._fmtrel(nextver, release_target)} '
1025
+ f'{args._fmtdist(distribution)}',
984
1026
  file=sys.stderr,
985
1027
  )
986
1028
  continue
@@ -990,9 +1032,9 @@ class update_:
990
1032
  continue
991
1033
 
992
1034
  print(
993
- f'{fmtrel(version, release, args)} updating to '
994
- f'{fmtrel(nextver, release_target, args)} '
995
- f'{fmtdist(distribution, args)} ..'
1035
+ f'{args._fmtrel(version, release)} updating to '
1036
+ f'{args._fmtrel(nextver, release_target)} '
1037
+ f'{args._fmtdist(distribution)} ..'
996
1038
  )
997
1039
 
998
1040
  # If the source was originally included, then include it in
@@ -1044,7 +1086,7 @@ class remove_:
1044
1086
  release = get_json(dfile).get('release') or '?'
1045
1087
  if not release_del or release == release_del:
1046
1088
  remove(args, version)
1047
- print(f'Version {fmtrel(version, release, args)} removed.')
1089
+ print(f'Version {args._fmtrel(version, release)} removed.')
1048
1090
 
1049
1091
 
1050
1092
  # COMMAND
@@ -1107,7 +1149,7 @@ class list_:
1107
1149
  )
1108
1150
  app = (
1109
1151
  f' not eligible for '
1110
- f'update because {fmtrel(nextver, nrelease, args)} '
1152
+ f'update because {args._fmtrel(nextver, nrelease)} '
1111
1153
  'is already installed.'
1112
1154
  )
1113
1155
  else:
@@ -1115,19 +1157,19 @@ class list_:
1115
1157
  # this same distribution anymore
1116
1158
  if nextver and distribution in files.get(nextver, {}):
1117
1159
  upd = (
1118
- f' updatable to {fmtrel(nextver, release_target, args)}'
1160
+ f' updatable to {args._fmtrel(nextver, release_target)}'
1119
1161
  )
1120
1162
  elif args.verbose:
1121
1163
  app = (
1122
1164
  ' not eligible for update because '
1123
- f'{fmtrel(nextver, release_target, args)} does '
1124
- f'not provide {fmtdist(distribution, args)}.'
1165
+ f'{args._fmtrel(nextver, release_target)} does '
1166
+ f'not provide {args._fmtdist(distribution)}.'
1125
1167
  )
1126
1168
 
1127
1169
  if release:
1128
1170
  print(
1129
- f'{fmtrel(version, release, args)}{upd} '
1130
- f'{fmtdist(distribution, args)}{app}'
1171
+ f'{args._fmtrel(version, release)}{upd} '
1172
+ f'{args._fmtdist(distribution)}{app}'
1131
1173
  )
1132
1174
 
1133
1175
 
@@ -1199,12 +1241,12 @@ class show_:
1199
1241
  args.re_match, f'{version}+{distribution}'
1200
1242
  ):
1201
1243
  print(
1202
- f'{fmtrel(version, release, args)} '
1203
- f'{fmtdist(distribution, args)}{app}'
1244
+ f'{args._fmtrel(version, release)} '
1245
+ f'{args._fmtdist(distribution)}{app}'
1204
1246
  )
1205
1247
  if not installable:
1206
1248
  print(
1207
- f'Warning: no {fmtdist(args._distribution, args)} '
1249
+ f'Warning: no {args._fmtdist(args._distribution)} '
1208
1250
  f'versions found in release "{release}".'
1209
1251
  )
1210
1252
 
@@ -1244,7 +1286,7 @@ class path_:
1244
1286
  else:
1245
1287
  path = args._versions / version
1246
1288
  if not path.exists():
1247
- return f'Version "{version}" is not installed.'
1289
+ return f'Version {version} is not installed.'
1248
1290
 
1249
1291
  if args.resolve:
1250
1292
  path = path.resolve()
File without changes
File without changes
File without changes
File without changes