ominfra 0.0.0.dev120__py3-none-any.whl → 0.0.0.dev122__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.
@@ -0,0 +1,4 @@
1
+ if __name__ == '__main__':
2
+ from .main import _main
3
+
4
+ _main()
@@ -46,6 +46,7 @@ from omlish.lite.runtime import is_debugger_attached
46
46
 
47
47
  from ....journald.messages import JournalctlMessage # noqa
48
48
  from ....journald.tailer import JournalctlTailerWorker
49
+ from ....threadworkers import ThreadWorkerGroup
49
50
  from ..auth import AwsSigner
50
51
  from ..logs import AwsLogMessageBuilder
51
52
  from .cursor import JournalctlToAwsCursor
@@ -63,6 +64,7 @@ class JournalctlToAwsDriver(ExitStacked):
63
64
  cursor_file: ta.Optional[str] = None
64
65
 
65
66
  runtime_limit: ta.Optional[float] = None
67
+ heartbeat_age_limit: ta.Optional[float] = 60.
66
68
 
67
69
  #
68
70
 
@@ -139,6 +141,12 @@ class JournalctlToAwsDriver(ExitStacked):
139
141
 
140
142
  #
141
143
 
144
+ @cached_nullary
145
+ def _worker_group(self) -> ThreadWorkerGroup:
146
+ return ThreadWorkerGroup()
147
+
148
+ #
149
+
142
150
  @cached_nullary
143
151
  def _journalctl_message_queue(self): # type: () -> queue.Queue[ta.Sequence[JournalctlMessage]]
144
152
  return queue.Queue()
@@ -165,6 +173,8 @@ class JournalctlToAwsDriver(ExitStacked):
165
173
 
166
174
  cmd=self._config.journalctl_cmd,
167
175
  shell_wrap=is_debugger_attached(),
176
+
177
+ worker_groups=[self._worker_group()],
168
178
  )
169
179
 
170
180
  #
@@ -178,26 +188,38 @@ class JournalctlToAwsDriver(ExitStacked):
178
188
 
179
189
  ensure_locked=self._ensure_locked,
180
190
  dry_run=self._config.aws_dry_run,
191
+
192
+ worker_groups=[self._worker_group()],
181
193
  )
182
194
 
183
195
  #
184
196
 
185
- def run(self) -> None:
186
- pw: JournalctlToAwsPosterWorker = self._aws_poster_worker()
187
- tw: JournalctlTailerWorker = self._journalctl_tailer_worker()
197
+ def _exit_contexts(self) -> None:
198
+ wg = self._worker_group()
199
+ wg.stop_all()
200
+ wg.join_all()
188
201
 
189
- ws = [pw, tw]
202
+ def run(self) -> None:
203
+ self._aws_poster_worker()
204
+ self._journalctl_tailer_worker()
190
205
 
191
- for w in ws:
192
- w.start()
206
+ wg = self._worker_group()
207
+ wg.start_all()
193
208
 
194
209
  start = time.time()
195
210
 
196
211
  while True:
197
- for w in ws:
198
- if not w.is_alive():
199
- log.critical('Worker died: %r', w)
200
- break
212
+ for w in wg.get_dead():
213
+ log.critical('Worker died: %r', w)
214
+ break
215
+
216
+ if (al := self._config.heartbeat_age_limit) is not None:
217
+ hbs = wg.check_heartbeats()
218
+ log.debug('Worker heartbeats: %r', hbs)
219
+ for w, age in hbs.items():
220
+ if age > al:
221
+ log.critical('Worker heartbeat age limit exceeded: %r %f > %f', w, age, al)
222
+ break
201
223
 
202
224
  if (rl := self._config.runtime_limit) is not None and time.time() - start >= rl:
203
225
  log.warning('Runtime limit reached')
@@ -205,6 +227,5 @@ class JournalctlToAwsDriver(ExitStacked):
205
227
 
206
228
  time.sleep(1.)
207
229
 
208
- for w in reversed(ws):
209
- w.stop()
210
- w.join()
230
+ wg.stop_all()
231
+ wg.join_all()
@@ -2,13 +2,12 @@
2
2
  # @omlish-amalg ../../../scripts/journald2aws.py
3
3
  import argparse
4
4
  import dataclasses as dc
5
- import json
6
5
  import os.path
7
6
  import sys
8
7
 
9
8
  from omlish.lite.logs import configure_standard_logging
10
- from omlish.lite.marshal import unmarshal_obj
11
9
 
10
+ from ....configs import read_config_file
12
11
  from .driver import JournalctlToAwsDriver
13
12
 
14
13
 
@@ -37,9 +36,7 @@ def _main() -> None:
37
36
 
38
37
  config: JournalctlToAwsDriver.Config
39
38
  if args.config_file:
40
- with open(os.path.expanduser(args.config_file)) as cf:
41
- config_dct = json.load(cf)
42
- config = unmarshal_obj(config_dct, JournalctlToAwsDriver.Config)
39
+ config = read_config_file(os.path.expanduser(args.config_file), JournalctlToAwsDriver.Config)
43
40
  else:
44
41
  config = JournalctlToAwsDriver.Config()
45
42
 
