ominfra 0.0.0.dev167__py3-none-any.whl → 0.0.0.dev169__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.
@@ -1726,6 +1726,8 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
1726
1726
  """
1727
1727
  TODO:
1728
1728
  - def maybe(v: lang.Maybe[T])
1729
+ - def not_ ?
1730
+ - ** class @dataclass Raise - user message should be able to be an exception type or instance or factory
1729
1731
  """
1730
1732
 
1731
1733
 
@@ -2412,6 +2414,28 @@ def strip_with_newline(s: str) -> str:
2412
2414
  return s.strip() + '\n'
2413
2415
 
2414
2416
 
2417
+ @ta.overload
2418
+ def split_keep_delimiter(s: str, d: str) -> str:
2419
+ ...
2420
+
2421
+
2422
+ @ta.overload
2423
+ def split_keep_delimiter(s: bytes, d: bytes) -> bytes:
2424
+ ...
2425
+
2426
+
2427
+ def split_keep_delimiter(s, d):
2428
+ ps = []
2429
+ i = 0
2430
+ while i < len(s):
2431
+ if (n := s.find(d, i)) < i:
2432
+ ps.append(s[i:])
2433
+ break
2434
+ ps.append(s[i:n + 1])
2435
+ i = n + 1
2436
+ return ps
2437
+
2438
+
2415
2439
  ##
2416
2440
 
2417
2441
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev167
3
+ Version: 0.0.0.dev169
4
4
  Summary: ominfra
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,8 +12,8 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omdev==0.0.0.dev167
16
- Requires-Dist: omlish==0.0.0.dev167
15
+ Requires-Dist: omdev==0.0.0.dev169
16
+ Requires-Dist: omlish==0.0.0.dev169
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -44,18 +44,23 @@ ominfra/manage/commands/ping.py,sha256=DVZFzL1Z_f-Bq53vxMrL3xOi0iK_nMonJE4KvQf9w
44
44
  ominfra/manage/commands/subprocess.py,sha256=yHGMbAI-xKe_9BUs5IZ3Yav8qRE-I9aGnBtTwW15Pnw,2440
45
45
  ominfra/manage/commands/types.py,sha256=XFZPeqeIBAaIIQF3pdPbGxLlb-LCrz6WtlDWO2q_vz0,210
46
46
  ominfra/manage/deploy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
- ominfra/manage/deploy/apps.py,sha256=jLvam6sVcWfRmyyLeJyTxz0IErWTn98rf1-8lIEEYlM,3292
48
- ominfra/manage/deploy/commands.py,sha256=N9qVntnRgJ_IneI7rEQB2Za0oU7gouPfm-sl2MCwW1E,764
49
- ominfra/manage/deploy/conf.py,sha256=HxKLWIT95StavJ0uHhMhuvWio1gTYBlDJv6N1_XdxEo,4417
47
+ ominfra/manage/deploy/apps.py,sha256=HE2-ThrWR4wplT5hpPSQx0ERAkV-f58hj38al_FaZ7o,4457
48
+ ominfra/manage/deploy/commands.py,sha256=fKFKhFwqIqC_PsgA-W66qIJ5S32xRgBBaRt3lbPX5Zg,763
49
+ ominfra/manage/deploy/conf.py,sha256=AvIZFNuURpS0mKZazr36oBMKi4kJDeTlt74JZmDrpcA,5227
50
50
  ominfra/manage/deploy/config.py,sha256=aR6ubMEWqkTI55XtcG1Cczn6YhCVN6eSL8DT5EHQJN0,166
51
- ominfra/manage/deploy/git.py,sha256=BGht0UmY1d76kSGHESWuG8qxNKLuOGrwey5V9M4pfCY,3746
52
- ominfra/manage/deploy/inject.py,sha256=o-bgWvziUuE5XzsE9_rcPSdWqPO9AAf2SbXamEpir2k,1978
51
+ ominfra/manage/deploy/deploy.py,sha256=APg8Pbni48ycw83JdsWZRlc1utVDCWlj4q8RCEDXPDg,564
52
+ ominfra/manage/deploy/git.py,sha256=cfTCx1qD-FQPFkbYW28tkU8nVxQbnfnWxpuJuGQHtBw,3753
53
+ ominfra/manage/deploy/inject.py,sha256=kzGl2N2jhijUw4-PYUK1LNG8_MJD7BMgCbi6nDViMWg,1965
53
54
  ominfra/manage/deploy/interp.py,sha256=OKkenH8YKEW_mEDR6X7_ZLxK9a1Ox6KHSwFPTHT6OzA,1029
