ominfra 0.0.0.dev178__py3-none-any.whl → 0.0.0.dev179__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.
ominfra/scripts/manage.py CHANGED
@@ -5126,30 +5126,6 @@ def as_injector_bindings(*args: InjectorBindingOrBindings) -> InjectorBindings:
5126
5126
  ##
5127
5127
 
5128
5128
 
5129
- @dc.dataclass(frozen=True)
5130
- class OverridesInjectorBindings(InjectorBindings):
5131
- p: InjectorBindings
5132
- m: ta.Mapping[InjectorKey, InjectorBinding]
5133
-
5134
- def bindings(self) -> ta.Iterator[InjectorBinding]:
5135
- for b in self.p.bindings():
5136
- yield self.m.get(b.key, b)
5137
-
5138
-
5139
- def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
5140
- m: ta.Dict[InjectorKey, InjectorBinding] = {}
5141
-
5142
- for b in as_injector_bindings(*args).bindings():
5143
- if b.key in m:
5144
- raise DuplicateInjectorKeyError(b.key)
5145
- m[b.key] = b
5146
-
5147
- return OverridesInjectorBindings(p, m)
5148
-
5149
-
5150
- ##
5151
-
5152
-
5153
5129
  def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey, InjectorProvider]:
5154
5130
  pm: ta.Dict[InjectorKey, InjectorProvider] = {}
5155
5131
  am: ta.Dict[InjectorKey, ta.List[InjectorProvider]] = {}
@@ -5173,6 +5149,31 @@ def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey,
5173
5149
  return pm
5174
5150
 
5175
5151
 
5152
+ ###
5153
+ # overrides
5154
+
5155
+
5156
+ @dc.dataclass(frozen=True)
5157
+ class OverridesInjectorBindings(InjectorBindings):
5158
+ p: InjectorBindings
5159
+ m: ta.Mapping[InjectorKey, InjectorBinding]
5160
+
5161
+ def bindings(self) -> ta.Iterator[InjectorBinding]:
5162
+ for b in self.p.bindings():
5163
+ yield self.m.get(b.key, b)
5164
+
5165
+
5166
+ def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
5167
+ m: ta.Dict[InjectorKey, InjectorBinding] = {}
5168
+
5169
+ for b in as_injector_bindings(*args).bindings():
5170
+ if b.key in m:
5171
+ raise DuplicateInjectorKeyError(b.key)
5172
+ m[b.key] = b
5173
+
5174
+ return OverridesInjectorBindings(p, m)
5175
+
5176
+
5176
5177
  ###
5177
5178
  # scopes
5178
5179
 
@@ -5197,7 +5198,7 @@ class InjectorScope(abc.ABC): # noqa
5197
5198
  @dc.dataclass(frozen=True)
5198
5199
  class State:
5199
5200
  seeds: ta.Dict[InjectorKey, ta.Any]
5200
- prvs: ta.Dict[InjectorKey, ta.Any] = dc.field(default_factory=dict)
5201
+ provisions: ta.Dict[InjectorKey, ta.Any] = dc.field(default_factory=dict)
5201
5202
 
