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.
@@ -66,7 +66,7 @@ import time
66
66
  import types
67
67
  import typing as ta
68
68
  import uuid
69
- import weakref # noqa
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__(self) -> None:
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 log.isEnabledFor(logging.DEBUG):
6498
- log.exception('Failed to inspect interp: %s', exe)
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
- log.debug('Invalid system version: %s', e)
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/pyenv.py
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
- class Pyenv:
6877
- def __init__(
6878
- self,
6879
- *,
6880
- root: ta.Optional[str] = None,
6881
- ) -> None:
6882
- if root is not None and not (isinstance(root, str) and root):
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
- super().__init__()
6886
-
6887
- self._root_kw = root
6888
-
6889
- @async_cached_nullary
6890
- async def root(self) -> ta.Optional[str]:
6891
- if self._root_kw is not None:
6892
- return self._root_kw
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
- # ../venvs.py
7917
+ # ../../interp/venvs.py
7949
7918
 
7950
7919
 
7951
7920
  ##
7952
7921
 
7953
7922
 
7954
- class Venv:
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
- name: str,
7958
- cfg: VenvConfig,
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
- self._name = name
7944
+
7945
+ self._path = path
7962
7946
  self._cfg = cfg
7963
7947
 
7964
- @property
7965
- def cfg(self) -> VenvConfig:
7966
- return self._cfg
7948
+ self._requirements_processor = requirements_processor
7949
+ self._log = log
7967
7950
 
7968
- DIR_NAME = '.venvs'
7951
+ @property
7952
+ def path(self) -> str:
7953
+ return self._path
7969
7954
 
7970
7955
  @property
7971
- def dir_name(self) -> str:
7972
- return os.path.join(self.DIR_NAME, self._name)
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.dir_name, 'bin/python')
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.dir_name):
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
- log.info('Using interpreter %s', (ie := await self.interp_exe()))
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
- rr = RequirementsRewriter(self._name)
8011
- reqs = [rr.rewrite(req) for req in sr]
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] = []