54
- ominfra/manage/deploy/paths.py,sha256=Rq9OsK3MpporH6tfuumzLO51e4Yz_wi6RiXEg82iyLY,6947
55
- ominfra/manage/deploy/specs.py,sha256=Xv1OQ3yQxOTR39Gj5ACgAn7vpgL-zVuGk1CHk_xmt5o,2883
56
- ominfra/manage/deploy/tmp.py,sha256=L0pIfQuxQ7_6gC_AAv7eubI37_IPzCVR29hkn1MHL2Q,1230
57
- ominfra/manage/deploy/types.py,sha256=o95wqvTGNRq8Cxx7VpqeX-9x1tI8k8BpqPFvJZkJYBA,305
55
+ ominfra/manage/deploy/specs.py,sha256=3VenKZyX_PmAFcqmvNQqRO-EaMuvFwWWaVF8Y_X9fHU,2865
56
+ ominfra/manage/deploy/tmp.py,sha256=dFJuqGfSf5otCxSaCI01a5UOSaArMlU4MzzYcyr74-s,1237
57
+ ominfra/manage/deploy/types.py,sha256=HzOTDmj9HcHWA_X1GGiwYZ98CGG7X_hdAGMvit_a_n4,829
58
58
  ominfra/manage/deploy/venvs.py,sha256=1co8l3eSxl2WccrF-XOTqeltoMccRH63o69NblV3yok,1366
59
+ ominfra/manage/deploy/paths/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
+ ominfra/manage/deploy/paths/inject.py,sha256=X81C-Qhef1LQ7tILWvkomBwFTvgooLVmWRnKL7TeVoI,596
61
+ ominfra/manage/deploy/paths/manager.py,sha256=gxr_CsjLmjxXx8w3J8ookJk9OGltCpyBFYBnxXaw5lg,1050
62
+ ominfra/manage/deploy/paths/owners.py,sha256=GmLy0E70C8CF3eYIdkAhBtYaZXW4QWmSzvgts5l1i_4,1379
63
+ ominfra/manage/deploy/paths/paths.py,sha256=gluJ0I-OPfig0YbjlqE1Tepv48oKIIv6lN6kcQLtUg0,6076
59
64
  ominfra/manage/remote/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
65
  ominfra/manage/remote/_main.py,sha256=p5KoiS2WMw6QAqlDl_Zun-JybmCsy8awIfpBMLBjGMY,4356
61
66
  ominfra/manage/remote/channel.py,sha256=36xR9Ti9ZA8TUBtxmY0u7_3Lv7E6wzQTxlZl7gLR5GE,2224
@@ -76,9 +81,9 @@ ominfra/manage/targets/connection.py,sha256=5e8h9Miej2DKJxZfLyxpGe8y-Y0V_b_AuUW1
76
81
  ominfra/manage/targets/inject.py,sha256=P4597xWM-V3I_gCt2O71OLhYQkkXtuJvkYRsIbhhMcE,1561
77
82
  ominfra/manage/targets/targets.py,sha256=CFl8Uirgn3gfowO1Fn-LBK-6qYqEMFJ9snPUl0gCRuM,1753
78
83
  ominfra/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
- ominfra/scripts/journald2aws.py,sha256=Cfxnq9N2PaRLBrGSPq4qi39SOGldKZXT_Ve2_uPWcpY,155191
80
- ominfra/scripts/manage.py,sha256=Yb_Ok9rB8zMycxED0fjzHQVNmVQ2qc14pASuzpves68,301283
81
- ominfra/scripts/supervisor.py,sha256=y9o5NyKKb7ugwk3ZEywzlqVFd7QDhsSO2_C8oSqO_28,273957
84
+ ominfra/scripts/journald2aws.py,sha256=OjbemVx-zyCBuWZVgTky66n2-MfyyS7ups4A9hsvHfA,155684
85
+ ominfra/scripts/manage.py,sha256=Xq28GDDorMsopZqYqZ240lcn1IFCHZi5-YOjZAEXVFY,306243
86
+ ominfra/scripts/supervisor.py,sha256=viYrsafRW5fLN2KUS1MtxCOm9CO4iOgIu60cmN3joSg,274450
82
87
  ominfra/supervisor/LICENSE.txt,sha256=yvqaMNsDhWxziHa9ien6qCW1SkZv-DQlAg96XjfSee8,1746
83
88
  ominfra/supervisor/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
84
89
  ominfra/supervisor/__main__.py,sha256=I0yFw-C08OOiZ3BF6lF1Oiv789EQXu-_j6whDhQUTEA,66
@@ -120,9 +125,9 @@ ominfra/tailscale/api.py,sha256=C5-t_b6jZXUWcy5k8bXm7CFnk73pSdrlMOgGDeGVrpw,1370
120
125
  ominfra/tailscale/cli.py,sha256=h6akQJMl0KuWLHS7Ur6WcBZ2JwF0DJQhsPTnFBdGyNk,3571