5202
5203
  def new_state(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> State:
5203
5204
  vs = dict(vs)
@@ -5277,11 +5278,11 @@ class ScopedInjectorProvider(InjectorProvider):
5277
5278
  def pfn(i: Injector) -> ta.Any:
5278
5279
  st = i[self.sc].state()
5279
5280
  try:
5280
- return st.prvs[self.k]
5281
+ return st.provisions[self.k]
5281
5282
  except KeyError:
5282
5283
  pass
5283
5284
  v = ufn(i)
5284
- st.prvs[self.k] = v
5285
+ st.provisions[self.k] = v
5285
5286
  return v
5286
5287
 
5287
5288
  ufn = self.p.provider_fn()
@@ -5305,9 +5306,7 @@ class _ScopeSeedInjectorProvider(InjectorProvider):
5305
5306
 
5306
5307
 
5307
5308
  def bind_injector_scope(sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
5308
- return as_injector_bindings(
5309
- InjectorBinder.bind(sc, singleton=True),
5310
- )
5309
+ return InjectorBinder.bind(sc, singleton=True)
5311
5310
 
5312
5311
 
5313
5312
  #
@@ -5847,6 +5846,8 @@ class InjectionApi:
5847
5846
  def as_bindings(self, *args: InjectorBindingOrBindings) -> InjectorBindings:
5848
5847
  return as_injector_bindings(*args)
5849
5848
 
5849
+ # overrides
5850
+
5850
5851
  def override(self, p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
5851
5852
  return injector_override(p, *args)
5852
5853
 
@@ -6705,6 +6706,9 @@ class TempDirAtomicPathSwapping(AtomicPathSwapping):
6705
6706
  # ../../../omdev/interp/types.py
6706
6707
 
6707
6708
 
6709
+ ##
6710
+
6711
+
6708
6712
  # See https://peps.python.org/pep-3149/
6709
6713
  INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
6710
6714
  ('debug', 'd'),
@@ -6736,6 +6740,9 @@ class InterpOpts:
6736
6740
  return s, cls(**kw)
6737
6741
 
6738
6742
 
6743
+ ##
6744
+
6745
+
6739
6746
  @dc.dataclass(frozen=True)
6740
6747
  class InterpVersion:
6741
6748
  version: Version
@@ -6761,6 +6768,9 @@ class InterpVersion:
6761
6768
  return None
6762
6769
 
6763
6770
 
6771
+ ##
6772
+
6773
+
6764
6774
  @dc.dataclass(frozen=True)
6765
6775
  class InterpSpecifier:
6766
6776
  specifier: Specifier
@@ -6788,12 +6798,25 @@ class InterpSpecifier:
6788
6798
  return self.contains(iv)
6789
6799
 
6790
6800
 
6801
+ ##
6802
+
6803
+
6791
6804
  @dc.dataclass(frozen=True)
6792
6805
  class Interp:
6793
6806
  exe: str
6794
6807
  version: InterpVersion
6795
6808
 
6796
6809
 
6810
+ ########################################
6811
+ # ../../../omdev/interp/uv/inject.py
6812
+
6813
+
6814
+ def bind_interp_uv() -> InjectorBindings:
6815
+ lst: ta.List[InjectorBindingOrBindings] = []
6816
+
6817
+ return inj.as_bindings(*lst)
6818
+
6819
+
6797
6820
  ########################################
6798
6821
  # ../../configs.py
6799
6822
 
@@ -7707,6 +7730,50 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
7707
7730
  return ret.decode().strip()
7708
7731
 
7709
7732
 
7733
+ ########################################
7734
+ # ../../../omdev/interp/providers/base.py
7735
+ """
7736
+ TODO:
7737
+ - backends
7738
+ - local builds
7739
+ - deadsnakes?
7740
+ - uv
7741
+ - loose versions
7742
+ """
7743
+
7744
+
7745
+ ##
7746
+
7747
+
7748
+ class InterpProvider(abc.ABC):
7749
+ name: ta.ClassVar[str]
7750
+
7751
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
7752
+ super().__init_subclass__(**kwargs)
7753
+ if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
7754
+ sfx = 'InterpProvider'
7755
+ if not cls.__name__.endswith(sfx):
7756
+ raise NameError(cls)
7757
+ setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
7758
+
7759
+ @abc.abstractmethod
7760
+ def get_installed_versions(self, spec: InterpSpecifier) -> ta.Awaitable[ta.Sequence[InterpVersion]]:
7761
+ raise NotImplementedError
7762
+
7763
+ @abc.abstractmethod
7764
+ def get_installed_version(self, version: InterpVersion) -> ta.Awaitable[Interp]:
7765
+ raise NotImplementedError
7766
+
7767
+ async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
7768
+ return []
7769
+
7770
+ async def install_version(self, version: InterpVersion) -> Interp:
7771
+ raise TypeError
7772
+
7773
+
7774
+ InterpProviders = ta.NewType('InterpProviders', ta.Sequence[InterpProvider])
7775
+
7776
+
7710
7777
  ########################################
7711
7778
  # ../bootstrap.py
7712
7779
 
@@ -8841,7 +8908,92 @@ class InterpInspector:
8841
8908
  return ret
8842
8909
 
8843
8910
 
8844
- INTERP_INSPECTOR = InterpInspector()
8911
+ ########################################
8912
+ # ../../../omdev/interp/resolvers.py
8913
+
8914
+
8915
+ @dc.dataclass(frozen=True)
8916
+ class InterpResolverProviders:
8917
+ providers: ta.Sequence[ta.Tuple[str, InterpProvider]]
8918
+
8919
+
8920
+ class InterpResolver:
8921
+ def __init__(
8922
+ self,
8923
+ providers: InterpResolverProviders,
8924
+ ) -> None:
8925
+ super().__init__()
8926
+
8927
+ self._providers: ta.Mapping[str, InterpProvider] = collections.OrderedDict(providers.providers)
8928
+
8929
+ async def _resolve_installed(self, spec: InterpSpecifier) -> ta.Optional[ta.Tuple[InterpProvider, InterpVersion]]:
8930
+ lst = [
8931
+ (i, si)
8932
+ for i, p in enumerate(self._providers.values())
8933
+ for si in await p.get_installed_versions(spec)
8934
+ if spec.contains(si)
8935
+ ]
8936
+
8937
+ slst = sorted(lst, key=lambda t: (-t[0], t[1].version))
8938
+ if not slst:
8939
+ return None
8940
+
8941
+ bi, bv = slst[-1]
8942
+ bp = list(self._providers.values())[bi]
8943
+ return (bp, bv)
8944
+
8945
+ async def resolve(
8946
+ self,
8947
+ spec: InterpSpecifier,
8948
+ *,
8949
+ install: bool = False,
8950
+ ) -> ta.Optional[Interp]:
8951
+ tup = await self._resolve_installed(spec)
8952
+ if tup is not None:
8953
+ bp, bv = tup
8954
+ return await bp.get_installed_version(bv)
8955
+
8956
+ if not install:
8957
+ return None
8958
+
8959
+ tp = list(self._providers.values())[0] # noqa
8960
+
8961
+ sv = sorted(
8962
+ [s for s in await tp.get_installable_versions(spec) if s in spec],
8963
+ key=lambda s: s.version,
8964
+ )
8965
+ if not sv:
8966
+ return None
8967
+
8968
+ bv = sv[-1]
8969
+ return await tp.install_version(bv)
8970
+
8971
+ async def list(self, spec: InterpSpecifier) -> None:
8972
+ print('installed:')
8973
+ for n, p in self._providers.items():
8974
+ lst = [
8975
+ si
8976
+ for si in await p.get_installed_versions(spec)
8977
+ if spec.contains(si)
8978
+ ]
8979
+ if lst:
8980
+ print(f' {n}')
8981
+ for si in lst:
8982
+ print(f' {si}')
8983
+
8984
+ print()
8985
+
8986
+ print('installable:')
8987
+ for n, p in self._providers.items():
8988
+ lst = [
8989
+ si
8990
+ for si in await p.get_installable_versions(spec)
8991
+ if spec.contains(si)
8992
+ ]
8993
+ if lst:
8994
+ print(f' {n}')
8995
+ for si in lst:
8996
+ print(f' {si}')
8845
8997
 
8846
8998
 
8847
8999
  ########################################
@@ -9495,47 +9647,7 @@ class YumSystemPackageManager(SystemPackageManager):
9495
9647
 
9496
9648
 
9497
9649
  ########################################
9498
- # ../../../omdev/interp/providers.py
9499
- """
9500
- TODO:
9501
- - backends
9502
- - local builds
9503
- - deadsnakes?
9504
- - uv
9505
- - loose versions
9506
- """
9507
-
9508
-
9509
- ##
9510
-
9511
-
9512
- class InterpProvider(abc.ABC):
9513
- name: ta.ClassVar[str]
9514
-
9515
- def __init_subclass__(cls, **kwargs: ta.Any) -> None:
9516
- super().__init_subclass__(**kwargs)
9517
- if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
9518
- sfx = 'InterpProvider'
9519
- if not cls.__name__.endswith(sfx):
9520
- raise NameError(cls)
9521
- setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
9522
-
9523
- @abc.abstractmethod
9524
- def get_installed_versions(self, spec: InterpSpecifier) -> ta.Awaitable[ta.Sequence[InterpVersion]]:
9525
- raise NotImplementedError
9526
-
9527
- @abc.abstractmethod
9528
- def get_installed_version(self, version: InterpVersion) -> ta.Awaitable[Interp]:
9529
- raise NotImplementedError
9530
-
9531
- async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
9532
- return []
9533
-
9534
- async def install_version(self, version: InterpVersion) -> Interp:
9535
- raise TypeError
9536
-
9537
-
9538
- ##
9650
+ # ../../../omdev/interp/providers/running.py
9539
9651
 
9540
9652
 
9541
9653
  class RunningInterpProvider(InterpProvider):
@@ -9556,416 +9668,234 @@ class RunningInterpProvider(InterpProvider):
9556
9668
 
9557
9669
 
9558
9670
  ########################################
9559
- # ../commands/inject.py
9671
+ # ../../../omdev/interp/providers/system.py
9672
+ """
9673
+ TODO:
9674
+ - python, python3, python3.12, ...
9675
+ - check if path py's are venvs: sys.prefix != sys.base_prefix
9676
+ """
9560
9677
 
9561
9678
 
9562
9679
  ##
9563
9680
 
9564
9681
 
9565
- def bind_command(
9566
- command_cls: ta.Type[Command],
9567
- executor_cls: ta.Optional[ta.Type[CommandExecutor]],
9568
- ) -> InjectorBindings:
9569
- lst: ta.List[InjectorBindingOrBindings] = [
9570
- inj.bind(CommandRegistration(command_cls), array=True),
9571
- ]
9572
-
9573
- if executor_cls is not None:
9574
- lst.extend([
9575
- inj.bind(executor_cls, singleton=True),
9576
- inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
9577
- ])
9682
+ class SystemInterpProvider(InterpProvider):
9683
+ @dc.dataclass(frozen=True)
9684
+ class Options:
9685
+ cmd: str = 'python3' # FIXME: unused lol
9686
+ path: ta.Optional[str] = None
9578
9687
 
9579
- return inj.as_bindings(*lst)
9688
+ inspect: bool = False
9580
9689
 
9690
+ def __init__(
9691
+ self,
9692
+ options: Options = Options(),
9693
+ *,
9694
+ inspector: ta.Optional[InterpInspector] = None,
9695
+ ) -> None:
9696
+ super().__init__()
9581
9697
 
9582
- ##
9698
+ self._options = options
9583
9699
 
9700
+ self._inspector = inspector
9584
9701
 
9585
- @dc.dataclass(frozen=True)
9586
- class _FactoryCommandExecutor(CommandExecutor):
9587
- factory: ta.Callable[[], CommandExecutor]
9702
+ #
9588
9703
 
9589
- def execute(self, i: Command) -> ta.Awaitable[Command.Output]:
9590
- return self.factory().execute(i)
9704
+ @staticmethod
9705
+ def _re_which(
9706
+ pat: re.Pattern,
9707
+ *,
9708
+ mode: int = os.F_OK | os.X_OK,
9709
+ path: ta.Optional[str] = None,
9710
+ ) -> ta.List[str]:
9711
+ if path is None:
9712
+ path = os.environ.get('PATH', None)
9713
+ if path is None:
9714
+ try:
9715
+ path = os.confstr('CS_PATH')
9716
+ except (AttributeError, ValueError):
9717
+ path = os.defpath
9591
9718
 
9719
+ if not path:
9720
+ return []
9592
9721
 
9593
- ##
9722
+ path = os.fsdecode(path)
9723
+ pathlst = path.split(os.pathsep)
9594
9724
 
9725
+ def _access_check(fn: str, mode: int) -> bool:
9726
+ return os.path.exists(fn) and os.access(fn, mode)
9595
9727
 
9596
- def bind_commands(
9597
- *,
9598
- main_config: MainConfig,
9599
- ) -> InjectorBindings:
9600
- lst: ta.List[InjectorBindingOrBindings] = [
9601
- inj.bind_array(CommandRegistration),
9602
- inj.bind_array_type(CommandRegistration, CommandRegistrations),
9728
+ out = []
9729
+ seen = set()
9730
+ for d in pathlst:
9731
+ normdir = os.path.normcase(d)
9732
+ if normdir not in seen:
9733
+ seen.add(normdir)
9734
+ if not _access_check(normdir, mode):
9735
+ continue
9736
+ for thefile in os.listdir(d):
9737
+ name = os.path.join(d, thefile)
9738
+ if not (
9739
+ os.path.isfile(name) and
9740
+ pat.fullmatch(thefile) and
9741
+ _access_check(name, mode)
9742
+ ):
9743
+ continue
9744
+ out.append(name)
9603
9745
 
9604
- inj.bind_array(CommandExecutorRegistration),
9605
- inj.bind_array_type(CommandExecutorRegistration, CommandExecutorRegistrations),
9746
+ return out
9606
9747
 
9607
- inj.bind(build_command_name_map, singleton=True),
9608
- ]
9748
+ @cached_nullary
9749
+ def exes(self) -> ta.List[str]:
9750
+ return self._re_which(
9751
+ re.compile(r'python3(\.\d+)?'),
9752
+ path=self._options.path,
9753
+ )
9609
9754
 
9610
9755
  #
9611
9756
 
9612
- def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
9613
- return ObjMarshalerInstaller(functools.partial(install_command_marshaling, cmds))
9757
+ async def get_exe_version(self, exe: str) -> ta.Optional[InterpVersion]:
9758
+ if not self._options.inspect:
9759
+ s = os.path.basename(exe)
9760
+ if s.startswith('python'):
9761
+ s = s[len('python'):]
9762
+ if '.' in s:
9763
+ try:
9764
+ return InterpVersion.parse(s)
9765
+ except InvalidVersion:
9766
+ pass
9767
+ ii = await check.not_none(self._inspector).inspect(exe)
9768
+ return ii.iv if ii is not None else None
9614
9769
 
9615
- lst.append(inj.bind(provide_obj_marshaler_installer, array=True))
9770
+ async def exe_versions(self) -> ta.Sequence[ta.Tuple[str, InterpVersion]]:
9771
+ lst = []
9772
+ for e in self.exes():
9773
+ if (ev := await self.get_exe_version(e)) is None:
9774
+ log.debug('Invalid system version: %s', e)
9775
+ continue
9776
+ lst.append((e, ev))
9777
+ return lst
9616
9778
 
9617
9779
  #
9618
9780
 
9619
- def provide_command_executor_map(
9620
- injector: Injector,
9621
- crs: CommandExecutorRegistrations,
9622
- ) -> CommandExecutorMap:
9623
- dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
9624
-
9625
- cr: CommandExecutorRegistration
9626
- for cr in crs:
9627
- if cr.command_cls in dct:
9628
- raise KeyError(cr.command_cls)
9629
-
9630
- factory = functools.partial(injector.provide, cr.executor_cls)
9631
- if main_config.debug:
9632
- ce = factory()
9633
- else:
9634
- ce = _FactoryCommandExecutor(factory)
9635
-
9636
- dct[cr.command_cls] = ce
9637
-
9638
- return CommandExecutorMap(dct)
9639
-
9640
- lst.extend([
9641
- inj.bind(provide_command_executor_map, singleton=True),
9642
-
9643
- inj.bind(LocalCommandExecutor, singleton=True, eager=main_config.debug),
9644
- ])
9645
-
9646
- #
9781
+ async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
9782
+ return [ev for e, ev in await self.exe_versions()]
9647
9783
 
9648
- lst.extend([
9649
- bind_command(PingCommand, PingCommandExecutor),
9650
- bind_command(SubprocessCommand, SubprocessCommandExecutor),
9651
- ])
9784
+ async def get_installed_version(self, version: InterpVersion) -> Interp:
9785
+ for e, ev in await self.exe_versions():
9786
+ if ev != version:
9787
+ continue
9788
+ return Interp(
9789
+ exe=e,
9790
+ version=ev,
9791
+ )
9792
+ raise KeyError(version)
9652
9793
 
9653
- #
9654
9794
 
9655
- return inj.as_bindings(*lst)
9795
+ ########################################
9796
+ # ../../../omdev/interp/pyenv/pyenv.py
9797
+ """
9798
+ TODO:
9799
+ - custom tags
9800
+ - 'aliases'
9801
+ - https://github.com/pyenv/pyenv/pull/2966
9802
+ - https://github.com/pyenv/pyenv/issues/218 (lol)
9803
+ - probably need custom (temp?) definition file
9804
+ - *or* python-build directly just into the versions dir?
9805
+ - optionally install / upgrade pyenv itself
9806
+ - new vers dont need these custom mac opts, only run on old vers
9807
+ """
9656
9808
 
9657
9809
 
9658
- ########################################
9659
- # ../deploy/paths/manager.py
9810
+ ##
9660
9811
 
9661
9812
 
9662
- class DeployPathsManager:
9813
+ class Pyenv:
9663
9814
  def __init__(
9664
9815
  self,
9665
9816
  *,
9666
- deploy_path_owners: DeployPathOwners,
9817
+ root: ta.Optional[str] = None,
9667
9818
  ) -> None:
9819
+ if root is not None and not (isinstance(root, str) and root):
9820
+ raise ValueError(f'pyenv_root: {root!r}')
9821
+
9668
9822
  super().__init__()
9669
9823
 
9670
- self._deploy_path_owners = deploy_path_owners
9824
+ self._root_kw = root
9671
9825
 
9672
- @cached_nullary
9673
- def owners_by_path(self) -> ta.Mapping[DeployPath, DeployPathOwner]:
9674
- dct: ta.Dict[DeployPath, DeployPathOwner] = {}
9675
- for o in self._deploy_path_owners:
9676
- for p in o.get_owned_deploy_paths():
9677
- if p in dct:
9678
- raise DeployPathError(f'Duplicate deploy path owner: {p}')
9679
- dct[p] = o
9680
- return dct
9826
+ @async_cached_nullary
9827
+ async def root(self) -> ta.Optional[str]:
9828
+ if self._root_kw is not None:
9829
+ return self._root_kw
9681
9830
 
9682
- def validate_deploy_paths(self) -> None:
9683
- self.owners_by_path()
9831
+ if shutil.which('pyenv'):
9832
+ return await asyncio_subprocesses.check_output_str('pyenv', 'root')
9684
9833
 
9834
+ d = os.path.expanduser('~/.pyenv')
9835
+ if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
9836
+ return d
9685
9837
 
9686
- ########################################
9687
- # ../deploy/tmp.py
9838
+ return None
9688
9839
 
9840
+ @async_cached_nullary
9841
+ async def exe(self) -> str:
9842
+ return os.path.join(check.not_none(await self.root()), 'bin', 'pyenv')
9689
9843
 
9690
- class DeployHomeAtomics(Func1[DeployHome, AtomicPathSwapping]):
9691
- pass
9844
+ async def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
9845
+ if (root := await self.root()) is None:
9846
+ return []
9847
+ ret = []
9848
+ vp = os.path.join(root, 'versions')
9849
+ if os.path.isdir(vp):
9850
+ for dn in os.listdir(vp):
9851
+ ep = os.path.join(vp, dn, 'bin', 'python')
9852
+ if not os.path.isfile(ep):
9853
+ continue
9854
+ ret.append((dn, ep))
9855
+ return ret
9692
9856
 
9857
+ async def installable_versions(self) -> ta.List[str]:
9858
+ if await self.root() is None:
9859
+ return []
9860
+ ret = []
9861
+ s = await asyncio_subprocesses.check_output_str(await self.exe(), 'install', '--list')
9862
+ for l in s.splitlines():
9863
+ if not l.startswith(' '):
9864
+ continue
9865
+ l = l.strip()
9866
+ if not l:
9867
+ continue
9868
+ ret.append(l)
9869
+ return ret
9693
9870
 
9694
- class DeployTmpManager(
9695
- SingleDirDeployPathOwner,
9696
- ):
9697
- def __init__(self) -> None:
9698
- super().__init__(
9699
- owned_dir='tmp',
9700
- )
9871
+ async def update(self) -> bool:
9872
+ if (root := await self.root()) is None:
9873
+ return False
9874
+ if not os.path.isdir(os.path.join(root, '.git')):
9875
+ return False
9876
+ await asyncio_subprocesses.check_call('git', 'pull', cwd=root)
9877
+ return True
9701
9878
 
9702
- def get_swapping(self, home: DeployHome) -> AtomicPathSwapping:
9703
- return TempDirAtomicPathSwapping(
9704
- temp_dir=self._make_dir(home),
9705
- root_dir=check.non_empty_str(home),
9706
- )
9707
9879
 
9880
+ ##
9708
9881
 
9709
- ########################################
9710
- # ../remote/connection.py
9711
9882
 
9883
+ @dc.dataclass(frozen=True)
9884
+ class PyenvInstallOpts:
9885
+ opts: ta.Sequence[str] = ()
9886
+ conf_opts: ta.Sequence[str] = ()
9887
+ cflags: ta.Sequence[str] = ()
9888
+ ldflags: ta.Sequence[str] = ()
9889
+ env: ta.Mapping[str, str] = dc.field(default_factory=dict)
9712
9890
 
9713
- ##
9714
-
9715
-
9716
- class PyremoteRemoteExecutionConnector:
9717
- def __init__(
9718
- self,
9719
- *,
9720
- spawning: RemoteSpawning,
9721
- msh: ObjMarshalerManager,
9722
- payload_file: ta.Optional[RemoteExecutionPayloadFile] = None,
9723
- ) -> None:
9724
- super().__init__()
9725
-
9726
- self._spawning = spawning
9727
- self._msh = msh
9728
- self._payload_file = payload_file
9729
-
9730
- #
9731
-
9732
- @cached_nullary
9733
- def _payload_src(self) -> str:
9734
- return get_remote_payload_src(file=self._payload_file)
9735
-
9736
- @cached_nullary
9737
- def _remote_src(self) -> ta.Sequence[str]:
9738
- return [
9739
- self._payload_src(),
9740
- '_remote_execution_main()',
9741
- ]
9742
-
9743
- @cached_nullary
9744
- def _spawn_src(self) -> str:
9745
- return pyremote_build_bootstrap_cmd(__package__ or 'manage')
9746
-
9747
- #
9748
-
9749
- @contextlib.asynccontextmanager
9750
- async def connect(
9751
- self,
9752
- tgt: RemoteSpawning.Target,
9753
- bs: MainBootstrap,
9754
- ) -> ta.AsyncGenerator[RemoteCommandExecutor, None]:
9755
- spawn_src = self._spawn_src()
9756
- remote_src = self._remote_src()
9757
-
9758
- async with self._spawning.spawn(
9759
- tgt,
9760
- spawn_src,
9761
- debug=bs.main_config.debug,
9762
- ) as proc:
9763
- res = await PyremoteBootstrapDriver( # noqa
9764
- remote_src,
9765
- PyremoteBootstrapOptions(
9766
- debug=bs.main_config.debug,
9767
- ),
9768
- ).async_run(
9769
- proc.stdout,
9770
- proc.stdin,
9771
- )
9772
-
9773
- chan = RemoteChannelImpl(
9774
- proc.stdout,
9775
- proc.stdin,
9776
- msh=self._msh,
9777
- )
9778
-
9779
- await chan.send_obj(bs)
9780
-
9781
- rce: RemoteCommandExecutor
9782
- async with aclosing(RemoteCommandExecutor(chan)) as rce:
9783
- await rce.start()
9784
-
9785
- yield rce
9786
-
9787
-
9788
- ##
9789
-
9790
-
9791
- class InProcessRemoteExecutionConnector:
9792
- def __init__(
9793
- self,
9794
- *,
9795
- msh: ObjMarshalerManager,
9796
- local_executor: LocalCommandExecutor,
9797
- ) -> None:
9798
- super().__init__()
9799
-
9800
- self._msh = msh
9801
- self._local_executor = local_executor
9802
-
9803
- @contextlib.asynccontextmanager
9804
- async def connect(self) -> ta.AsyncGenerator[RemoteCommandExecutor, None]:
9805
- r0, w0 = asyncio_create_bytes_channel()
9806
- r1, w1 = asyncio_create_bytes_channel()
9807
-
9808
- remote_chan = RemoteChannelImpl(r0, w1, msh=self._msh)
9809
- local_chan = RemoteChannelImpl(r1, w0, msh=self._msh)
9810
-
9811
- rch = _RemoteCommandHandler(
9812
- remote_chan,
9813
- self._local_executor,
9814
- )
9815
- rch_task = asyncio.create_task(rch.run()) # noqa
9816
- try:
9817
- rce: RemoteCommandExecutor
9818
- async with aclosing(RemoteCommandExecutor(local_chan)) as rce:
9819
- await rce.start()
9820
-
9821
- yield rce
9822
-
9823
- finally:
9824
- rch.stop()
9825
- await rch_task
9826
-
9827
-
9828
- ########################################
9829
- # ../system/commands.py
9830
-
9831
-
9832
- ##
9833
-
9834
-
9835
- @dc.dataclass(frozen=True)
9836
- class CheckSystemPackageCommand(Command['CheckSystemPackageCommand.Output']):
9837
- pkgs: ta.Sequence[str] = ()
9838
-
9839
- def __post_init__(self) -> None:
9840
- check.not_isinstance(self.pkgs, str)
9841
-
9842
- @dc.dataclass(frozen=True)
9843
- class Output(Command.Output):
9844
- pkgs: ta.Sequence[SystemPackage]
9845
-
9846
-
9847
- class CheckSystemPackageCommandExecutor(CommandExecutor[CheckSystemPackageCommand, CheckSystemPackageCommand.Output]):
9848
- def __init__(
9849
- self,
9850
- *,
9851
- mgr: SystemPackageManager,
9852
- ) -> None:
9853
- super().__init__()
9854
-
9855
- self._mgr = mgr
9856
-
9857
- async def execute(self, cmd: CheckSystemPackageCommand) -> CheckSystemPackageCommand.Output:
9858
- log.info('Checking system package!')
9859
-
9860
- ret = await self._mgr.query(*cmd.pkgs)
9861
-
9862
- return CheckSystemPackageCommand.Output(list(ret.values()))
9863
-
9864
-
9865
- ########################################
9866
- # ../../../omdev/interp/pyenv.py
9867
- """
9868
- TODO:
9869
- - custom tags
9870
- - 'aliases'
9871
- - https://github.com/pyenv/pyenv/pull/2966
9872
- - https://github.com/pyenv/pyenv/issues/218 (lol)
9873
- - probably need custom (temp?) definition file
9874
- - *or* python-build directly just into the versions dir?
9875
- - optionally install / upgrade pyenv itself
9876
- - new vers dont need these custom mac opts, only run on old vers
9877
- """
9878
-
9879
-
9880
- ##
9881
-
9882
-
9883
- class Pyenv:
9884
- def __init__(
9885
- self,
9886
- *,
9887
- root: ta.Optional[str] = None,
9888
- ) -> None:
9889
- if root is not None and not (isinstance(root, str) and root):
9890
- raise ValueError(f'pyenv_root: {root!r}')
9891
-
9892
- super().__init__()
9893
-
9894
- self._root_kw = root
9895
-
9896
- @async_cached_nullary
9897
- async def root(self) -> ta.Optional[str]:
9898
- if self._root_kw is not None:
9899
- return self._root_kw
9900
-
9901
- if shutil.which('pyenv'):
9902
- return await asyncio_subprocesses.check_output_str('pyenv', 'root')
9903
-
9904
- d = os.path.expanduser('~/.pyenv')
9905
- if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
9906
- return d
9907
-
9908
- return None
9909
-
9910
- @async_cached_nullary
9911
- async def exe(self) -> str:
9912
- return os.path.join(check.not_none(await self.root()), 'bin', 'pyenv')
9913
-
9914
- async def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
9915
- if (root := await self.root()) is None:
9916
- return []
9917
- ret = []
9918
- vp = os.path.join(root, 'versions')
9919
- if os.path.isdir(vp):
9920
- for dn in os.listdir(vp):
9921
- ep = os.path.join(vp, dn, 'bin', 'python')
9922
- if not os.path.isfile(ep):
9923
- continue
9924
- ret.append((dn, ep))
9925
- return ret
9926
-
9927
- async def installable_versions(self) -> ta.List[str]:
9928
- if await self.root() is None:
9929
- return []
9930
- ret = []
9931
- s = await asyncio_subprocesses.check_output_str(await self.exe(), 'install', '--list')
9932
- for l in s.splitlines():
9933
- if not l.startswith(' '):
9934
- continue
9935
- l = l.strip()
9936
- if not l:
9937
- continue
9938
- ret.append(l)
9939
- return ret
9940
-
9941
- async def update(self) -> bool:
9942
- if (root := await self.root()) is None:
9943
- return False
9944
- if not os.path.isdir(os.path.join(root, '.git')):
9945
- return False
9946
- await asyncio_subprocesses.check_call('git', 'pull', cwd=root)
9947
- return True
9948
-
9949
-
9950
- ##
9951
-
9952
-
9953
- @dc.dataclass(frozen=True)
9954
- class PyenvInstallOpts:
9955
- opts: ta.Sequence[str] = ()
9956
- conf_opts: ta.Sequence[str] = ()
9957
- cflags: ta.Sequence[str] = ()
9958
- ldflags: ta.Sequence[str] = ()
9959
- env: ta.Mapping[str, str] = dc.field(default_factory=dict)
9960
-
9961
- def merge(self, *others: 'PyenvInstallOpts') -> 'PyenvInstallOpts':
9962
- return PyenvInstallOpts(
9963
- opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
9964
- conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
9965
- cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
9966
- ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
9967
- env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
9968
- )
9891
+ def merge(self, *others: 'PyenvInstallOpts') -> 'PyenvInstallOpts':
9892
+ return PyenvInstallOpts(
9893
+ opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
9894
+ conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
9895
+ cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
9896
+ ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
9897
+ env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
9898
+ )
9969
9899
 
9970
9900
 
9971
9901
  # TODO: https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-for-maximum-performance
@@ -10093,9 +10023,10 @@ class PyenvVersionInstaller:
10093
10023
  opts: ta.Optional[PyenvInstallOpts] = None,
10094
10024
  interp_opts: InterpOpts = InterpOpts(),
10095
10025
  *,
10026
+ pyenv: Pyenv,
10027
+
10096
10028
  install_name: ta.Optional[str] = None,
10097
10029
  no_default_opts: bool = False,
10098
- pyenv: Pyenv = Pyenv(),
10099
10030
  ) -> None:
10100
10031
  super().__init__()
10101
10032
 
@@ -10185,26 +10116,26 @@ class PyenvVersionInstaller:
10185
10116
 
10186
10117
 
10187
10118
  class PyenvInterpProvider(InterpProvider):
10188
- def __init__(
10189
- self,
10190
- pyenv: Pyenv = Pyenv(),
10119
+ @dc.dataclass(frozen=True)
10120
+ class Options:
10121
+ inspect: bool = False
10191
10122
 
10192
- inspect: bool = False,
10193
- inspector: InterpInspector = INTERP_INSPECTOR,
10123
+ try_update: bool = False
10194
10124
 
10125
+ def __init__(
10126
+ self,
10127
+ options: Options = Options(),
10195
10128
  *,
10196
-
10197
- try_update: bool = False,
10129
+ pyenv: Pyenv,
10130
+ inspector: InterpInspector,
10198
10131
  ) -> None:
10199
10132
  super().__init__()
10200
10133
 
10201
- self._pyenv = pyenv
10134
+ self._options = options
10202
10135
 
10203
- self._inspect = inspect
10136
+ self._pyenv = pyenv
10204
10137
  self._inspector = inspector
10205
10138
 
10206
- self._try_update = try_update
10207
-
10208
10139
  #
10209
10140
 
10210
10141
  @staticmethod
@@ -10229,7 +10160,7 @@ class PyenvInterpProvider(InterpProvider):
10229
10160
 
10230
10161
  async def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
10231
10162
  iv: ta.Optional[InterpVersion]
10232
- if self._inspect:
10163
+ if self._options.inspect:
10233
10164
  try:
10234
10165
  iv = check.not_none(await self._inspector.inspect(ep)).iv
10235
10166
  except Exception as e: # noqa
@@ -10285,7 +10216,7 @@ class PyenvInterpProvider(InterpProvider):
10285
10216
  async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
10286
10217
  lst = await self._get_installable_versions(spec)
10287
10218
 
10288
- if self._try_update and not any(v in spec for v in lst):
10219
+ if self._options.try_update and not any(v in spec for v in lst):
10289
10220
  if self._pyenv.update():
10290
10221
  lst = await self._get_installable_versions(spec)
10291
10222
 
@@ -10301,6 +10232,7 @@ class PyenvInterpProvider(InterpProvider):
10301
10232
  installer = PyenvVersionInstaller(
10302
10233
  inst_version,
10303
10234
  interp_opts=inst_opts,
10235
+ pyenv=self._pyenv,
10304
10236
  )
10305
10237
 
10306
10238
  exe = await installer.install()
@@ -10308,116 +10240,344 @@ class PyenvInterpProvider(InterpProvider):
10308
10240
 
10309
10241
 
10310
10242
  ########################################
10311
- # ../../../omdev/interp/system.py
10312
- """
10313
- TODO:
10314
- - python, python3, python3.12, ...
10315
- - check if path py's are venvs: sys.prefix != sys.base_prefix
10316
- """
10243
+ # ../commands/inject.py
10244
+
10245
+
10246
+ ##
10247
+
10248
+
10249
+ def bind_command(
10250
+ command_cls: ta.Type[Command],
10251
+ executor_cls: ta.Optional[ta.Type[CommandExecutor]],
10252
+ ) -> InjectorBindings:
10253
+ lst: ta.List[InjectorBindingOrBindings] = [
10254
+ inj.bind(CommandRegistration(command_cls), array=True),
10255
+ ]
10256
+
10257
+ if executor_cls is not None:
10258
+ lst.extend([
10259
+ inj.bind(executor_cls, singleton=True),
10260
+ inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
10261
+ ])
10262
+
10263
+ return inj.as_bindings(*lst)
10264
+
10265
+
10266
+ ##
10267
+
10268
+
10269
+ @dc.dataclass(frozen=True)
10270
+ class _FactoryCommandExecutor(CommandExecutor):
10271
+ factory: ta.Callable[[], CommandExecutor]
10272
+
10273
+ def execute(self, i: Command) -> ta.Awaitable[Command.Output]:
10274
+ return self.factory().execute(i)
10275
+
10276
+
10277
+ ##
10278
+
10279
+
10280
+ def bind_commands(
10281
+ *,
10282
+ main_config: MainConfig,
10283
+ ) -> InjectorBindings:
10284
+ lst: ta.List[InjectorBindingOrBindings] = [
10285
+ inj.bind_array(CommandRegistration),
10286
+ inj.bind_array_type(CommandRegistration, CommandRegistrations),
10287
+
10288
+ inj.bind_array(CommandExecutorRegistration),
10289
+ inj.bind_array_type(CommandExecutorRegistration, CommandExecutorRegistrations),
10290
+
10291
+ inj.bind(build_command_name_map, singleton=True),
10292
+ ]
10293
+
10294
+ #
10295
+
10296
+ def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
10297
+ return ObjMarshalerInstaller(functools.partial(install_command_marshaling, cmds))
10298
+
10299
+ lst.append(inj.bind(provide_obj_marshaler_installer, array=True))
10300
+
10301
+ #
10302
+
10303
+ def provide_command_executor_map(
10304
+ injector: Injector,
10305
+ crs: CommandExecutorRegistrations,
10306
+ ) -> CommandExecutorMap:
10307
+ dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
10308
+
10309
+ cr: CommandExecutorRegistration
10310
+ for cr in crs:
10311
+ if cr.command_cls in dct:
10312
+ raise KeyError(cr.command_cls)
10313
+
10314
+ factory = functools.partial(injector.provide, cr.executor_cls)
10315
+ if main_config.debug:
10316
+ ce = factory()
10317
+ else:
10318
+ ce = _FactoryCommandExecutor(factory)
10319
+
10320
+ dct[cr.command_cls] = ce
10321
+
10322
+ return CommandExecutorMap(dct)
10323
+
10324
+ lst.extend([
10325
+ inj.bind(provide_command_executor_map, singleton=True),
10326
+
10327
+ inj.bind(LocalCommandExecutor, singleton=True, eager=main_config.debug),
10328
+ ])
10329
+
10330
+ #
10331
+
10332
+ lst.extend([
10333
+ bind_command(PingCommand, PingCommandExecutor),
10334
+ bind_command(SubprocessCommand, SubprocessCommandExecutor),
10335
+ ])
10336
+
10337
+ #
10338
+
10339
+ return inj.as_bindings(*lst)
10340
+
10341
+
10342
+ ########################################
10343
+ # ../deploy/paths/manager.py
10344
+
10345
+
10346
+ class DeployPathsManager:
10347
+ def __init__(
10348
+ self,
10349
+ *,
10350
+ deploy_path_owners: DeployPathOwners,
10351
+ ) -> None:
10352
+ super().__init__()
10353
+
10354
+ self._deploy_path_owners = deploy_path_owners
10355
+
10356
+ @cached_nullary
10357
+ def owners_by_path(self) -> ta.Mapping[DeployPath, DeployPathOwner]:
10358
+ dct: ta.Dict[DeployPath, DeployPathOwner] = {}
10359
+ for o in self._deploy_path_owners:
10360
+ for p in o.get_owned_deploy_paths():
10361
+ if p in dct:
10362
+ raise DeployPathError(f'Duplicate deploy path owner: {p}')
10363
+ dct[p] = o
10364
+ return dct
10365
+
10366
+ def validate_deploy_paths(self) -> None:
10367
+ self.owners_by_path()
10368
+
10369
+
10370
+ ########################################
10371
+ # ../deploy/tmp.py
10372
+
10373
+
10374
+ class DeployHomeAtomics(Func1[DeployHome, AtomicPathSwapping]):
10375
+ pass
10376
+
10377
+
10378
+ class DeployTmpManager(
10379
+ SingleDirDeployPathOwner,
10380
+ ):
10381
+ def __init__(self) -> None:
10382
+ super().__init__(
10383
+ owned_dir='tmp',
10384
+ )
10385
+
10386
+ def get_swapping(self, home: DeployHome) -> AtomicPathSwapping:
10387
+ return TempDirAtomicPathSwapping(
10388
+ temp_dir=self._make_dir(home),
10389
+ root_dir=check.non_empty_str(home),
10390
+ )
10391
+
10392
+
10393
+ ########################################
10394
+ # ../remote/connection.py
10395
+
10396
+
10397
+ ##
10398
+
10399
+
10400
+ class PyremoteRemoteExecutionConnector:
10401
+ def __init__(
10402
+ self,
10403
+ *,
10404
+ spawning: RemoteSpawning,
10405
+ msh: ObjMarshalerManager,
10406
+ payload_file: ta.Optional[RemoteExecutionPayloadFile] = None,
10407
+ ) -> None:
10408
+ super().__init__()
10409
+
10410
+ self._spawning = spawning
10411
+ self._msh = msh
10412
+ self._payload_file = payload_file
10413
+
10414
+ #
10415
+
10416
+ @cached_nullary
10417
+ def _payload_src(self) -> str:
10418
+ return get_remote_payload_src(file=self._payload_file)
10419
+
10420
+ @cached_nullary
10421
+ def _remote_src(self) -> ta.Sequence[str]:
10422
+ return [
10423
+ self._payload_src(),
10424
+ '_remote_execution_main()',
10425
+ ]
10426
+
10427
+ @cached_nullary
10428
+ def _spawn_src(self) -> str:
10429
+ return pyremote_build_bootstrap_cmd(__package__ or 'manage')
10430
+
10431
+ #
10432
+
10433
+ @contextlib.asynccontextmanager
10434
+ async def connect(
10435
+ self,
10436
+ tgt: RemoteSpawning.Target,
10437
+ bs: MainBootstrap,
10438
+ ) -> ta.AsyncGenerator[RemoteCommandExecutor, None]:
10439
+ spawn_src = self._spawn_src()
10440
+ remote_src = self._remote_src()
10441
+
10442
+ async with self._spawning.spawn(
10443
+ tgt,
10444
+ spawn_src,
10445
+ debug=bs.main_config.debug,
10446
+ ) as proc:
10447
+ res = await PyremoteBootstrapDriver( # noqa
10448
+ remote_src,
10449
+ PyremoteBootstrapOptions(
10450
+ debug=bs.main_config.debug,
10451
+ ),
10452
+ ).async_run(
10453
+ proc.stdout,
10454
+ proc.stdin,
10455
+ )
10456
+
10457
+ chan = RemoteChannelImpl(
10458
+ proc.stdout,
10459
+ proc.stdin,
10460
+ msh=self._msh,
10461
+ )
10462
+
10463
+ await chan.send_obj(bs)
10464
+
10465
+ rce: RemoteCommandExecutor
10466
+ async with aclosing(RemoteCommandExecutor(chan)) as rce:
10467
+ await rce.start()
10468
+
10469
+ yield rce
10470
+
10471
+
10472
+ ##
10473
+
10474
+
10475
+ class InProcessRemoteExecutionConnector:
10476
+ def __init__(
10477
+ self,
10478
+ *,
10479
+ msh: ObjMarshalerManager,
10480
+ local_executor: LocalCommandExecutor,
10481
+ ) -> None:
10482
+ super().__init__()
10483
+
10484
+ self._msh = msh
10485
+ self._local_executor = local_executor
10486
+
10487
+ @contextlib.asynccontextmanager
10488
+ async def connect(self) -> ta.AsyncGenerator[RemoteCommandExecutor, None]:
10489
+ r0, w0 = asyncio_create_bytes_channel()
10490
+ r1, w1 = asyncio_create_bytes_channel()
10491
+
10492
+ remote_chan = RemoteChannelImpl(r0, w1, msh=self._msh)
10493
+ local_chan = RemoteChannelImpl(r1, w0, msh=self._msh)
10494
+
10495
+ rch = _RemoteCommandHandler(
10496
+ remote_chan,
10497
+ self._local_executor,
10498
+ )
10499
+ rch_task = asyncio.create_task(rch.run()) # noqa
10500
+ try:
10501
+ rce: RemoteCommandExecutor
10502
+ async with aclosing(RemoteCommandExecutor(local_chan)) as rce:
10503
+ await rce.start()
10504
+
10505
+ yield rce
10506
+
10507
+ finally:
10508
+ rch.stop()
10509
+ await rch_task
10510
+
10511
+
10512
+ ########################################
10513
+ # ../system/commands.py
10514
+
10515
+
10516
+ ##
10517
+
10518
+
10519
+ @dc.dataclass(frozen=True)
10520
+ class CheckSystemPackageCommand(Command['CheckSystemPackageCommand.Output']):
10521
+ pkgs: ta.Sequence[str] = ()
10522
+
10523
+ def __post_init__(self) -> None:
10524
+ check.not_isinstance(self.pkgs, str)
10525
+
10526
+ @dc.dataclass(frozen=True)
10527
+ class Output(Command.Output):
10528
+ pkgs: ta.Sequence[SystemPackage]
10317
10529
 
10318
10530
 
10319
- ##
10531
+ class CheckSystemPackageCommandExecutor(CommandExecutor[CheckSystemPackageCommand, CheckSystemPackageCommand.Output]):
10532
+ def __init__(
10533
+ self,
10534
+ *,
10535
+ mgr: SystemPackageManager,
10536
+ ) -> None:
10537
+ super().__init__()
10320
10538
 
10539
+ self._mgr = mgr
10321
10540
 
10322
- @dc.dataclass(frozen=True)
10323
- class SystemInterpProvider(InterpProvider):
10324
- cmd: str = 'python3'
10325
- path: ta.Optional[str] = None
10541
+ async def execute(self, cmd: CheckSystemPackageCommand) -> CheckSystemPackageCommand.Output:
10542
+ log.info('Checking system package!')
10326
10543
 
10327
- inspect: bool = False
10328
- inspector: InterpInspector = INTERP_INSPECTOR
10544
+ ret = await self._mgr.query(*cmd.pkgs)
10329
10545
 
10330
- #
10546
+ return CheckSystemPackageCommand.Output(list(ret.values()))
10331
10547
 
10332
- @staticmethod
10333
- def _re_which(
10334
- pat: re.Pattern,
10335
- *,
10336
- mode: int = os.F_OK | os.X_OK,
10337
- path: ta.Optional[str] = None,
10338
- ) -> ta.List[str]:
10339
- if path is None:
10340
- path = os.environ.get('PATH', None)
10341
- if path is None:
10342
- try:
10343
- path = os.confstr('CS_PATH')
10344
- except (AttributeError, ValueError):
10345
- path = os.defpath
10346
10548
 
10347
- if not path:
10348
- return []
10549
+ ########################################
10550
+ # ../../../omdev/interp/providers/inject.py
10349
10551
 
10350
- path = os.fsdecode(path)
10351
- pathlst = path.split(os.pathsep)
10352
10552
 
10353
- def _access_check(fn: str, mode: int) -> bool:
10354
- return os.path.exists(fn) and os.access(fn, mode)
10553
+ def bind_interp_providers() -> InjectorBindings:
10554
+ lst: ta.List[InjectorBindingOrBindings] = [
10555
+ inj.bind_array(InterpProvider),
10556
+ inj.bind_array_type(InterpProvider, InterpProviders),
10355
10557
 
10356
- out = []
10357
- seen = set()
10358
- for d in pathlst:
10359
- normdir = os.path.normcase(d)
10360
- if normdir not in seen:
10361
- seen.add(normdir)
10362
- if not _access_check(normdir, mode):
10363
- continue
10364
- for thefile in os.listdir(d):
10365
- name = os.path.join(d, thefile)
10366
- if not (
10367
- os.path.isfile(name) and
10368
- pat.fullmatch(thefile) and
10369
- _access_check(name, mode)
10370
- ):
10371
- continue
10372
- out.append(name)
10558
+ inj.bind(RunningInterpProvider, singleton=True),
10559
+ inj.bind(InterpProvider, to_key=RunningInterpProvider, array=True),
10373
10560
 
10374
- return out
10561
+ inj.bind(SystemInterpProvider, singleton=True),
10562
+ inj.bind(InterpProvider, to_key=SystemInterpProvider, array=True),
10563
+ ]
10375
10564
 
10376
- @cached_nullary
10377
- def exes(self) -> ta.List[str]:
10378
- return self._re_which(
10379
- re.compile(r'python3(\.\d+)?'),
10380
- path=self.path,
10381
- )
10565
+ return inj.as_bindings(*lst)
10382
10566
 
10383
- #
10384
10567
 
10385
- async def get_exe_version(self, exe: str) -> ta.Optional[InterpVersion]:
10386
- if not self.inspect:
10387
- s = os.path.basename(exe)
10388
- if s.startswith('python'):
10389
- s = s[len('python'):]
10390
- if '.' in s:
10391
- try:
10392
- return InterpVersion.parse(s)
10393
- except InvalidVersion:
10394
- pass
10395
- ii = await self.inspector.inspect(exe)
10396
- return ii.iv if ii is not None else None
10568
+ ########################################
10569
+ # ../../../omdev/interp/pyenv/inject.py
10397
10570
 
10398
- async def exe_versions(self) -> ta.Sequence[ta.Tuple[str, InterpVersion]]:
10399
- lst = []
10400
- for e in self.exes():
10401
- if (ev := await self.get_exe_version(e)) is None:
10402
- log.debug('Invalid system version: %s', e)
10403
- continue
10404
- lst.append((e, ev))
10405
- return lst
10406
10571
 
10407
- #
10572
+ def bind_interp_pyenv() -> InjectorBindings:
10573
+ lst: ta.List[InjectorBindingOrBindings] = [
10574
+ inj.bind(Pyenv, singleton=True),
10408
10575
 
10409
- async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
10410
- return [ev for e, ev in await self.exe_versions()]
10576
+ inj.bind(PyenvInterpProvider, singleton=True),
10577
+ inj.bind(InterpProvider, to_key=PyenvInterpProvider, array=True),
10578
+ ]
10411
10579
 
10412
- async def get_installed_version(self, version: InterpVersion) -> Interp:
10413
- for e, ev in await self.exe_versions():
10414
- if ev != version:
10415
- continue
10416
- return Interp(
10417
- exe=e,
10418
- version=ev,
10419
- )
10420
- raise KeyError(version)
10580
+ return inj.as_bindings(*lst)
10421
10581
 
10422
10582
 
10423
10583
  ########################################
@@ -10784,101 +10944,44 @@ class SshManageTargetConnector(ManageTargetConnector):
10784
10944
 
10785
10945
 
10786
10946
  ########################################
10787
- # ../../../omdev/interp/resolvers.py
10788
-
10789
-
10790
- INTERP_PROVIDER_TYPES_BY_NAME: ta.Mapping[str, ta.Type[InterpProvider]] = {
10791
- cls.name: cls for cls in deep_subclasses(InterpProvider) if abc.ABC not in cls.__bases__ # type: ignore
10792
- }
10793
-
10794
-
10795
- class InterpResolver:
10796
- def __init__(
10797
- self,
10798
- providers: ta.Sequence[ta.Tuple[str, InterpProvider]],
10799
- ) -> None:
10800
- super().__init__()
10801
-
10802
- self._providers: ta.Mapping[str, InterpProvider] = collections.OrderedDict(providers)
10803
-
10804
- async def _resolve_installed(self, spec: InterpSpecifier) -> ta.Optional[ta.Tuple[InterpProvider, InterpVersion]]:
10805
- lst = [
10806
- (i, si)
10807
- for i, p in enumerate(self._providers.values())
10808
- for si in await p.get_installed_versions(spec)
10809
- if spec.contains(si)
10810
- ]
10811
-
10812
- slst = sorted(lst, key=lambda t: (-t[0], t[1].version))
10813
- if not slst:
10814
- return None
10947
+ # ../../../omdev/interp/inject.py
10815
10948
 
10816
- bi, bv = slst[-1]
10817
- bp = list(self._providers.values())[bi]
10818
- return (bp, bv)
10819
10949
 
10820
- async def resolve(
10821
- self,
10822
- spec: InterpSpecifier,
10823
- *,
10824
- install: bool = False,
10825
- ) -> ta.Optional[Interp]:
10826
- tup = await self._resolve_installed(spec)
10827
- if tup is not None:
10828
- bp, bv = tup
10829
- return await bp.get_installed_version(bv)
10950
+ def bind_interp() -> InjectorBindings:
10951
+ lst: ta.List[InjectorBindingOrBindings] = [
10952
+ bind_interp_providers(),
10830
10953
 
10831
- if not install:
10832
- return None
10954
+ bind_interp_pyenv(),
10833
10955
 
10834
- tp = list(self._providers.values())[0] # noqa
10956
+ bind_interp_uv(),
10835
10957
 
10836
- sv = sorted(
10837
- [s for s in await tp.get_installable_versions(spec) if s in spec],
10838
- key=lambda s: s.version,
10839
- )
10840
- if not sv:
10841
- return None
10958
+ inj.bind(InterpInspector, singleton=True),
10959
+ ]
10842
10960
 
10843
- bv = sv[-1]
10844
- return await tp.install_version(bv)
10961
+ #
10845
10962
 
10846
- async def list(self, spec: InterpSpecifier) -> None:
10847
- print('installed:')
10848
- for n, p in self._providers.items():
10849
- lst = [
10850
- si
10851
- for si in await p.get_installed_versions(spec)
10852
- if spec.contains(si)
10963
+ def provide_interp_resolver_providers(injector: Injector) -> InterpResolverProviders:
10964
+ # FIXME: lol
10965
+ rps: ta.List[ta.Any] = [
10966
+ injector.provide(c)
10967
+ for c in [
10968
+ PyenvInterpProvider,
10969
+ RunningInterpProvider,
10970
+ SystemInterpProvider,
10853
10971
  ]
10854
- if lst:
10855
- print(f' {n}')
10856
- for si in lst:
10857
- print(f' {si}')
10858
-
10859
- print()
10972
+ ]
10860
10973
 
10861
- print('installable:')
10862
- for n, p in self._providers.items():
10863
- lst = [
10864
- si
10865
- for si in await p.get_installable_versions(spec)
10866
- if spec.contains(si)
10867
- ]
10868
- if lst:
10869
- print(f' {n}')
10870
- for si in lst:
10871
- print(f' {si}')
10974
+ return InterpResolverProviders([(rp.name, rp) for rp in rps])
10872
10975
 
10976
+ lst.append(inj.bind(provide_interp_resolver_providers, singleton=True))
10873
10977
 
10874
- DEFAULT_INTERP_RESOLVER = InterpResolver([(p.name, p) for p in [
10875
- # pyenv is preferred to system interpreters as it tends to have more support for things like tkinter
10876
- PyenvInterpProvider(try_update=True),
10978
+ lst.extend([
10979
+ inj.bind(InterpResolver, singleton=True),
10980
+ ])
10877
10981
 
10878
- RunningInterpProvider(),
10982
+ #
10879
10983
 
10880
- SystemInterpProvider(),
10881
- ]])
10984
+ return inj.as_bindings(*lst)
10882
10985
 
10883
10986
 
10884
10987
  ########################################
@@ -10910,6 +11013,15 @@ def bind_targets() -> InjectorBindings:
10910
11013
  return inj.as_bindings(*lst)
10911
11014
 
10912
11015
 
11016
+ ########################################
11017
+ # ../../../omdev/interp/default.py
11018
+
11019
+
11020
+ @cached_nullary
11021
+ def get_default_interp_resolver() -> InterpResolver:
11022
+ return inj.create_injector(bind_interp())[InterpResolver]
11023
+
11024
+
10913
11025
  ########################################
10914
11026
  # ../deploy/interp.py
10915
11027
 
@@ -10932,7 +11044,7 @@ class InterpCommand(Command['InterpCommand.Output']):
10932
11044
  class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
10933
11045
  async def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
10934
11046
  i = InterpSpecifier.parse(check.not_none(cmd.spec))
10935
- o = check.not_none(await DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
11047
+ o = check.not_none(await get_default_interp_resolver().resolve(i, install=cmd.install))
10936
11048
  return InterpCommand.Output(
10937
11049
  exe=o.exe,
10938
11050
  version=str(o.version.version),
@@ -10959,7 +11071,7 @@ class DeployVenvManager:
10959
11071
  ) -> None:
10960
11072
  if spec.interp is not None:
10961
11073
  i = InterpSpecifier.parse(check.not_none(spec.interp))
10962
- o = check.not_none(await DEFAULT_INTERP_RESOLVER.resolve(i))
11074
+ o = check.not_none(await get_default_interp_resolver().resolve(i))
10963
11075
  sys_exe = o.exe
10964
11076
  else:
10965
11077
  sys_exe = 'python3'
@@ -11166,16 +11278,18 @@ class DeployDriver:
11166
11278
  self,
11167
11279
  *,
11168
11280
  spec: DeploySpec,
11281
+ home: DeployHome,
11282
+ time: DeployTime,
11169
11283
 
11170
- deploys: DeployManager,
11171
11284
  paths: DeployPathsManager,
11172
11285
  apps: DeployAppManager,
11173
11286
  ) -> None:
11174
11287
  super().__init__()
11175
11288
 
11176
11289
  self._spec = spec
11290
+ self._home = home
11291
+ self._time = time
11177
11292
 
11178
- self._deploys = deploys
11179
11293
  self._paths = paths
11180
11294
  self._apps = apps
11181
11295
 
@@ -11184,17 +11298,8 @@ class DeployDriver:
11184
11298
 
11185
11299
  #
11186
11300
 
11187
- hs = check.non_empty_str(self._spec.home)
11188
- hs = os.path.expanduser(hs)
11189
- hs = os.path.realpath(hs)
11190
- hs = os.path.abspath(hs)
11191
-
11192
- home = DeployHome(hs)
11193
-
11194
- #
11195
-
11196
11301
  deploy_tags = DeployTagMap(
11197
- self._deploys.make_deploy_time(),
11302
+ self._time,
11198
11303
  self._spec.key(),
11199
11304
  )
11200
11305
 
@@ -11209,7 +11314,7 @@ class DeployDriver:
11209
11314
 
11210
11315
  await self._apps.prepare_app(
11211
11316
  app,
11212
- home,
11317
+ self._home,
11213
11318
  app_tags,
11214
11319
  )
11215
11320
 
@@ -11247,10 +11352,57 @@ class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]
11247
11352
  # ../deploy/inject.py
11248
11353
 
11249
11354
 
11355
+ ##
11356
+
11357
+
11250
11358
  class DeployInjectorScope(ContextvarInjectorScope):
11251
11359
  pass
11252
11360
 
11253
11361
 
11362
+ def bind_deploy_scope() -> InjectorBindings:
11363
+ lst: ta.List[InjectorBindingOrBindings] = [
11364
+ inj.bind_scope(DeployInjectorScope),
11365
+ inj.bind_scope_seed(DeploySpec, DeployInjectorScope),
11366
+
11367
+ inj.bind(DeployDriver, in_=DeployInjectorScope),
11368
+ ]
11369
+
11370
+ #
11371
+
11372
+ def provide_deploy_driver_factory(injector: Injector, sc: DeployInjectorScope) -> DeployDriverFactory:
11373
+ @contextlib.contextmanager
11374
+ def factory(spec: DeploySpec) -> ta.Iterator[DeployDriver]:
11375
+ with sc.enter({
11376
+ inj.as_key(DeploySpec): spec,
11377
+ }):
11378
+ yield injector[DeployDriver]
11379
+ return DeployDriverFactory(factory)
11380
+ lst.append(inj.bind(provide_deploy_driver_factory, singleton=True))
11381
+
11382
+ #
11383
+
11384
+ def provide_deploy_home(deploy: DeploySpec) -> DeployHome:
11385
+ hs = check.non_empty_str(deploy.home)
11386
+ hs = os.path.expanduser(hs)
11387
+ hs = os.path.realpath(hs)
11388
+ hs = os.path.abspath(hs)
11389
+ return DeployHome(hs)
11390
+ lst.append(inj.bind(provide_deploy_home, in_=DeployInjectorScope))
11391
+
11392
+ #
11393
+
11394
+ def provide_deploy_time(deploys: DeployManager) -> DeployTime:
11395
+ return deploys.make_deploy_time()
11396
+ lst.append(inj.bind(provide_deploy_time, in_=DeployInjectorScope))
11397
+
11398
+ #
11399
+
11400
+ return inj.as_bindings(*lst)
11401
+
11402
+
11403
+ ##
11404
+
11405
+
11254
11406
  def bind_deploy(
11255
11407
  *,
11256
11408
  deploy_config: DeployConfig,
@@ -11259,6 +11411,8 @@ def bind_deploy(
11259
11411
  inj.bind(deploy_config),
11260
11412
 
11261
11413
  bind_deploy_paths(),
11414
+
11415
+ bind_deploy_scope(),
11262
11416
  ]
11263
11417
 
11264
11418
  #
@@ -11294,25 +11448,6 @@ def bind_deploy(
11294
11448
 
11295
11449
  #
11296
11450
 
11297
- def provide_deploy_driver_factory(injector: Injector, sc: DeployInjectorScope) -> DeployDriverFactory:
11298
- @contextlib.contextmanager
11299
- def factory(spec: DeploySpec) -> ta.Iterator[DeployDriver]:
11300
- with sc.enter({
11301
- inj.as_key(DeploySpec): spec,
11302
- }):
11303
- yield injector[DeployDriver]
11304
- return DeployDriverFactory(factory)
11305
- lst.append(inj.bind(provide_deploy_driver_factory, singleton=True))
11306
-
11307
- lst.extend([
11308
- inj.bind_scope(DeployInjectorScope),
11309
- inj.bind_scope_seed(DeploySpec, DeployInjectorScope),
11310
-
11311
- inj.bind(DeployDriver, in_=DeployInjectorScope),
11312
- ])
11313
-
11314
- #
11315
-
11316
11451
  lst.extend([
11317
11452
  bind_command(DeployCommand, DeployCommandExecutor),
11318
11453
  bind_command(InterpCommand, InterpCommandExecutor),