ominfra 0.0.0.dev177__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
@@ -17,6 +17,7 @@ import base64
17
17
  import collections
18
18
  import collections.abc
19
19
  import contextlib
20
+ import contextvars
20
21
  import ctypes as ct
21
22
  import dataclasses as dc
22
23
  import datetime
@@ -4905,6 +4906,10 @@ class InjectorBinding:
4905
4906
  key: InjectorKey
4906
4907
  provider: InjectorProvider
4907
4908
 
4909
+ def __post_init__(self) -> None:
4910
+ check.isinstance(self.key, InjectorKey)
4911
+ check.isinstance(self.provider, InjectorProvider)
4912
+
4908
4913
 
4909
4914
  class InjectorBindings(abc.ABC):
4910
4915
  @abc.abstractmethod
@@ -5121,6 +5126,33 @@ def as_injector_bindings(*args: InjectorBindingOrBindings) -> InjectorBindings:
5121
5126
  ##
5122
5127
 
5123
5128
 
5129
+ def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey, InjectorProvider]:
5130
+ pm: ta.Dict[InjectorKey, InjectorProvider] = {}
5131
+ am: ta.Dict[InjectorKey, ta.List[InjectorProvider]] = {}
5132
+
5133
+ for b in bs.bindings():
5134
+ if b.key.array:
5135
+ al = am.setdefault(b.key, [])
5136
+ if isinstance(b.provider, ArrayInjectorProvider):
5137
+ al.extend(b.provider.ps)
5138
+ else:
5139
+ al.append(b.provider)
5140
+ else:
5141
+ if b.key in pm:
5142
+ raise KeyError(b.key)
5143
+ pm[b.key] = b.provider
5144
+
5145
+ if am:
5146
+ for k, aps in am.items():
5147
+ pm[k] = ArrayInjectorProvider(aps)
5148
+
5149
+ return pm
5150
+
5151
+
5152
+ ###
5153
+ # overrides
5154
+
5155
+
5124
5156
  @dc.dataclass(frozen=True)
5125
5157
  class OverridesInjectorBindings(InjectorBindings):
5126
5158
  p: InjectorBindings
@@ -5142,30 +5174,160 @@ def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) ->
5142
5174
  return OverridesInjectorBindings(p, m)
5143
5175
 
5144
5176
 
5145
- ##
5177
+ ###
5178
+ # scopes
5146
5179
 
5147
5180
 
5148
- def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey, InjectorProvider]:
5149
- pm: ta.Dict[InjectorKey, InjectorProvider] = {}
5150
- am: ta.Dict[InjectorKey, ta.List[InjectorProvider]] = {}
5181
+ class InjectorScope(abc.ABC): # noqa
5182
+ def __init__(
5183
+ self,
5184
+ *,
5185
+ _i: Injector,
5186
+ ) -> None:
5187
+ check.not_in(abc.ABC, type(self).__bases__)
5151
5188
 
