omdev 0.0.0.dev180__py3-none-any.whl → 0.0.0.dev182__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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] = []