ominfra 0.0.0.dev181__py3-none-any.whl → 0.0.0.dev183__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
@@ -129,6 +129,7 @@ AtomicPathSwapState = ta.Literal['open', 'committed', 'aborted'] # ta.TypeAlias
129
129
 
130
130
  # ../configs.py
131
131
  ConfigMapping = ta.Mapping[str, ta.Any]
132
+ IniConfigSectionSettingsMap = ta.Mapping[str, ta.Mapping[str, ta.Union[str, ta.Sequence[str]]]] # ta.TypeAlias
132
133
 
133
134
  # ../../omlish/subprocesses.py
134
135
  SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
@@ -4252,6 +4253,18 @@ def build_command_name_map(crs: CommandRegistrations) -> CommandNameMap:
4252
4253
  return CommandNameMap(dct)
4253
4254
 
4254
4255
 
4256
+ ########################################
4257
+ # ../deploy/paths/specs.py
4258
+
4259
+
4260
+ def check_valid_deploy_spec_path(s: str) -> str:
4261
+ check.non_empty_str(s)
4262
+ for c in ['..', '//']:
4263
+ check.not_in(c, s)
4264
+ check.arg(not s.startswith('/'))
4265
+ return s
4266
+
4267
+
4255
4268
  ########################################
4256
4269
  # ../remote/config.py
4257
4270
 
@@ -6225,6 +6238,17 @@ def register_type_obj_marshaler(ty: type, om: ObjMarshaler) -> None:
6225
6238
  _REGISTERED_OBJ_MARSHALERS_BY_TYPE[ty] = om
6226
6239
 
6227
6240
 
6241
+ def register_single_field_type_obj_marshaler(fld, ty=None):
6242
+ def inner(ty): # noqa
6243
+ register_type_obj_marshaler(ty, SingleFieldObjMarshaler(ty, fld))
6244
+ return ty
6245
+
6246
+ if ty is not None:
6247
+ return inner(ty)
6248
+ else:
6249
+ return inner
6250
+
6251
+
6228
6252
  ##
6229
6253
 
6230
6254
 
@@ -6821,6 +6845,9 @@ def bind_interp_uv() -> InjectorBindings:
6821
6845
  # ../../configs.py
6822
6846
 
6823
6847
 