ominfra/configs.py ADDED
@@ -0,0 +1,70 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import json
4
+ import typing as ta
5
+
6
+ from omdev.toml.parser import toml_loads
7
+ from omlish.lite.check import check_isinstance
8
+ from omlish.lite.check import check_not_isinstance
9
+ from omlish.lite.marshal import unmarshal_obj
10
+
11
+
12
+ T = ta.TypeVar('T')
13
+
14
+ ConfigMapping = ta.Mapping[str, ta.Any]
15
+
16
+
17
+ def read_config_file(
18
+ path: str,
19
+ cls: ta.Type[T],
20
+ *,
21
+ prepare: ta.Optional[ta.Callable[[ConfigMapping], ConfigMapping]] = None,
22
+ ) -> T:
23
+ with open(path) as cf:
24
+ if path.endswith('.toml'):
25
+ config_dct = toml_loads(cf.read())
26
+ else:
27
+ config_dct = json.loads(cf.read())
28
+
29
+ if prepare is not None:
30
+ config_dct = prepare(config_dct) # type: ignore
31
+
32
+ return unmarshal_obj(config_dct, cls)
33
+
34
+
35
+ def build_config_named_children(
36
+ o: ta.Union[
37
+ ta.Sequence[ConfigMapping],
38
+ ta.Mapping[str, ConfigMapping],
39
+ None,
40
+ ],
41
+ *,
42
+ name_key: str = 'name',
43
+ ) -> ta.Optional[ta.Sequence[ConfigMapping]]:
44
+ if o is None:
45
+ return None
46
+
47
+ lst: ta.List[ConfigMapping] = []
48
+ if isinstance(o, ta.Mapping):
49
+ for k, v in o.items():
50
+ check_isinstance(v, ta.Mapping)
51
+ if name_key in v:
52
+ n = v[name_key]
53
+ if k != n:
54
+ raise KeyError(f'Given names do not match: {n} != {k}')
55
+ lst.append(v)
56
+ else:
57
+ lst.append({name_key: k, **v})
58
+
59
+ else:
60
+ check_not_isinstance(o, str)
61
+ lst.extend(o)
62
+
63
+ seen = set()
64
+ for d in lst:
65
+ n = d['name']
66
+ if n in d:
67
+ raise KeyError(f'Duplicate name: {n}')
68
+ seen.add(n)
69
+
70
+ return lst
@@ -66,6 +66,7 @@ import subprocess
66
66
  import sys
67
67
  import textwrap
68
68
  import threading
69
+ import types
69
70
  import typing as ta
70
71
  import uuid
71
72
  import weakref # noqa
@@ -129,7 +130,7 @@ class _cached_nullary: # noqa
129
130
  return bound
130
131
 
131
132
 
132
- def cached_nullary(fn: ta.Callable[..., T]) -> ta.Callable[..., T]:
133
+ def cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
133
134
  return _cached_nullary(fn)
134
135
 
135
136
 
@@ -255,6 +256,14 @@ def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
255
256
  return it
256
257
 
257
258
 
259
+ def is_new_type(spec: ta.Any) -> bool:
260
+ if isinstance(ta.NewType, type):
261
+ return isinstance(spec, ta.NewType)
262
+ else:
263
+ # Before https://github.com/python/cpython/commit/c2f33dfc83ab270412bf243fb21f724037effa1a
264
+ return isinstance(spec, types.FunctionType) and spec.__code__ is ta.NewType.__code__.co_consts[1] # type: ignore # noqa
265
+
266
+
258
267
  def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
259
268
  seen = set()
260
269
  todo = list(reversed(cls.__subclasses__()))
@@ -103,7 +103,7 @@ class _cached_nullary: # noqa
103
103
  return bound
104
104
 
105
105
 
106
- def cached_nullary(fn: ta.Callable[..., T]) -> ta.Callable[..., T]:
106
+ def cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
107
107
  return _cached_nullary(fn)
108
108
 
109
109
 
@@ -24,6 +24,7 @@ import subprocess
24
24
  import sys
25
25
  import textwrap
26
26
  import threading
27
+ import types
27
28
  import typing as ta
28
29
  import uuid
29
30
  import weakref # noqa
@@ -212,7 +213,7 @@ class _cached_nullary: # noqa
212
213
  return bound
213
214
 
214
215
 
215
- def cached_nullary(fn: ta.Callable[..., T]) -> ta.Callable[..., T]:
216
+ def cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
216
217
  return _cached_nullary(fn)
217
218
 
218
219
 
@@ -338,6 +339,14 @@ def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
338
339
  return it
339
340
 
340
341
 
342
+ def is_new_type(spec: ta.Any) -> bool:
343
+ if isinstance(ta.NewType, type):
344
+ return isinstance(spec, ta.NewType)
345
+ else:
346
+ # Before https://github.com/python/cpython/commit/c2f33dfc83ab270412bf243fb21f724037effa1a
347
+ return isinstance(spec, types.FunctionType) and spec.__code__ is ta.NewType.__code__.co_consts[1] # type: ignore # noqa
348
+
349
+
341
350
  def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
342
351
  seen = set()
343
352
  todo = list(reversed(cls.__subclasses__()))