5152
- for b in bs.bindings():
5153
- if b.key.array:
5154
- al = am.setdefault(b.key, [])
5155
- if isinstance(b.provider, ArrayInjectorProvider):
5156
- al.extend(b.provider.ps)
5157
- else:
5158
- al.append(b.provider)
5189
+ super().__init__()
5190
+
5191
+ self._i = _i
5192
+
5193
+ all_seeds: ta.Iterable[_InjectorScopeSeed] = self._i.provide(InjectorKey(_InjectorScopeSeed, array=True))
5194
+ self._sks = {s.k for s in all_seeds if s.sc is type(self)}
5195
+
5196
+ #
5197
+
5198
+ @dc.dataclass(frozen=True)
5199
+ class State:
5200
+ seeds: ta.Dict[InjectorKey, ta.Any]
5201
+ provisions: ta.Dict[InjectorKey, ta.Any] = dc.field(default_factory=dict)
5202
+
5203
+ def new_state(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> State:
5204
+ vs = dict(vs)
5205
+ check.equal(set(vs.keys()), self._sks)
5206
+ return InjectorScope.State(vs)
5207
+
5208
+ #
5209
+
5210
+ @abc.abstractmethod
5211
+ def state(self) -> State:
5212
+ raise NotImplementedError
5213
+
5214
+ @abc.abstractmethod
5215
+ def enter(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> ta.ContextManager[None]:
5216
+ raise NotImplementedError
5217
+
5218
+
5219
+ class ExclusiveInjectorScope(InjectorScope, abc.ABC):
5220
+ _st: ta.Optional[InjectorScope.State] = None
5221
+
5222
+ def state(self) -> InjectorScope.State:
5223
+ return check.not_none(self._st)
5224
+
5225
+ @contextlib.contextmanager
5226
+ def enter(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> ta.Iterator[None]:
5227
+ check.none(self._st)
5228
+ self._st = self.new_state(vs)
5229
+ try:
5230
+ yield
5231
+ finally:
5232
+ self._st = None
5233
+
5234
+
5235
+ class ContextvarInjectorScope(InjectorScope, abc.ABC):
5236
+ _cv: contextvars.ContextVar
5237
+
5238
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
5239
+ super().__init_subclass__(**kwargs)
5240
+ check.not_in(abc.ABC, cls.__bases__)
5241
+ check.state(not hasattr(cls, '_cv'))
5242
+ cls._cv = contextvars.ContextVar(f'{cls.__name__}_cv')
5243
+
5244
+ def state(self) -> InjectorScope.State:
5245
+ return self._cv.get()
5246
+
5247
+ @contextlib.contextmanager
5248
+ def enter(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> ta.Iterator[None]:
5249
+ try:
5250
+ self._cv.get()
5251
+ except LookupError:
5252
+ pass
5159
5253
  else:
5160
- if b.key in pm:
5161
- raise KeyError(b.key)
5162
- pm[b.key] = b.provider
5254
+ raise RuntimeError(f'Scope already entered: {self}')
5255
+ st = self.new_state(vs)
5256
+ tok = self._cv.set(st)
5257
+ try:
5258
+ yield
5259
+ finally:
5260
+ self._cv.reset(tok)
5163
5261
 
5164
- if am:
5165
- for k, aps in am.items():
5166
- pm[k] = ArrayInjectorProvider(aps)
5167
5262
 
5168
- return pm
5263
+ #
5264
+
5265
+
5266
+ @dc.dataclass(frozen=True)
5267
+ class ScopedInjectorProvider(InjectorProvider):
5268
+ p: InjectorProvider
5269
+ k: InjectorKey
5270
+ sc: ta.Type[InjectorScope]
5271
+
5272
+ def __post_init__(self) -> None:
5273
+ check.isinstance(self.p, InjectorProvider)
5274
+ check.isinstance(self.k, InjectorKey)
5275
+ check.issubclass(self.sc, InjectorScope)
5276
+
5277
+ def provider_fn(self) -> InjectorProviderFn:
5278
+ def pfn(i: Injector) -> ta.Any:
5279
+ st = i[self.sc].state()
5280
+ try:
5281
+ return st.provisions[self.k]
5282
+ except KeyError:
5283
+ pass
5284
+ v = ufn(i)
5285
+ st.provisions[self.k] = v
5286
+ return v
5287
+
5288
+ ufn = self.p.provider_fn()
5289
+ return pfn
5290
+
5291
+
5292
+ @dc.dataclass(frozen=True)
5293
+ class _ScopeSeedInjectorProvider(InjectorProvider):
5294
+ k: InjectorKey
5295
+ sc: ta.Type[InjectorScope]
5296
+
5297
+ def __post_init__(self) -> None:
5298
+ check.isinstance(self.k, InjectorKey)
5299
+ check.issubclass(self.sc, InjectorScope)
5300
+
5301
+ def provider_fn(self) -> InjectorProviderFn:
5302
+ def pfn(i: Injector) -> ta.Any:
5303
+ st = i[self.sc].state()
5304
+ return st.seeds[self.k]
5305
+ return pfn
5306
+
5307
+
5308
+ def bind_injector_scope(sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
5309
+ return InjectorBinder.bind(sc, singleton=True)
5310
+
5311
+
5312
+ #
5313
+
5314
+
5315
+ @dc.dataclass(frozen=True)
5316
+ class _InjectorScopeSeed:
5317
+ sc: ta.Type['InjectorScope']
5318
+ k: InjectorKey
5319
+
5320
+ def __post_init__(self) -> None:
5321
+ check.issubclass(self.sc, InjectorScope)
5322
+ check.isinstance(self.k, InjectorKey)
5323
+
5324
+
5325
+ def bind_injector_scope_seed(k: ta.Any, sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
5326
+ kk = as_injector_key(k)
5327
+ return as_injector_bindings(
5328
+ InjectorBinding(kk, _ScopeSeedInjectorProvider(kk, sc)),
5329
+ InjectorBinder.bind(_InjectorScopeSeed(sc, kk), array=True),
5330
+ )
5169
5331
 
5170
5332
 
5171
5333
  ###
@@ -5312,13 +5474,21 @@ _INJECTOR_EAGER_ARRAY_KEY: InjectorKey[_InjectorEager] = InjectorKey(_InjectorEa
5312
5474
 
5313
5475
 
5314
5476
  class _Injector(Injector):
5477
+ _DEFAULT_BINDINGS: ta.ClassVar[ta.List[InjectorBinding]] = []
5478
+
5315
5479
  def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
5316
5480
  super().__init__()
5317
5481
 
5318
5482
  self._bs = check.isinstance(bs, InjectorBindings)
5319
5483
  self._p: ta.Optional[Injector] = check.isinstance(p, (Injector, type(None)))
5320
5484
 
5321
- self._pfm = {k: v.provider_fn() for k, v in build_injector_provider_map(bs).items()}
5485
+ self._pfm = {
5486
+ k: v.provider_fn()
5487
+ for k, v in build_injector_provider_map(as_injector_bindings(
5488
+ *self._DEFAULT_BINDINGS,
5489
+ bs,
5490
+ )).items()
5491
+ }
5322
5492
 
5323
5493
  if _INJECTOR_INJECTOR_KEY in self._pfm:
5324
5494
  raise DuplicateInjectorKeyError(_INJECTOR_INJECTOR_KEY)
@@ -5485,6 +5655,7 @@ class InjectorBinder:
5485
5655
  to_const: ta.Any = None,
5486
5656
  to_key: ta.Any = None,
5487
5657
 
5658
+ in_: ta.Optional[ta.Type[InjectorScope]] = None,
5488
5659
  singleton: bool = False,
5489
5660
 
5490
5661
  eager: bool = False,
@@ -5494,12 +5665,12 @@ class InjectorBinder:
5494
5665
  if isinstance(obj, cls._BANNED_BIND_TYPES):
5495
5666
  raise TypeError(obj)
5496
5667
 
5497
- ##
5668
+ #
5498
5669
 
5499
5670
  if key is not None:
5500
5671
  key = as_injector_key(key)
5501
5672
 
5502
- ##
5673
+ #
5503
5674
 
5504
5675
  has_to = (
5505
5676
  to_fn is not None or
@@ -5529,7 +5700,7 @@ class InjectorBinder:
5529
5700
  key = InjectorKey(type(obj))
5530
5701
  del has_to
5531
5702
 
5532
- ##
5703
+ #
5533
5704
 
5534
5705
  if tag is not None:
5535
5706
  if key.tag is not None:
@@ -5539,7 +5710,7 @@ class InjectorBinder:
5539
5710
  if array is not None:
5540
5711
  key = dc.replace(key, array=array)
5541
5712
 
5542
- ##
5713
+ #
5543
5714
 
5544
5715
  providers: ta.List[InjectorProvider] = []
5545
5716
  if to_fn is not None:
@@ -5554,23 +5725,34 @@ class InjectorBinder:
5554
5725
  raise TypeError('Must specify provider')
5555
5726
  if len(providers) > 1:
5556
5727
  raise TypeError('May not specify multiple providers')
5557
- provider, = providers
5728
+ provider = check.single(providers)
5558
5729
 
5559
- ##
5730
+ #
5560
5731
 
5732
+ pws: ta.List[ta.Any] = []
5733
+ if in_ is not None:
5734
+ check.issubclass(in_, InjectorScope)
5735
+ check.not_in(abc.ABC, in_.__bases__)
5736
+ pws.append(functools.partial(ScopedInjectorProvider, k=key, sc=in_))
5561
5737
  if singleton:
5562
- provider = SingletonInjectorProvider(provider)
5738
+ pws.append(SingletonInjectorProvider)
5739
+ if len(pws) > 1:
5740
+ raise TypeError('May not specify multiple provider wrappers')
5741
+ elif pws:
5742
+ provider = check.single(pws)(provider)
5743
+
5744
+ #
5563
5745
 
5564
5746
  binding = InjectorBinding(key, provider)
5565
5747
 
5566
- ##
5748
+ #
5567
5749
 
5568
5750
  extras: ta.List[InjectorBinding] = []
5569
5751
 
5570
5752
  if eager:
5571
5753
  extras.append(bind_injector_eager_key(key))
5572
5754
 
5573
- ##
5755
+ #
5574
5756
 
5575
5757
  if extras:
5576
5758
  return as_injector_bindings(binding, *extras)
@@ -5643,7 +5825,8 @@ def bind_injector_eager_key(key: ta.Any) -> InjectorBinding:
5643
5825
  return InjectorBinding(_INJECTOR_EAGER_ARRAY_KEY, ConstInjectorProvider(_InjectorEager(as_injector_key(key))))
5644
5826
 
5645
5827
 
5646
- ##
5828
+ ###
5829
+ # api
5647
5830
 
5648
5831
 
5649
5832
  class InjectionApi:
@@ -5663,9 +5846,19 @@ class InjectionApi:
5663
5846
  def as_bindings(self, *args: InjectorBindingOrBindings) -> InjectorBindings:
5664
5847
  return as_injector_bindings(*args)
5665
5848
 
5849
+ # overrides
5850
+
5666
5851
  def override(self, p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
5667
5852
  return injector_override(p, *args)
5668
5853
 
5854
+ # scopes
5855
+
5856
+ def bind_scope(self, sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
5857
+ return bind_injector_scope(sc)
5858
+
5859
+ def bind_scope_seed(self, k: ta.Any, sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
5860
+ return bind_injector_scope_seed(k, sc)
5861
+
5669
5862
  # injector
5670
5863
 
5671
5864
  def create_injector(self, *args: InjectorBindingOrBindings, parent: ta.Optional[Injector] = None) -> Injector:
@@ -5686,6 +5879,7 @@ class InjectionApi:
5686
5879
  to_const: ta.Any = None,
5687
5880
  to_key: ta.Any = None,
5688
5881
 
5882
+ in_: ta.Optional[ta.Type[InjectorScope]] = None,
5689
5883
  singleton: bool = False,
5690
5884
 
5691
5885
  eager: bool = False,
@@ -5702,6 +5896,7 @@ class InjectionApi:
5702
5896
  to_const=to_const,
5703
5897
  to_key=to_key,
5704
5898
 
5899
+ in_=in_,
5705
5900
  singleton=singleton,
5706
5901
 
5707
5902
  eager=eager,
@@ -6511,6 +6706,9 @@ class TempDirAtomicPathSwapping(AtomicPathSwapping):
6511
6706
  # ../../../omdev/interp/types.py
6512
6707
 
6513
6708
 
6709
+ ##
6710
+
6711
+
6514
6712
  # See https://peps.python.org/pep-3149/
6515
6713
  INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
6516
6714
  ('debug', 'd'),
@@ -6542,6 +6740,9 @@ class InterpOpts:
6542
6740
  return s, cls(**kw)
6543
6741
 
6544
6742
 
6743
+ ##
6744
+
6745
+
6545
6746
  @dc.dataclass(frozen=True)
6546
6747
  class InterpVersion:
6547
6748
  version: Version
@@ -6567,6 +6768,9 @@ class InterpVersion:
6567
6768
  return None
6568
6769
 
6569
6770
 
6771
+ ##
6772
+
6773
+
6570
6774
  @dc.dataclass(frozen=True)
6571
6775
  class InterpSpecifier:
6572
6776
  specifier: Specifier
@@ -6594,12 +6798,25 @@ class InterpSpecifier:
6594
6798
  return self.contains(iv)
6595
6799
 
6596
6800
 
6801
+ ##
6802
+
6803
+
6597
6804
  @dc.dataclass(frozen=True)
6598
6805
  class Interp:
6599
6806
  exe: str
6600
6807
  version: InterpVersion
6601
6808
 
6602
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
+
6603
6820
  ########################################
6604
6821
  # ../../configs.py
6605
6822
 
@@ -7513,6 +7730,50 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
7513
7730
  return ret.decode().strip()
7514
7731
 
7515
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
+
7516
7777
  ########################################
7517
7778
  # ../bootstrap.py
7518
7779
 
@@ -7547,6 +7808,37 @@ class LocalCommandExecutor(CommandExecutor):
7547
7808
  return await ce.execute(cmd)
7548
7809
 
7549
7810
 
7811
+ ########################################
7812
+ # ../deploy/deploy.py
7813
+
7814
+
7815
+ DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
7816
+
7817
+
7818
+ DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
7819
+
7820
+
7821
+ class DeployManager:
7822
+ def __init__(
7823
+ self,
7824
+ *,
7825
+
7826
+ utc_clock: ta.Optional[DeployManagerUtcClock] = None,
7827
+ ):
7828
+ super().__init__()
7829
+
7830
+ self._utc_clock = utc_clock
7831
+
7832
+ def _utc_now(self) -> datetime.datetime:
7833
+ if self._utc_clock is not None:
7834
+ return self._utc_clock() # noqa
7835
+ else:
7836
+ return datetime.datetime.now(tz=datetime.timezone.utc) # noqa
7837
+
7838
+ def make_deploy_time(self) -> DeployTime:
7839
+ return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
7840
+
7841
+
7550
7842
  ########################################
7551
7843
  # ../deploy/paths/paths.py
7552
7844
  """
@@ -8616,25 +8908,110 @@ class InterpInspector:
8616
8908
  return ret
8617
8909
 
8618
8910
 
8619
- INTERP_INSPECTOR = InterpInspector()
8620
-
8621
-
8622
8911
  ########################################
8623
- # ../commands/subprocess.py
8624
-
8625
-
8626
- ##
8912
+ # ../../../omdev/interp/resolvers.py
8627
8913
 
8628
8914
 
8629
8915
  @dc.dataclass(frozen=True)
8630
- class SubprocessCommand(Command['SubprocessCommand.Output']):
8631
- cmd: ta.Sequence[str]
8916
+ class InterpResolverProviders:
8917
+ providers: ta.Sequence[ta.Tuple[str, InterpProvider]]
8632
8918
 
8633
- shell: bool = False
8634
- cwd: ta.Optional[str] = None
8635
- env: ta.Optional[ta.Mapping[str, str]] = None
8636
8919
 
8637
- stdout: str = 'pipe' # SubprocessChannelOption
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}')
8997
+
8998
+
8999
+ ########################################
9000
+ # ../commands/subprocess.py
9001
+
9002
+
9003
+ ##
9004
+
9005
+
9006
+ @dc.dataclass(frozen=True)
9007
+ class SubprocessCommand(Command['SubprocessCommand.Output']):
9008
+ cmd: ta.Sequence[str]
9009
+
9010
+ shell: bool = False
9011
+ cwd: ta.Optional[str] = None
9012
+ env: ta.Optional[ta.Mapping[str, str]] = None
9013
+
9014
+ stdout: str = 'pipe' # SubprocessChannelOption
8638
9015
  stderr: str = 'pipe' # SubprocessChannelOption
8639
9016
 
8640
9017
  input: ta.Optional[bytes] = None
@@ -9270,47 +9647,7 @@ class YumSystemPackageManager(SystemPackageManager):
9270
9647
 
9271
9648
 
9272
9649
  ########################################
9273
- # ../../../omdev/interp/providers.py
9274
- """
9275
- TODO:
9276
- - backends
9277
- - local builds
9278
- - deadsnakes?
9279
- - uv
9280
- - loose versions
9281
- """
9282
-
9283
-
9284
- ##
9285
-
9286
-
9287
- class InterpProvider(abc.ABC):
9288
- name: ta.ClassVar[str]
9289
-
9290
- def __init_subclass__(cls, **kwargs: ta.Any) -> None:
9291
- super().__init_subclass__(**kwargs)
9292
- if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
9293
- sfx = 'InterpProvider'
9294
- if not cls.__name__.endswith(sfx):
9295
- raise NameError(cls)
9296
- setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
9297
-
9298
- @abc.abstractmethod
9299
- def get_installed_versions(self, spec: InterpSpecifier) -> ta.Awaitable[ta.Sequence[InterpVersion]]:
9300
- raise NotImplementedError
9301
-
9302
- @abc.abstractmethod
9303
- def get_installed_version(self, version: InterpVersion) -> ta.Awaitable[Interp]:
9304
- raise NotImplementedError
9305
-
9306
- async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
9307
- return []
9308
-
9309
- async def install_version(self, version: InterpVersion) -> Interp:
9310
- raise TypeError
9311
-
9312
-
9313
- ##
9650
+ # ../../../omdev/interp/providers/running.py
9314
9651
 
9315
9652
 
9316
9653
  class RunningInterpProvider(InterpProvider):
@@ -9331,314 +9668,132 @@ class RunningInterpProvider(InterpProvider):
9331
9668
 
9332
9669
 
9333
9670
  ########################################
9334
- # ../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
+ """
9335
9677
 
9336
9678
 
9337
9679
  ##
9338
9680
 
9339
9681
 
9340
- def bind_command(
9341
- command_cls: ta.Type[Command],
9342
- executor_cls: ta.Optional[ta.Type[CommandExecutor]],
9343
- ) -> InjectorBindings:
9344
- lst: ta.List[InjectorBindingOrBindings] = [
9345
- inj.bind(CommandRegistration(command_cls), array=True),
9346
- ]
9347
-
9348
- if executor_cls is not None:
9349
- lst.extend([
9350
- inj.bind(executor_cls, singleton=True),
9351
- inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
9352
- ])
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
9353
9687
 
9354
- return inj.as_bindings(*lst)
9688
+ inspect: bool = False
9355
9689
 
9690
+ def __init__(
9691
+ self,
9692
+ options: Options = Options(),
9693
+ *,
9694
+ inspector: ta.Optional[InterpInspector] = None,
9695
+ ) -> None:
9696
+ super().__init__()
9356
9697
 
9357
- ##
9698
+ self._options = options
9358
9699
 
9700
+ self._inspector = inspector
9359
9701
 
9360
- @dc.dataclass(frozen=True)
9361
- class _FactoryCommandExecutor(CommandExecutor):
9362
- factory: ta.Callable[[], CommandExecutor]
9702
+ #
9363
9703
 
9364
- def execute(self, i: Command) -> ta.Awaitable[Command.Output]:
9365
- 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
9366
9718
 
9719
+ if not path:
9720
+ return []
9367
9721
 
9368
- ##
9722
+ path = os.fsdecode(path)
9723
+ pathlst = path.split(os.pathsep)
9369
9724
 
9725
+ def _access_check(fn: str, mode: int) -> bool:
9726
+ return os.path.exists(fn) and os.access(fn, mode)
9370
9727
 
9371
- def bind_commands(
9372
- *,
9373
- main_config: MainConfig,
9374
- ) -> InjectorBindings:
9375
- lst: ta.List[InjectorBindingOrBindings] = [
9376
- inj.bind_array(CommandRegistration),
9377
- 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)
9378
9745
 
9379
- inj.bind_array(CommandExecutorRegistration),
9380
- inj.bind_array_type(CommandExecutorRegistration, CommandExecutorRegistrations),
9746
+ return out
9381
9747
 
9382
- inj.bind(build_command_name_map, singleton=True),
9383
- ]
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
+ )
9384
9754
 
9385
9755
  #
9386
9756
 
9387
- def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
9388
- 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
9389
9769
 
9390
- 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
9391
9778
 
9392
9779
  #
9393
9780
 
9394
- def provide_command_executor_map(
9395
- injector: Injector,
9396
- crs: CommandExecutorRegistrations,
9397
- ) -> CommandExecutorMap:
9398
- dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
9781
+ async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
9782
+ return [ev for e, ev in await self.exe_versions()]
9399
9783
 
9400
- cr: CommandExecutorRegistration
9401
- for cr in crs:
9402
- if cr.command_cls in dct:
9403
- raise KeyError(cr.command_cls)
9404
-
9405
- factory = functools.partial(injector.provide, cr.executor_cls)
9406
- if main_config.debug:
9407
- ce = factory()
9408
- else:
9409
- ce = _FactoryCommandExecutor(factory)
9410
-
9411
- dct[cr.command_cls] = ce
9412
-
9413
- return CommandExecutorMap(dct)
9414
-
9415
- lst.extend([
9416
- inj.bind(provide_command_executor_map, singleton=True),
9417
-
9418
- inj.bind(LocalCommandExecutor, singleton=True, eager=main_config.debug),
9419
- ])
9420
-
9421
- #
9422
-
9423
- lst.extend([
9424
- bind_command(PingCommand, PingCommandExecutor),
9425
- bind_command(SubprocessCommand, SubprocessCommandExecutor),
9426
- ])
9427
-
9428
- #
9429
-
9430
- return inj.as_bindings(*lst)
9431
-
9432
-
9433
- ########################################
9434
- # ../deploy/paths/manager.py
9435
-
9436
-
9437
- class DeployPathsManager:
9438
- def __init__(
9439
- self,
9440
- *,
9441
- deploy_path_owners: DeployPathOwners,
9442
- ) -> None:
9443
- super().__init__()
9444
-
9445
- self._deploy_path_owners = deploy_path_owners
9446
-
9447
- @cached_nullary
9448
- def owners_by_path(self) -> ta.Mapping[DeployPath, DeployPathOwner]:
9449
- dct: ta.Dict[DeployPath, DeployPathOwner] = {}
9450
- for o in self._deploy_path_owners:
9451
- for p in o.get_owned_deploy_paths():
9452
- if p in dct:
9453
- raise DeployPathError(f'Duplicate deploy path owner: {p}')
9454
- dct[p] = o
9455
- return dct
9456
-
9457
- def validate_deploy_paths(self) -> None:
9458
- self.owners_by_path()
9459
-
9460
-
9461
- ########################################
9462
- # ../deploy/tmp.py
9463
-
9464
-
9465
- class DeployHomeAtomics(Func1[DeployHome, AtomicPathSwapping]):
9466
- pass
9467
-
9468
-
9469
- class DeployTmpManager(
9470
- SingleDirDeployPathOwner,
9471
- ):
9472
- def __init__(self) -> None:
9473
- super().__init__(
9474
- owned_dir='tmp',
9475
- )
9476
-
9477
- def get_swapping(self, home: DeployHome) -> AtomicPathSwapping:
9478
- return TempDirAtomicPathSwapping(
9479
- temp_dir=self._make_dir(home),
9480
- root_dir=check.non_empty_str(home),
9481
- )
9482
-
9483
-
9484
- ########################################
9485
- # ../remote/connection.py
9486
-
9487
-
9488
- ##
9489
-
9490
-
9491
- class PyremoteRemoteExecutionConnector:
9492
- def __init__(
9493
- self,
9494
- *,
9495
- spawning: RemoteSpawning,
9496
- msh: ObjMarshalerManager,
9497
- payload_file: ta.Optional[RemoteExecutionPayloadFile] = None,
9498
- ) -> None:
9499
- super().__init__()
9500
-
9501
- self._spawning = spawning
9502
- self._msh = msh
9503
- self._payload_file = payload_file
9504
-
9505
- #
9506
-
9507
- @cached_nullary
9508
- def _payload_src(self) -> str:
9509
- return get_remote_payload_src(file=self._payload_file)
9510
-
9511
- @cached_nullary
9512
- def _remote_src(self) -> ta.Sequence[str]:
9513
- return [
9514
- self._payload_src(),
9515
- '_remote_execution_main()',
9516
- ]
9517
-
9518
- @cached_nullary
9519
- def _spawn_src(self) -> str:
9520
- return pyremote_build_bootstrap_cmd(__package__ or 'manage')
9521
-
9522
- #
9523
-
9524
- @contextlib.asynccontextmanager
9525
- async def connect(
9526
- self,
9527
- tgt: RemoteSpawning.Target,
9528
- bs: MainBootstrap,
9529
- ) -> ta.AsyncGenerator[RemoteCommandExecutor, None]:
9530
- spawn_src = self._spawn_src()
9531
- remote_src = self._remote_src()
9532
-
9533
- async with self._spawning.spawn(
9534
- tgt,
9535
- spawn_src,
9536
- debug=bs.main_config.debug,
9537
- ) as proc:
9538
- res = await PyremoteBootstrapDriver( # noqa
9539
- remote_src,
9540
- PyremoteBootstrapOptions(
9541
- debug=bs.main_config.debug,
9542
- ),
9543
- ).async_run(
9544
- proc.stdout,
9545
- proc.stdin,
9546
- )
9547
-
9548
- chan = RemoteChannelImpl(
9549
- proc.stdout,
9550
- proc.stdin,
9551
- msh=self._msh,
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,
9552
9791
  )
9553
-
9554
- await chan.send_obj(bs)
9555
-
9556
- rce: RemoteCommandExecutor
9557
- async with aclosing(RemoteCommandExecutor(chan)) as rce:
9558
- await rce.start()
9559
-
9560
- yield rce
9561
-
9562
-
9563
- ##
9564
-
9565
-
9566
- class InProcessRemoteExecutionConnector:
9567
- def __init__(
9568
- self,
9569
- *,
9570
- msh: ObjMarshalerManager,
9571
- local_executor: LocalCommandExecutor,
9572
- ) -> None:
9573
- super().__init__()
9574
-
9575
- self._msh = msh
9576
- self._local_executor = local_executor
9577
-
9578
- @contextlib.asynccontextmanager
9579
- async def connect(self) -> ta.AsyncGenerator[RemoteCommandExecutor, None]:
9580
- r0, w0 = asyncio_create_bytes_channel()
9581
- r1, w1 = asyncio_create_bytes_channel()
9582
-
9583
- remote_chan = RemoteChannelImpl(r0, w1, msh=self._msh)
9584
- local_chan = RemoteChannelImpl(r1, w0, msh=self._msh)
9585
-
9586
- rch = _RemoteCommandHandler(
9587
- remote_chan,
9588
- self._local_executor,
9589
- )
9590
- rch_task = asyncio.create_task(rch.run()) # noqa
9591
- try:
9592
- rce: RemoteCommandExecutor
9593
- async with aclosing(RemoteCommandExecutor(local_chan)) as rce:
9594
- await rce.start()
9595
-
9596
- yield rce
9597
-
9598
- finally:
9599
- rch.stop()
9600
- await rch_task
9601
-
9602
-
9603
- ########################################
9604
- # ../system/commands.py
9605
-
9606
-
9607
- ##
9608
-
9609
-
9610
- @dc.dataclass(frozen=True)
9611
- class CheckSystemPackageCommand(Command['CheckSystemPackageCommand.Output']):
9612
- pkgs: ta.Sequence[str] = ()
9613
-
9614
- def __post_init__(self) -> None:
9615
- check.not_isinstance(self.pkgs, str)
9616
-
9617
- @dc.dataclass(frozen=True)
9618
- class Output(Command.Output):
9619
- pkgs: ta.Sequence[SystemPackage]
9620
-
9621
-
9622
- class CheckSystemPackageCommandExecutor(CommandExecutor[CheckSystemPackageCommand, CheckSystemPackageCommand.Output]):
9623
- def __init__(
9624
- self,
9625
- *,
9626
- mgr: SystemPackageManager,
9627
- ) -> None:
9628
- super().__init__()
9629
-
9630
- self._mgr = mgr
9631
-
9632
- async def execute(self, cmd: CheckSystemPackageCommand) -> CheckSystemPackageCommand.Output:
9633
- log.info('Checking system package!')
9634
-
9635
- ret = await self._mgr.query(*cmd.pkgs)
9636
-
9637
- return CheckSystemPackageCommand.Output(list(ret.values()))
9792
+ raise KeyError(version)
9638
9793
 
9639
9794
 
9640
9795
  ########################################
9641
- # ../../../omdev/interp/pyenv.py
9796
+ # ../../../omdev/interp/pyenv/pyenv.py
9642
9797
  """
9643
9798
  TODO:
9644
9799
  - custom tags
@@ -9868,9 +10023,10 @@ class PyenvVersionInstaller:
9868
10023
  opts: ta.Optional[PyenvInstallOpts] = None,
9869
10024
  interp_opts: InterpOpts = InterpOpts(),
9870
10025
  *,
10026
+ pyenv: Pyenv,
10027
+
9871
10028
  install_name: ta.Optional[str] = None,
9872
10029
  no_default_opts: bool = False,
9873
- pyenv: Pyenv = Pyenv(),
9874
10030
  ) -> None:
9875
10031
  super().__init__()
9876
10032
 
@@ -9960,26 +10116,26 @@ class PyenvVersionInstaller:
9960
10116
 
9961
10117
 
9962
10118
  class PyenvInterpProvider(InterpProvider):
9963
- def __init__(
9964
- self,
9965
- pyenv: Pyenv = Pyenv(),
10119
+ @dc.dataclass(frozen=True)
10120
+ class Options:
10121
+ inspect: bool = False
9966
10122
 
9967
- inspect: bool = False,
9968
- inspector: InterpInspector = INTERP_INSPECTOR,
10123
+ try_update: bool = False
9969
10124
 
10125
+ def __init__(
10126
+ self,
10127
+ options: Options = Options(),
9970
10128
  *,
9971
-
9972
- try_update: bool = False,
10129
+ pyenv: Pyenv,
10130
+ inspector: InterpInspector,
9973
10131
  ) -> None:
9974
10132
  super().__init__()
9975
10133
 
9976
- self._pyenv = pyenv
10134
+ self._options = options
9977
10135
 
9978
- self._inspect = inspect
10136
+ self._pyenv = pyenv
9979
10137
  self._inspector = inspector
9980
10138
 
9981
- self._try_update = try_update
9982
-
9983
10139
  #
9984
10140
 
9985
10141
  @staticmethod
@@ -10004,7 +10160,7 @@ class PyenvInterpProvider(InterpProvider):
10004
10160
 
10005
10161
  async def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
10006
10162
  iv: ta.Optional[InterpVersion]
10007
- if self._inspect:
10163
+ if self._options.inspect:
10008
10164
  try:
10009
10165
  iv = check.not_none(await self._inspector.inspect(ep)).iv
10010
10166
  except Exception as e: # noqa
@@ -10060,7 +10216,7 @@ class PyenvInterpProvider(InterpProvider):
10060
10216
  async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
10061
10217
  lst = await self._get_installable_versions(spec)
10062
10218
 
10063
- 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):
10064
10220
  if self._pyenv.update():
10065
10221
  lst = await self._get_installable_versions(spec)
10066
10222
 
@@ -10076,6 +10232,7 @@ class PyenvInterpProvider(InterpProvider):
10076
10232
  installer = PyenvVersionInstaller(
10077
10233
  inst_version,
10078
10234
  interp_opts=inst_opts,
10235
+ pyenv=self._pyenv,
10079
10236
  )
10080
10237
 
10081
10238
  exe = await installer.install()
@@ -10083,116 +10240,344 @@ class PyenvInterpProvider(InterpProvider):
10083
10240
 
10084
10241
 
10085
10242
  ########################################
10086
- # ../../../omdev/interp/system.py
10087
- """
10088
- TODO:
10089
- - python, python3, python3.12, ...
10090
- - check if path py's are venvs: sys.prefix != sys.base_prefix
10091
- """
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]
10092
10529
 
10093
10530
 
10094
- ##
10531
+ class CheckSystemPackageCommandExecutor(CommandExecutor[CheckSystemPackageCommand, CheckSystemPackageCommand.Output]):
10532
+ def __init__(
10533
+ self,
10534
+ *,
10535
+ mgr: SystemPackageManager,
10536
+ ) -> None:
10537
+ super().__init__()
10095
10538
 
10539
+ self._mgr = mgr
10096
10540
 
10097
- @dc.dataclass(frozen=True)
10098
- class SystemInterpProvider(InterpProvider):
10099
- cmd: str = 'python3'
10100
- path: ta.Optional[str] = None
10541
+ async def execute(self, cmd: CheckSystemPackageCommand) -> CheckSystemPackageCommand.Output:
10542
+ log.info('Checking system package!')
10101
10543
 
10102
- inspect: bool = False
10103
- inspector: InterpInspector = INTERP_INSPECTOR
10544
+ ret = await self._mgr.query(*cmd.pkgs)
10104
10545
 
10105
- #
10546
+ return CheckSystemPackageCommand.Output(list(ret.values()))
10106
10547
 
10107
- @staticmethod
10108
- def _re_which(
10109
- pat: re.Pattern,
10110
- *,
10111
- mode: int = os.F_OK | os.X_OK,
10112
- path: ta.Optional[str] = None,
10113
- ) -> ta.List[str]:
10114
- if path is None:
10115
- path = os.environ.get('PATH', None)
10116
- if path is None:
10117
- try:
10118
- path = os.confstr('CS_PATH')
10119
- except (AttributeError, ValueError):
10120
- path = os.defpath
10121
10548
 
10122
- if not path:
10123
- return []
10549
+ ########################################
10550
+ # ../../../omdev/interp/providers/inject.py
10124
10551
 
10125
- path = os.fsdecode(path)
10126
- pathlst = path.split(os.pathsep)
10127
10552
 
10128
- def _access_check(fn: str, mode: int) -> bool:
10129
- 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),
10130
10557
 
10131
- out = []
10132
- seen = set()
10133
- for d in pathlst:
10134
- normdir = os.path.normcase(d)
10135
- if normdir not in seen:
10136
- seen.add(normdir)
10137
- if not _access_check(normdir, mode):
10138
- continue
10139
- for thefile in os.listdir(d):
10140
- name = os.path.join(d, thefile)
10141
- if not (
10142
- os.path.isfile(name) and
10143
- pat.fullmatch(thefile) and
10144
- _access_check(name, mode)
10145
- ):
10146
- continue
10147
- out.append(name)
10558
+ inj.bind(RunningInterpProvider, singleton=True),
10559
+ inj.bind(InterpProvider, to_key=RunningInterpProvider, array=True),
10148
10560
 
10149
- return out
10561
+ inj.bind(SystemInterpProvider, singleton=True),
10562
+ inj.bind(InterpProvider, to_key=SystemInterpProvider, array=True),
10563
+ ]
10150
10564
 
10151
- @cached_nullary
10152
- def exes(self) -> ta.List[str]:
10153
- return self._re_which(
10154
- re.compile(r'python3(\.\d+)?'),
10155
- path=self.path,
10156
- )
10565
+ return inj.as_bindings(*lst)
10157
10566
 
10158
- #
10159
10567
 
10160
- async def get_exe_version(self, exe: str) -> ta.Optional[InterpVersion]:
10161
- if not self.inspect:
10162
- s = os.path.basename(exe)
10163
- if s.startswith('python'):
10164
- s = s[len('python'):]
10165
- if '.' in s:
10166
- try:
10167
- return InterpVersion.parse(s)
10168
- except InvalidVersion:
10169
- pass
10170
- ii = await self.inspector.inspect(exe)
10171
- return ii.iv if ii is not None else None
10568
+ ########################################
10569
+ # ../../../omdev/interp/pyenv/inject.py
10172
10570
 
10173
- async def exe_versions(self) -> ta.Sequence[ta.Tuple[str, InterpVersion]]:
10174
- lst = []
10175
- for e in self.exes():
10176
- if (ev := await self.get_exe_version(e)) is None:
10177
- log.debug('Invalid system version: %s', e)
10178
- continue
10179
- lst.append((e, ev))
10180
- return lst
10181
10571
 
10182
- #
10572
+ def bind_interp_pyenv() -> InjectorBindings:
10573
+ lst: ta.List[InjectorBindingOrBindings] = [
10574
+ inj.bind(Pyenv, singleton=True),
10183
10575
 
10184
- async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
10185
- 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
+ ]
10186
10579
 
10187
- async def get_installed_version(self, version: InterpVersion) -> Interp:
10188
- for e, ev in await self.exe_versions():
10189
- if ev != version:
10190
- continue
10191
- return Interp(
10192
- exe=e,
10193
- version=ev,
10194
- )
10195
- raise KeyError(version)
10580
+ return inj.as_bindings(*lst)
10196
10581
 
10197
10582
 
10198
10583
  ########################################
@@ -10559,101 +10944,44 @@ class SshManageTargetConnector(ManageTargetConnector):
10559
10944
 
10560
10945
 
10561
10946
  ########################################
10562
- # ../../../omdev/interp/resolvers.py
10563
-
10564
-
10565
- INTERP_PROVIDER_TYPES_BY_NAME: ta.Mapping[str, ta.Type[InterpProvider]] = {
10566
- cls.name: cls for cls in deep_subclasses(InterpProvider) if abc.ABC not in cls.__bases__ # type: ignore
10567
- }
10568
-
10569
-
10570
- class InterpResolver:
10571
- def __init__(
10572
- self,
10573
- providers: ta.Sequence[ta.Tuple[str, InterpProvider]],
10574
- ) -> None:
10575
- super().__init__()
10576
-
10577
- self._providers: ta.Mapping[str, InterpProvider] = collections.OrderedDict(providers)
10578
-
10579
- async def _resolve_installed(self, spec: InterpSpecifier) -> ta.Optional[ta.Tuple[InterpProvider, InterpVersion]]:
10580
- lst = [
10581
- (i, si)
10582
- for i, p in enumerate(self._providers.values())
10583
- for si in await p.get_installed_versions(spec)
10584
- if spec.contains(si)
10585
- ]
10586
-
10587
- slst = sorted(lst, key=lambda t: (-t[0], t[1].version))
10588
- if not slst:
10589
- return None
10947
+ # ../../../omdev/interp/inject.py
10590
10948
 
10591
- bi, bv = slst[-1]
10592
- bp = list(self._providers.values())[bi]
10593
- return (bp, bv)
10594
10949
 
10595
- async def resolve(
10596
- self,
10597
- spec: InterpSpecifier,
10598
- *,
10599
- install: bool = False,
10600
- ) -> ta.Optional[Interp]:
10601
- tup = await self._resolve_installed(spec)
10602
- if tup is not None:
10603
- bp, bv = tup
10604
- return await bp.get_installed_version(bv)
10950
+ def bind_interp() -> InjectorBindings:
10951
+ lst: ta.List[InjectorBindingOrBindings] = [
10952
+ bind_interp_providers(),
10605
10953
 
10606
- if not install:
10607
- return None
10954
+ bind_interp_pyenv(),
10608
10955
 
10609
- tp = list(self._providers.values())[0] # noqa
10956
+ bind_interp_uv(),
10610
10957
 
10611
- sv = sorted(
10612
- [s for s in await tp.get_installable_versions(spec) if s in spec],
10613
- key=lambda s: s.version,
10614
- )
10615
- if not sv:
10616
- return None
10958
+ inj.bind(InterpInspector, singleton=True),
10959
+ ]
10617
10960
 
10618
- bv = sv[-1]
10619
- return await tp.install_version(bv)
10961
+ #
10620
10962
 
10621
- async def list(self, spec: InterpSpecifier) -> None:
10622
- print('installed:')
10623
- for n, p in self._providers.items():
10624
- lst = [
10625
- si
10626
- for si in await p.get_installed_versions(spec)
10627
- 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,
10628
10971
  ]
10629
- if lst:
10630
- print(f' {n}')
10631
- for si in lst:
10632
- print(f' {si}')
10633
-
10634
- print()
10972
+ ]
10635
10973
 
10636
- print('installable:')
10637
- for n, p in self._providers.items():
10638
- lst = [
10639
- si
10640
- for si in await p.get_installable_versions(spec)
10641
- if spec.contains(si)
10642
- ]
10643
- if lst:
10644
- print(f' {n}')
10645
- for si in lst:
10646
- print(f' {si}')
10974
+ return InterpResolverProviders([(rp.name, rp) for rp in rps])
10647
10975
 
10976
+ lst.append(inj.bind(provide_interp_resolver_providers, singleton=True))
10648
10977
 
10649
- DEFAULT_INTERP_RESOLVER = InterpResolver([(p.name, p) for p in [
10650
- # pyenv is preferred to system interpreters as it tends to have more support for things like tkinter
10651
- PyenvInterpProvider(try_update=True),
10978
+ lst.extend([
10979
+ inj.bind(InterpResolver, singleton=True),
10980
+ ])
10652
10981
 
10653
- RunningInterpProvider(),
10982
+ #
10654
10983
 
10655
- SystemInterpProvider(),
10656
- ]])
10984
+ return inj.as_bindings(*lst)
10657
10985
 
10658
10986
 
10659
10987
  ########################################
@@ -10685,6 +11013,15 @@ def bind_targets() -> InjectorBindings:
10685
11013
  return inj.as_bindings(*lst)
10686
11014
 
10687
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
+
10688
11025
  ########################################
10689
11026
  # ../deploy/interp.py
10690
11027
 
@@ -10707,7 +11044,7 @@ class InterpCommand(Command['InterpCommand.Output']):
10707
11044
  class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
10708
11045
  async def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
10709
11046
  i = InterpSpecifier.parse(check.not_none(cmd.spec))
10710
- 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))
10711
11048
  return InterpCommand.Output(
10712
11049
  exe=o.exe,
10713
11050
  version=str(o.version.version),
@@ -10734,7 +11071,7 @@ class DeployVenvManager:
10734
11071
  ) -> None:
10735
11072
  if spec.interp is not None:
10736
11073
  i = InterpSpecifier.parse(check.not_none(spec.interp))
10737
- o = check.not_none(await DEFAULT_INTERP_RESOLVER.resolve(i))
11074
+ o = check.not_none(await get_default_interp_resolver().resolve(i))
10738
11075
  sys_exe = o.exe
10739
11076
  else:
10740
11077
  sys_exe = 'python3'
@@ -10929,65 +11266,46 @@ class DeployAppManager(DeployPathOwner):
10929
11266
 
10930
11267
 
10931
11268
  ########################################
10932
- # ../deploy/deploy.py
10933
-
10934
-
10935
- DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
11269
+ # ../deploy/driver.py
10936
11270
 
10937
11271
 
10938
- DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
11272
+ class DeployDriverFactory(Func1[DeploySpec, ta.ContextManager['DeployDriver']]):
11273
+ pass
10939
11274
 
10940
11275
 
10941
- class DeployManager:
11276
+ class DeployDriver:
10942
11277
  def __init__(
10943
11278
  self,
10944
11279
  *,
10945
- apps: DeployAppManager,
10946
- paths: DeployPathsManager,
11280
+ spec: DeploySpec,
11281
+ home: DeployHome,
11282
+ time: DeployTime,
10947
11283
 
10948
- utc_clock: ta.Optional[DeployManagerUtcClock] = None,
10949
- ):
11284
+ paths: DeployPathsManager,
11285
+ apps: DeployAppManager,
11286
+ ) -> None:
10950
11287
  super().__init__()
10951
11288
 
10952
- self._apps = apps
10953
- self._paths = paths
10954
-
10955
- self._utc_clock = utc_clock
10956
-
10957
- def _utc_now(self) -> datetime.datetime:
10958
- if self._utc_clock is not None:
10959
- return self._utc_clock() # noqa
10960
- else:
10961
- return datetime.datetime.now(tz=datetime.timezone.utc) # noqa
11289
+ self._spec = spec
11290
+ self._home = home
11291
+ self._time = time
10962
11292
 
10963
- def _make_deploy_time(self) -> DeployTime:
10964
- return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
11293
+ self._paths = paths
11294
+ self._apps = apps
10965
11295
 
10966
- async def run_deploy(
10967
- self,
10968
- spec: DeploySpec,
10969
- ) -> None:
11296
+ async def drive_deploy(self) -> None:
10970
11297
  self._paths.validate_deploy_paths()
10971
11298
 
10972
11299
  #
10973
11300
 
10974
- hs = check.non_empty_str(spec.home)
10975
- hs = os.path.expanduser(hs)
10976
- hs = os.path.realpath(hs)
10977
- hs = os.path.abspath(hs)
10978
-
10979
- home = DeployHome(hs)
10980
-
10981
- #
10982
-
10983
11301
  deploy_tags = DeployTagMap(
10984
- self._make_deploy_time(),
10985
- spec.key(),
11302
+ self._time,
11303
+ self._spec.key(),
10986
11304
  )
10987
11305
 
10988
11306
  #
10989
11307
 
10990
- for app in spec.apps:
11308
+ for app in self._spec.apps:
10991
11309
  app_tags = deploy_tags.add(
10992
11310
  app.app,
10993
11311
  app.key(),
@@ -10996,7 +11314,7 @@ class DeployManager:
10996
11314
 
10997
11315
  await self._apps.prepare_app(
10998
11316
  app,
10999
- home,
11317
+ self._home,
11000
11318
  app_tags,
11001
11319
  )
11002
11320
 
@@ -11019,12 +11337,13 @@ class DeployCommand(Command['DeployCommand.Output']):
11019
11337
 
11020
11338
  @dc.dataclass(frozen=True)
11021
11339
  class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
11022
- _deploy: DeployManager
11340
+ _driver_factory: DeployDriverFactory
11023
11341
 
11024
11342
  async def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
11025
11343
  log.info('Deploying! %r', cmd.spec)
11026
11344
 
11027
- await self._deploy.run_deploy(cmd.spec)
11345
+ with self._driver_factory(cmd.spec) as driver:
11346
+ await driver.drive_deploy()
11028
11347
 
11029
11348
  return DeployCommand.Output()
11030
11349
 
@@ -11033,6 +11352,57 @@ class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]
11033
11352
  # ../deploy/inject.py
11034
11353
 
11035
11354
 
11355
+ ##
11356
+
11357
+
11358
+ class DeployInjectorScope(ContextvarInjectorScope):
11359
+ pass
11360
+
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
+
11036
11406
  def bind_deploy(
11037
11407
  *,
11038
11408
  deploy_config: DeployConfig,
@@ -11041,6 +11411,8 @@ def bind_deploy(
11041
11411
  inj.bind(deploy_config),
11042
11412
 
11043
11413
  bind_deploy_paths(),
11414
+
11415
+ bind_deploy_scope(),
11044
11416
  ]
11045
11417
 
11046
11418
  #