pystand 1.0__tar.gz → 1.1__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.
@@ -5,6 +5,7 @@ check:
5
5
  ruff check *.py
6
6
  flake8 *.py
7
7
  mypy *.py
8
+ pyright *.py
8
9
  vermin -vv --no-tips -i *.py
9
10
 
10
11
  build:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pystand
3
- Version: 1.0
3
+ Version: 1.1
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
@@ -313,6 +313,13 @@ Note, consistent with this, you actually don't need to specify a
313
313
  minor version, e.g. `pystand install 3` would also install `3.12.3`
314
314
  (assuming `3.12.3` is the latest available version for Python 3).
315
315
 
316
+ After installs or updates or removals,`pystand` also maintains symbolic
317
+ links to each latest installed version in it's version directory, e.g. a
318
+ symlink `~/.local/share/pystand/versions/3.12` will be created pointing
319
+ to `~/.local/share/pystand/versions/3.12.3` so that you can optionally
320
+ hard code the symlink directory in places where it can not be set
321
+ dynamically (i.e. where using `pystand path` is not an option).
322
+
316
323
  ## Command Default Options
317
324
 
318
325
  You can add default global options to a personal configuration file
@@ -297,6 +297,13 @@ Note, consistent with this, you actually don't need to specify a
297
297
  minor version, e.g. `pystand install 3` would also install `3.12.3`
298
298
  (assuming `3.12.3` is the latest available version for Python 3).
299
299
 
300
+ After installs or updates or removals,`pystand` also maintains symbolic
301
+ links to each latest installed version in it's version directory, e.g. a
302
+ symlink `~/.local/share/pystand/versions/3.12` will be created pointing
303
+ to `~/.local/share/pystand/versions/3.12.3` so that you can optionally
304
+ hard code the symlink directory in places where it can not be set
305
+ dynamically (i.e. where using `pystand path` is not an option).
306
+
300
307
  ## Command Default Options
301
308
 
302
309
  You can add default global options to a personal configuration file
@@ -47,6 +47,7 @@ linters = [
47
47
  "ruff check",
48
48
  "flake8",
49
49
  "mypy",
50
+ "pyright",
50
51
  ]
51
52
 
52
53
  # vim:se sw=2:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pystand
3
- Version: 1.0
3
+ Version: 1.1
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
@@ -313,6 +313,13 @@ Note, consistent with this, you actually don't need to specify a
313
313
  minor version, e.g. `pystand install 3` would also install `3.12.3`
314
314
  (assuming `3.12.3` is the latest available version for Python 3).
315
315
 
316
+ After installs or updates or removals,`pystand` also maintains symbolic
317
+ links to each latest installed version in it's version directory, e.g. a
318
+ symlink `~/.local/share/pystand/versions/3.12` will be created pointing
319
+ to `~/.local/share/pystand/versions/3.12.3` so that you can optionally
320
+ hard code the symlink directory in places where it can not be set
321
+ dynamically (i.e. where using `pystand path` is not an option).
322
+
316
323
  ## Command Default Options
317
324
 
318
325
  You can add default global options to a personal configuration file
@@ -18,11 +18,11 @@ import urllib.request
18
18
  from argparse import ArgumentParser, Namespace
19
19
  from collections import defaultdict
20
20
  from pathlib import Path
21
- from typing import Iterable, Iterator, Optional
21
+ from typing import Any, Iterable, Iterator, Optional
22
22
 
23
23
  import argcomplete
24
24
  import platformdirs
25
- from packaging.version import Version
25
+ from packaging.version import parse as parse_version
26
26
 
27
27
  REPO_OWNER = 'indygreg'
28
28
  REPO = 'python-build-standalone'
@@ -90,7 +90,7 @@ def set_json(file: Path, data: dict) -> Optional[str]:
90
90
  # The gh handle is an opaque github instance handle
91
91
  get_gh_handle = None
92
92
 
93
- def get_gh(args: Namespace):
93
+ def get_gh(args: Namespace) -> Any:
94
94
  'Return a GitHub handle'
95
95
  # The gh handle is a global to lazily create it only if/when needed
96
96
  global get_gh_handle
@@ -113,7 +113,7 @@ def rm_path(path: Path) -> None:
113
113
  class VersionMatcher:
114
114
  'Match a version string to a list of versions'
115
115
  def __init__(self, seq: Iterable[str]) -> None:
116
- self.seq = sorted(seq, key=Version, reverse=True)
116
+ self.seq = sorted(seq, key=parse_version, reverse=True)
117
117
 
118
118
  def match(self, version: str, *,
119
119
  upconvert_minor: bool = False) -> Optional[str]:
@@ -165,7 +165,8 @@ def get_version_names(args: Namespace) -> list[str]:
165
165
  unknowns = [f'"{u}"' for u in unknown]
166
166
  sys.exit(f'Error: version{s} {", ".join(unknowns)} not found.')
167
167
 
168
- return sorted(all_names - given, key=Version) if args.all else versions
168
+ return sorted(all_names - given, key=parse_version) \
169
+ if args.all else versions
169
170
 
170
171
  def get_latest_release_tag(args: Namespace) -> str:
171
172
  'Return the latest release tag'
@@ -191,7 +192,7 @@ def get_latest_release_tag(args: Namespace) -> str:
191
192
  args._latest_release.write_text(tag + '\n')
192
193
  return tag
193
194
 
194
- def get_release_files(args, tag, implementation: str = None) -> dict:
195
+ def get_release_files(args, tag, implementation: Optional[str] = None) -> dict:
195
196
  'Return the release files for the given tag'
196
197
  # Look for tag data in our release cache
197
198
  jfile = args._releases / tag
@@ -226,6 +227,48 @@ def get_release_files(args, tag, implementation: str = None) -> dict:
226
227
 
227
228
  return files.get(implementation, {}) if implementation else files
228
229
 
230
+ def update_version_symlinks(args: Namespace) -> None:
231
+ 'Create/update symlinks pointing to latest version'
232
+ base = args._versions
233
+ if not base.exists():
234
+ return
235
+
236
+ # Record of all the existing symlinks and version dirs
237
+ oldlinks = {}
238
+ vers = []
239
+ for path in base.iterdir():
240
+ if not path.name.startswith('.'):
241
+ if path.is_symlink():
242
+ oldlinks[path.name] = os.readlink(str(path))
243
+ else:
244
+ vers.append(path)
245
+
246
+ # Create a map of all the new major version links
247
+ newlinks_all = defaultdict(list)
248
+ for path in vers:
249
+ namevers = path.name
250
+ while '.' in namevers[:-1]:
251
+ namevers_major = namevers.rsplit('.', maxsplit=1)[0]
252
+ newlinks_all[namevers_major].append(namevers)
253
+ namevers = namevers_major
254
+
255
+ newlinks = {k: sorted(v, key=parse_version)[-1] for k, v in
256
+ newlinks_all.items()}
257
+
258
+ # Remove all old or invalid existing links
259
+ for name, tgt in oldlinks.items():
260
+ new_tgt = newlinks.get(name)
261
+ if not new_tgt or new_tgt != tgt:
262
+ path = Path(base / name)
263
+ path.unlink()
264
+
265
+ # Create all needed new links
266
+ for name, tgt in newlinks.items():
267
+ old_tgt = oldlinks.get(name)
268
+ if not old_tgt or old_tgt != tgt:
269
+ path = Path(base / name)
270
+ path.symlink_to(tgt, target_is_directory=True)
271
+
229
272
  def purge_unused_releases(args: Namespace) -> None:
230
273
  'Purge old releases that are no longer needed and have expired'
231
274
  releases = set(f.name for f in args._releases.iterdir())
@@ -405,6 +448,7 @@ def main() -> Optional[str]:
405
448
  args._releases.mkdir(parents=True, exist_ok=True)
406
449
 
407
450
  result = args.func(args)
451
+ update_version_symlinks(args)
408
452
  purge_unused_releases(args)
409
453
  return result
410
454
 
@@ -609,7 +653,7 @@ class _show(COMMAND):
609
653
  installed[vdir.name] = distro
610
654
 
611
655
  installable = False
612
- for version in sorted(files, key=Version):
656
+ for version in sorted(files, key=parse_version):
613
657
  installed_distribution = installed.get(version)
614
658
  for distribution in files[version]:
615
659
  app = ' (installed)' \
File without changes
File without changes
File without changes