omdev 0.0.0.dev180__py3-none-any.whl → 0.0.0.dev182__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.
- omdev/interp/inject.py +1 -1
- omdev/interp/inspect.py +9 -4
- omdev/interp/providers/system.py +5 -2
- omdev/interp/pyenv/inject.py +1 -1
- omdev/interp/pyenv/install.py +251 -0
- omdev/interp/pyenv/provider.py +144 -0
- omdev/interp/pyenv/pyenv.py +0 -380
- omdev/interp/venvs.py +114 -0
- omdev/pyproject/configs.py +3 -4
- omdev/pyproject/inject.py +12 -0
- omdev/pyproject/venvs.py +16 -52
- omdev/scripts/interp.py +121 -114
- omdev/scripts/pyproject.py +477 -343
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/RECORD +19 -15
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/top_level.txt +0 -0
omdev/scripts/pyproject.py
CHANGED
@@ -66,7 +66,7 @@ import time
|
|
66
66
|
import types
|
67
67
|
import typing as ta
|
68
68
|
import uuid
|
69
|
-
import weakref
|
69
|
+
import weakref
|
70
70
|
import zipfile
|
71
71
|
|
72
72
|
|
@@ -108,6 +108,11 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
|
|
108
108
|
CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
|
109
109
|
CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
|
110
110
|
|
111
|
+
# ../../omlish/lite/typing.py
|
112
|
+
A0 = ta.TypeVar('A0')
|
113
|
+
A1 = ta.TypeVar('A1')
|
114
|
+
A2 = ta.TypeVar('A2')
|
115
|
+
|
111
116
|
# ../packaging/specifiers.py
|
112
117
|
UnparsedVersion = ta.Union['Version', str]
|
113
118
|
UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
|
@@ -2575,6 +2580,54 @@ def format_num_bytes(num_bytes: int) -> str:
|
|
2575
2580
|
return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
|
2576
2581
|
|
2577
2582
|
|
2583
|
+
########################################
|
2584
|
+
# ../../../omlish/lite/typing.py
|
2585
|
+
|
2586
|
+
|
2587
|
+
##
|
2588
|
+
# A workaround for typing deficiencies (like `Argument 2 to NewType(...) must be subclassable`).
|
2589
|
+
|
2590
|
+
|
2591
|
+
@dc.dataclass(frozen=True)
|
2592
|
+
class AnyFunc(ta.Generic[T]):
|
2593
|
+
fn: ta.Callable[..., T]
|
2594
|
+
|
2595
|
+
def __call__(self, *args: ta.Any, **kwargs: ta.Any) -> T:
|
2596
|
+
return self.fn(*args, **kwargs)
|
2597
|
+
|
2598
|
+
|
2599
|
+
@dc.dataclass(frozen=True)
|
2600
|
+
class Func0(ta.Generic[T]):
|
2601
|
+
fn: ta.Callable[[], T]
|
2602
|
+
|
2603
|
+
def __call__(self) -> T:
|
2604
|
+
return self.fn()
|
2605
|
+
|
2606
|
+
|
2607
|
+
@dc.dataclass(frozen=True)
|
2608
|
+
class Func1(ta.Generic[A0, T]):
|
2609
|
+
fn: ta.Callable[[A0], T]
|
2610
|
+
|
2611
|
+
def __call__(self, a0: A0) -> T:
|
2612
|
+
return self.fn(a0)
|
2613
|
+
|
2614
|
+
|
2615
|
+
@dc.dataclass(frozen=True)
|
2616
|
+
class Func2(ta.Generic[A0, A1, T]):
|
2617
|
+
fn: ta.Callable[[A0, A1], T]
|
2618
|
+
|
2619
|
+
def __call__(self, a0: A0, a1: A1) -> T:
|
2620
|
+
return self.fn(a0, a1)
|
2621
|
+
|
2622
|
+
|
2623
|
+
@dc.dataclass(frozen=True)
|
2624
|
+
class Func3(ta.Generic[A0, A1, A2, T]):
|
2625
|
+
fn: ta.Callable[[A0, A1, A2], T]
|
2626
|
+
|
2627
|
+
def __call__(self, a0: A0, a1: A1, a2: A2) -> T:
|
2628
|
+
return self.fn(a0, a1, a2)
|
2629
|
+
|
2630
|
+
|
2578
2631
|
########################################
|
2579
2632
|
# ../../../omlish/logs/filters.py
|
2580
2633
|
|
@@ -5547,104 +5600,6 @@ def bind_interp_uv() -> InjectorBindings:
|
|
5547
5600
|
return inj.as_bindings(*lst)
|
5548
5601
|
|
5549
5602
|
|
5550
|
-
########################################
|
5551
|
-
# ../configs.py
|
5552
|
-
|
5553
|
-
|
5554
|
-
@dc.dataclass(frozen=True)
|
5555
|
-
class VenvConfig:
|
5556
|
-
inherits: ta.Optional[ta.Sequence[str]] = None
|
5557
|
-
interp: ta.Optional[str] = None
|
5558
|
-
requires: ta.Optional[ta.List[str]] = None
|
5559
|
-
docker: ta.Optional[str] = None
|
5560
|
-
srcs: ta.Optional[ta.List[str]] = None
|
5561
|
-
use_uv: ta.Optional[bool] = None
|
5562
|
-
|
5563
|
-
|
5564
|
-
@dc.dataclass(frozen=True)
|
5565
|
-
class PyprojectConfig:
|
5566
|
-
pkgs: ta.Sequence[str] = dc.field(default_factory=list)
|
5567
|
-
srcs: ta.Mapping[str, ta.Sequence[str]] = dc.field(default_factory=dict)
|
5568
|
-
venvs: ta.Mapping[str, VenvConfig] = dc.field(default_factory=dict)
|
5569
|
-
|
5570
|
-
venvs_dir: str = '.venvs'
|
5571
|
-
versions_file: ta.Optional[str] = '.versions'
|
5572
|
-
|
5573
|
-
|
5574
|
-
class PyprojectConfigPreparer:
|
5575
|
-
def __init__(
|
5576
|
-
self,
|
5577
|
-
*,
|
5578
|
-
python_versions: ta.Optional[ta.Mapping[str, str]] = None,
|
5579
|
-
) -> None:
|
5580
|
-
super().__init__()
|
5581
|
-
|
5582
|
-
self._python_versions = python_versions or {}
|
5583
|
-
|
5584
|
-
def _inherit_venvs(self, m: ta.Mapping[str, VenvConfig]) -> ta.Mapping[str, VenvConfig]:
|
5585
|
-
done: ta.Dict[str, VenvConfig] = {}
|
5586
|
-
|
5587
|
-
def rec(k):
|
5588
|
-
try:
|
5589
|
-
return done[k]
|
5590
|
-
except KeyError:
|
5591
|
-
pass
|
5592
|
-
|
5593
|
-
c = m[k]
|
5594
|
-
kw = dc.asdict(c)
|
5595
|
-
for i in c.inherits or ():
|
5596
|
-
ic = rec(i)
|
5597
|
-
kw.update({k: v for k, v in dc.asdict(ic).items() if v is not None and kw.get(k) is None})
|
5598
|
-
del kw['inherits']
|
5599
|
-
|
5600
|
-
d = done[k] = VenvConfig(**kw)
|
5601
|
-
return d
|
5602
|
-
|
5603
|
-
for k in m:
|
5604
|
-
rec(k)
|
5605
|
-
return done
|
5606
|
-
|
5607
|
-
def _resolve_srcs(
|
5608
|
-
self,
|
5609
|
-
lst: ta.Sequence[str],
|
5610
|
-
aliases: ta.Mapping[str, ta.Sequence[str]],
|
5611
|
-
) -> ta.List[str]:
|
5612
|
-
todo = list(reversed(lst))
|
5613
|
-
raw: ta.List[str] = []
|
5614
|
-
seen: ta.Set[str] = set()
|
5615
|
-
|
5616
|
-
while todo:
|
5617
|
-
cur = todo.pop()
|
5618
|
-
if cur in seen:
|
5619
|
-
continue
|
5620
|
-
|
5621
|
-
seen.add(cur)
|
5622
|
-
if not cur.startswith('@'):
|
5623
|
-
raw.append(cur)
|
5624
|
-
continue
|
5625
|
-
|
5626
|
-
todo.extend(aliases[cur[1:]][::-1])
|
5627
|
-
|
5628
|
-
return raw
|
5629
|
-
|
5630
|
-
def _fixup_interp(self, s: ta.Optional[str]) -> ta.Optional[str]:
|
5631
|
-
if not s or not s.startswith('@'):
|
5632
|
-
return s
|
5633
|
-
return self._python_versions[s[1:]]
|
5634
|
-
|
5635
|
-
def prepare_config(self, dct: ta.Mapping[str, ta.Any]) -> PyprojectConfig:
|
5636
|
-
pcfg: PyprojectConfig = unmarshal_obj(dct, PyprojectConfig)
|
5637
|
-
|
5638
|
-
ivs = dict(self._inherit_venvs(pcfg.venvs or {}))
|
5639
|
-
for k, v in ivs.items():
|
5640
|
-
v = dc.replace(v, srcs=self._resolve_srcs(v.srcs or [], pcfg.srcs or {}))
|
5641
|
-
v = dc.replace(v, interp=self._fixup_interp(v.interp))
|
5642
|
-
ivs[k] = v
|
5643
|
-
|
5644
|
-
pcfg = dc.replace(pcfg, venvs=ivs)
|
5645
|
-
return pcfg
|
5646
|
-
|
5647
|
-
|
5648
5603
|
########################################
|
5649
5604
|
# ../../../omlish/logs/standard.py
|
5650
5605
|
"""
|
@@ -6443,9 +6398,15 @@ class InterpInspection:
|
|
6443
6398
|
|
6444
6399
|
|
6445
6400
|
class InterpInspector:
|
6446
|
-
def __init__(
|
6401
|
+
def __init__(
|
6402
|
+
self,
|
6403
|
+
*,
|
6404
|
+
log: ta.Optional[logging.Logger] = None,
|
6405
|
+
) -> None:
|
6447
6406
|
super().__init__()
|
6448
6407
|
|
6408
|
+
self._log = log
|
6409
|
+
|
6449
6410
|
self._cache: ta.Dict[str, ta.Optional[InterpInspection]] = {}
|
6450
6411
|
|
6451
6412
|
_RAW_INSPECTION_CODE = """
|
@@ -6494,13 +6455,95 @@ class InterpInspector:
|
|
6494
6455
|
try:
|
6495
6456
|
ret = await self._inspect(exe)
|
6496
6457
|
except Exception as e: # noqa
|
6497
|
-
if
|
6498
|
-
|
6458
|
+
if self._log is not None and self._log.isEnabledFor(logging.DEBUG):
|
6459
|
+
self._log.exception('Failed to inspect interp: %s', exe)
|
6499
6460
|
ret = None
|
6500
6461
|
self._cache[exe] = ret
|
6501
6462
|
return ret
|
6502
6463
|
|
6503
6464
|
|
6465
|
+
########################################
|
6466
|
+
# ../../interp/pyenv/pyenv.py
|
6467
|
+
"""
|
6468
|
+
TODO:
|
6469
|
+
- custom tags
|
6470
|
+
- 'aliases'
|
6471
|
+
- https://github.com/pyenv/pyenv/pull/2966
|
6472
|
+
- https://github.com/pyenv/pyenv/issues/218 (lol)
|
6473
|
+
- probably need custom (temp?) definition file
|
6474
|
+
- *or* python-build directly just into the versions dir?
|
6475
|
+
- optionally install / upgrade pyenv itself
|
6476
|
+
- new vers dont need these custom mac opts, only run on old vers
|
6477
|
+
"""
|
6478
|
+
|
6479
|
+
|
6480
|
+
class Pyenv:
|
6481
|
+
def __init__(
|
6482
|
+
self,
|
6483
|
+
*,
|
6484
|
+
root: ta.Optional[str] = None,
|
6485
|
+
) -> None:
|
6486
|
+
if root is not None and not (isinstance(root, str) and root):
|
6487
|
+
raise ValueError(f'pyenv_root: {root!r}')
|
6488
|
+
|
6489
|
+
super().__init__()
|
6490
|
+
|
6491
|
+
self._root_kw = root
|
6492
|
+
|
6493
|
+
@async_cached_nullary
|
6494
|
+
async def root(self) -> ta.Optional[str]:
|
6495
|
+
if self._root_kw is not None:
|
6496
|
+
return self._root_kw
|
6497
|
+
|
6498
|
+
if shutil.which('pyenv'):
|
6499
|
+
return await asyncio_subprocesses.check_output_str('pyenv', 'root')
|
6500
|
+
|
6501
|
+
d = os.path.expanduser('~/.pyenv')
|
6502
|
+
if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
|
6503
|
+
return d
|
6504
|
+
|
6505
|
+
return None
|
6506
|
+
|
6507
|
+
@async_cached_nullary
|
6508
|
+
async def exe(self) -> str:
|
6509
|
+
return os.path.join(check.not_none(await self.root()), 'bin', 'pyenv')
|
6510
|
+
|
6511
|
+
async def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
|
6512
|
+
if (root := await self.root()) is None:
|
6513
|
+
return []
|
6514
|
+
ret = []
|
6515
|
+
vp = os.path.join(root, 'versions')
|
6516
|
+
if os.path.isdir(vp):
|
6517
|
+
for dn in os.listdir(vp):
|
6518
|
+
ep = os.path.join(vp, dn, 'bin', 'python')
|
6519
|
+
if not os.path.isfile(ep):
|
6520
|
+
continue
|
6521
|
+
ret.append((dn, ep))
|
6522
|
+
return ret
|
6523
|
+
|
6524
|
+
async def installable_versions(self) -> ta.List[str]:
|
6525
|
+
if await self.root() is None:
|
6526
|
+
return []
|
6527
|
+
ret = []
|
6528
|
+
s = await asyncio_subprocesses.check_output_str(await self.exe(), 'install', '--list')
|
6529
|
+
for l in s.splitlines():
|
6530
|
+
if not l.startswith(' '):
|
6531
|
+
continue
|
6532
|
+
l = l.strip()
|
6533
|
+
if not l:
|
6534
|
+
continue
|
6535
|
+
ret.append(l)
|
6536
|
+
return ret
|
6537
|
+
|
6538
|
+
async def update(self) -> bool:
|
6539
|
+
if (root := await self.root()) is None:
|
6540
|
+
return False
|
6541
|
+
if not os.path.isdir(os.path.join(root, '.git')):
|
6542
|
+
return False
|
6543
|
+
await asyncio_subprocesses.check_call('git', 'pull', cwd=root)
|
6544
|
+
return True
|
6545
|
+
|
6546
|
+
|
6504
6547
|
########################################
|
6505
6548
|
# ../../interp/resolvers.py
|
6506
6549
|
|
@@ -6755,12 +6798,14 @@ class SystemInterpProvider(InterpProvider):
|
|
6755
6798
|
options: Options = Options(),
|
6756
6799
|
*,
|
6757
6800
|
inspector: ta.Optional[InterpInspector] = None,
|
6801
|
+
log: ta.Optional[logging.Logger] = None,
|
6758
6802
|
) -> None:
|
6759
6803
|
super().__init__()
|
6760
6804
|
|
6761
6805
|
self._options = options
|
6762
6806
|
|
6763
6807
|
self._inspector = inspector
|
6808
|
+
self._log = log
|
6764
6809
|
|
6765
6810
|
#
|
6766
6811
|
|
@@ -6834,7 +6879,8 @@ class SystemInterpProvider(InterpProvider):
|
|
6834
6879
|
lst = []
|
6835
6880
|
for e in self.exes():
|
6836
6881
|
if (ev := await self.get_exe_version(e)) is None:
|
6837
|
-
|
6882
|
+
if self._log is not None:
|
6883
|
+
self._log.debug('Invalid system version: %s', e)
|
6838
6884
|
continue
|
6839
6885
|
lst.append((e, ev))
|
6840
6886
|
return lst
|
@@ -6856,109 +6902,28 @@ class SystemInterpProvider(InterpProvider):
|
|
6856
6902
|
|
6857
6903
|
|
6858
6904
|
########################################
|
6859
|
-
# ../../interp/pyenv/
|
6860
|
-
"""
|
6861
|
-
TODO:
|
6862
|
-
- custom tags
|
6863
|
-
- 'aliases'
|
6864
|
-
- https://github.com/pyenv/pyenv/pull/2966
|
6865
|
-
- https://github.com/pyenv/pyenv/issues/218 (lol)
|
6866
|
-
- probably need custom (temp?) definition file
|
6867
|
-
- *or* python-build directly just into the versions dir?
|
6868
|
-
- optionally install / upgrade pyenv itself
|
6869
|
-
- new vers dont need these custom mac opts, only run on old vers
|
6870
|
-
"""
|
6905
|
+
# ../../interp/pyenv/install.py
|
6871
6906
|
|
6872
6907
|
|
6873
6908
|
##
|
6874
6909
|
|
6875
6910
|
|
6876
|
-
|
6877
|
-
|
6878
|
-
|
6879
|
-
|
6880
|
-
|
6881
|
-
|
6882
|
-
|
6883
|
-
raise ValueError(f'pyenv_root: {root!r}')
|
6911
|
+
@dc.dataclass(frozen=True)
|
6912
|
+
class PyenvInstallOpts:
|
6913
|
+
opts: ta.Sequence[str] = ()
|
6914
|
+
conf_opts: ta.Sequence[str] = ()
|
6915
|
+
cflags: ta.Sequence[str] = ()
|
6916
|
+
ldflags: ta.Sequence[str] = ()
|
6917
|
+
env: ta.Mapping[str, str] = dc.field(default_factory=dict)
|
6884
6918
|
|
6885
|
-
|
6886
|
-
|
6887
|
-
|
6888
|
-
|
6889
|
-
|
6890
|
-
|
6891
|
-
|
6892
|
-
|
6893
|
-
|
6894
|
-
if shutil.which('pyenv'):
|
6895
|
-
return await asyncio_subprocesses.check_output_str('pyenv', 'root')
|
6896
|
-
|
6897
|
-
d = os.path.expanduser('~/.pyenv')
|
6898
|
-
if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
|
6899
|
-
return d
|
6900
|
-
|
6901
|
-
return None
|
6902
|
-
|
6903
|
-
@async_cached_nullary
|
6904
|
-
async def exe(self) -> str:
|
6905
|
-
return os.path.join(check.not_none(await self.root()), 'bin', 'pyenv')
|
6906
|
-
|
6907
|
-
async def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
|
6908
|
-
if (root := await self.root()) is None:
|
6909
|
-
return []
|
6910
|
-
ret = []
|
6911
|
-
vp = os.path.join(root, 'versions')
|
6912
|
-
if os.path.isdir(vp):
|
6913
|
-
for dn in os.listdir(vp):
|
6914
|
-
ep = os.path.join(vp, dn, 'bin', 'python')
|
6915
|
-
if not os.path.isfile(ep):
|
6916
|
-
continue
|
6917
|
-
ret.append((dn, ep))
|
6918
|
-
return ret
|
6919
|
-
|
6920
|
-
async def installable_versions(self) -> ta.List[str]:
|
6921
|
-
if await self.root() is None:
|
6922
|
-
return []
|
6923
|
-
ret = []
|
6924
|
-
s = await asyncio_subprocesses.check_output_str(await self.exe(), 'install', '--list')
|
6925
|
-
for l in s.splitlines():
|
6926
|
-
if not l.startswith(' '):
|
6927
|
-
continue
|
6928
|
-
l = l.strip()
|
6929
|
-
if not l:
|
6930
|
-
continue
|
6931
|
-
ret.append(l)
|
6932
|
-
return ret
|
6933
|
-
|
6934
|
-
async def update(self) -> bool:
|
6935
|
-
if (root := await self.root()) is None:
|
6936
|
-
return False
|
6937
|
-
if not os.path.isdir(os.path.join(root, '.git')):
|
6938
|
-
return False
|
6939
|
-
await asyncio_subprocesses.check_call('git', 'pull', cwd=root)
|
6940
|
-
return True
|
6941
|
-
|
6942
|
-
|
6943
|
-
##
|
6944
|
-
|
6945
|
-
|
6946
|
-
@dc.dataclass(frozen=True)
|
6947
|
-
class PyenvInstallOpts:
|
6948
|
-
opts: ta.Sequence[str] = ()
|
6949
|
-
conf_opts: ta.Sequence[str] = ()
|
6950
|
-
cflags: ta.Sequence[str] = ()
|
6951
|
-
ldflags: ta.Sequence[str] = ()
|
6952
|
-
env: ta.Mapping[str, str] = dc.field(default_factory=dict)
|
6953
|
-
|
6954
|
-
def merge(self, *others: 'PyenvInstallOpts') -> 'PyenvInstallOpts':
|
6955
|
-
return PyenvInstallOpts(
|
6956
|
-
opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
|
6957
|
-
conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
|
6958
|
-
cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
|
6959
|
-
ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
|
6960
|
-
env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
|
6961
|
-
)
|
6919
|
+
def merge(self, *others: 'PyenvInstallOpts') -> 'PyenvInstallOpts':
|
6920
|
+
return PyenvInstallOpts(
|
6921
|
+
opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
|
6922
|
+
conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
|
6923
|
+
cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
|
6924
|
+
ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
|
6925
|
+
env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
|
6926
|
+
)
|
6962
6927
|
|
6963
6928
|
|
6964
6929
|
# TODO: https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-for-maximum-performance
|
@@ -7175,133 +7140,6 @@ class PyenvVersionInstaller:
|
|
7175
7140
|
return exe
|
7176
7141
|
|
7177
7142
|
|
7178
|
-
##
|
7179
|
-
|
7180
|
-
|
7181
|
-
class PyenvInterpProvider(InterpProvider):
|
7182
|
-
@dc.dataclass(frozen=True)
|
7183
|
-
class Options:
|
7184
|
-
inspect: bool = False
|
7185
|
-
|
7186
|
-
try_update: bool = False
|
7187
|
-
|
7188
|
-
def __init__(
|
7189
|
-
self,
|
7190
|
-
options: Options = Options(),
|
7191
|
-
*,
|
7192
|
-
pyenv: Pyenv,
|
7193
|
-
inspector: InterpInspector,
|
7194
|
-
) -> None:
|
7195
|
-
super().__init__()
|
7196
|
-
|
7197
|
-
self._options = options
|
7198
|
-
|
7199
|
-
self._pyenv = pyenv
|
7200
|
-
self._inspector = inspector
|
7201
|
-
|
7202
|
-
#
|
7203
|
-
|
7204
|
-
@staticmethod
|
7205
|
-
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
7206
|
-
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
7207
|
-
if s.endswith(sfx):
|
7208
|
-
return s[:-len(sfx)], True
|
7209
|
-
return s, False
|
7210
|
-
ok = {}
|
7211
|
-
s, ok['debug'] = strip_sfx(s, '-debug')
|
7212
|
-
s, ok['threaded'] = strip_sfx(s, 't')
|
7213
|
-
try:
|
7214
|
-
v = Version(s)
|
7215
|
-
except InvalidVersion:
|
7216
|
-
return None
|
7217
|
-
return InterpVersion(v, InterpOpts(**ok))
|
7218
|
-
|
7219
|
-
class Installed(ta.NamedTuple):
|
7220
|
-
name: str
|
7221
|
-
exe: str
|
7222
|
-
version: InterpVersion
|
7223
|
-
|
7224
|
-
async def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
|
7225
|
-
iv: ta.Optional[InterpVersion]
|
7226
|
-
if self._options.inspect:
|
7227
|
-
try:
|
7228
|
-
iv = check.not_none(await self._inspector.inspect(ep)).iv
|
7229
|
-
except Exception as e: # noqa
|
7230
|
-
return None
|
7231
|
-
else:
|
7232
|
-
iv = self.guess_version(vn)
|
7233
|
-
if iv is None:
|
7234
|
-
return None
|
7235
|
-
return PyenvInterpProvider.Installed(
|
7236
|
-
name=vn,
|
7237
|
-
exe=ep,
|
7238
|
-
version=iv,
|
7239
|
-
)
|
7240
|
-
|
7241
|
-
async def installed(self) -> ta.Sequence[Installed]:
|
7242
|
-
ret: ta.List[PyenvInterpProvider.Installed] = []
|
7243
|
-
for vn, ep in await self._pyenv.version_exes():
|
7244
|
-
if (i := await self._make_installed(vn, ep)) is None:
|
7245
|
-
log.debug('Invalid pyenv version: %s', vn)
|
7246
|
-
continue
|
7247
|
-
ret.append(i)
|
7248
|
-
return ret
|
7249
|
-
|
7250
|
-
#
|
7251
|
-
|
7252
|
-
async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
7253
|
-
return [i.version for i in await self.installed()]
|
7254
|
-
|
7255
|
-
async def get_installed_version(self, version: InterpVersion) -> Interp:
|
7256
|
-
for i in await self.installed():
|
7257
|
-
if i.version == version:
|
7258
|
-
return Interp(
|
7259
|
-
exe=i.exe,
|
7260
|
-
version=i.version,
|
7261
|
-
)
|
7262
|
-
raise KeyError(version)
|
7263
|
-
|
7264
|
-
#
|
7265
|
-
|
7266
|
-
async def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
7267
|
-
lst = []
|
7268
|
-
|
7269
|
-
for vs in await self._pyenv.installable_versions():
|
7270
|
-
if (iv := self.guess_version(vs)) is None:
|
7271
|
-
continue
|
7272
|
-
if iv.opts.debug:
|
7273
|
-
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
7274
|
-
for d in [False, True]:
|
7275
|
-
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
7276
|
-
|
7277
|
-
return lst
|
7278
|
-
|
7279
|
-
async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
7280
|
-
lst = await self._get_installable_versions(spec)
|
7281
|
-
|
7282
|
-
if self._options.try_update and not any(v in spec for v in lst):
|
7283
|
-
if self._pyenv.update():
|
7284
|
-
lst = await self._get_installable_versions(spec)
|
7285
|
-
|
7286
|
-
return lst
|
7287
|
-
|
7288
|
-
async def install_version(self, version: InterpVersion) -> Interp:
|
7289
|
-
inst_version = str(version.version)
|
7290
|
-
inst_opts = version.opts
|
7291
|
-
if inst_opts.threaded:
|
7292
|
-
inst_version += 't'
|
7293
|
-
inst_opts = dc.replace(inst_opts, threaded=False)
|
7294
|
-
|
7295
|
-
installer = PyenvVersionInstaller(
|
7296
|
-
inst_version,
|
7297
|
-
interp_opts=inst_opts,
|
7298
|
-
pyenv=self._pyenv,
|
7299
|
-
)
|
7300
|
-
|
7301
|
-
exe = await installer.install()
|
7302
|
-
return Interp(exe, version)
|
7303
|
-
|
7304
|
-
|
7305
7143
|
########################################
|
7306
7144
|
# ../pkg.py
|
7307
7145
|
"""
|
@@ -7879,6 +7717,137 @@ def bind_interp_providers() -> InjectorBindings:
|
|
7879
7717
|
return inj.as_bindings(*lst)
|
7880
7718
|
|
7881
7719
|
|
7720
|
+
########################################
|
7721
|
+
# ../../interp/pyenv/provider.py
|
7722
|
+
|
7723
|
+
|
7724
|
+
class PyenvInterpProvider(InterpProvider):
|
7725
|
+
@dc.dataclass(frozen=True)
|
7726
|
+
class Options:
|
7727
|
+
inspect: bool = False
|
7728
|
+
|
7729
|
+
try_update: bool = False
|
7730
|
+
|
7731
|
+
def __init__(
|
7732
|
+
self,
|
7733
|
+
options: Options = Options(),
|
7734
|
+
*,
|
7735
|
+
pyenv: Pyenv,
|
7736
|
+
inspector: InterpInspector,
|
7737
|
+
log: ta.Optional[logging.Logger] = None,
|
7738
|
+
) -> None:
|
7739
|
+
super().__init__()
|
7740
|
+
|
7741
|
+
self._options = options
|
7742
|
+
|
7743
|
+
self._pyenv = pyenv
|
7744
|
+
self._inspector = inspector
|
7745
|
+
self._log = log
|
7746
|
+
|
7747
|
+
#
|
7748
|
+
|
7749
|
+
@staticmethod
|
7750
|
+
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
7751
|
+
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
7752
|
+
if s.endswith(sfx):
|
7753
|
+
return s[:-len(sfx)], True
|
7754
|
+
return s, False
|
7755
|
+
ok = {}
|
7756
|
+
s, ok['debug'] = strip_sfx(s, '-debug')
|
7757
|
+
s, ok['threaded'] = strip_sfx(s, 't')
|
7758
|
+
try:
|
7759
|
+
v = Version(s)
|
7760
|
+
except InvalidVersion:
|
7761
|
+
return None
|
7762
|
+
return InterpVersion(v, InterpOpts(**ok))
|
7763
|
+
|
7764
|
+
class Installed(ta.NamedTuple):
|
7765
|
+
name: str
|
7766
|
+
exe: str
|
7767
|
+
version: InterpVersion
|
7768
|
+
|
7769
|
+
async def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
|
7770
|
+
iv: ta.Optional[InterpVersion]
|
7771
|
+
if self._options.inspect:
|
7772
|
+
try:
|
7773
|
+
iv = check.not_none(await self._inspector.inspect(ep)).iv
|
7774
|
+
except Exception as e: # noqa
|
7775
|
+
return None
|
7776
|
+
else:
|
7777
|
+
iv = self.guess_version(vn)
|
7778
|
+
if iv is None:
|
7779
|
+
return None
|
7780
|
+
return PyenvInterpProvider.Installed(
|
7781
|
+
name=vn,
|
7782
|
+
exe=ep,
|
7783
|
+
version=iv,
|
7784
|
+
)
|
7785
|
+
|
7786
|
+
async def installed(self) -> ta.Sequence[Installed]:
|
7787
|
+
ret: ta.List[PyenvInterpProvider.Installed] = []
|
7788
|
+
for vn, ep in await self._pyenv.version_exes():
|
7789
|
+
if (i := await self._make_installed(vn, ep)) is None:
|
7790
|
+
if self._log is not None:
|
7791
|
+
self._log.debug('Invalid pyenv version: %s', vn)
|
7792
|
+
continue
|
7793
|
+
ret.append(i)
|
7794
|
+
return ret
|
7795
|
+
|
7796
|
+
#
|
7797
|
+
|
7798
|
+
async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
7799
|
+
return [i.version for i in await self.installed()]
|
7800
|
+
|
7801
|
+
async def get_installed_version(self, version: InterpVersion) -> Interp:
|
7802
|
+
for i in await self.installed():
|
7803
|
+
if i.version == version:
|
7804
|
+
return Interp(
|
7805
|
+
exe=i.exe,
|
7806
|
+
version=i.version,
|
7807
|
+
)
|
7808
|
+
raise KeyError(version)
|
7809
|
+
|
7810
|
+
#
|
7811
|
+
|
7812
|
+
async def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
7813
|
+
lst = []
|
7814
|
+
|
7815
|
+
for vs in await self._pyenv.installable_versions():
|
7816
|
+
if (iv := self.guess_version(vs)) is None:
|
7817
|
+
continue
|
7818
|
+
if iv.opts.debug:
|
7819
|
+
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
7820
|
+
for d in [False, True]:
|
7821
|
+
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
7822
|
+
|
7823
|
+
return lst
|
7824
|
+
|
7825
|
+
async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
7826
|
+
lst = await self._get_installable_versions(spec)
|
7827
|
+
|
7828
|
+
if self._options.try_update and not any(v in spec for v in lst):
|
7829
|
+
if self._pyenv.update():
|
7830
|
+
lst = await self._get_installable_versions(spec)
|
7831
|
+
|
7832
|
+
return lst
|
7833
|
+
|
7834
|
+
async def install_version(self, version: InterpVersion) -> Interp:
|
7835
|
+
inst_version = str(version.version)
|
7836
|
+
inst_opts = version.opts
|
7837
|
+
if inst_opts.threaded:
|
7838
|
+
inst_version += 't'
|
7839
|
+
inst_opts = dc.replace(inst_opts, threaded=False)
|
7840
|
+
|
7841
|
+
installer = PyenvVersionInstaller(
|
7842
|
+
inst_version,
|
7843
|
+
interp_opts=inst_opts,
|
7844
|
+
pyenv=self._pyenv,
|
7845
|
+
)
|
7846
|
+
|
7847
|
+
exe = await installer.install()
|
7848
|
+
return Interp(exe, version)
|
7849
|
+
|
7850
|
+
|
7882
7851
|
########################################
|
7883
7852
|
# ../../interp/pyenv/inject.py
|
7884
7853
|
|
@@ -7945,31 +7914,47 @@ def get_default_interp_resolver() -> InterpResolver:
|
|
7945
7914
|
|
7946
7915
|
|
7947
7916
|
########################################
|
7948
|
-
#
|
7917
|
+
# ../../interp/venvs.py
|
7949
7918
|
|
7950
7919
|
|
7951
7920
|
##
|
7952
7921
|
|
7953
7922
|
|
7954
|
-
|
7923
|
+
@dc.dataclass(frozen=True)
|
7924
|
+
class InterpVenvConfig:
|
7925
|
+
interp: ta.Optional[str] = None
|
7926
|
+
requires: ta.Optional[ta.Sequence[str]] = None
|
7927
|
+
use_uv: ta.Optional[bool] = None
|
7928
|
+
|
7929
|
+
|
7930
|
+
class InterpVenvRequirementsProcessor(Func2['InterpVenv', ta.Sequence[str], ta.Sequence[str]]):
|
7931
|
+
pass
|
7932
|
+
|
7933
|
+
|
7934
|
+
class InterpVenv:
|
7955
7935
|
def __init__(
|
7956
7936
|
self,
|
7957
|
-
|
7958
|
-
cfg:
|
7937
|
+
path: str,
|
7938
|
+
cfg: InterpVenvConfig,
|
7939
|
+
*,
|
7940
|
+
requirements_processor: ta.Optional[InterpVenvRequirementsProcessor] = None,
|
7941
|
+
log: ta.Optional[logging.Logger] = None,
|
7959
7942
|
) -> None:
|
7960
7943
|
super().__init__()
|
7961
|
-
|
7944
|
+
|
7945
|
+
self._path = path
|
7962
7946
|
self._cfg = cfg
|
7963
7947
|
|
7964
|
-
|
7965
|
-
|
7966
|
-
return self._cfg
|
7948
|
+
self._requirements_processor = requirements_processor
|
7949
|
+
self._log = log
|
7967
7950
|
|
7968
|
-
|
7951
|
+
@property
|
7952
|
+
def path(self) -> str:
|
7953
|
+
return self._path
|
7969
7954
|
|
7970
7955
|
@property
|
7971
|
-
def
|
7972
|
-
return
|
7956
|
+
def cfg(self) -> InterpVenvConfig:
|
7957
|
+
return self._cfg
|
7973
7958
|
|
7974
7959
|
@async_cached_nullary
|
7975
7960
|
async def interp_exe(self) -> str:
|
@@ -7978,19 +7963,23 @@ class Venv:
|
|
7978
7963
|
|
7979
7964
|
@cached_nullary
|
7980
7965
|
def exe(self) -> str:
|
7981
|
-
ve = os.path.join(self.
|
7966
|
+
ve = os.path.join(self._path, 'bin/python')
|
7982
7967
|
if not os.path.isfile(ve):
|
7983
7968
|
raise Exception(f'venv exe {ve} does not exist or is not a file!')
|
7984
7969
|
return ve
|
7985
7970
|
|
7986
7971
|
@async_cached_nullary
|
7987
7972
|
async def create(self) -> bool:
|
7988
|
-
if os.path.exists(dn := self.
|
7973
|
+
if os.path.exists(dn := self._path):
|
7989
7974
|
if not os.path.isdir(dn):
|
7990
7975
|
raise Exception(f'{dn} exists but is not a directory!')
|
7991
7976
|
return False
|
7992
7977
|
|
7993
|
-
|
7978
|
+
ie = await self.interp_exe()
|
7979
|
+
|
7980
|
+
if self._log is not None:
|
7981
|
+
self._log.info('Using interpreter %s', ie)
|
7982
|
+
|
7994
7983
|
await asyncio_subprocesses.check_call(ie, '-m', 'venv', dn)
|
7995
7984
|
|
7996
7985
|
ve = self.exe()
|
@@ -8007,8 +7996,9 @@ class Venv:
|
|
8007
7996
|
)
|
8008
7997
|
|
8009
7998
|
if sr := self._cfg.requires:
|
8010
|
-
|
8011
|
-
|
7999
|
+
reqs = list(sr)
|
8000
|
+
if self._requirements_processor is not None:
|
8001
|
+
reqs = list(self._requirements_processor(self, reqs))
|
8012
8002
|
|
8013
8003
|
# TODO: automatically try slower uv download when it fails? lol
|
8014
8004
|
# Caused by: Failed to download distribution due to network timeout. Try increasing UV_HTTP_TIMEOUT (current value: 30s). # noqa
|
@@ -8026,6 +8016,150 @@ class Venv:
|
|
8026
8016
|
|
8027
8017
|
return True
|
8028
8018
|
|
8019
|
+
|
8020
|
+
########################################
|
8021
|
+
# ../configs.py
|
8022
|
+
|
8023
|
+
|
8024
|
+
@dc.dataclass(frozen=True)
|
8025
|
+
class VenvConfig(InterpVenvConfig):
|
8026
|
+
inherits: ta.Optional[ta.Sequence[str]] = None
|
8027
|
+
docker: ta.Optional[str] = None
|
8028
|
+
srcs: ta.Optional[ta.List[str]] = None
|
8029
|
+
|
8030
|
+
|
8031
|
+
@dc.dataclass(frozen=True)
|
8032
|
+
class PyprojectConfig:
|
8033
|
+
pkgs: ta.Sequence[str] = dc.field(default_factory=list)
|
8034
|
+
srcs: ta.Mapping[str, ta.Sequence[str]] = dc.field(default_factory=dict)
|
8035
|
+
venvs: ta.Mapping[str, VenvConfig] = dc.field(default_factory=dict)
|
8036
|
+
|
8037
|
+
venvs_dir: str = '.venvs'
|
8038
|
+
versions_file: ta.Optional[str] = '.versions'
|
8039
|
+
|
8040
|
+
|
8041
|
+
class PyprojectConfigPreparer:
|
8042
|
+
def __init__(
|
8043
|
+
self,
|
8044
|
+
*,
|
8045
|
+
python_versions: ta.Optional[ta.Mapping[str, str]] = None,
|
8046
|
+
) -> None:
|
8047
|
+
super().__init__()
|
8048
|
+
|
8049
|
+
self._python_versions = python_versions or {}
|
8050
|
+
|
8051
|
+
def _inherit_venvs(self, m: ta.Mapping[str, VenvConfig]) -> ta.Mapping[str, VenvConfig]:
|
8052
|
+
done: ta.Dict[str, VenvConfig] = {}
|
8053
|
+
|
8054
|
+
def rec(k):
|
8055
|
+
try:
|
8056
|
+
return done[k]
|
8057
|
+
except KeyError:
|
8058
|
+
pass
|
8059
|
+
|
8060
|
+
c = m[k]
|
8061
|
+
kw = dc.asdict(c)
|
8062
|
+
for i in c.inherits or ():
|
8063
|
+
ic = rec(i)
|
8064
|
+
kw.update({k: v for k, v in dc.asdict(ic).items() if v is not None and kw.get(k) is None})
|
8065
|
+
del kw['inherits']
|
8066
|
+
|
8067
|
+
d = done[k] = VenvConfig(**kw)
|
8068
|
+
return d
|
8069
|
+
|
8070
|
+
for k in m:
|
8071
|
+
rec(k)
|
8072
|
+
return done
|
8073
|
+
|
8074
|
+
def _resolve_srcs(
|
8075
|
+
self,
|
8076
|
+
lst: ta.Sequence[str],
|
8077
|
+
aliases: ta.Mapping[str, ta.Sequence[str]],
|
8078
|
+
) -> ta.List[str]:
|
8079
|
+
todo = list(reversed(lst))
|
8080
|
+
raw: ta.List[str] = []
|
8081
|
+
seen: ta.Set[str] = set()
|
8082
|
+
|
8083
|
+
while todo:
|
8084
|
+
cur = todo.pop()
|
8085
|
+
if cur in seen:
|
8086
|
+
continue
|
8087
|
+
|
8088
|
+
seen.add(cur)
|
8089
|
+
if not cur.startswith('@'):
|
8090
|
+
raw.append(cur)
|
8091
|
+
continue
|
8092
|
+
|
8093
|
+
todo.extend(aliases[cur[1:]][::-1])
|
8094
|
+
|
8095
|
+
return raw
|
8096
|
+
|
8097
|
+
def _fixup_interp(self, s: ta.Optional[str]) -> ta.Optional[str]:
|
8098
|
+
if not s or not s.startswith('@'):
|
8099
|
+
return s
|
8100
|
+
return self._python_versions[s[1:]]
|
8101
|
+
|
8102
|
+
def prepare_config(self, dct: ta.Mapping[str, ta.Any]) -> PyprojectConfig:
|
8103
|
+
pcfg: PyprojectConfig = unmarshal_obj(dct, PyprojectConfig)
|
8104
|
+
|
8105
|
+
ivs = dict(self._inherit_venvs(pcfg.venvs or {}))
|
8106
|
+
for k, v in ivs.items():
|
8107
|
+
v = dc.replace(v, srcs=self._resolve_srcs(v.srcs or [], pcfg.srcs or {}))
|
8108
|
+
v = dc.replace(v, interp=self._fixup_interp(v.interp))
|
8109
|
+
ivs[k] = v
|
8110
|
+
|
8111
|
+
pcfg = dc.replace(pcfg, venvs=ivs)
|
8112
|
+
return pcfg
|
8113
|
+
|
8114
|
+
|
8115
|
+
########################################
|
8116
|
+
# ../venvs.py
|
8117
|
+
|
8118
|
+
|
8119
|
+
##
|
8120
|
+
|
8121
|
+
|
8122
|
+
class Venv:
|
8123
|
+
def __init__(
|
8124
|
+
self,
|
8125
|
+
name: str,
|
8126
|
+
cfg: VenvConfig,
|
8127
|
+
) -> None:
|
8128
|
+
super().__init__()
|
8129
|
+
self._name = name
|
8130
|
+
self._cfg = cfg
|
8131
|
+
|
8132
|
+
@property
|
8133
|
+
def cfg(self) -> VenvConfig:
|
8134
|
+
return self._cfg
|
8135
|
+
|
8136
|
+
DIR_NAME = '.venvs'
|
8137
|
+
|
8138
|
+
@property
|
8139
|
+
def dir_name(self) -> str:
|
8140
|
+
return os.path.join(self.DIR_NAME, self._name)
|
8141
|
+
|
8142
|
+
@cached_nullary
|
8143
|
+
def _iv(self) -> InterpVenv:
|
8144
|
+
rr = RequirementsRewriter(self._name)
|
8145
|
+
|
8146
|
+
return InterpVenv(
|
8147
|
+
self.dir_name,
|
8148
|
+
self._cfg,
|
8149
|
+
requirements_processor=InterpVenvRequirementsProcessor(
|
8150
|
+
lambda iv, reqs: [rr.rewrite(req) for req in reqs] # noqa
|
8151
|
+
),
|
8152
|
+
log=log,
|
8153
|
+
)
|
8154
|
+
|
8155
|
+
@cached_nullary
|
8156
|
+
def exe(self) -> str:
|
8157
|
+
return self._iv().exe()
|
8158
|
+
|
8159
|
+
@async_cached_nullary
|
8160
|
+
async def create(self) -> bool:
|
8161
|
+
return await self._iv().create()
|
8162
|
+
|
8029
8163
|
@staticmethod
|
8030
8164
|
def _resolve_srcs(raw: ta.List[str]) -> ta.List[str]:
|
8031
8165
|
out: list[str] = []
|