121
126
  ominfra/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
127
  ominfra/tools/listresources.py,sha256=4qVg5txsb10EHhvqXXeM6gJ2jx9LbroEnPydDv1uXs0,6176
123
- ominfra-0.0.0.dev167.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
124
- ominfra-0.0.0.dev167.dist-info/METADATA,sha256=JFB_UaK98rzNZ6vQWBEVI9f7zjJ4DeSf00RpqMQrczs,731
125
- ominfra-0.0.0.dev167.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
126
- ominfra-0.0.0.dev167.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
127
- ominfra-0.0.0.dev167.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
128
- ominfra-0.0.0.dev167.dist-info/RECORD,,
128
+ ominfra-0.0.0.dev169.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
129
+ ominfra-0.0.0.dev169.dist-info/METADATA,sha256=_6arfj7ECnJB7EoB6PCvXeAJHqqE7JWT825sVwCzr8Q,731
130
+ ominfra-0.0.0.dev169.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
131
+ ominfra-0.0.0.dev169.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
132
+ ominfra-0.0.0.dev169.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
133
+ ominfra-0.0.0.dev169.dist-info/RECORD,,
@@ -1,243 +0,0 @@
1
- # ruff: noqa: UP006 UP007
2
- """
3
- TODO:
4
- - run/{.pid,.sock}
5
- - logs/...
6
- - current symlink
7
- - conf/{nginx,supervisor}
8
- - env/?
9
- - apps/<app>/shared
10
- """
11
- import abc
12
- import dataclasses as dc
13
- import os.path
14
- import typing as ta
15
-
16
- from omlish.lite.cached import cached_nullary
17
- from omlish.lite.check import check
18
-
19
- from .types import DeployHome
20
-
21
-
22
- DeployPathKind = ta.Literal['dir', 'file'] # ta.TypeAlias
23
- DeployPathPlaceholder = ta.Literal['app', 'tag'] # ta.TypeAlias
24
-
25
-
26
- ##
27
-
28
-
29
- DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER = '@'
30
- DEPLOY_PATH_PLACEHOLDER_SEPARATORS = '-.'
31
-
32
- DEPLOY_PATH_PLACEHOLDERS: ta.FrozenSet[str] = frozenset([
33
- 'app',
34
- 'tag',
35
- ])
36
-
37
-
38
- class DeployPathError(Exception):
39
- pass
40
-
41
-
42
- @dc.dataclass(frozen=True)
43
- class DeployPathPart(abc.ABC): # noqa
44
- @property
45
- @abc.abstractmethod
46
- def kind(self) -> DeployPathKind:
47
- raise NotImplementedError
48
-
49
- @abc.abstractmethod
50
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
51
- raise NotImplementedError
52
-
53
-
54
- #
55
-
56
-
57
- class DirDeployPathPart(DeployPathPart, abc.ABC):
58
- @property
59
- def kind(self) -> DeployPathKind:
60
- return 'dir'
61
-
62
- @classmethod
63
- def parse(cls, s: str) -> 'DirDeployPathPart':
64
- if DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER in s:
65
- check.equal(s[0], DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER)
66
- return PlaceholderDirDeployPathPart(s[1:])
67
- else:
68
- return ConstDirDeployPathPart(s)
69
-
70
-
71
- class FileDeployPathPart(DeployPathPart, abc.ABC):
72
- @property
73
- def kind(self) -> DeployPathKind:
74
- return 'file'
75
-
76
- @classmethod
77
- def parse(cls, s: str) -> 'FileDeployPathPart':
78
- if DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER in s:
79
- check.equal(s[0], DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER)
80
- if not any(c in s for c in DEPLOY_PATH_PLACEHOLDER_SEPARATORS):
81
- return PlaceholderFileDeployPathPart(s[1:], '')
82
- else:
83
- p = min(f for c in DEPLOY_PATH_PLACEHOLDER_SEPARATORS if (f := s.find(c)) > 0)
84
- return PlaceholderFileDeployPathPart(s[1:p], s[p:])
85
- else:
86
- return ConstFileDeployPathPart(s)
87
-
88
-
89
- #
90
-
91
-
92
- @dc.dataclass(frozen=True)
93
- class ConstDeployPathPart(DeployPathPart, abc.ABC):
94
- name: str
95
-
96
- def __post_init__(self) -> None:
97
- check.non_empty_str(self.name)
98
- check.not_in('/', self.name)
99
- check.not_in(DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER, self.name)
100
-
101
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
102
- return self.name
103
-
104
-
105
- class ConstDirDeployPathPart(ConstDeployPathPart, DirDeployPathPart):
106
- pass
107
-
108
-
109
- class ConstFileDeployPathPart(ConstDeployPathPart, FileDeployPathPart):
110
- pass
111
-
112
-
113
- #
114
-
115
-
116
- @dc.dataclass(frozen=True)
117
- class PlaceholderDeployPathPart(DeployPathPart, abc.ABC):
118
- placeholder: str # DeployPathPlaceholder
119
-
120
- def __post_init__(self) -> None:
121
- check.non_empty_str(self.placeholder)
122
- for c in [*DEPLOY_PATH_PLACEHOLDER_SEPARATORS, DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER, '/']:
123
- check.not_in(c, self.placeholder)
124
- check.in_(self.placeholder, DEPLOY_PATH_PLACEHOLDERS)
125
-
126
- def _render_placeholder(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
127
- if placeholders is not None:
128
- return placeholders[self.placeholder] # type: ignore
129
- else:
130
- return DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER + self.placeholder
131
-
132
-
133
- @dc.dataclass(frozen=True)
134
- class PlaceholderDirDeployPathPart(PlaceholderDeployPathPart, DirDeployPathPart):
135
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
136
- return self._render_placeholder(placeholders)
137
-
138
-
139
- @dc.dataclass(frozen=True)
140
- class PlaceholderFileDeployPathPart(PlaceholderDeployPathPart, FileDeployPathPart):
141
- suffix: str
142
-
143
- def __post_init__(self) -> None:
144
- super().__post_init__()
145
- if self.suffix:
146
- for c in [DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER, '/']:
147
- check.not_in(c, self.suffix)
148
-
149
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
150
- return self._render_placeholder(placeholders) + self.suffix
151
-
152
-
153
- ##
154
-
155
-
156
- @dc.dataclass(frozen=True)
157
- class DeployPath:
158
- parts: ta.Sequence[DeployPathPart]
159
-
160
- def __post_init__(self) -> None:
161
- hash(self)
162
-
163
- check.not_empty(self.parts)
164
- for p in self.parts[:-1]:
165
- check.equal(p.kind, 'dir')
166
-
167
- pd = {}
168
- for i, p in enumerate(self.parts):
169
- if isinstance(p, PlaceholderDeployPathPart):
170
- if p.placeholder in pd:
171
- raise DeployPathError('Duplicate placeholders in path', self)
172
- pd[p.placeholder] = i
173
-
174
- if 'tag' in pd:
175
- if 'app' not in pd or pd['app'] >= pd['tag']:
176
- raise DeployPathError('Tag placeholder in path without preceding app', self)
177
-
178
- @property
179
- def kind(self) -> ta.Literal['file', 'dir']:
180
- return self.parts[-1].kind
181
-
182
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
183
- return os.path.join( # noqa
184
- *[p.render(placeholders) for p in self.parts],
185
- *([''] if self.kind == 'dir' else []),
186
- )
187
-
188
- @classmethod
189
- def parse(cls, s: str) -> 'DeployPath':
190
- tail_parse: ta.Callable[[str], DeployPathPart]
191
- if s.endswith('/'):
192
- tail_parse = DirDeployPathPart.parse
193
- s = s[:-1]
194
- else:
195
- tail_parse = FileDeployPathPart.parse
196
- ps = check.non_empty_str(s).split('/')
197
- return cls((
198
- *([DirDeployPathPart.parse(p) for p in ps[:-1]] if len(ps) > 1 else []),
199
- tail_parse(ps[-1]),
200
- ))
201
-
202
-
203
- ##
204
-
205
-
206
- class DeployPathOwner(abc.ABC):
207
- @abc.abstractmethod
208
- def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
209
- raise NotImplementedError
210
-
211
-
212
- DeployPathOwners = ta.NewType('DeployPathOwners', ta.Sequence[DeployPathOwner])
213
-
214
-
215
- class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
216
- def __init__(
217
- self,
218
- *args: ta.Any,
219
- owned_dir: str,
220
- deploy_home: ta.Optional[DeployHome],
221
- **kwargs: ta.Any,
222
- ) -> None:
223
- super().__init__(*args, **kwargs)
224
-
225
- check.not_in('/', owned_dir)
226
- self._owned_dir: str = check.non_empty_str(owned_dir)
227
-
228
- self._deploy_home = deploy_home
229
-
230
- self._owned_deploy_paths = frozenset([DeployPath.parse(self._owned_dir + '/')])
231
-
232
- @cached_nullary
233
- def _dir(self) -> str:
234
- return os.path.join(check.non_empty_str(self._deploy_home), self._owned_dir)
235
-
236
- @cached_nullary
237
- def _make_dir(self) -> str:
238
- if not os.path.isdir(d := self._dir()):
239
- os.makedirs(d, exist_ok=True)
240
- return d
241
-
242
- def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
243
- return self._owned_deploy_paths