ominfra 0.0.0.dev134__py3-none-any.whl → 0.0.0.dev135__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.
File without changes
@@ -0,0 +1,25 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import abc
3
+ import dataclasses as dc
4
+ import typing as ta
5
+
6
+
7
+ CommandInputT = ta.TypeVar('CommandInputT', bound='Command.Input')
8
+ CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
9
+
10
+
11
+ ##
12
+
13
+
14
+ class Command(abc.ABC, ta.Generic[CommandInputT, CommandOutputT]):
15
+ @dc.dataclass(frozen=True)
16
+ class Input(abc.ABC): # noqa
17
+ pass
18
+
19
+ @dc.dataclass(frozen=True)
20
+ class Output(abc.ABC): # noqa
21
+ pass
22
+
23
+ @abc.abstractmethod
24
+ def _execute(self, inp: CommandInputT) -> CommandOutputT:
25
+ raise NotImplementedError
@@ -0,0 +1,73 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
3
+ import os
4
+ import subprocess
5
+ import time
6
+ import typing as ta
7
+
8
+ from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
9
+
10
+ from .base import Command
11
+
12
+
13
+ ##
14
+
15
+
16
+ class SubprocessCommand(Command['SubprocessCommand.Input', 'SubprocessCommand.Output']):
17
+ @dc.dataclass(frozen=True)
18
+ class Input(Command.Input):
19
+ args: ta.Sequence[str]
20
+
21
+ shell: bool = False
22
+ cwd: ta.Optional[str] = None
23
+ env: ta.Optional[ta.Mapping[str, str]] = None
24
+
25
+ capture_stdout: bool = False
26
+ capture_stderr: bool = False
27
+
28
+ input: ta.Optional[bytes] = None
29
+ timeout: ta.Optional[float] = None
30
+
31
+ def __post_init__(self) -> None:
32
+ if isinstance(self.args, str):
33
+ raise TypeError(self.args)
34
+
35
+ @dc.dataclass(frozen=True)
36
+ class Output(Command.Output):
37
+ rc: int
38
+ pid: int
39
+
40
+ elapsed_s: float
41
+
42
+ stdout: ta.Optional[bytes] = None
43
+ stderr: ta.Optional[bytes] = None
44
+
45
+ def _execute(self, inp: Input) -> Output:
46
+ proc = subprocess.Popen(
47
+ subprocess_maybe_shell_wrap_exec(*inp.args),
48
+
49
+ shell=inp.shell,
50
+ cwd=inp.cwd,
51
+ env={**os.environ, **(inp.env or {})},
52
+
53
+ stdin=subprocess.PIPE if inp.input is not None else None,
54
+ stdout=subprocess.PIPE if inp.capture_stdout else None,
55
+ stderr=subprocess.PIPE if inp.capture_stderr else None,
56
+ )
57
+
58
+ start_time = time.time()
59
+ stdout, stderr = proc.communicate(
60
+ input=inp.input,
61
+ timeout=inp.timeout,
62
+ )
63
+ end_time = time.time()
64
+
65
+ return SubprocessCommand.Output(
66
+ rc=proc.returncode,
67
+ pid=proc.pid,
68
+
69
+ elapsed_s=end_time - start_time,
70
+
71
+ stdout=stdout, # noqa
72
+ stderr=stderr, # noqa
73
+ )
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env python3
2
+ # @omlish-amalg ./_manage.py
3
+ # ruff: noqa: UP006 UP007
4
+ import inspect
5
+ import json
6
+ import os
7
+ import shlex
8
+ import struct
9
+ import subprocess
10
+ import sys
11
+ import typing as ta
12
+
13
+ from omlish.lite.check import check_not_none
14
+ from omlish.lite.json import json_dumps_compact
15
+ from omlish.lite.marshal import marshal_obj
16
+ from omlish.lite.marshal import unmarshal_obj
17
+ from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
18
+
19
+ from ...pyremote import PyremoteBootstrapDriver
20
+ from ...pyremote import pyremote_bootstrap_finalize
21
+ from ...pyremote import pyremote_build_bootstrap_cmd
22
+ from .commands.subprocess import SubprocessCommand
23
+
24
+
25
+ ##
26
+
27
+
28
+ def _send_obj(f: ta.IO, o: ta.Any) -> None:
29
+ j = json_dumps_compact(marshal_obj(o))
30
+ d = j.encode('utf-8')
31
+
32
+ f.write(struct.pack('<I', len(d)))
33
+ f.write(d)
34
+ f.flush()
35
+
36
+
37
+ def _recv_obj(f: ta.IO, ty: type) -> ta.Any:
38
+ d = f.read(4)
39
+ if not d:
40
+ return None
41
+ if len(d) != 4:
42
+ raise Exception
43
+
44
+ sz = struct.unpack('<I', d)[0]
45
+ d = f.read(sz)
46
+ if not d:
47
+ raise Exception
48
+
49
+ j = json.loads(d.decode('utf-8'))
50
+ return unmarshal_obj(j, ty)
51
+
52
+
53
+ def _remote_main() -> None:
54
+ rt = pyremote_bootstrap_finalize() # noqa
55
+
56
+ while True:
57
+ i = _recv_obj(rt.input, SubprocessCommand.Input)
58
+ if i is None:
59
+ break
60
+
61
+ o = SubprocessCommand()._execute(i) # noqa
62
+
63
+ _send_obj(sys.stdout.buffer, o)
64
+
65
+
66
+ def _main() -> None:
67
+ import argparse
68
+
69
+ parser = argparse.ArgumentParser()
70
+
71
+ parser.add_argument('--ssh')
72
+ parser.add_argument('--python', default='python3')
73
+ parser.add_argument('--_amalg-file')
74
+
75
+ args = parser.parse_args()
76
+
77
+ #
78
+
79
+ self_src = inspect.getsource(sys.modules[__name__])
80
+ self_src_lines = self_src.splitlines()
81
+ for l in self_src_lines:
82
+ if l.startswith('# @omlish-amalg-output '):
83
+ is_self_amalg = True
84
+ break
85
+ else:
86
+ is_self_amalg = False
87
+
88
+ if is_self_amalg:
89
+ amalg_src = self_src
90
+ else:
91
+ amalg_file = args._amalg_file # noqa
92
+ if amalg_file is None:
93
+ amalg_file = os.path.join(os.path.dirname(__file__), '_manage.py')
94
+ with open(amalg_file) as f:
95
+ amalg_src = f.read()
96
+
97
+ #
98
+
99
+ remote_src = '\n\n'.join([
100
+ '__name__ = "__remote__"',
101
+ amalg_src,
102
+ '_remote_main()',
103
+ ])
104
+
105
+ #
106
+
107
+ bs_src = pyremote_build_bootstrap_cmd(__package__ or 'manage')
108
+
109
+ if args.ssh is not None:
110
+ sh_src = ' '.join([args.python, '-c', shlex.quote(bs_src)])
111
+ sh_cmd = f'{args.ssh} {shlex.quote(sh_src)}'
112
+ cmd = [sh_cmd]
113
+ shell = True
114
+ else:
115
+ cmd = [args.python, '-c', bs_src]
116
+ shell = False
117
+
118
+ proc = subprocess.Popen(
119
+ subprocess_maybe_shell_wrap_exec(*cmd),
120
+ shell=shell,
121
+ stdin=subprocess.PIPE,
122
+ stdout=subprocess.PIPE,
123
+ )
124
+
125
+ stdin = check_not_none(proc.stdin)
126
+ stdout = check_not_none(proc.stdout)
127
+
128
+ res = PyremoteBootstrapDriver(remote_src).run(stdin, stdout)
129
+ print(res)
130
+
131
+ #
132
+
133
+ for ci in [
134
+ SubprocessCommand.Input(
135
+ args=['python3', '-'],
136
+ input=b'print(1)\n',
137
+ capture_stdout=True,
138
+ ),
139
+ SubprocessCommand.Input(
140
+ args=['uname'],
141
+ capture_stdout=True,
142
+ ),
143
+ ]:
144
+ _send_obj(stdin, ci)
145
+
146
+ o = _recv_obj(stdout, SubprocessCommand.Output)
147
+
148
+ print(o)
149
+
150
+ try:
151
+ stdin.close()
152
+ except BrokenPipeError:
153
+ pass
154
+
155
+ proc.wait()
156
+
157
+
158
+ if __name__ == '__main__':
159
+ _main()
ominfra/pyremote.py CHANGED
@@ -107,6 +107,9 @@ def _pyremote_bootstrap_main(context_name: str) -> None:
107
107
 