6848
+ ##
6849
+
6850
+
6824
6851
  def parse_config_file(
6825
6852
  name: str,
6826
6853
  f: ta.TextIO,
@@ -6864,6 +6891,9 @@ def read_config_file(
6864
6891
  return msh.unmarshal_obj(config_dct, cls)
6865
6892
 
6866
6893
 
6894
+ ##
6895
+
6896
+
6867
6897
  def build_config_named_children(
6868
6898
  o: ta.Union[
6869
6899
  ta.Sequence[ConfigMapping],
@@ -6902,6 +6932,30 @@ def build_config_named_children(
6902
6932
  return lst
6903
6933
 
6904
6934
 
6935
+ ##
6936
+
6937
+
6938
+ def render_ini_config(
6939
+ settings_by_section: IniConfigSectionSettingsMap,
6940
+ ) -> str:
6941
+ out = io.StringIO()
6942
+
6943
+ for i, (section, settings) in enumerate(settings_by_section.items()):
6944
+ if i:
6945
+ out.write('\n')
6946
+
6947
+ out.write(f'[{section}]\n')
6948
+
6949
+ for k, v in settings.items():
6950
+ if isinstance(v, str):
6951
+ out.write(f'{k}={v}\n')
6952
+ else:
6953
+ for vv in v:
6954
+ out.write(f'{k}={vv}\n')
6955
+
6956
+ return out.getvalue()
6957
+
6958
+
6905
6959
  ########################################
6906
6960
  # ../commands/marshal.py
6907
6961
 
@@ -7035,7 +7089,7 @@ def _register_deploy_tag(cls):
7035
7089
  _DEPLOY_TAGS_BY_NAME[cls.tag_name] = cls
7036
7090
  _DEPLOY_TAGS_BY_KWARG[cls.tag_kwarg] = cls
7037
7091
 
7038
- register_type_obj_marshaler(cls, SingleFieldObjMarshaler(cls, 's'))
7092
+ register_single_field_type_obj_marshaler('s', cls)
7039
7093
 
7040
7094
  return cls
7041
7095
 
@@ -7808,6 +7862,95 @@ class LocalCommandExecutor(CommandExecutor):
7808
7862
  return await ce.execute(cmd)
7809
7863
 
7810
7864
 
7865
+ ########################################
7866
+ # ../deploy/conf/specs.py
7867
+
7868
+
7869
+ ##
7870
+
7871
+
7872
+ class DeployAppConfContent(abc.ABC): # noqa
7873
+ pass
7874
+
7875
+
7876
+ #
7877
+
7878
+
7879
+ @register_single_field_type_obj_marshaler('body')
7880
+ @dc.dataclass(frozen=True)
7881
+ class RawDeployAppConfContent(DeployAppConfContent):
7882
+ body: str
7883
+
7884
+
7885
+ #
7886
+
7887
+
7888
+ @register_single_field_type_obj_marshaler('obj')
7889
+ @dc.dataclass(frozen=True)
7890
+ class JsonDeployAppConfContent(DeployAppConfContent):
7891
+ obj: ta.Any
7892
+
7893
+
7894
+ #
7895
+
7896
+
7897
+ @register_single_field_type_obj_marshaler('sections')
7898
+ @dc.dataclass(frozen=True)
7899
+ class IniDeployAppConfContent(DeployAppConfContent):
7900
+ sections: IniConfigSectionSettingsMap
7901
+
7902
+
7903
+ ##
7904
+
7905
+
7906
+ @dc.dataclass(frozen=True)
7907
+ class DeployAppConfFile:
7908
+ path: str
7909
+ content: DeployAppConfContent
7910
+
7911
+ def __post_init__(self) -> None:
7912
+ check_valid_deploy_spec_path(self.path)
7913
+
7914
+
7915
+ ##
7916
+
7917
+
7918
+ @dc.dataclass(frozen=True)
7919
+ class DeployAppConfLink: # noqa
7920
+ """
7921
+ May be either:
7922
+ - @conf(.ext)* - links a single file in root of app conf dir to conf/@conf/@dst(.ext)*
7923
+ - @conf/file - links a single file in a single subdir to conf/@conf/@dst--file
7924
+ - @conf/ - links a directory in root of app conf dir to conf/@conf/@dst/
7925
+ """
7926
+
7927
+ src: str
7928
+
7929
+ kind: ta.Literal['current_only', 'all_active'] = 'current_only'
7930
+
7931
+ def __post_init__(self) -> None:
7932
+ check_valid_deploy_spec_path(self.src)
7933
+ if '/' in self.src:
7934
+ check.equal(self.src.count('/'), 1)
7935
+
7936
+
7937
+ ##
7938
+
7939
+
7940
+ @dc.dataclass(frozen=True)
7941
+ class DeployAppConfSpec:
7942
+ files: ta.Optional[ta.Sequence[DeployAppConfFile]] = None
7943
+
7944
+ links: ta.Optional[ta.Sequence[DeployAppConfLink]] = None
7945
+
7946
+ def __post_init__(self) -> None:
7947
+ if self.files:
7948
+ seen: ta.Set[str] = set()
7949
+ for f in self.files:
7950
+ check.not_in(f.path, seen)
7951
+ seen.add(f.path)
7952
+
7953
+
7811
7954
  ########################################
7812
7955
  # ../deploy/deploy.py
7813
7956
 
@@ -8038,283 +8181,125 @@ class DeployPath:
8038
8181
 
8039
8182
 
8040
8183
  ########################################
8041
- # ../deploy/specs.py
8184
+ # ../remote/execution.py
8185
+ """
8186
+ TODO:
8187
+ - sequence all messages
8188
+ """
8042
8189
 
8043
8190
 
8044
8191
  ##
8045
8192
 
8046
8193
 
8047
- def check_valid_deploy_spec_path(s: str) -> str:
8048
- check.non_empty_str(s)
8049
- for c in ['..', '//']:
8050
- check.not_in(c, s)
8051
- check.arg(not s.startswith('/'))
8052
- return s
8194
+ class _RemoteProtocol:
8195
+ class Message(abc.ABC): # noqa
8196
+ async def send(self, chan: RemoteChannel) -> None:
8197
+ await chan.send_obj(self, _RemoteProtocol.Message)
8053
8198
 
8199
+ @classmethod
8200
+ async def recv(cls: ta.Type[T], chan: RemoteChannel) -> ta.Optional[T]:
8201
+ return await chan.recv_obj(cls)
8054
8202
 
8055
- class DeploySpecKeyed(ta.Generic[KeyDeployTagT]):
8056
- @cached_nullary
8057
- def _key_str(self) -> str:
8058
- return hashlib.sha256(repr(self).encode('utf-8')).hexdigest()[:8]
8203
+ #
8059
8204
 
8060
- @abc.abstractmethod
8061
- def key(self) -> KeyDeployTagT:
8062
- raise NotImplementedError
8205
+ class Request(Message, abc.ABC): # noqa
8206
+ pass
8063
8207
 
8208
+ @dc.dataclass(frozen=True)
8209
+ class CommandRequest(Request):
8210
+ seq: int
8211
+ cmd: Command
8064
8212
 
8065
- ##
8213
+ @dc.dataclass(frozen=True)
8214
+ class PingRequest(Request):
8215
+ time: float
8066
8216
 
8217
+ #
8067
8218
 
8068
- @dc.dataclass(frozen=True)
8069
- class DeployGitRepo:
8070
- host: ta.Optional[str] = None
8071
- username: ta.Optional[str] = None
8072
- path: ta.Optional[str] = None
8219
+ class Response(Message, abc.ABC): # noqa
8220
+ pass
8073
8221
 
8074
- def __post_init__(self) -> None:
8075
- check.not_in('..', check.non_empty_str(self.host))
8076
- check.not_in('.', check.non_empty_str(self.path))
8222
+ @dc.dataclass(frozen=True)
8223
+ class LogResponse(Response):
8224
+ s: str
8077
8225
 
8226
+ @dc.dataclass(frozen=True)
8227
+ class CommandResponse(Response):
8228
+ seq: int
8229
+ res: CommandOutputOrExceptionData
8078
8230
 
8079
- @dc.dataclass(frozen=True)
8080
- class DeployGitSpec:
8081
- repo: DeployGitRepo
8082
- rev: DeployRev
8231
+ @dc.dataclass(frozen=True)
8232
+ class PingResponse(Response):
8233
+ time: float
8083
8234
 
8084
- subtrees: ta.Optional[ta.Sequence[str]] = None
8085
8235
 
8086
- def __post_init__(self) -> None:
8087
- check.non_empty_str(self.rev)
8088
- if self.subtrees is not None:
8089
- for st in self.subtrees:
8090
- check.non_empty_str(st)
8236
+ ##
8091
8237
 
8092
8238
 
8093
- ##
8239
+ class _RemoteLogHandler(logging.Handler):
8240
+ def __init__(
8241
+ self,
8242
+ chan: RemoteChannel,
8243
+ loop: ta.Any = None,
8244
+ ) -> None:
8245
+ super().__init__()
8094
8246
 
8247
+ self._chan = chan
8248
+ self._loop = loop
8095
8249
 
8096
- @dc.dataclass(frozen=True)
8097
- class DeployVenvSpec:
8098
- interp: ta.Optional[str] = None
8250
+ def emit(self, record):
8251
+ msg = self.format(record)
8099
8252
 
8100
- requirements_files: ta.Optional[ta.Sequence[str]] = None
8101
- extra_dependencies: ta.Optional[ta.Sequence[str]] = None
8253
+ async def inner():
8254
+ await _RemoteProtocol.LogResponse(msg).send(self._chan)
8102
8255
 
8103
- use_uv: bool = False
8256
+ loop = self._loop
8257
+ if loop is None:
8258
+ loop = asyncio.get_running_loop()
8259
+ if loop is not None:
8260
+ asyncio.run_coroutine_threadsafe(inner(), loop)
8104
8261
 
8105
8262
 
8106
8263
  ##
8107
8264
 
8108
8265
 
8109
- @dc.dataclass(frozen=True)
8110
- class DeployAppConfFile:
8111
- path: str
8112
- body: str
8266
+ class _RemoteCommandHandler:
8267
+ DEFAULT_PING_INTERVAL_S: float = 3.
8113
8268
 
8114
- def __post_init__(self) -> None:
8115
- check_valid_deploy_spec_path(self.path)
8269
+ def __init__(
8270
+ self,
8271
+ chan: RemoteChannel,
8272
+ executor: CommandExecutor,
8273
+ *,
8274
+ stop: ta.Optional[asyncio.Event] = None,
8275
+ ping_interval_s: float = DEFAULT_PING_INTERVAL_S,
8276
+ ) -> None:
8277
+ super().__init__()
8116
8278
 
8279
+ self._chan = chan
8280
+ self._executor = executor
8281
+ self._stop = stop if stop is not None else asyncio.Event()
8282
+ self._ping_interval_s = ping_interval_s
8117
8283
 
8118
- #
8284
+ self._cmds_by_seq: ta.Dict[int, _RemoteCommandHandler._Command] = {}
8119
8285
 
8286
+ self._last_ping_send: float = 0.
8287
+ self._ping_in_flight: bool = False
8288
+ self._last_ping_recv: ta.Optional[float] = None
8120
8289
 
8121
- @dc.dataclass(frozen=True)
8122
- class DeployAppConfLink: # noqa
8123
- """
8124
- May be either:
8125
- - @conf(.ext)* - links a single file in root of app conf dir to conf/@conf/@dst(.ext)*
8126
- - @conf/file - links a single file in a single subdir to conf/@conf/@dst--file
8127
- - @conf/ - links a directory in root of app conf dir to conf/@conf/@dst/
8128
- """
8290
+ def stop(self) -> None:
8291
+ self._stop.set()
8129
8292
 
8130
- src: str
8293
+ @dc.dataclass(frozen=True)
8294
+ class _Command:
8295
+ req: _RemoteProtocol.CommandRequest
8296
+ fut: asyncio.Future
8131
8297
 
8132
- kind: ta.Literal['current_only', 'all_active'] = 'current_only'
8298
+ async def run(self) -> None:
8299
+ log.debug('_RemoteCommandHandler loop start: %r', self)
8133
8300
 
8134
- def __post_init__(self) -> None:
8135
- check_valid_deploy_spec_path(self.src)
8136
- if '/' in self.src:
8137
- check.equal(self.src.count('/'), 1)
8138
-
8139
-
8140
- #
8141
-
8142
-
8143
- @dc.dataclass(frozen=True)
8144
- class DeployAppConfSpec:
8145
- files: ta.Optional[ta.Sequence[DeployAppConfFile]] = None
8146
-
8147
- links: ta.Optional[ta.Sequence[DeployAppConfLink]] = None
8148
-
8149
- def __post_init__(self) -> None:
8150
- if self.files:
8151
- seen: ta.Set[str] = set()
8152
- for f in self.files:
8153
- check.not_in(f.path, seen)
8154
- seen.add(f.path)
8155
-
8156
-
8157
- ##
8158
-
8159
-
8160
- @dc.dataclass(frozen=True)
8161
- class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
8162
- app: DeployApp
8163
-
8164
- git: DeployGitSpec
8165
-
8166
- venv: ta.Optional[DeployVenvSpec] = None
8167
-
8168
- conf: ta.Optional[DeployAppConfSpec] = None
8169
-
8170
- # @ta.override
8171
- def key(self) -> DeployAppKey:
8172
- return DeployAppKey(self._key_str())
8173
-
8174
-
8175
- ##
8176
-
8177
-
8178
- @dc.dataclass(frozen=True)
8179
- class DeploySpec(DeploySpecKeyed[DeployKey]):
8180
- home: DeployHome
8181
-
8182
- apps: ta.Sequence[DeployAppSpec]
8183
-
8184
- def __post_init__(self) -> None:
8185
- check.non_empty_str(self.home)
8186
-
8187
- seen: ta.Set[DeployApp] = set()
8188
- for a in self.apps:
8189
- if a.app in seen:
8190
- raise KeyError(a.app)
8191
- seen.add(a.app)
8192
-
8193
- # @ta.override
8194
- def key(self) -> DeployKey:
8195
- return DeployKey(self._key_str())
8196
-
8197
-
8198
- ########################################
8199
- # ../remote/execution.py
8200
- """
8201
- TODO:
8202
- - sequence all messages
8203
- """
8204
-
8205
-
8206
- ##
8207
-
8208
-
8209
- class _RemoteProtocol:
8210
- class Message(abc.ABC): # noqa
8211
- async def send(self, chan: RemoteChannel) -> None:
8212
- await chan.send_obj(self, _RemoteProtocol.Message)
8213
-
8214
- @classmethod
8215
- async def recv(cls: ta.Type[T], chan: RemoteChannel) -> ta.Optional[T]:
8216
- return await chan.recv_obj(cls)
8217
-
8218
- #
8219
-
8220
- class Request(Message, abc.ABC): # noqa
8221
- pass
8222
-
8223
- @dc.dataclass(frozen=True)
8224
- class CommandRequest(Request):
8225
- seq: int
8226
- cmd: Command
8227
-
8228
- @dc.dataclass(frozen=True)
8229
- class PingRequest(Request):
8230
- time: float
8231
-
8232
- #
8233
-
8234
- class Response(Message, abc.ABC): # noqa
8235
- pass
8236
-
8237
- @dc.dataclass(frozen=True)
8238
- class LogResponse(Response):
8239
- s: str
8240
-
8241
- @dc.dataclass(frozen=True)
8242
- class CommandResponse(Response):
8243
- seq: int
8244
- res: CommandOutputOrExceptionData
8245
-
8246
- @dc.dataclass(frozen=True)
8247
- class PingResponse(Response):
8248
- time: float
8249
-
8250
-
8251
- ##
8252
-
8253
-
8254
- class _RemoteLogHandler(logging.Handler):
8255
- def __init__(
8256
- self,
8257
- chan: RemoteChannel,
8258
- loop: ta.Any = None,
8259
- ) -> None:
8260
- super().__init__()
8261
-
8262
- self._chan = chan
8263
- self._loop = loop
8264
-
8265
- def emit(self, record):
8266
- msg = self.format(record)
8267
-
8268
- async def inner():
8269
- await _RemoteProtocol.LogResponse(msg).send(self._chan)
8270
-
8271
- loop = self._loop
8272
- if loop is None:
8273
- loop = asyncio.get_running_loop()
8274
- if loop is not None:
8275
- asyncio.run_coroutine_threadsafe(inner(), loop)
8276
-
8277
-
8278
- ##
8279
-
8280
-
8281
- class _RemoteCommandHandler:
8282
- DEFAULT_PING_INTERVAL_S: float = 3.
8283
-
8284
- def __init__(
8285
- self,
8286
- chan: RemoteChannel,
8287
- executor: CommandExecutor,
8288
- *,
8289
- stop: ta.Optional[asyncio.Event] = None,
8290
- ping_interval_s: float = DEFAULT_PING_INTERVAL_S,
8291
- ) -> None:
8292
- super().__init__()
8293
-
8294
- self._chan = chan
8295
- self._executor = executor
8296
- self._stop = stop if stop is not None else asyncio.Event()
8297
- self._ping_interval_s = ping_interval_s
8298
-
8299
- self._cmds_by_seq: ta.Dict[int, _RemoteCommandHandler._Command] = {}
8300
-
8301
- self._last_ping_send: float = 0.
8302
- self._ping_in_flight: bool = False
8303
- self._last_ping_recv: ta.Optional[float] = None
8304
-
8305
- def stop(self) -> None:
8306
- self._stop.set()
8307
-
8308
- @dc.dataclass(frozen=True)
8309
- class _Command:
8310
- req: _RemoteProtocol.CommandRequest
8311
- fut: asyncio.Future
8312
-
8313
- async def run(self) -> None:
8314
- log.debug('_RemoteCommandHandler loop start: %r', self)
8315
-
8316
- stop_task = asyncio.create_task(self._stop.wait())
8317
- recv_task: ta.Optional[asyncio.Task] = None
8301
+ stop_task = asyncio.create_task(self._stop.wait())
8302
+ recv_task: ta.Optional[asyncio.Task] = None
8318
8303
 
8319
8304
  while not self._stop.is_set():
8320
8305
  if recv_task is None:
@@ -8914,6 +8899,88 @@ class InterpInspector:
8914
8899
  return ret
8915
8900
 
8916
8901
 
8902
+ ########################################
8903
+ # ../../../omdev/interp/pyenv/pyenv.py
8904
+ """
8905
+ TODO:
8906
+ - custom tags
8907
+ - 'aliases'
8908
+ - https://github.com/pyenv/pyenv/pull/2966
8909
+ - https://github.com/pyenv/pyenv/issues/218 (lol)
8910
+ - probably need custom (temp?) definition file
8911
+ - *or* python-build directly just into the versions dir?
8912
+ - optionally install / upgrade pyenv itself
8913
+ - new vers dont need these custom mac opts, only run on old vers
8914
+ """
8915
+
8916
+
8917
+ class Pyenv:
8918
+ def __init__(
8919
+ self,
8920
+ *,
8921
+ root: ta.Optional[str] = None,
8922
+ ) -> None:
8923
+ if root is not None and not (isinstance(root, str) and root):
8924
+ raise ValueError(f'pyenv_root: {root!r}')
8925
+
8926
+ super().__init__()
8927
+
8928
+ self._root_kw = root
8929
+
8930
+ @async_cached_nullary
8931
+ async def root(self) -> ta.Optional[str]:
8932
+ if self._root_kw is not None:
8933
+ return self._root_kw
8934
+
8935
+ if shutil.which('pyenv'):
8936
+ return await asyncio_subprocesses.check_output_str('pyenv', 'root')
8937
+
8938
+ d = os.path.expanduser('~/.pyenv')
8939
+ if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
8940
+ return d
8941
+
8942
+ return None
8943
+
8944
+ @async_cached_nullary
8945
+ async def exe(self) -> str:
8946
+ return os.path.join(check.not_none(await self.root()), 'bin', 'pyenv')
8947
+
8948
+ async def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
8949
+ if (root := await self.root()) is None:
8950
+ return []
8951
+ ret = []
8952
+ vp = os.path.join(root, 'versions')
8953
+ if os.path.isdir(vp):
8954
+ for dn in os.listdir(vp):
8955
+ ep = os.path.join(vp, dn, 'bin', 'python')
8956
+ if not os.path.isfile(ep):
8957
+ continue
8958
+ ret.append((dn, ep))
8959
+ return ret
8960
+
8961
+ async def installable_versions(self) -> ta.List[str]:
8962
+ if await self.root() is None:
8963
+ return []
8964
+ ret = []
8965
+ s = await asyncio_subprocesses.check_output_str(await self.exe(), 'install', '--list')
8966
+ for l in s.splitlines():
8967
+ if not l.startswith(' '):
8968
+ continue
8969
+ l = l.strip()
8970
+ if not l:
8971
+ continue
8972
+ ret.append(l)
8973
+ return ret
8974
+
8975
+ async def update(self) -> bool:
8976
+ if (root := await self.root()) is None:
8977
+ return False
8978
+ if not os.path.isdir(os.path.join(root, '.git')):
8979
+ return False
8980
+ await asyncio_subprocesses.check_call('git', 'pull', cwd=root)
8981
+ return True
8982
+
8983
+
8917
8984
  ########################################
8918
8985
  # ../../../omdev/interp/resolvers.py
8919
8986
 
@@ -9073,7 +9140,7 @@ class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCom
9073
9140
 
9074
9141
 
9075
9142
  ########################################
9076
- # ../deploy/conf.py
9143
+ # ../deploy/conf/manager.py
9077
9144
  """
9078
9145
  TODO:
9079
9146
  - @conf DeployPathPlaceholder? :|
@@ -9094,6 +9161,19 @@ TODO:
9094
9161
 
9095
9162
 
9096
9163
  class DeployConfManager:
9164
+ def _render_app_conf_content(self, ac: DeployAppConfContent) -> str:
9165
+ if isinstance(ac, RawDeployAppConfContent):
9166
+ return ac.body
9167
+
9168
+ elif isinstance(ac, JsonDeployAppConfContent):
9169
+ return strip_with_newline(json_dumps_pretty(ac.obj))
9170
+
9171
+ elif isinstance(ac, IniDeployAppConfContent):
9172
+ return strip_with_newline(render_ini_config(ac.sections))
9173
+
9174
+ else:
9175
+ raise TypeError(ac)
9176
+
9097
9177
  async def _write_app_conf_file(
9098
9178
  self,
9099
9179
  acf: DeployAppConfFile,
@@ -9102,10 +9182,12 @@ class DeployConfManager:
9102
9182
  conf_file = os.path.join(app_conf_dir, acf.path)
9103
9183
  check.arg(is_path_in_dir(app_conf_dir, conf_file))
9104
9184
 
9185
+ body = self._render_app_conf_content(acf.content)
9186
+
9105
9187
  os.makedirs(os.path.dirname(conf_file), exist_ok=True)
9106
9188
 
9107
9189
  with open(conf_file, 'w') as f: # noqa
9108
- f.write(acf.body)
9190
+ f.write(body)
9109
9191
 
9110
9192
  #
9111
9193
 
@@ -9252,33 +9334,132 @@ class DeployPathOwner(abc.ABC):
9252
9334
  raise NotImplementedError
9253
9335
 
9254
9336
 
9255
- DeployPathOwners = ta.NewType('DeployPathOwners', ta.Sequence[DeployPathOwner])
9337
+ DeployPathOwners = ta.NewType('DeployPathOwners', ta.Sequence[DeployPathOwner])
9338
+
9339
+
9340
+ class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
9341
+ def __init__(
9342
+ self,
9343
+ *args: ta.Any,
9344
+ owned_dir: str,
9345
+ **kwargs: ta.Any,
9346
+ ) -> None:
9347
+ super().__init__(*args, **kwargs)
9348
+
9349
+ check.not_in('/', owned_dir)
9350
+ self._owned_dir: str = check.non_empty_str(owned_dir)
9351
+
9352
+ self._owned_deploy_paths = frozenset([DeployPath.parse(self._owned_dir + '/')])
9353
+
9354
+ def _dir(self, home: DeployHome) -> str:
9355
+ return os.path.join(check.non_empty_str(home), self._owned_dir)
9356
+
9357
+ def _make_dir(self, home: DeployHome) -> str:
9358
+ if not os.path.isdir(d := self._dir(home)):
9359
+ os.makedirs(d, exist_ok=True)
9360
+ return d
9361
+
9362
+ def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
9363
+ return self._owned_deploy_paths
9364
+
9365
+
9366
+ ########################################
9367
+ # ../deploy/specs.py
9368
+
9369
+
9370
+ ##
9371
+
9372
+
9373
+ class DeploySpecKeyed(ta.Generic[KeyDeployTagT]):
9374
+ @cached_nullary
9375
+ def _key_str(self) -> str:
9376
+ return hashlib.sha256(repr(self).encode('utf-8')).hexdigest()[:8]
9377
+
9378
+ @abc.abstractmethod
9379
+ def key(self) -> KeyDeployTagT:
9380
+ raise NotImplementedError
9381
+
9382
+
9383
+ ##
9384
+
9385
+
9386
+ @dc.dataclass(frozen=True)
9387
+ class DeployGitRepo:
9388
+ host: ta.Optional[str] = None
9389
+ username: ta.Optional[str] = None
9390
+ path: ta.Optional[str] = None
9391
+
9392
+ def __post_init__(self) -> None:
9393
+ check.not_in('..', check.non_empty_str(self.host))
9394
+ check.not_in('.', check.non_empty_str(self.path))
9395
+
9396
+
9397
+ @dc.dataclass(frozen=True)
9398
+ class DeployGitSpec:
9399
+ repo: DeployGitRepo
9400
+ rev: DeployRev
9401
+
9402
+ subtrees: ta.Optional[ta.Sequence[str]] = None
9403
+
9404
+ def __post_init__(self) -> None:
9405
+ check.non_empty_str(self.rev)
9406
+ if self.subtrees is not None:
9407
+ for st in self.subtrees:
9408
+ check.non_empty_str(st)
9409
+
9410
+
9411
+ ##
9412
+
9413
+
9414
+ @dc.dataclass(frozen=True)
9415
+ class DeployVenvSpec:
9416
+ interp: ta.Optional[str] = None
9417
+
9418
+ requirements_files: ta.Optional[ta.Sequence[str]] = None
9419
+ extra_dependencies: ta.Optional[ta.Sequence[str]] = None
9420
+
9421
+ use_uv: bool = False
9422
+
9423
+
9424
+ ##
9425
+
9426
+
9427
+ @dc.dataclass(frozen=True)
9428
+ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
9429
+ app: DeployApp
9430
+
9431
+ git: DeployGitSpec
9432
+
9433
+ venv: ta.Optional[DeployVenvSpec] = None
9434
+
9435
+ conf: ta.Optional[DeployAppConfSpec] = None
9436
+
9437
+ # @ta.override
9438
+ def key(self) -> DeployAppKey:
9439
+ return DeployAppKey(self._key_str())
9440
+
9256
9441
 
9442
+ ##
9257
9443
 
9258
- class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
9259
- def __init__(
9260
- self,
9261
- *args: ta.Any,
9262
- owned_dir: str,
9263
- **kwargs: ta.Any,
9264
- ) -> None:
9265
- super().__init__(*args, **kwargs)
9266
9444
 
9267
- check.not_in('/', owned_dir)
9268
- self._owned_dir: str = check.non_empty_str(owned_dir)
9445
+ @dc.dataclass(frozen=True)
9446
+ class DeploySpec(DeploySpecKeyed[DeployKey]):
9447
+ home: DeployHome
9269
9448
 
9270
- self._owned_deploy_paths = frozenset([DeployPath.parse(self._owned_dir + '/')])
9449
+ apps: ta.Sequence[DeployAppSpec]
9271
9450
 
9272
- def _dir(self, home: DeployHome) -> str:
9273
- return os.path.join(check.non_empty_str(home), self._owned_dir)
9451
+ def __post_init__(self) -> None:
9452
+ check.non_empty_str(self.home)
9274
9453
 
9275
- def _make_dir(self, home: DeployHome) -> str:
9276
- if not os.path.isdir(d := self._dir(home)):
9277
- os.makedirs(d, exist_ok=True)
9278
- return d
9454
+ seen: ta.Set[DeployApp] = set()
9455
+ for a in self.apps:
9456
+ if a.app in seen:
9457
+ raise KeyError(a.app)
9458
+ seen.add(a.app)
9279
9459
 
9280
- def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
9281
- return self._owned_deploy_paths
9460
+ # @ta.override
9461
+ def key(self) -> DeployKey:
9462
+ return DeployKey(self._key_str())
9282
9463
 
9283
9464
 
9284
9465
  ########################################
@@ -9802,88 +9983,7 @@ class SystemInterpProvider(InterpProvider):
9802
9983
 
9803
9984
 
9804
9985
  ########################################
9805
- # ../../../omdev/interp/pyenv/pyenv.py
9806
- """
9807
- TODO:
9808
- - custom tags
9809
- - 'aliases'
9810
- - https://github.com/pyenv/pyenv/pull/2966
9811
- - https://github.com/pyenv/pyenv/issues/218 (lol)
9812
- - probably need custom (temp?) definition file
9813
- - *or* python-build directly just into the versions dir?
9814
- - optionally install / upgrade pyenv itself
9815
- - new vers dont need these custom mac opts, only run on old vers
9816
- """
9817
-
9818
-
9819
- ##
9820
-
9821
-
9822
- class Pyenv:
9823
- def __init__(
9824
- self,
9825
- *,
9826
- root: ta.Optional[str] = None,
9827
- ) -> None:
9828
- if root is not None and not (isinstance(root, str) and root):
9829
- raise ValueError(f'pyenv_root: {root!r}')
9830
-
9831
- super().__init__()
9832
-
9833
- self._root_kw = root
9834
-
9835
- @async_cached_nullary
9836
- async def root(self) -> ta.Optional[str]:
9837
- if self._root_kw is not None:
9838
- return self._root_kw
9839
-
9840
- if shutil.which('pyenv'):
9841
- return await asyncio_subprocesses.check_output_str('pyenv', 'root')
9842
-
9843
- d = os.path.expanduser('~/.pyenv')
9844
- if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
9845
- return d
9846
-
9847
- return None
9848
-
9849
- @async_cached_nullary
9850
- async def exe(self) -> str:
9851
- return os.path.join(check.not_none(await self.root()), 'bin', 'pyenv')
9852
-
9853
- async def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
9854
- if (root := await self.root()) is None:
9855
- return []
9856
- ret = []
9857
- vp = os.path.join(root, 'versions')
9858
- if os.path.isdir(vp):
9859
- for dn in os.listdir(vp):
9860
- ep = os.path.join(vp, dn, 'bin', 'python')
9861
- if not os.path.isfile(ep):
9862
- continue
9863
- ret.append((dn, ep))
9864
- return ret
9865
-
9866
- async def installable_versions(self) -> ta.List[str]:
9867
- if await self.root() is None:
9868
- return []
9869
- ret = []
9870
- s = await asyncio_subprocesses.check_output_str(await self.exe(), 'install', '--list')
9871
- for l in s.splitlines():
9872
- if not l.startswith(' '):
9873
- continue
9874
- l = l.strip()
9875
- if not l:
9876
- continue
9877
- ret.append(l)
9878
- return ret
9879
-
9880
- async def update(self) -> bool:
9881
- if (root := await self.root()) is None:
9882
- return False
9883
- if not os.path.isdir(os.path.join(root, '.git')):
9884
- return False
9885
- await asyncio_subprocesses.check_call('git', 'pull', cwd=root)
9886
- return True
9986
+ # ../../../omdev/interp/pyenv/install.py
9887
9987
 
9888
9988
 
9889
9989
  ##
@@ -10094,161 +10194,31 @@ class PyenvVersionInstaller:
10094
10194
  conf_args = [
10095
10195
  *opts.opts,
10096
10196
  self._version,
10097
- ]
10098
-
10099
- full_args: ta.List[str]
10100
- if self._given_install_name is not None:
10101
- full_args = [
10102
- os.path.join(check.not_none(await self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'), # noqa
10103
- *conf_args,
10104
- await self.install_dir(),
10105
- ]
10106
- else:
10107
- full_args = [
10108
- await self._pyenv.exe(),
10109
- 'install',
10110
- *conf_args,
10111
- ]
10112
-
10113
- await asyncio_subprocesses.check_call(
10114
- *full_args,
10115
- env=env,
10116
- )
10117
-
10118
- exe = os.path.join(await self.install_dir(), 'bin', 'python')
10119
- if not os.path.isfile(exe):
10120
- raise RuntimeError(f'Interpreter not found: {exe}')
10121
- return exe
10122
-
10123
-
10124
- ##
10125
-
10126
-
10127
- class PyenvInterpProvider(InterpProvider):
10128
- @dc.dataclass(frozen=True)
10129
- class Options:
10130
- inspect: bool = False
10131
-
10132
- try_update: bool = False
10133
-
10134
- def __init__(
10135
- self,
10136
- options: Options = Options(),
10137
- *,
10138
- pyenv: Pyenv,
10139
- inspector: InterpInspector,
10140
- log: ta.Optional[logging.Logger] = None,
10141
- ) -> None:
10142
- super().__init__()
10143
-
10144
- self._options = options
10145
-
10146
- self._pyenv = pyenv
10147
- self._inspector = inspector
10148
- self._log = log
10149
-
10150
- #
10151
-
10152
- @staticmethod
10153
- def guess_version(s: str) -> ta.Optional[InterpVersion]:
10154
- def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
10155
- if s.endswith(sfx):
10156
- return s[:-len(sfx)], True
10157
- return s, False
10158
- ok = {}
10159
- s, ok['debug'] = strip_sfx(s, '-debug')
10160
- s, ok['threaded'] = strip_sfx(s, 't')
10161
- try:
10162
- v = Version(s)
10163
- except InvalidVersion:
10164
- return None
10165
- return InterpVersion(v, InterpOpts(**ok))
10166
-
10167
- class Installed(ta.NamedTuple):
10168
- name: str
10169
- exe: str
10170
- version: InterpVersion
10171
-
10172
- async def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
10173
- iv: ta.Optional[InterpVersion]
10174
- if self._options.inspect:
10175
- try:
10176
- iv = check.not_none(await self._inspector.inspect(ep)).iv
10177
- except Exception as e: # noqa
10178
- return None
10179
- else:
10180
- iv = self.guess_version(vn)
10181
- if iv is None:
10182
- return None
10183
- return PyenvInterpProvider.Installed(
10184
- name=vn,
10185
- exe=ep,
10186
- version=iv,
10187
- )
10188
-
10189
- async def installed(self) -> ta.Sequence[Installed]:
10190
- ret: ta.List[PyenvInterpProvider.Installed] = []
10191
- for vn, ep in await self._pyenv.version_exes():
10192
- if (i := await self._make_installed(vn, ep)) is None:
10193
- if self._log is not None:
10194
- self._log.debug('Invalid pyenv version: %s', vn)
10195
- continue
10196
- ret.append(i)
10197
- return ret
10198
-
10199
- #
10200
-
10201
- async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
10202
- return [i.version for i in await self.installed()]
10203
-
10204
- async def get_installed_version(self, version: InterpVersion) -> Interp:
10205
- for i in await self.installed():
10206
- if i.version == version:
10207
- return Interp(
10208
- exe=i.exe,
10209
- version=i.version,
10210
- )
10211
- raise KeyError(version)
10212
-
10213
- #
10214
-
10215
- async def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
10216
- lst = []
10217
-
10218
- for vs in await self._pyenv.installable_versions():
10219
- if (iv := self.guess_version(vs)) is None:
10220
- continue
10221
- if iv.opts.debug:
10222
- raise Exception('Pyenv installable versions not expected to have debug suffix')
10223
- for d in [False, True]:
10224
- lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
10225
-
10226
- return lst
10227
-
10228
- async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
10229
- lst = await self._get_installable_versions(spec)
10230
-
10231
- if self._options.try_update and not any(v in spec for v in lst):
10232
- if self._pyenv.update():
10233
- lst = await self._get_installable_versions(spec)
10234
-
10235
- return lst
10197
+ ]
10236
10198
 
10237
- async def install_version(self, version: InterpVersion) -> Interp:
10238
- inst_version = str(version.version)
10239
- inst_opts = version.opts
10240
- if inst_opts.threaded:
10241
- inst_version += 't'
10242
- inst_opts = dc.replace(inst_opts, threaded=False)
10199
+ full_args: ta.List[str]
10200
+ if self._given_install_name is not None:
10201
+ full_args = [
10202
+ os.path.join(check.not_none(await self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'), # noqa
10203
+ *conf_args,
10204
+ await self.install_dir(),
10205
+ ]
10206
+ else:
10207
+ full_args = [
10208
+ await self._pyenv.exe(),
10209
+ 'install',
10210
+ *conf_args,
10211
+ ]
10243
10212
 
10244
- installer = PyenvVersionInstaller(
10245
- inst_version,
10246
- interp_opts=inst_opts,
10247
- pyenv=self._pyenv,
10213
+ await asyncio_subprocesses.check_call(
10214
+ *full_args,
10215
+ env=env,
10248
10216
  )
10249
10217
 
10250
- exe = await installer.install()
10251
- return Interp(exe, version)
10218
+ exe = os.path.join(await self.install_dir(), 'bin', 'python')
10219
+ if not os.path.isfile(exe):
10220
+ raise RuntimeError(f'Interpreter not found: {exe}')
10221
+ return exe
10252
10222
 
10253
10223
 
10254
10224
  ########################################
@@ -10351,6 +10321,18 @@ def bind_commands(
10351
10321
  return inj.as_bindings(*lst)
10352
10322
 
10353
10323
 
10324
+ ########################################
10325
+ # ../deploy/inject_.py
10326
+
10327
+
10328
+ def bind_deploy_manager(cls: type) -> InjectorBindings:
10329
+ return inj.as_bindings(
10330
+ inj.bind(cls, singleton=True),
10331
+
10332
+ *([inj.bind(DeployPathOwner, to_key=cls, array=True)] if issubclass(cls, DeployPathOwner) else []),
10333
+ )
10334
+
10335
+
10354
10336
  ########################################
10355
10337
  # ../deploy/paths/manager.py
10356
10338
 
@@ -10578,15 +10560,143 @@ def bind_interp_providers() -> InjectorBindings:
10578
10560
 
10579
10561
 
10580
10562
  ########################################
10581
- # ../../../omdev/interp/pyenv/inject.py
10563
+ # ../../../omdev/interp/pyenv/provider.py
10582
10564
 
10583
10565
 
10584
- def bind_interp_pyenv() -> InjectorBindings:
10585
- lst: ta.List[InjectorBindingOrBindings] = [
10586
- inj.bind(Pyenv, singleton=True),
10566
+ class PyenvInterpProvider(InterpProvider):
10567
+ @dc.dataclass(frozen=True)
10568
+ class Options:
10569
+ inspect: bool = False
10587
10570
 
10588
- inj.bind(PyenvInterpProvider, singleton=True),
10589
- inj.bind(InterpProvider, to_key=PyenvInterpProvider, array=True),
10571
+ try_update: bool = False
10572
+
10573
+ def __init__(
10574
+ self,
10575
+ options: Options = Options(),
10576
+ *,
10577
+ pyenv: Pyenv,
10578
+ inspector: InterpInspector,
10579
+ log: ta.Optional[logging.Logger] = None,
10580
+ ) -> None:
10581
+ super().__init__()
10582
+
10583
+ self._options = options
10584
+
10585
+ self._pyenv = pyenv
10586
+ self._inspector = inspector
10587
+ self._log = log
10588
+
10589
+ #
10590
+
10591
+ @staticmethod
10592
+ def guess_version(s: str) -> ta.Optional[InterpVersion]:
10593
+ def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
10594
+ if s.endswith(sfx):
10595
+ return s[:-len(sfx)], True
10596
+ return s, False
10597
+ ok = {}
10598
+ s, ok['debug'] = strip_sfx(s, '-debug')
10599
+ s, ok['threaded'] = strip_sfx(s, 't')
10600
+ try:
10601
+ v = Version(s)
10602
+ except InvalidVersion:
10603
+ return None
10604
+ return InterpVersion(v, InterpOpts(**ok))
10605
+
10606
+ class Installed(ta.NamedTuple):
10607
+ name: str
10608
+ exe: str
10609
+ version: InterpVersion
10610
+
10611
+ async def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
10612
+ iv: ta.Optional[InterpVersion]
10613
+ if self._options.inspect:
10614
+ try:
10615
+ iv = check.not_none(await self._inspector.inspect(ep)).iv
10616
+ except Exception as e: # noqa
10617
+ return None
10618
+ else:
10619
+ iv = self.guess_version(vn)
10620
+ if iv is None:
10621
+ return None
10622
+ return PyenvInterpProvider.Installed(
10623
+ name=vn,
10624
+ exe=ep,
10625
+ version=iv,
10626
+ )
10627
+
10628
+ async def installed(self) -> ta.Sequence[Installed]:
10629
+ ret: ta.List[PyenvInterpProvider.Installed] = []
10630
+ for vn, ep in await self._pyenv.version_exes():
10631
+ if (i := await self._make_installed(vn, ep)) is None:
10632
+ if self._log is not None:
10633
+ self._log.debug('Invalid pyenv version: %s', vn)
10634
+ continue
10635
+ ret.append(i)
10636
+ return ret
10637
+
10638
+ #
10639
+
10640
+ async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
10641
+ return [i.version for i in await self.installed()]
10642
+
10643
+ async def get_installed_version(self, version: InterpVersion) -> Interp:
10644
+ for i in await self.installed():
10645
+ if i.version == version:
10646
+ return Interp(
10647
+ exe=i.exe,
10648
+ version=i.version,
10649
+ )
10650
+ raise KeyError(version)
10651
+
10652
+ #
10653
+
10654
+ async def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
10655
+ lst = []
10656
+
10657
+ for vs in await self._pyenv.installable_versions():
10658
+ if (iv := self.guess_version(vs)) is None:
10659
+ continue
10660
+ if iv.opts.debug:
10661
+ raise Exception('Pyenv installable versions not expected to have debug suffix')
10662
+ for d in [False, True]:
10663
+ lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
10664
+
10665
+ return lst
10666
+
10667
+ async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
10668
+ lst = await self._get_installable_versions(spec)
10669
+
10670
+ if self._options.try_update and not any(v in spec for v in lst):
10671
+ if self._pyenv.update():
10672
+ lst = await self._get_installable_versions(spec)
10673
+
10674
+ return lst
10675
+
10676
+ async def install_version(self, version: InterpVersion) -> Interp:
10677
+ inst_version = str(version.version)
10678
+ inst_opts = version.opts
10679
+ if inst_opts.threaded:
10680
+ inst_version += 't'
10681
+ inst_opts = dc.replace(inst_opts, threaded=False)
10682
+
10683
+ installer = PyenvVersionInstaller(
10684
+ inst_version,
10685
+ interp_opts=inst_opts,
10686
+ pyenv=self._pyenv,
10687
+ )
10688
+
10689
+ exe = await installer.install()
10690
+ return Interp(exe, version)
10691
+
10692
+
10693
+ ########################################
10694
+ # ../deploy/conf/inject.py
10695
+
10696
+
10697
+ def bind_deploy_conf() -> InjectorBindings:
10698
+ lst: ta.List[InjectorBindingOrBindings] = [
10699
+ bind_deploy_manager(DeployConfManager),
10590
10700
  ]
10591
10701
 
10592
10702
  return inj.as_bindings(*lst)
@@ -10955,6 +11065,50 @@ class SshManageTargetConnector(ManageTargetConnector):
10955
11065
  yield rce
10956
11066
 
10957
11067
 
11068
+ ########################################
11069
+ # ../../../omdev/interp/pyenv/inject.py
11070
+
11071
+
11072
+ def bind_interp_pyenv() -> InjectorBindings:
11073
+ lst: ta.List[InjectorBindingOrBindings] = [
11074
+ inj.bind(Pyenv, singleton=True),
11075
+
11076
+ inj.bind(PyenvInterpProvider, singleton=True),
11077
+ inj.bind(InterpProvider, to_key=PyenvInterpProvider, array=True),
11078
+ ]
11079
+
11080
+ return inj.as_bindings(*lst)
11081
+
11082
+
11083
+ ########################################
11084
+ # ../targets/inject.py
11085
+
11086
+
11087
+ def bind_targets() -> InjectorBindings:
11088
+ lst: ta.List[InjectorBindingOrBindings] = [
11089
+ inj.bind(LocalManageTargetConnector, singleton=True),
11090
+ inj.bind(DockerManageTargetConnector, singleton=True),
11091
+ inj.bind(SshManageTargetConnector, singleton=True),
11092
+
11093
+ inj.bind(TypeSwitchedManageTargetConnector, singleton=True),
11094
+ inj.bind(ManageTargetConnector, to_key=TypeSwitchedManageTargetConnector),
11095
+ ]
11096
+
11097
+ #
11098
+
11099
+ def provide_manage_target_connector_map(injector: Injector) -> ManageTargetConnectorMap:
11100
+ return ManageTargetConnectorMap({
11101
+ LocalManageTarget: injector[LocalManageTargetConnector],
11102
+ DockerManageTarget: injector[DockerManageTargetConnector],
11103
+ SshManageTarget: injector[SshManageTargetConnector],
11104
+ })
11105
+ lst.append(inj.bind(provide_manage_target_connector_map, singleton=True))
11106
+
11107
+ #
11108
+
11109
+ return inj.as_bindings(*lst)
11110
+
11111
+
10958
11112
  ########################################
10959
11113
  # ../../../omdev/interp/inject.py
10960
11114
 
@@ -10996,35 +11150,6 @@ def bind_interp() -> InjectorBindings:
10996
11150
  return inj.as_bindings(*lst)
10997
11151
 
10998
11152
 
10999
- ########################################
11000
- # ../targets/inject.py
11001
-
11002
-
11003
- def bind_targets() -> InjectorBindings:
11004
- lst: ta.List[InjectorBindingOrBindings] = [
11005
- inj.bind(LocalManageTargetConnector, singleton=True),
11006
- inj.bind(DockerManageTargetConnector, singleton=True),
11007
- inj.bind(SshManageTargetConnector, singleton=True),
11008
-
11009
- inj.bind(TypeSwitchedManageTargetConnector, singleton=True),
11010
- inj.bind(ManageTargetConnector, to_key=TypeSwitchedManageTargetConnector),
11011
- ]
11012
-
11013
- #
11014
-
11015
- def provide_manage_target_connector_map(injector: Injector) -> ManageTargetConnectorMap:
11016
- return ManageTargetConnectorMap({
11017
- LocalManageTarget: injector[LocalManageTargetConnector],
11018
- DockerManageTarget: injector[DockerManageTargetConnector],
11019
- SshManageTarget: injector[SshManageTargetConnector],
11020
- })
11021
- lst.append(inj.bind(provide_manage_target_connector_map, singleton=True))
11022
-
11023
- #
11024
-
11025
- return inj.as_bindings(*lst)
11026
-
11027
-
11028
11153
  ########################################
11029
11154
  # ../../../omdev/interp/default.py
11030
11155
 
@@ -11422,6 +11547,8 @@ def bind_deploy(
11422
11547
  lst: ta.List[InjectorBindingOrBindings] = [
11423
11548
  inj.bind(deploy_config),
11424
11549
 
11550
+ bind_deploy_conf(),
11551
+
11425
11552
  bind_deploy_paths(),
11426
11553
 
11427
11554
  bind_deploy_scope(),
@@ -11429,27 +11556,16 @@ def bind_deploy(
11429
11556
 
11430
11557
  #
11431
11558
 
11432
- def bind_manager(cls: type) -> InjectorBindings:
11433
- return inj.as_bindings(
11434
- inj.bind(cls, singleton=True),
11435
-
11436
- *([inj.bind(DeployPathOwner, to_key=cls, array=True)] if issubclass(cls, DeployPathOwner) else []),
11437
- )
11438
-
11439
- #
11440
-
11441
11559
  lst.extend([
11442
- bind_manager(DeployAppManager),
11443
-
11444
- bind_manager(DeployConfManager),
11560
+ bind_deploy_manager(DeployAppManager),
11445
11561
 
11446
- bind_manager(DeployGitManager),
11562
+ bind_deploy_manager(DeployGitManager),
11447
11563
 
11448
- bind_manager(DeployManager),
11564
+ bind_deploy_manager(DeployManager),
11449
11565
 
11450
- bind_manager(DeployTmpManager),
11566
+ bind_deploy_manager(DeployTmpManager),
11451
11567
 
11452
- bind_manager(DeployVenvManager),
11568
+ bind_deploy_manager(DeployVenvManager),
11453
11569
  ])
11454
11570
 
11455
11571
  #