ominfra 0.0.0.dev181__py3-none-any.whl → 0.0.0.dev183__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ominfra/scripts/manage.py CHANGED
@@ -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
  #