ominfra 0.0.0.dev178__py3-none-any.whl → 0.0.0.dev179__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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),