ominfra 0.0.0.dev7__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/__about__.py +27 -0
- ominfra/__init__.py +0 -0
- ominfra/bootstrap/__init__.py +0 -0
- ominfra/bootstrap/bootstrap.py +8 -0
- ominfra/cmds.py +83 -0
- ominfra/deploy/__init__.py +0 -0
- ominfra/deploy/_executor.py +1036 -0
- ominfra/deploy/configs.py +19 -0
- ominfra/deploy/executor/__init__.py +1 -0
- ominfra/deploy/executor/base.py +115 -0
- ominfra/deploy/executor/concerns/__init__.py +0 -0
- ominfra/deploy/executor/concerns/dirs.py +28 -0
- ominfra/deploy/executor/concerns/nginx.py +47 -0
- ominfra/deploy/executor/concerns/repo.py +17 -0
- ominfra/deploy/executor/concerns/supervisor.py +46 -0
- ominfra/deploy/executor/concerns/systemd.py +88 -0
- ominfra/deploy/executor/concerns/user.py +25 -0
- ominfra/deploy/executor/concerns/venv.py +22 -0
- ominfra/deploy/executor/main.py +119 -0
- ominfra/deploy/poly/__init__.py +1 -0
- ominfra/deploy/poly/_main.py +725 -0
- ominfra/deploy/poly/base.py +179 -0
- ominfra/deploy/poly/configs.py +38 -0
- ominfra/deploy/poly/deploy.py +25 -0
- ominfra/deploy/poly/main.py +18 -0
- ominfra/deploy/poly/nginx.py +60 -0
- ominfra/deploy/poly/repo.py +41 -0
- ominfra/deploy/poly/runtime.py +39 -0
- ominfra/deploy/poly/site.py +11 -0
- ominfra/deploy/poly/supervisor.py +64 -0
- ominfra/deploy/poly/venv.py +52 -0
- ominfra/deploy/remote.py +91 -0
- ominfra/pyremote/__init__.py +0 -0
- ominfra/pyremote/_runcommands.py +824 -0
- ominfra/pyremote/bootstrap.py +149 -0
- ominfra/pyremote/runcommands.py +56 -0
- ominfra/ssh.py +191 -0
- ominfra/tools/__init__.py +0 -0
- ominfra/tools/listresources.py +256 -0
- ominfra-0.0.0.dev7.dist-info/LICENSE +21 -0
- ominfra-0.0.0.dev7.dist-info/METADATA +19 -0
- ominfra-0.0.0.dev7.dist-info/RECORD +44 -0
- ominfra-0.0.0.dev7.dist-info/WHEEL +5 -0
- ominfra-0.0.0.dev7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,824 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# noinspection DuplicatedCode
|
3
|
+
# @omdev-amalg-output runcommands.py
|
4
|
+
# ruff: noqa: UP006 UP007
|
5
|
+
import abc
|
6
|
+
import base64
|
7
|
+
import collections.abc
|
8
|
+
import dataclasses as dc
|
9
|
+
import datetime
|
10
|
+
import decimal
|
11
|
+
import enum
|
12
|
+
import fractions
|
13
|
+
import functools
|
14
|
+
import inspect
|
15
|
+
import io
|
16
|
+
import json
|
17
|
+
import logging
|
18
|
+
import os
|
19
|
+
import shlex
|
20
|
+
import subprocess
|
21
|
+
import sys
|
22
|
+
import textwrap
|
23
|
+
import typing as ta
|
24
|
+
import uuid
|
25
|
+
import weakref # noqa
|
26
|
+
import zlib
|
27
|
+
|
28
|
+
|
29
|
+
T = ta.TypeVar('T')
|
30
|
+
|
31
|
+
|
32
|
+
########################################
|
33
|
+
# ../bootstrap.py
|
34
|
+
"""
|
35
|
+
Basically this: https://mitogen.networkgenomics.com/howitworks.html
|
36
|
+
"""
|
37
|
+
|
38
|
+
|
39
|
+
##
|
40
|
+
|
41
|
+
|
42
|
+
_BOOTSTRAP_COMM_FD = 100
|
43
|
+
_BOOTSTRAP_SRC_FD = 101
|
44
|
+
|
45
|
+
_BOOTSTRAP_CHILD_PID_VAR = '_PYR_CPID'
|
46
|
+
_BOOTSTRAP_ARGV0_VAR = '_PYR_ARGV0'
|
47
|
+
|
48
|
+
BOOTSTRAP_ACK0 = b'OPYR000\n'
|
49
|
+
BOOTSTRAP_ACK1 = b'OPYR001\n'
|
50
|
+
|
51
|
+
_BOOTSTRAP_PROC_TITLE_FMT = '(pyremote:%s)'
|
52
|
+
|
53
|
+
_BOOTSTRAP_IMPORTS = [
|
54
|
+
'base64',
|
55
|
+
'os',
|
56
|
+
'sys',
|
57
|
+
'zlib',
|
58
|
+
]
|
59
|
+
|
60
|
+
|
61
|
+
def _bootstrap_main(context_name: str, main_z_len: int) -> None:
|
62
|
+
# Two copies of main src to be sent to parent
|
63
|
+
r0, w0 = os.pipe()
|
64
|
+
r1, w1 = os.pipe()
|
65
|
+
|
66
|
+
if (cp := os.fork()):
|
67
|
+
# Parent process
|
68
|
+
|
69
|
+
# Dup original stdin to comm_fd for use as comm channel
|
70
|
+
os.dup2(0, _BOOTSTRAP_COMM_FD)
|
71
|
+
|
72
|
+
# Overwrite stdin (fed to python repl) with first copy of src
|
73
|
+
os.dup2(r0, 0)
|
74
|
+
|
75
|
+
# Dup second copy of src to src_fd to recover after launch
|
76
|
+
os.dup2(r1, _BOOTSTRAP_SRC_FD)
|
77
|
+
|
78
|
+
# Close remaining fd's
|
79
|
+
for f in [r0, w0, r1, w1]:
|
80
|
+
os.close(f)
|
81
|
+
|
82
|
+
# Save child pid to close after relaunch
|
83
|
+
os.environ[_BOOTSTRAP_CHILD_PID_VAR] = str(cp)
|
84
|
+
|
85
|
+
# Save original argv0
|
86
|
+
os.environ[_BOOTSTRAP_ARGV0_VAR] = sys.executable
|
87
|
+
|
88
|
+
# Start repl reading stdin from r0
|
89
|
+
os.execl(sys.executable, sys.executable + (_BOOTSTRAP_PROC_TITLE_FMT % (context_name,)))
|
90
|
+
|
91
|
+
else:
|
92
|
+
# Child process
|
93
|
+
|
94
|
+
# Write first ack
|
95
|
+
os.write(1, BOOTSTRAP_ACK0)
|
96
|
+
|
97
|
+
# Read main src from stdin
|
98
|
+
main_src = zlib.decompress(os.fdopen(0, 'rb').read(main_z_len))
|
99
|
+
|
100
|
+
# Write both copies of main src
|
101
|
+
for w in [w0, w1]:
|
102
|
+
fp = os.fdopen(w, 'wb', 0)
|
103
|
+
fp.write(main_src)
|
104
|
+
fp.close()
|
105
|
+
|
106
|
+
# Write second ack
|
107
|
+
os.write(1, BOOTSTRAP_ACK1)
|
108
|
+
|
109
|
+
sys.exit(0)
|
110
|
+
|
111
|
+
|
112
|
+
#
|
113
|
+
|
114
|
+
|
115
|
+
def bootstrap_payload(context_name: str, main_z_len: int) -> str:
|
116
|
+
bs_src = textwrap.dedent(inspect.getsource(_bootstrap_main))
|
117
|
+
|
118
|
+
for gl in [
|
119
|
+
'_BOOTSTRAP_COMM_FD',
|
120
|
+
'_BOOTSTRAP_SRC_FD',
|
121
|
+
|
122
|
+
'_BOOTSTRAP_CHILD_PID_VAR',
|
123
|
+
'_BOOTSTRAP_ARGV0_VAR',
|
124
|
+
|
125
|
+
'BOOTSTRAP_ACK0',
|
126
|
+
'BOOTSTRAP_ACK1',
|
127
|
+
|
128
|
+
'_BOOTSTRAP_PROC_TITLE_FMT',
|
129
|
+
]:
|
130
|
+
bs_src = bs_src.replace(gl, repr(globals()[gl]))
|
131
|
+
|
132
|
+
bs_src = '\n'.join(
|
133
|
+
cl
|
134
|
+
for l in bs_src.splitlines()
|
135
|
+
if (cl := (l.split('#')[0]).rstrip())
|
136
|
+
if cl.strip()
|
137
|
+
)
|
138
|
+
|
139
|
+
bs_z = zlib.compress(bs_src.encode('utf-8'))
|
140
|
+
bs_z64 = base64.encodebytes(bs_z).replace(b'\n', b'')
|
141
|
+
|
142
|
+
stmts = [
|
143
|
+
f'import {", ".join(_BOOTSTRAP_IMPORTS)}',
|
144
|
+
f'exec(zlib.decompress(base64.decodebytes({bs_z64!r})))',
|
145
|
+
f'_bootstrap_main({context_name!r}, {main_z_len})',
|
146
|
+
]
|
147
|
+
|
148
|
+
cmd = '; '.join(stmts)
|
149
|
+
return cmd
|
150
|
+
|
151
|
+
|
152
|
+
#
|
153
|
+
|
154
|
+
|
155
|
+
class PostBoostrap(ta.NamedTuple):
|
156
|
+
input: ta.BinaryIO
|
157
|
+
main_src: str
|
158
|
+
|
159
|
+
|
160
|
+
def post_boostrap() -> PostBoostrap:
|
161
|
+
# Restore original argv0
|
162
|
+
sys.executable = os.environ.pop(_BOOTSTRAP_ARGV0_VAR)
|
163
|
+
|
164
|
+
# Reap boostrap child
|
165
|
+
os.waitpid(int(os.environ.pop(_BOOTSTRAP_CHILD_PID_VAR)), 0)
|
166
|
+
|
167
|
+
# Read second copy of main src
|
168
|
+
r1 = os.fdopen(_BOOTSTRAP_SRC_FD, 'rb', 0)
|
169
|
+
main_src = r1.read().decode('utf-8')
|
170
|
+
r1.close()
|
171
|
+
|
172
|
+
return PostBoostrap(
|
173
|
+
input=os.fdopen(_BOOTSTRAP_COMM_FD, 'rb', 0),
|
174
|
+
main_src=main_src,
|
175
|
+
)
|
176
|
+
|
177
|
+
|
178
|
+
########################################
|
179
|
+
# ../../../omlish/lite/cached.py
|
180
|
+
|
181
|
+
|
182
|
+
class cached_nullary: # noqa
|
183
|
+
def __init__(self, fn):
|
184
|
+
super().__init__()
|
185
|
+
self._fn = fn
|
186
|
+
self._value = self._missing = object()
|
187
|
+
functools.update_wrapper(self, fn)
|
188
|
+
|
189
|
+
def __call__(self, *args, **kwargs): # noqa
|
190
|
+
if self._value is self._missing:
|
191
|
+
self._value = self._fn()
|
192
|
+
return self._value
|
193
|
+
|
194
|
+
def __get__(self, instance, owner): # noqa
|
195
|
+
bound = instance.__dict__[self._fn.__name__] = self.__class__(self._fn.__get__(instance, owner))
|
196
|
+
return bound
|
197
|
+
|
198
|
+
|
199
|
+
########################################
|
200
|
+
# ../../../omlish/lite/check.py
|
201
|
+
# ruff: noqa: UP006 UP007
|
202
|
+
|
203
|
+
|
204
|
+
def check_isinstance(v: T, spec: ta.Union[ta.Type[T], tuple]) -> T:
|
205
|
+
if not isinstance(v, spec):
|
206
|
+
raise TypeError(v)
|
207
|
+
return v
|
208
|
+
|
209
|
+
|
210
|
+
def check_not_isinstance(v: T, spec: ta.Union[type, tuple]) -> T:
|
211
|
+
if isinstance(v, spec):
|
212
|
+
raise TypeError(v)
|
213
|
+
return v
|
214
|
+
|
215
|
+
|
216
|
+
def check_not_none(v: ta.Optional[T]) -> T:
|
217
|
+
if v is None:
|
218
|
+
raise ValueError
|
219
|
+
return v
|
220
|
+
|
221
|
+
|
222
|
+
def check_not(v: ta.Any) -> None:
|
223
|
+
if v:
|
224
|
+
raise ValueError(v)
|
225
|
+
return v
|
226
|
+
|
227
|
+
|
228
|
+
########################################
|
229
|
+
# ../../../omlish/lite/json.py
|
230
|
+
|
231
|
+
|
232
|
+
##
|
233
|
+
|
234
|
+
|
235
|
+
JSON_PRETTY_INDENT = 2
|
236
|
+
|
237
|
+
JSON_PRETTY_KWARGS: ta.Mapping[str, ta.Any] = dict(
|
238
|
+
indent=JSON_PRETTY_INDENT,
|
239
|
+
)
|
240
|
+
|
241
|
+
json_dump_pretty: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON_PRETTY_KWARGS) # type: ignore
|
242
|
+
json_dumps_pretty: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_PRETTY_KWARGS)
|
243
|
+
|
244
|
+
|
245
|
+
##
|
246
|
+
|
247
|
+
|
248
|
+
JSON_COMPACT_SEPARATORS = (',', ':')
|
249
|
+
|
250
|
+
JSON_COMPACT_KWARGS: ta.Mapping[str, ta.Any] = dict(
|
251
|
+
indent=None,
|
252
|
+
separators=JSON_COMPACT_SEPARATORS,
|
253
|
+
)
|
254
|
+
|
255
|
+
json_dump_compact: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON_COMPACT_KWARGS) # type: ignore
|
256
|
+
json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_COMPACT_KWARGS)
|
257
|
+
|
258
|
+
|
259
|
+
########################################
|
260
|
+
# ../../../omlish/lite/reflect.py
|
261
|
+
# ruff: noqa: UP006
|
262
|
+
|
263
|
+
|
264
|
+
_GENERIC_ALIAS_TYPES = (
|
265
|
+
ta._GenericAlias, # type: ignore # noqa
|
266
|
+
*([ta._SpecialGenericAlias] if hasattr(ta, '_SpecialGenericAlias') else []), # noqa
|
267
|
+
)
|
268
|
+
|
269
|
+
|
270
|
+
def is_generic_alias(obj, *, origin: ta.Any = None) -> bool:
|
271
|
+
return (
|
272
|
+
isinstance(obj, _GENERIC_ALIAS_TYPES) and
|
273
|
+
(origin is None or ta.get_origin(obj) is origin)
|
274
|
+
)
|
275
|
+
|
276
|
+
|
277
|
+
is_union_alias = functools.partial(is_generic_alias, origin=ta.Union)
|
278
|
+
is_callable_alias = functools.partial(is_generic_alias, origin=ta.Callable)
|
279
|
+
|
280
|
+
|
281
|
+
def is_optional_alias(spec: ta.Any) -> bool:
|
282
|
+
return (
|
283
|
+
isinstance(spec, _GENERIC_ALIAS_TYPES) and # noqa
|
284
|
+
ta.get_origin(spec) is ta.Union and
|
285
|
+
len(ta.get_args(spec)) == 2 and
|
286
|
+
any(a in (None, type(None)) for a in ta.get_args(spec))
|
287
|
+
)
|
288
|
+
|
289
|
+
|
290
|
+
def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
|
291
|
+
[it] = [it for it in ta.get_args(spec) if it not in (None, type(None))]
|
292
|
+
return it
|
293
|
+
|
294
|
+
|
295
|
+
def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
|
296
|
+
seen = set()
|
297
|
+
todo = list(reversed(cls.__subclasses__()))
|
298
|
+
while todo:
|
299
|
+
cur = todo.pop()
|
300
|
+
if cur in seen:
|
301
|
+
continue
|
302
|
+
seen.add(cur)
|
303
|
+
yield cur
|
304
|
+
todo.extend(reversed(cur.__subclasses__()))
|
305
|
+
|
306
|
+
|
307
|
+
########################################
|
308
|
+
# ../../../omlish/lite/logs.py
|
309
|
+
"""
|
310
|
+
TODO:
|
311
|
+
- debug
|
312
|
+
"""
|
313
|
+
# ruff: noqa: UP007
|
314
|
+
|
315
|
+
|
316
|
+
log = logging.getLogger(__name__)
|
317
|
+
|
318
|
+
|
319
|
+
class JsonLogFormatter(logging.Formatter):
|
320
|
+
|
321
|
+
KEYS: ta.Mapping[str, bool] = {
|
322
|
+
'name': False,
|
323
|
+
'msg': False,
|
324
|
+
'args': False,
|
325
|
+
'levelname': False,
|
326
|
+
'levelno': False,
|
327
|
+
'pathname': False,
|
328
|
+
'filename': False,
|
329
|
+
'module': False,
|
330
|
+
'exc_info': True,
|
331
|
+
'exc_text': True,
|
332
|
+
'stack_info': True,
|
333
|
+
'lineno': False,
|
334
|
+
'funcName': False,
|
335
|
+
'created': False,
|
336
|
+
'msecs': False,
|
337
|
+
'relativeCreated': False,
|
338
|
+
'thread': False,
|
339
|
+
'threadName': False,
|
340
|
+
'processName': False,
|
341
|
+
'process': False,
|
342
|
+
}
|
343
|
+
|
344
|
+
def format(self, record: logging.LogRecord) -> str:
|
345
|
+
dct = {
|
346
|
+
k: v
|
347
|
+
for k, o in self.KEYS.items()
|
348
|
+
for v in [getattr(record, k)]
|
349
|
+
if not (o and v is None)
|
350
|
+
}
|
351
|
+
return json_dumps_compact(dct)
|
352
|
+
|
353
|
+
|
354
|
+
def configure_standard_logging(level: ta.Union[int, str] = logging.INFO) -> None:
|
355
|
+
logging.root.addHandler(logging.StreamHandler())
|
356
|
+
logging.root.setLevel(level)
|
357
|
+
|
358
|
+
|
359
|
+
########################################
|
360
|
+
# ../../../omlish/lite/marshal.py
|
361
|
+
"""
|
362
|
+
TODO:
|
363
|
+
- pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
|
364
|
+
"""
|
365
|
+
# ruff: noqa: UP006 UP007
|
366
|
+
|
367
|
+
|
368
|
+
##
|
369
|
+
|
370
|
+
|
371
|
+
class ObjMarshaler(abc.ABC):
|
372
|
+
@abc.abstractmethod
|
373
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
374
|
+
raise NotImplementedError
|
375
|
+
|
376
|
+
@abc.abstractmethod
|
377
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
378
|
+
raise NotImplementedError
|
379
|
+
|
380
|
+
|
381
|
+
class NopObjMarshaler(ObjMarshaler):
|
382
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
383
|
+
return o
|
384
|
+
|
385
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
386
|
+
return o
|
387
|
+
|
388
|
+
|
389
|
+
@dc.dataclass()
|
390
|
+
class ProxyObjMarshaler(ObjMarshaler):
|
391
|
+
m: ta.Optional[ObjMarshaler] = None
|
392
|
+
|
393
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
394
|
+
return check_not_none(self.m).marshal(o)
|
395
|
+
|
396
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
397
|
+
return check_not_none(self.m).unmarshal(o)
|
398
|
+
|
399
|
+
|
400
|
+
@dc.dataclass(frozen=True)
|
401
|
+
class CastObjMarshaler(ObjMarshaler):
|
402
|
+
ty: type
|
403
|
+
|
404
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
405
|
+
return o
|
406
|
+
|
407
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
408
|
+
return self.ty(o)
|
409
|
+
|
410
|
+
|
411
|
+
class DynamicObjMarshaler(ObjMarshaler):
|
412
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
413
|
+
return marshal_obj(o)
|
414
|
+
|
415
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
416
|
+
return o
|
417
|
+
|
418
|
+
|
419
|
+
@dc.dataclass(frozen=True)
|
420
|
+
class Base64ObjMarshaler(ObjMarshaler):
|
421
|
+
ty: type
|
422
|
+
|
423
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
424
|
+
return base64.b64encode(o).decode('ascii')
|
425
|
+
|
426
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
427
|
+
return self.ty(base64.b64decode(o))
|
428
|
+
|
429
|
+
|
430
|
+
@dc.dataclass(frozen=True)
|
431
|
+
class EnumObjMarshaler(ObjMarshaler):
|
432
|
+
ty: type
|
433
|
+
|
434
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
435
|
+
return o.name
|
436
|
+
|
437
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
438
|
+
return self.ty.__members__[o] # type: ignore
|
439
|
+
|
440
|
+
|
441
|
+
@dc.dataclass(frozen=True)
|
442
|
+
class OptionalObjMarshaler(ObjMarshaler):
|
443
|
+
item: ObjMarshaler
|
444
|
+
|
445
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
446
|
+
if o is None:
|
447
|
+
return None
|
448
|
+
return self.item.marshal(o)
|
449
|
+
|
450
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
451
|
+
if o is None:
|
452
|
+
return None
|
453
|
+
return self.item.unmarshal(o)
|
454
|
+
|
455
|
+
|
456
|
+
@dc.dataclass(frozen=True)
|
457
|
+
class MappingObjMarshaler(ObjMarshaler):
|
458
|
+
ty: type
|
459
|
+
km: ObjMarshaler
|
460
|
+
vm: ObjMarshaler
|
461
|
+
|
462
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
463
|
+
return {self.km.marshal(k): self.vm.marshal(v) for k, v in o.items()}
|
464
|
+
|
465
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
466
|
+
return self.ty((self.km.unmarshal(k), self.vm.unmarshal(v)) for k, v in o.items())
|
467
|
+
|
468
|
+
|
469
|
+
@dc.dataclass(frozen=True)
|
470
|
+
class IterableObjMarshaler(ObjMarshaler):
|
471
|
+
ty: type
|
472
|
+
item: ObjMarshaler
|
473
|
+
|
474
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
475
|
+
return [self.item.marshal(e) for e in o]
|
476
|
+
|
477
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
478
|
+
return self.ty(self.item.unmarshal(e) for e in o)
|
479
|
+
|
480
|
+
|
481
|
+
@dc.dataclass(frozen=True)
|
482
|
+
class DataclassObjMarshaler(ObjMarshaler):
|
483
|
+
ty: type
|
484
|
+
fs: ta.Mapping[str, ObjMarshaler]
|
485
|
+
|
486
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
487
|
+
return {k: m.marshal(getattr(o, k)) for k, m in self.fs.items()}
|
488
|
+
|
489
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
490
|
+
return self.ty(**{k: self.fs[k].unmarshal(v) for k, v in o.items()})
|
491
|
+
|
492
|
+
|
493
|
+
@dc.dataclass(frozen=True)
|
494
|
+
class PolymorphicObjMarshaler(ObjMarshaler):
|
495
|
+
class Impl(ta.NamedTuple):
|
496
|
+
ty: type
|
497
|
+
tag: str
|
498
|
+
m: ObjMarshaler
|
499
|
+
|
500
|
+
impls_by_ty: ta.Mapping[type, Impl]
|
501
|
+
impls_by_tag: ta.Mapping[str, Impl]
|
502
|
+
|
503
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
504
|
+
impl = self.impls_by_ty[type(o)]
|
505
|
+
return {impl.tag: impl.m.marshal(o)}
|
506
|
+
|
507
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
508
|
+
[(t, v)] = o.items()
|
509
|
+
impl = self.impls_by_tag[t]
|
510
|
+
return impl.m.unmarshal(v)
|
511
|
+
|
512
|
+
|
513
|
+
@dc.dataclass(frozen=True)
|
514
|
+
class DatetimeObjMarshaler(ObjMarshaler):
|
515
|
+
ty: type
|
516
|
+
|
517
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
518
|
+
return o.isoformat()
|
519
|
+
|
520
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
521
|
+
return self.ty.fromisoformat(o) # type: ignore
|
522
|
+
|
523
|
+
|
524
|
+
class DecimalObjMarshaler(ObjMarshaler):
|
525
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
526
|
+
return str(check_isinstance(o, decimal.Decimal))
|
527
|
+
|
528
|
+
def unmarshal(self, v: ta.Any) -> ta.Any:
|
529
|
+
return decimal.Decimal(check_isinstance(v, str))
|
530
|
+
|
531
|
+
|
532
|
+
class FractionObjMarshaler(ObjMarshaler):
|
533
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
534
|
+
fr = check_isinstance(o, fractions.Fraction)
|
535
|
+
return [fr.numerator, fr.denominator]
|
536
|
+
|
537
|
+
def unmarshal(self, v: ta.Any) -> ta.Any:
|
538
|
+
num, denom = check_isinstance(v, list)
|
539
|
+
return fractions.Fraction(num, denom)
|
540
|
+
|
541
|
+
|
542
|
+
class UuidObjMarshaler(ObjMarshaler):
|
543
|
+
def marshal(self, o: ta.Any) -> ta.Any:
|
544
|
+
return str(o)
|
545
|
+
|
546
|
+
def unmarshal(self, o: ta.Any) -> ta.Any:
|
547
|
+
return uuid.UUID(o)
|
548
|
+
|
549
|
+
|
550
|
+
_OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
|
551
|
+
**{t: NopObjMarshaler() for t in (type(None),)},
|
552
|
+
**{t: CastObjMarshaler(t) for t in (int, float, str, bool)},
|
553
|
+
**{t: Base64ObjMarshaler(t) for t in (bytes, bytearray)},
|
554
|
+
**{t: IterableObjMarshaler(t, DynamicObjMarshaler()) for t in (list, tuple, set, frozenset)},
|
555
|
+
**{t: MappingObjMarshaler(t, DynamicObjMarshaler(), DynamicObjMarshaler()) for t in (dict,)},
|
556
|
+
|
557
|
+
ta.Any: DynamicObjMarshaler(),
|
558
|
+
|
559
|
+
**{t: DatetimeObjMarshaler(t) for t in (datetime.date, datetime.time, datetime.datetime)},
|
560
|
+
decimal.Decimal: DecimalObjMarshaler(),
|
561
|
+
fractions.Fraction: FractionObjMarshaler(),
|
562
|
+
uuid.UUID: UuidObjMarshaler(),
|
563
|
+
}
|
564
|
+
|
565
|
+
_OBJ_MARSHALER_GENERIC_MAPPING_TYPES: ta.Dict[ta.Any, type] = {
|
566
|
+
**{t: t for t in (dict,)},
|
567
|
+
**{t: dict for t in (collections.abc.Mapping, collections.abc.MutableMapping)},
|
568
|
+
}
|
569
|
+
|
570
|
+
_OBJ_MARSHALER_GENERIC_ITERABLE_TYPES: ta.Dict[ta.Any, type] = {
|
571
|
+
**{t: t for t in (list, tuple, set, frozenset)},
|
572
|
+
**{t: frozenset for t in (collections.abc.Set, collections.abc.MutableSet)},
|
573
|
+
**{t: tuple for t in (collections.abc.Sequence, collections.abc.MutableSequence)},
|
574
|
+
}
|
575
|
+
|
576
|
+
|
577
|
+
def register_opj_marshaler(ty: ta.Any, m: ObjMarshaler) -> None:
|
578
|
+
if ty in _OBJ_MARSHALERS:
|
579
|
+
raise KeyError(ty)
|
580
|
+
_OBJ_MARSHALERS[ty] = m
|
581
|
+
|
582
|
+
|
583
|
+
def _make_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
|
584
|
+
if isinstance(ty, type) and abc.ABC in ty.__bases__:
|
585
|
+
impls = [ # type: ignore
|
586
|
+
PolymorphicObjMarshaler.Impl(
|
587
|
+
ity,
|
588
|
+
ity.__qualname__,
|
589
|
+
get_obj_marshaler(ity),
|
590
|
+
)
|
591
|
+
for ity in deep_subclasses(ty)
|
592
|
+
if abc.ABC not in ity.__bases__
|
593
|
+
]
|
594
|
+
return PolymorphicObjMarshaler(
|
595
|
+
{i.ty: i for i in impls},
|
596
|
+
{i.tag: i for i in impls},
|
597
|
+
)
|
598
|
+
|
599
|
+
if isinstance(ty, type) and issubclass(ty, enum.Enum):
|
600
|
+
return EnumObjMarshaler(ty)
|
601
|
+
|
602
|
+
if dc.is_dataclass(ty):
|
603
|
+
return DataclassObjMarshaler(
|
604
|
+
ty,
|
605
|
+
{f.name: get_obj_marshaler(f.type) for f in dc.fields(ty)},
|
606
|
+
)
|
607
|
+
|
608
|
+
if is_generic_alias(ty):
|
609
|
+
try:
|
610
|
+
mt = _OBJ_MARSHALER_GENERIC_MAPPING_TYPES[ta.get_origin(ty)]
|
611
|
+
except KeyError:
|
612
|
+
pass
|
613
|
+
else:
|
614
|
+
k, v = ta.get_args(ty)
|
615
|
+
return MappingObjMarshaler(mt, get_obj_marshaler(k), get_obj_marshaler(v))
|
616
|
+
|
617
|
+
try:
|
618
|
+
st = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES[ta.get_origin(ty)]
|
619
|
+
except KeyError:
|
620
|
+
pass
|
621
|
+
else:
|
622
|
+
[e] = ta.get_args(ty)
|
623
|
+
return IterableObjMarshaler(st, get_obj_marshaler(e))
|
624
|
+
|
625
|
+
if is_union_alias(ty):
|
626
|
+
return OptionalObjMarshaler(get_obj_marshaler(get_optional_alias_arg(ty)))
|
627
|
+
|
628
|
+
raise TypeError(ty)
|
629
|
+
|
630
|
+
|
631
|
+
def get_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
|
632
|
+
try:
|
633
|
+
return _OBJ_MARSHALERS[ty]
|
634
|
+
except KeyError:
|
635
|
+
pass
|
636
|
+
|
637
|
+
p = ProxyObjMarshaler()
|
638
|
+
_OBJ_MARSHALERS[ty] = p
|
639
|
+
try:
|
640
|
+
m = _make_obj_marshaler(ty)
|
641
|
+
except Exception:
|
642
|
+
del _OBJ_MARSHALERS[ty]
|
643
|
+
raise
|
644
|
+
else:
|
645
|
+
p.m = m
|
646
|
+
_OBJ_MARSHALERS[ty] = m
|
647
|
+
return m
|
648
|
+
|
649
|
+
|
650
|
+
def marshal_obj(o: ta.Any, ty: ta.Any = None) -> ta.Any:
|
651
|
+
return get_obj_marshaler(ty if ty is not None else type(o)).marshal(o)
|
652
|
+
|
653
|
+
|
654
|
+
def unmarshal_obj(o: ta.Any, ty: ta.Union[ta.Type[T], ta.Any]) -> T:
|
655
|
+
return get_obj_marshaler(ty).unmarshal(o)
|
656
|
+
|
657
|
+
|
658
|
+
########################################
|
659
|
+
# ../../../omlish/lite/runtime.py
|
660
|
+
|
661
|
+
|
662
|
+
@cached_nullary
|
663
|
+
def is_debugger_attached() -> bool:
|
664
|
+
return any(frame[1].endswith('pydevd.py') for frame in inspect.stack())
|
665
|
+
|
666
|
+
|
667
|
+
REQUIRED_PYTHON_VERSION = (3, 8)
|
668
|
+
|
669
|
+
|
670
|
+
def check_runtime_version() -> None:
|
671
|
+
if sys.version_info < REQUIRED_PYTHON_VERSION:
|
672
|
+
raise OSError(
|
673
|
+
f'Requires python {REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
|
674
|
+
|
675
|
+
|
676
|
+
########################################
|
677
|
+
# ../../../omlish/lite/subprocesses.py
|
678
|
+
# ruff: noqa: UP006 UP007
|
679
|
+
|
680
|
+
|
681
|
+
##
|
682
|
+
|
683
|
+
|
684
|
+
_SUBPROCESS_SHELL_WRAP_EXECS = False
|
685
|
+
|
686
|
+
|
687
|
+
def subprocess_shell_wrap_exec(*args: str) -> ta.Tuple[str, ...]:
|
688
|
+
return ('sh', '-c', ' '.join(map(shlex.quote, args)))
|
689
|
+
|
690
|
+
|
691
|
+
def subprocess_maybe_shell_wrap_exec(*args: str) -> ta.Tuple[str, ...]:
|
692
|
+
if _SUBPROCESS_SHELL_WRAP_EXECS or is_debugger_attached():
|
693
|
+
return subprocess_shell_wrap_exec(*args)
|
694
|
+
else:
|
695
|
+
return args
|
696
|
+
|
697
|
+
|
698
|
+
def _prepare_subprocess_invocation(
|
699
|
+
*args: str,
|
700
|
+
env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
701
|
+
extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
702
|
+
quiet: bool = False,
|
703
|
+
shell: bool = False,
|
704
|
+
**kwargs: ta.Any,
|
705
|
+
) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
|
706
|
+
log.debug(args)
|
707
|
+
if extra_env:
|
708
|
+
log.debug(extra_env)
|
709
|
+
|
710
|
+
if extra_env:
|
711
|
+
env = {**(env if env is not None else os.environ), **extra_env}
|
712
|
+
|
713
|
+
if quiet and 'stderr' not in kwargs:
|
714
|
+
if not log.isEnabledFor(logging.DEBUG):
|
715
|
+
kwargs['stderr'] = subprocess.DEVNULL
|
716
|
+
|
717
|
+
if not shell:
|
718
|
+
args = subprocess_maybe_shell_wrap_exec(*args)
|
719
|
+
|
720
|
+
return args, dict(
|
721
|
+
env=env,
|
722
|
+
shell=shell,
|
723
|
+
**kwargs,
|
724
|
+
)
|
725
|
+
|
726
|
+
|
727
|
+
def subprocess_check_call(*args: str, stdout=sys.stderr, **kwargs: ta.Any) -> None:
|
728
|
+
args, kwargs = _prepare_subprocess_invocation(*args, stdout=stdout, **kwargs)
|
729
|
+
return subprocess.check_call(args, **kwargs) # type: ignore
|
730
|
+
|
731
|
+
|
732
|
+
def subprocess_check_output(*args: str, **kwargs: ta.Any) -> bytes:
|
733
|
+
args, kwargs = _prepare_subprocess_invocation(*args, **kwargs)
|
734
|
+
return subprocess.check_output(args, **kwargs)
|
735
|
+
|
736
|
+
|
737
|
+
def subprocess_check_output_str(*args: str, **kwargs: ta.Any) -> str:
|
738
|
+
return subprocess_check_output(*args, **kwargs).decode().strip()
|
739
|
+
|
740
|
+
|
741
|
+
##
|
742
|
+
|
743
|
+
|
744
|
+
DEFAULT_SUBPROCESS_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
|
745
|
+
FileNotFoundError,
|
746
|
+
subprocess.CalledProcessError,
|
747
|
+
)
|
748
|
+
|
749
|
+
|
750
|
+
def subprocess_try_call(
|
751
|
+
*args: str,
|
752
|
+
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
753
|
+
**kwargs: ta.Any,
|
754
|
+
) -> bool:
|
755
|
+
try:
|
756
|
+
subprocess_check_call(*args, **kwargs)
|
757
|
+
except try_exceptions as e: # noqa
|
758
|
+
if log.isEnabledFor(logging.DEBUG):
|
759
|
+
log.exception('command failed')
|
760
|
+
return False
|
761
|
+
else:
|
762
|
+
return True
|
763
|
+
|
764
|
+
|
765
|
+
def subprocess_try_output(
|
766
|
+
*args: str,
|
767
|
+
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
768
|
+
**kwargs: ta.Any,
|
769
|
+
) -> ta.Optional[bytes]:
|
770
|
+
try:
|
771
|
+
return subprocess_check_output(*args, **kwargs)
|
772
|
+
except try_exceptions as e: # noqa
|
773
|
+
if log.isEnabledFor(logging.DEBUG):
|
774
|
+
log.exception('command failed')
|
775
|
+
return None
|
776
|
+
|
777
|
+
|
778
|
+
def subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
|
779
|
+
out = subprocess_try_output(*args, **kwargs)
|
780
|
+
return out.decode().strip() if out is not None else None
|
781
|
+
|
782
|
+
|
783
|
+
########################################
|
784
|
+
# runcommands.py
|
785
|
+
|
786
|
+
|
787
|
+
@dc.dataclass(frozen=True)
|
788
|
+
class CommandRequest:
|
789
|
+
cmd: ta.Sequence[str]
|
790
|
+
in_: ta.Optional[bytes] = None
|
791
|
+
|
792
|
+
|
793
|
+
@dc.dataclass(frozen=True)
|
794
|
+
class CommandResponse:
|
795
|
+
req: CommandRequest
|
796
|
+
rc: int
|
797
|
+
out: bytes
|
798
|
+
err: bytes
|
799
|
+
|
800
|
+
|
801
|
+
def _run_commands_loop(input: ta.BinaryIO, output: ta.BinaryIO = sys.stdout.buffer) -> None: # noqa
|
802
|
+
while (l := input.readline().decode('utf-8').strip()):
|
803
|
+
req: CommandRequest = unmarshal_obj(json.loads(l), CommandRequest)
|
804
|
+
proc = subprocess.Popen( # type: ignore
|
805
|
+
subprocess_maybe_shell_wrap_exec(*req.cmd),
|
806
|
+
**(dict(stdin=io.BytesIO(req.in_)) if req.in_ is not None else {}),
|
807
|
+
stdout=subprocess.PIPE,
|
808
|
+
stderr=subprocess.PIPE,
|
809
|
+
)
|
810
|
+
out, err = proc.communicate()
|
811
|
+
resp = CommandResponse(
|
812
|
+
req=req,
|
813
|
+
rc=proc.returncode,
|
814
|
+
out=out, # noqa
|
815
|
+
err=err, # noqa
|
816
|
+
)
|
817
|
+
output.write(json_dumps_compact(marshal_obj(resp)).encode('utf-8'))
|
818
|
+
output.write(b'\n')
|
819
|
+
output.flush()
|
820
|
+
|
821
|
+
|
822
|
+
def run_commands_main() -> None:
|
823
|
+
bs = post_boostrap()
|
824
|
+
_run_commands_loop(bs.input)
|