108
108
 
109
109
  def pyremote_build_bootstrap_cmd(context_name: str) -> str:
110
+ if any(c in context_name for c in '\'"'):
111
+ raise NameError(context_name)
112
+
110
113
  bs_src = textwrap.dedent(inspect.getsource(_pyremote_bootstrap_main))
111
114
 
112
115
  for gl in [
@@ -133,6 +136,9 @@ def pyremote_build_bootstrap_cmd(context_name: str) -> str:
133
136
  bs_z = zlib.compress(bs_src.encode('utf-8'))
134
137
  bs_z64 = base64.encodebytes(bs_z).replace(b'\n', b'')
135
138
 
139
+ def dq_repr(o: ta.Any) -> str:
140
+ return '"' + repr(o)[1:-1] + '"'
141
+
136
142
  stmts = [
137
143
  f'import {", ".join(_PYREMOTE_BOOTSTRAP_IMPORTS)}',
138
144
  f'exec(zlib.decompress(base64.decodebytes({bs_z64!r})))',
@@ -326,7 +332,7 @@ class PyremoteBootstrapDriver:
326
332
  def run(self, stdin: ta.IO, stdout: ta.IO) -> Result:
327
333
  gen = self.gen()
328
334
 
329
- gi: bytes | None = None
335
+ gi: ta.Optional[bytes] = None
330
336
  while True:
331
337
  try:
332
338
  if gi is not None:
@@ -2289,6 +2289,13 @@ class PolymorphicObjMarshaler(ObjMarshaler):
2289
2289
  impls_by_ty: ta.Mapping[type, Impl]
2290
2290
  impls_by_tag: ta.Mapping[str, Impl]
2291
2291
 
2292
+ @classmethod
2293
+ def of(cls, impls: ta.Iterable[Impl]) -> 'PolymorphicObjMarshaler':
2294
+ return cls(
2295
+ {i.ty: i for i in impls},
2296
+ {i.tag: i for i in impls},
2297
+ )
2298
+
2292
2299
  def marshal(self, o: ta.Any) -> ta.Any:
2293
2300
  impl = self.impls_by_ty[type(o)]
2294
2301
  return {impl.tag: impl.m.marshal(o)}
@@ -2376,7 +2383,7 @@ def _make_obj_marshaler(
2376
2383
  ) -> ObjMarshaler:
2377
2384
  if isinstance(ty, type):
2378
2385
  if abc.ABC in ty.__bases__:
2379
- impls = [ # type: ignore
2386
+ return PolymorphicObjMarshaler.of([ # type: ignore
2380
2387
  PolymorphicObjMarshaler.Impl(
2381
2388
  ity,
2382
2389
  ity.__qualname__,
@@ -2384,11 +2391,7 @@ def _make_obj_marshaler(
2384
2391
  )
2385
2392
  for ity in deep_subclasses(ty)
2386
2393
  if abc.ABC not in ity.__bases__
2387
- ]
2388
- return PolymorphicObjMarshaler(
2389
- {i.ty: i for i in impls},
2390
- {i.tag: i for i in impls},
2391
- )
2394
+ ])
2392
2395
 
2393
2396
  if issubclass(ty, enum.Enum):
2394
2397
  return EnumObjMarshaler(ty)
@@ -4861,6 +4861,13 @@ class PolymorphicObjMarshaler(ObjMarshaler):
4861
4861
  impls_by_ty: ta.Mapping[type, Impl]
4862
4862
  impls_by_tag: ta.Mapping[str, Impl]
4863
4863
 
4864
+ @classmethod
4865
+ def of(cls, impls: ta.Iterable[Impl]) -> 'PolymorphicObjMarshaler':
4866
+ return cls(
4867
+ {i.ty: i for i in impls},
4868
+ {i.tag: i for i in impls},
4869
+ )
4870
+
4864
4871
  def marshal(self, o: ta.Any) -> ta.Any:
4865
4872
  impl = self.impls_by_ty[type(o)]
4866
4873
  return {impl.tag: impl.m.marshal(o)}
@@ -4948,7 +4955,7 @@ def _make_obj_marshaler(
4948
4955
  ) -> ObjMarshaler:
4949
4956
  if isinstance(ty, type):
4950
4957
  if abc.ABC in ty.__bases__:
4951
- impls = [ # type: ignore
4958
+ return PolymorphicObjMarshaler.of([ # type: ignore
4952
4959
  PolymorphicObjMarshaler.Impl(
4953
4960
  ity,
4954
4961
  ity.__qualname__,
@@ -4956,11 +4963,7 @@ def _make_obj_marshaler(
4956
4963
  )
4957
4964
  for ity in deep_subclasses(ty)
4958
4965
  if abc.ABC not in ity.__bases__
4959
- ]
4960
- return PolymorphicObjMarshaler(
4961
- {i.ty: i for i in impls},
4962
- {i.tag: i for i in impls},
4963
- )
4966
+ ])
4964
4967
 
4965
4968
  if issubclass(ty, enum.Enum):
4966
4969
  return EnumObjMarshaler(ty)
@@ -6182,6 +6185,7 @@ class CoroHttpServerConnectionFdIoHandler(SocketFdIoHandler):
6182
6185
  *,
6183
6186
  read_size: int = 0x10000,
6184
6187
  write_size: int = 0x10000,
6188
+ log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
6185
6189
  ) -> None:
6186
6190
  check_state(not sock.getblocking())
6187
6191
 
@@ -6190,6 +6194,7 @@ class CoroHttpServerConnectionFdIoHandler(SocketFdIoHandler):
6190
6194
  self._handler = handler
6191
6195
  self._read_size = read_size
6192
6196
  self._write_size = write_size
6197
+ self._log_handler = log_handler
6193
6198
 
6194
6199
  self._read_buf = ReadableListBuffer()
6195
6200
  self._write_buf: IncrementalWriteBuffer | None = None
@@ -6224,7 +6229,8 @@ class CoroHttpServerConnectionFdIoHandler(SocketFdIoHandler):
6224
6229
  break
6225
6230
 
6226
6231
  if isinstance(o, CoroHttpServer.AnyLogIo):
6227
- print(o)
6232
+ if self._log_handler is not None:
6233
+ self._log_handler(self._coro_srv, o)
6228
6234
  o = None
6229
6235
 
6230
6236
  elif isinstance(o, CoroHttpServer.ReadIo):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev134
3
+ Version: 0.0.0.dev135
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.dev134
16
- Requires-Dist: omlish==0.0.0.dev134
15
+ Requires-Dist: omdev==0.0.0.dev135
16
+ Requires-Dist: omlish==0.0.0.dev135
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -3,7 +3,7 @@ ominfra/__about__.py,sha256=6i1AoruFYQCd-PyhhbDQDWY2d1tiQu9nkwWr-fXAqfY,705
3
3
  ominfra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  ominfra/cmds.py,sha256=E0AfnvEmnKntXWvmLW5L05_NeDpBET1VBXn7vV6EwBQ,2083
5
5
  ominfra/configs.py,sha256=8aU1Qmbr-qjaE2iP3gAbA2SWJYMPZ-uGK007L01PoOI,1727
6
- ominfra/pyremote.py,sha256=nqzmMKZvUMiw2y65v8VVjx832sKfSB7jBOkP8SQBmGI,10121
6
+ ominfra/pyremote.py,sha256=z3D9nj26omHgl3_q_h7uSHRtgYu-Sb06fMQCqS69_QI,10289
7
7
  ominfra/ssh.py,sha256=jQpc4WvkMckIfk4vILda8zFaeharRqc_6wxW50b0OjQ,5431
8
8
  ominfra/threadworkers.py,sha256=oX4ubZn7h932saXpRIJu2MNhBExgGGMuGhdXarZxLJw,4948
9
9
  ominfra/clouds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -30,7 +30,7 @@ ominfra/journald/tailer.py,sha256=5abcFMfgi7fnY9ZEQe2ZVobaJxjQkeu6d9Kagw33a1w,33
30
30
  ominfra/manage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  ominfra/manage/manage.py,sha256=BttL8LFEknHZE_h2Pt5dAqbfUkv6qy43WI0raXBZ1a8,151
32
32
  ominfra/manage/deploy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- ominfra/manage/deploy/_executor.py,sha256=lk1pXkwqijudeQ9Ti8oxP4O-qUFDFzcXI8H-Ox5K-SA,35385
33
+ ominfra/manage/deploy/_executor.py,sha256=amAnQWG5PBrsYVqd7K6xILyq88mhTUZucTWtSenceTE,35466
34
34
  ominfra/manage/deploy/configs.py,sha256=qi0kwT7G2NH7dXLOQic-u6R3yeadup_QtvrjwWIggbM,435
35
35
  ominfra/manage/deploy/remote.py,sha256=WHHElLmYk6wEDC4R8s_jHi1z71CCuV3soqF3elzIWEM,2145
36
36
  ominfra/manage/deploy/executor/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
@@ -56,9 +56,15 @@ ominfra/manage/deploy/poly/runtime.py,sha256=G66AI-a27Wi6QYpG3M0ryqI7c5MnJkXt-_j
56
56
  ominfra/manage/deploy/poly/site.py,sha256=QJwDDJoVm2-kxi4bxIrp-mn4y2qDLuW3CAUax3W8gv8,239
57
57
  ominfra/manage/deploy/poly/supervisor.py,sha256=zkl6VQBcAZaMAhyR9DbbbqULcgFCDZoe9S_vP-mMFQ8,2289
58
58
  ominfra/manage/deploy/poly/venv.py,sha256=BoipDEa4NTeodjf3L57KJfq9eGKLagFNKwD8pS4yrzA,1552
59
+ ominfra/manage/new/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
60
+ ominfra/manage/new/_manage.py,sha256=ycfNgyFofYs6f5jYV2SrAprGChQd-uVhWc79Tvd9iB0,40217
61
+ ominfra/manage/new/main.py,sha256=WeiBAM7HQosnDbKahRmDWNvNYUZaKH2d3ooaqk9kQ04,3558
62
+ ominfra/manage/new/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
+ ominfra/manage/new/commands/base.py,sha256=TTrHL213jf-ClVqToiNHuxQey1Yf6052E8u3E9hAf7Q,574
64
+ ominfra/manage/new/commands/subprocess.py,sha256=GpbD-cTorgCRg203lCl81HAh-NBYA6ObKa5ZP2ss9rg,1884
59
65
  ominfra/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- ominfra/scripts/journald2aws.py,sha256=o6WgI9R3MXdH4-BKUYAK2pcu3E5T0GAl5pggzR4Lhrk,131537
61
- ominfra/scripts/supervisor.py,sha256=gy2P8K8aATDQDvSFqgnvIun2qlZrWg2F066kzcoQRdc,245528
66
+ ominfra/scripts/journald2aws.py,sha256=kCyeyCo53GRoCKAdVAe0aGkgmINxi8IBcUfRXphACek,131618
67
+ ominfra/scripts/supervisor.py,sha256=GO_e3e6WWqdBE-WejtaTrzU8yjtZBiadD-o2Nb-aJ6o,245838
62
68
  ominfra/supervisor/LICENSE.txt,sha256=yvqaMNsDhWxziHa9ien6qCW1SkZv-DQlAg96XjfSee8,1746
63
69
  ominfra/supervisor/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
64
70
  ominfra/supervisor/__main__.py,sha256=I0yFw-C08OOiZ3BF6lF1Oiv789EQXu-_j6whDhQUTEA,66
@@ -100,9 +106,9 @@ ominfra/tailscale/api.py,sha256=C5-t_b6jZXUWcy5k8bXm7CFnk73pSdrlMOgGDeGVrpw,1370
100
106
  ominfra/tailscale/cli.py,sha256=DSGp4hn5xwOW-l_u_InKlSF6kIobxtUtVssf_73STs0,3567
101
107
  ominfra/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
108
  ominfra/tools/listresources.py,sha256=4qVg5txsb10EHhvqXXeM6gJ2jx9LbroEnPydDv1uXs0,6176
103
- ominfra-0.0.0.dev134.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
104
- ominfra-0.0.0.dev134.dist-info/METADATA,sha256=pJ8EYFED29tv1T8wzhlFFm2rKeNXXS2c4Iw8hjFEvSA,731
105
- ominfra-0.0.0.dev134.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
106
- ominfra-0.0.0.dev134.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
107
- ominfra-0.0.0.dev134.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
108
- ominfra-0.0.0.dev134.dist-info/RECORD,,
109
+ ominfra-0.0.0.dev135.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
110
+ ominfra-0.0.0.dev135.dist-info/METADATA,sha256=l8zW4aJP_w8h0b-wfdGnlXqPlofGjiw0HLQAfdFfJK4,731
111
+ ominfra-0.0.0.dev135.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
112
+ ominfra-0.0.0.dev135.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
113
+ ominfra-0.0.0.dev135.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
114
+ ominfra-0.0.0.dev135.dist-info/RECORD,,