ominfra 0.0.0.dev404__py3-none-any.whl → 0.0.0.dev406__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/.manifests.json +6 -6
- ominfra/scripts/journald2aws.py +452 -447
- ominfra/scripts/manage.py +199 -194
- {ominfra-0.0.0.dev404.dist-info → ominfra-0.0.0.dev406.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev404.dist-info → ominfra-0.0.0.dev406.dist-info}/RECORD +9 -9
- {ominfra-0.0.0.dev404.dist-info → ominfra-0.0.0.dev406.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev404.dist-info → ominfra-0.0.0.dev406.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev404.dist-info → ominfra-0.0.0.dev406.dist-info}/licenses/LICENSE +0 -0
- {ominfra-0.0.0.dev404.dist-info → ominfra-0.0.0.dev406.dist-info}/top_level.txt +0 -0
ominfra/scripts/journald2aws.py
CHANGED
@@ -79,9 +79,6 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
|
|
79
79
|
CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
|
80
80
|
CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
|
81
81
|
|
82
|
-
# ../../../../omlish/configs/formats.py
|
83
|
-
ConfigDataT = ta.TypeVar('ConfigDataT', bound='ConfigData')
|
84
|
-
|
85
82
|
# ../../../../omlish/lite/contextmanagers.py
|
86
83
|
ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
|
87
84
|
AsyncExitStackedT = ta.TypeVar('AsyncExitStackedT', bound='AsyncExitStacked')
|
@@ -89,6 +86,9 @@ AsyncExitStackedT = ta.TypeVar('AsyncExitStackedT', bound='AsyncExitStacked')
|
|
89
86
|
# ../../../threadworkers.py
|
90
87
|
ThreadWorkerT = ta.TypeVar('ThreadWorkerT', bound='ThreadWorker')
|
91
88
|
|
89
|
+
# ../../../../omlish/configs/formats.py
|
90
|
+
ConfigDataT = ta.TypeVar('ConfigDataT', bound='ConfigData')
|
91
|
+
|
92
92
|
|
93
93
|
########################################
|
94
94
|
# ../../../../../omlish/configs/types.py
|
@@ -1730,6 +1730,201 @@ class Checks:
|
|
1730
1730
|
check = Checks()
|
1731
1731
|
|
1732
1732
|
|
1733
|
+
########################################
|
1734
|
+
# ../../../../../omlish/lite/contextmanagers.py
|
1735
|
+
|
1736
|
+
|
1737
|
+
##
|
1738
|
+
|
1739
|
+
|
1740
|
+
class ExitStacked:
|
1741
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
1742
|
+
super().__init_subclass__(**kwargs)
|
1743
|
+
|
1744
|
+
for a in ('__enter__', '__exit__'):
|
1745
|
+
for b in cls.__bases__:
|
1746
|
+
if b is ExitStacked:
|
1747
|
+
continue
|
1748
|
+
try:
|
1749
|
+
fn = getattr(b, a)
|
1750
|
+
except AttributeError:
|
1751
|
+
pass
|
1752
|
+
else:
|
1753
|
+
if fn is not getattr(ExitStacked, a):
|
1754
|
+
raise TypeError(f'ExitStacked subclass {cls} must not not override {a} via {b}')
|
1755
|
+
|
1756
|
+
_exit_stack: ta.Optional[contextlib.ExitStack] = None
|
1757
|
+
|
1758
|
+
@contextlib.contextmanager
|
1759
|
+
def _exit_stacked_init_wrapper(self) -> ta.Iterator[None]:
|
1760
|
+
"""
|
1761
|
+
Overridable wrapper around __enter__ which deliberately does not have access to an _exit_stack yet. Intended for
|
1762
|
+
things like wrapping __enter__ in a lock.
|
1763
|
+
"""
|
1764
|
+
|
1765
|
+
yield
|
1766
|
+
|
1767
|
+
@ta.final
|
1768
|
+
def __enter__(self: ExitStackedT) -> ExitStackedT:
|
1769
|
+
"""
|
1770
|
+
Final because any contexts entered during this init must be exited if any exception is thrown, and user
|
1771
|
+
overriding would likely interfere with that. Override `_enter_contexts` for such init.
|
1772
|
+
"""
|
1773
|
+
|
1774
|
+
with self._exit_stacked_init_wrapper():
|
1775
|
+
if self._exit_stack is not None:
|
1776
|
+
raise RuntimeError
|
1777
|
+
es = self._exit_stack = contextlib.ExitStack()
|
1778
|
+
es.__enter__()
|
1779
|
+
try:
|
1780
|
+
self._enter_contexts()
|
1781
|
+
except Exception: # noqa
|
1782
|
+
es.__exit__(*sys.exc_info())
|
1783
|
+
raise
|
1784
|
+
return self
|
1785
|
+
|
1786
|
+
@ta.final
|
1787
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
1788
|
+
if (es := self._exit_stack) is None:
|
1789
|
+
return None
|
1790
|
+
try:
|
1791
|
+
self._exit_contexts()
|
1792
|
+
except Exception: # noqa
|
1793
|
+
es.__exit__(*sys.exc_info())
|
1794
|
+
raise
|
1795
|
+
return es.__exit__(exc_type, exc_val, exc_tb)
|
1796
|
+
|
1797
|
+
def _enter_contexts(self) -> None:
|
1798
|
+
pass
|
1799
|
+
|
1800
|
+
def _exit_contexts(self) -> None:
|
1801
|
+
pass
|
1802
|
+
|
1803
|
+
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
1804
|
+
if (es := self._exit_stack) is None:
|
1805
|
+
raise RuntimeError
|
1806
|
+
return es.enter_context(cm)
|
1807
|
+
|
1808
|
+
|
1809
|
+
class AsyncExitStacked:
|
1810
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
1811
|
+
super().__init_subclass__(**kwargs)
|
1812
|
+
|
1813
|
+
for a in ('__aenter__', '__aexit__'):
|
1814
|
+
for b in cls.__bases__:
|
1815
|
+
if b is AsyncExitStacked:
|
1816
|
+
continue
|
1817
|
+
try:
|
1818
|
+
fn = getattr(b, a)
|
1819
|
+
except AttributeError:
|
1820
|
+
pass
|
1821
|
+
else:
|
1822
|
+
if fn is not getattr(AsyncExitStacked, a):
|
1823
|
+
raise TypeError(f'AsyncExitStacked subclass {cls} must not not override {a} via {b}')
|
1824
|
+
|
1825
|
+
_exit_stack: ta.Optional[contextlib.AsyncExitStack] = None
|
1826
|
+
|
1827
|
+
@contextlib.asynccontextmanager
|
1828
|
+
async def _async_exit_stacked_init_wrapper(self) -> ta.AsyncGenerator[None, None]:
|
1829
|
+
yield
|
1830
|
+
|
1831
|
+
@ta.final
|
1832
|
+
async def __aenter__(self: AsyncExitStackedT) -> AsyncExitStackedT:
|
1833
|
+
async with self._async_exit_stacked_init_wrapper():
|
1834
|
+
if self._exit_stack is not None:
|
1835
|
+
raise RuntimeError
|
1836
|
+
es = self._exit_stack = contextlib.AsyncExitStack()
|
1837
|
+
await es.__aenter__()
|
1838
|
+
try:
|
1839
|
+
await self._async_enter_contexts()
|
1840
|
+
except Exception: # noqa
|
1841
|
+
await es.__aexit__(*sys.exc_info())
|
1842
|
+
raise
|
1843
|
+
return self
|
1844
|
+
|
1845
|
+
@ta.final
|
1846
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
1847
|
+
if (es := self._exit_stack) is None:
|
1848
|
+
return None
|
1849
|
+
try:
|
1850
|
+
await self._async_exit_contexts()
|
1851
|
+
except Exception: # noqa
|
1852
|
+
await es.__aexit__(*sys.exc_info())
|
1853
|
+
raise
|
1854
|
+
return await es.__aexit__(exc_type, exc_val, exc_tb)
|
1855
|
+
|
1856
|
+
async def _async_enter_contexts(self) -> None:
|
1857
|
+
pass
|
1858
|
+
|
1859
|
+
async def _async_exit_contexts(self) -> None:
|
1860
|
+
pass
|
1861
|
+
|
1862
|
+
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
1863
|
+
if (es := self._exit_stack) is None:
|
1864
|
+
raise RuntimeError
|
1865
|
+
return es.enter_context(cm)
|
1866
|
+
|
1867
|
+
async def _enter_async_context(self, cm: ta.AsyncContextManager[T]) -> T:
|
1868
|
+
if (es := self._exit_stack) is None:
|
1869
|
+
raise RuntimeError
|
1870
|
+
return await es.enter_async_context(cm)
|
1871
|
+
|
1872
|
+
|
1873
|
+
##
|
1874
|
+
|
1875
|
+
|
1876
|
+
@contextlib.contextmanager
|
1877
|
+
def defer(fn: ta.Callable, *args: ta.Any, **kwargs: ta.Any) -> ta.Generator[ta.Callable, None, None]:
|
1878
|
+
if args or kwargs:
|
1879
|
+
fn = functools.partial(fn, *args, **kwargs)
|
1880
|
+
try:
|
1881
|
+
yield fn
|
1882
|
+
finally:
|
1883
|
+
fn()
|
1884
|
+
|
1885
|
+
|
1886
|
+
@contextlib.asynccontextmanager
|
1887
|
+
async def adefer(fn: ta.Awaitable) -> ta.AsyncGenerator[ta.Awaitable, None]:
|
1888
|
+
try:
|
1889
|
+
yield fn
|
1890
|
+
finally:
|
1891
|
+
await fn
|
1892
|
+
|
1893
|
+
|
1894
|
+
##
|
1895
|
+
|
1896
|
+
|
1897
|
+
@contextlib.contextmanager
|
1898
|
+
def attr_setting(obj, attr, val, *, default=None): # noqa
|
1899
|
+
not_set = object()
|
1900
|
+
orig = getattr(obj, attr, not_set)
|
1901
|
+
try:
|
1902
|
+
setattr(obj, attr, val)
|
1903
|
+
if orig is not not_set:
|
1904
|
+
yield orig
|
1905
|
+
else:
|
1906
|
+
yield default
|
1907
|
+
finally:
|
1908
|
+
if orig is not_set:
|
1909
|
+
delattr(obj, attr)
|
1910
|
+
else:
|
1911
|
+
setattr(obj, attr, orig)
|
1912
|
+
|
1913
|
+
|
1914
|
+
##
|
1915
|
+
|
1916
|
+
|
1917
|
+
class aclosing(contextlib.AbstractAsyncContextManager): # noqa
|
1918
|
+
def __init__(self, thing):
|
1919
|
+
self.thing = thing
|
1920
|
+
|
1921
|
+
async def __aenter__(self):
|
1922
|
+
return self.thing
|
1923
|
+
|
1924
|
+
async def __aexit__(self, *exc_info):
|
1925
|
+
await self.thing.aclose()
|
1926
|
+
|
1927
|
+
|
1733
1928
|
########################################
|
1734
1929
|
# ../../../../../omlish/lite/json.py
|
1735
1930
|
|
@@ -2728,52 +2923,239 @@ class JournalctlToAwsCursor:
|
|
2728
2923
|
|
2729
2924
|
|
2730
2925
|
########################################
|
2731
|
-
#
|
2926
|
+
# ../../../../threadworkers.py
|
2732
2927
|
"""
|
2733
|
-
|
2734
|
-
-
|
2735
|
-
- single file, as this is intended to be amalg'd and thus all included anyway
|
2928
|
+
FIXME:
|
2929
|
+
- group is racy af - meditate on has_started, etc
|
2736
2930
|
|
2737
2931
|
TODO:
|
2738
|
-
-
|
2739
|
-
-
|
2740
|
-
|
2932
|
+
- overhaul stop lol
|
2933
|
+
- group -> 'context'? :|
|
2934
|
+
- shared stop_event?
|
2741
2935
|
"""
|
2742
2936
|
|
2743
2937
|
|
2744
2938
|
##
|
2745
2939
|
|
2746
2940
|
|
2747
|
-
|
2748
|
-
|
2749
|
-
|
2750
|
-
|
2751
|
-
|
2941
|
+
class ThreadWorker(ExitStacked, abc.ABC):
|
2942
|
+
def __init__(
|
2943
|
+
self,
|
2944
|
+
*,
|
2945
|
+
stop_event: ta.Optional[threading.Event] = None,
|
2946
|
+
worker_groups: ta.Optional[ta.Iterable['ThreadWorkerGroup']] = None,
|
2947
|
+
) -> None:
|
2948
|
+
super().__init__()
|
2752
2949
|
|
2950
|
+
if stop_event is None:
|
2951
|
+
stop_event = threading.Event()
|
2952
|
+
self._stop_event = stop_event
|
2753
2953
|
|
2754
|
-
|
2954
|
+
self._lock = threading.RLock()
|
2955
|
+
self._thread: ta.Optional[threading.Thread] = None
|
2956
|
+
self._last_heartbeat: ta.Optional[float] = None
|
2755
2957
|
|
2958
|
+
for g in worker_groups or []:
|
2959
|
+
g.add(self)
|
2756
2960
|
|
2757
|
-
|
2758
|
-
@property
|
2759
|
-
def file_exts(self) -> ta.Sequence[str]:
|
2760
|
-
return ()
|
2961
|
+
#
|
2761
2962
|
|
2762
|
-
|
2763
|
-
|
2963
|
+
@contextlib.contextmanager
|
2964
|
+
def _exit_stacked_init_wrapper(self) -> ta.Iterator[None]:
|
2965
|
+
with self._lock:
|
2966
|
+
yield
|
2764
2967
|
|
2765
2968
|
#
|
2766
2969
|
|
2767
|
-
def
|
2768
|
-
|
2769
|
-
return self.load_str(f.read())
|
2770
|
-
|
2771
|
-
@abc.abstractmethod
|
2772
|
-
def load_str(self, s: str) -> ConfigDataT:
|
2773
|
-
raise NotImplementedError
|
2970
|
+
def should_stop(self) -> bool:
|
2971
|
+
return self._stop_event.is_set()
|
2774
2972
|
|
2973
|
+
class Stopping(Exception): # noqa
|
2974
|
+
pass
|
2775
2975
|
|
2776
|
-
#
|
2976
|
+
#
|
2977
|
+
|
2978
|
+
@property
|
2979
|
+
def last_heartbeat(self) -> ta.Optional[float]:
|
2980
|
+
return self._last_heartbeat
|
2981
|
+
|
2982
|
+
def _heartbeat(
|
2983
|
+
self,
|
2984
|
+
*,
|
2985
|
+
no_stop_check: bool = False,
|
2986
|
+
) -> None:
|
2987
|
+
self._last_heartbeat = time.time()
|
2988
|
+
|
2989
|
+
if not no_stop_check and self.should_stop():
|
2990
|
+
log.info('Stopping: %s', self)
|
2991
|
+
raise ThreadWorker.Stopping
|
2992
|
+
|
2993
|
+
#
|
2994
|
+
|
2995
|
+
def has_started(self) -> bool:
|
2996
|
+
return self._thread is not None
|
2997
|
+
|
2998
|
+
def is_alive(self) -> bool:
|
2999
|
+
return (thr := self._thread) is not None and thr.is_alive()
|
3000
|
+
|
3001
|
+
def start(self) -> None:
|
3002
|
+
with self._lock:
|
3003
|
+
if self._thread is not None:
|
3004
|
+
raise RuntimeError('Thread already started: %r', self)
|
3005
|
+
|
3006
|
+
thr = threading.Thread(target=self.__thread_main)
|
3007
|
+
self._thread = thr
|
3008
|
+
thr.start()
|
3009
|
+
|
3010
|
+
#
|
3011
|
+
|
3012
|
+
def __thread_main(self) -> None:
|
3013
|
+
try:
|
3014
|
+
self._run()
|
3015
|
+
except ThreadWorker.Stopping:
|
3016
|
+
log.exception('Thread worker stopped: %r', self)
|
3017
|
+
except Exception: # noqa
|
3018
|
+
log.exception('Error in worker thread: %r', self)
|
3019
|
+
raise
|
3020
|
+
|
3021
|
+
@abc.abstractmethod
|
3022
|
+
def _run(self) -> None:
|
3023
|
+
raise NotImplementedError
|
3024
|
+
|
3025
|
+
#
|
3026
|
+
|
3027
|
+
def stop(self) -> None:
|
3028
|
+
self._stop_event.set()
|
3029
|
+
|
3030
|
+
def join(
|
3031
|
+
self,
|
3032
|
+
timeout: ta.Optional[float] = None,
|
3033
|
+
*,
|
3034
|
+
unless_not_started: bool = False,
|
3035
|
+
) -> None:
|
3036
|
+
with self._lock:
|
3037
|
+
if self._thread is None:
|
3038
|
+
if not unless_not_started:
|
3039
|
+
raise RuntimeError('Thread not started: %r', self)
|
3040
|
+
return
|
3041
|
+
self._thread.join(timeout)
|
3042
|
+
|
3043
|
+
|
3044
|
+
##
|
3045
|
+
|
3046
|
+
|
3047
|
+
class ThreadWorkerGroup:
|
3048
|
+
@dc.dataclass()
|
3049
|
+
class _State:
|
3050
|
+
worker: ThreadWorker
|
3051
|
+
|
3052
|
+
last_heartbeat: ta.Optional[float] = None
|
3053
|
+
|
3054
|
+
def __init__(self) -> None:
|
3055
|
+
super().__init__()
|
3056
|
+
|
3057
|
+
self._lock = threading.RLock()
|
3058
|
+
self._states: ta.Dict[ThreadWorker, ThreadWorkerGroup._State] = {}
|
3059
|
+
self._last_heartbeat_check: ta.Optional[float] = None
|
3060
|
+
|
3061
|
+
#
|
3062
|
+
|
3063
|
+
def add(self, *workers: ThreadWorker) -> 'ThreadWorkerGroup':
|
3064
|
+
with self._lock:
|
3065
|
+
for w in workers:
|
3066
|
+
if w in self._states:
|
3067
|
+
raise KeyError(w)
|
3068
|
+
self._states[w] = ThreadWorkerGroup._State(w)
|
3069
|
+
|
3070
|
+
return self
|
3071
|
+
|
3072
|
+
#
|
3073
|
+
|
3074
|
+
def start_all(self) -> None:
|
3075
|
+
thrs = list(self._states)
|
3076
|
+
with self._lock:
|
3077
|
+
for thr in thrs:
|
3078
|
+
if not thr.has_started():
|
3079
|
+
thr.start()
|
3080
|
+
|
3081
|
+
def stop_all(self) -> None:
|
3082
|
+
for w in reversed(list(self._states)):
|
3083
|
+
if w.has_started():
|
3084
|
+
w.stop()
|
3085
|
+
|
3086
|
+
def join_all(self, timeout: ta.Optional[float] = None) -> None:
|
3087
|
+
for w in reversed(list(self._states)):
|
3088
|
+
if w.has_started():
|
3089
|
+
w.join(timeout, unless_not_started=True)
|
3090
|
+
|
3091
|
+
#
|
3092
|
+
|
3093
|
+
def get_dead(self) -> ta.List[ThreadWorker]:
|
3094
|
+
with self._lock:
|
3095
|
+
return [thr for thr in self._states if not thr.is_alive()]
|
3096
|
+
|
3097
|
+
def check_heartbeats(self) -> ta.Dict[ThreadWorker, float]:
|
3098
|
+
with self._lock:
|
3099
|
+
dct: ta.Dict[ThreadWorker, float] = {}
|
3100
|
+
for thr, st in self._states.items():
|
3101
|
+
if not thr.has_started():
|
3102
|
+
continue
|
3103
|
+
hb = thr.last_heartbeat
|
3104
|
+
if hb is None:
|
3105
|
+
hb = time.time()
|
3106
|
+
st.last_heartbeat = hb
|
3107
|
+
dct[st.worker] = time.time() - hb
|
3108
|
+
self._last_heartbeat_check = time.time()
|
3109
|
+
return dct
|
3110
|
+
|
3111
|
+
|
3112
|
+
########################################
|
3113
|
+
# ../../../../../omlish/configs/formats.py
|
3114
|
+
"""
|
3115
|
+
Notes:
|
3116
|
+
- necessarily string-oriented
|
3117
|
+
- single file, as this is intended to be amalg'd and thus all included anyway
|
3118
|
+
|
3119
|
+
TODO:
|
3120
|
+
- ConfigDataMapper? to_map -> ConfigMap?
|
3121
|
+
- nginx ?
|
3122
|
+
- raw ?
|
3123
|
+
"""
|
3124
|
+
|
3125
|
+
|
3126
|
+
##
|
3127
|
+
|
3128
|
+
|
3129
|
+
@dc.dataclass(frozen=True)
|
3130
|
+
class ConfigData(abc.ABC): # noqa
|
3131
|
+
@abc.abstractmethod
|
3132
|
+
def as_map(self) -> ConfigMap:
|
3133
|
+
raise NotImplementedError
|
3134
|
+
|
3135
|
+
|
3136
|
+
#
|
3137
|
+
|
3138
|
+
|
3139
|
+
class ConfigLoader(abc.ABC, ta.Generic[ConfigDataT]):
|
3140
|
+
@property
|
3141
|
+
def file_exts(self) -> ta.Sequence[str]:
|
3142
|
+
return ()
|
3143
|
+
|
3144
|
+
def match_file(self, n: str) -> bool:
|
3145
|
+
return '.' in n and n.split('.')[-1] in check.not_isinstance(self.file_exts, str)
|
3146
|
+
|
3147
|
+
#
|
3148
|
+
|
3149
|
+
def load_file(self, p: str) -> ConfigDataT:
|
3150
|
+
with open(p) as f:
|
3151
|
+
return self.load_str(f.read())
|
3152
|
+
|
3153
|
+
@abc.abstractmethod
|
3154
|
+
def load_str(self, s: str) -> ConfigDataT:
|
3155
|
+
raise NotImplementedError
|
3156
|
+
|
3157
|
+
|
3158
|
+
#
|
2777
3159
|
|
2778
3160
|
|
2779
3161
|
class ConfigRenderer(abc.ABC, ta.Generic[ConfigDataT]):
|
@@ -3193,249 +3575,59 @@ class ReadableListBuffer:
|
|
3193
3575
|
while i < len(lst):
|
3194
3576
|
if (p := lst[i].find(delim)) >= 0:
|
3195
3577
|
return self._chop(i, p + len(delim))
|
3196
|
-
i += 1
|
3197
|
-
|
3198
|
-
return i
|
3199
|
-
|
3200
|
-
def read_until(self, delim: bytes = b'\n') -> ta.Optional[bytes]:
|
3201
|
-
r = self.read_until_(delim)
|
3202
|
-
return r if isinstance(r, bytes) else None
|
3203
|
-
|
3204
|
-
|
3205
|
-
##
|
3206
|
-
|
3207
|
-
|
3208
|
-
class IncrementalWriteBuffer:
|
3209
|
-
def __init__(
|
3210
|
-
self,
|
3211
|
-
data: bytes,
|
3212
|
-
*,
|
3213
|
-
write_size: int = 0x10000,
|
3214
|
-
) -> None:
|
3215
|
-
super().__init__()
|
3216
|
-
|
3217
|
-
check.not_empty(data)
|
3218
|
-
self._len = len(data)
|
3219
|
-
self._write_size = write_size
|
3220
|
-
|
3221
|
-
self._lst = [
|
3222
|
-
data[i:i + write_size]
|
3223
|
-
for i in range(0, len(data), write_size)
|
3224
|
-
]
|
3225
|
-
self._pos = 0
|
3226
|
-
|
3227
|
-
@property
|
3228
|
-
def rem(self) -> int:
|
3229
|
-
return self._len - self._pos
|
3230
|
-
|
3231
|
-
def write(self, fn: ta.Callable[[bytes], int]) -> int:
|
3232
|
-
lst = check.not_empty(self._lst)
|
3233
|
-
|
3234
|
-
t = 0
|
3235
|
-
for i, d in enumerate(lst): # noqa
|
3236
|
-
n = fn(check.not_empty(d))
|
3237
|
-
if not n:
|
3238
|
-
break
|
3239
|
-
t += n
|
3240
|
-
|
3241
|
-
if t:
|
3242
|
-
self._lst = [
|
3243
|
-
*([d[n:]] if n < len(d) else []),
|
3244
|
-
*lst[i + 1:],
|
3245
|
-
]
|
3246
|
-
self._pos += t
|
3247
|
-
|
3248
|
-
return t
|
3249
|
-
|
3250
|
-
|
3251
|
-
########################################
|
3252
|
-
# ../../../../../omlish/lite/contextmanagers.py
|
3253
|
-
|
3254
|
-
|
3255
|
-
##
|
3256
|
-
|
3257
|
-
|
3258
|
-
class ExitStacked:
|
3259
|
-
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
3260
|
-
super().__init_subclass__(**kwargs)
|
3261
|
-
|
3262
|
-
for a in ('__enter__', '__exit__'):
|
3263
|
-
for b in cls.__bases__:
|
3264
|
-
if b is ExitStacked:
|
3265
|
-
continue
|
3266
|
-
try:
|
3267
|
-
fn = getattr(b, a)
|
3268
|
-
except AttributeError:
|
3269
|
-
pass
|
3270
|
-
else:
|
3271
|
-
if fn is not getattr(ExitStacked, a):
|
3272
|
-
raise TypeError(f'ExitStacked subclass {cls} must not not override {a} via {b}')
|
3273
|
-
|
3274
|
-
_exit_stack: ta.Optional[contextlib.ExitStack] = None
|
3275
|
-
|
3276
|
-
@contextlib.contextmanager
|
3277
|
-
def _exit_stacked_init_wrapper(self) -> ta.Iterator[None]:
|
3278
|
-
"""
|
3279
|
-
Overridable wrapper around __enter__ which deliberately does not have access to an _exit_stack yet. Intended for
|
3280
|
-
things like wrapping __enter__ in a lock.
|
3281
|
-
"""
|
3282
|
-
|
3283
|
-
yield
|
3284
|
-
|
3285
|
-
@ta.final
|
3286
|
-
def __enter__(self: ExitStackedT) -> ExitStackedT:
|
3287
|
-
"""
|
3288
|
-
Final because any contexts entered during this init must be exited if any exception is thrown, and user
|
3289
|
-
overriding would likely interfere with that. Override `_enter_contexts` for such init.
|
3290
|
-
"""
|
3291
|
-
|
3292
|
-
with self._exit_stacked_init_wrapper():
|
3293
|
-
check.state(self._exit_stack is None)
|
3294
|
-
es = self._exit_stack = contextlib.ExitStack()
|
3295
|
-
es.__enter__()
|
3296
|
-
try:
|
3297
|
-
self._enter_contexts()
|
3298
|
-
except Exception: # noqa
|
3299
|
-
es.__exit__(*sys.exc_info())
|
3300
|
-
raise
|
3301
|
-
return self
|
3302
|
-
|
3303
|
-
@ta.final
|
3304
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
3305
|
-
if (es := self._exit_stack) is None:
|
3306
|
-
return None
|
3307
|
-
try:
|
3308
|
-
self._exit_contexts()
|
3309
|
-
except Exception: # noqa
|
3310
|
-
es.__exit__(*sys.exc_info())
|
3311
|
-
raise
|
3312
|
-
return es.__exit__(exc_type, exc_val, exc_tb)
|
3313
|
-
|
3314
|
-
def _enter_contexts(self) -> None:
|
3315
|
-
pass
|
3316
|
-
|
3317
|
-
def _exit_contexts(self) -> None:
|
3318
|
-
pass
|
3319
|
-
|
3320
|
-
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
3321
|
-
es = check.not_none(self._exit_stack)
|
3322
|
-
return es.enter_context(cm)
|
3323
|
-
|
3324
|
-
|
3325
|
-
class AsyncExitStacked:
|
3326
|
-
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
3327
|
-
super().__init_subclass__(**kwargs)
|
3328
|
-
|
3329
|
-
for a in ('__aenter__', '__aexit__'):
|
3330
|
-
for b in cls.__bases__:
|
3331
|
-
if b is AsyncExitStacked:
|
3332
|
-
continue
|
3333
|
-
try:
|
3334
|
-
fn = getattr(b, a)
|
3335
|
-
except AttributeError:
|
3336
|
-
pass
|
3337
|
-
else:
|
3338
|
-
if fn is not getattr(AsyncExitStacked, a):
|
3339
|
-
raise TypeError(f'AsyncExitStacked subclass {cls} must not not override {a} via {b}')
|
3340
|
-
|
3341
|
-
_exit_stack: ta.Optional[contextlib.AsyncExitStack] = None
|
3342
|
-
|
3343
|
-
@contextlib.asynccontextmanager
|
3344
|
-
async def _async_exit_stacked_init_wrapper(self) -> ta.AsyncGenerator[None, None]:
|
3345
|
-
yield
|
3346
|
-
|
3347
|
-
@ta.final
|
3348
|
-
async def __aenter__(self: AsyncExitStackedT) -> AsyncExitStackedT:
|
3349
|
-
async with self._async_exit_stacked_init_wrapper():
|
3350
|
-
check.state(self._exit_stack is None)
|
3351
|
-
es = self._exit_stack = contextlib.AsyncExitStack()
|
3352
|
-
await es.__aenter__()
|
3353
|
-
try:
|
3354
|
-
await self._async_enter_contexts()
|
3355
|
-
except Exception: # noqa
|
3356
|
-
await es.__aexit__(*sys.exc_info())
|
3357
|
-
raise
|
3358
|
-
return self
|
3359
|
-
|
3360
|
-
@ta.final
|
3361
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
3362
|
-
if (es := self._exit_stack) is None:
|
3363
|
-
return None
|
3364
|
-
try:
|
3365
|
-
await self._async_exit_contexts()
|
3366
|
-
except Exception: # noqa
|
3367
|
-
await es.__aexit__(*sys.exc_info())
|
3368
|
-
raise
|
3369
|
-
return await es.__aexit__(exc_type, exc_val, exc_tb)
|
3370
|
-
|
3371
|
-
async def _async_enter_contexts(self) -> None:
|
3372
|
-
pass
|
3373
|
-
|
3374
|
-
async def _async_exit_contexts(self) -> None:
|
3375
|
-
pass
|
3376
|
-
|
3377
|
-
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
3378
|
-
es = check.not_none(self._exit_stack)
|
3379
|
-
return es.enter_context(cm)
|
3380
|
-
|
3381
|
-
async def _enter_async_context(self, cm: ta.AsyncContextManager[T]) -> T:
|
3382
|
-
es = check.not_none(self._exit_stack)
|
3383
|
-
return await es.enter_async_context(cm)
|
3384
|
-
|
3385
|
-
|
3386
|
-
##
|
3387
|
-
|
3388
|
-
|
3389
|
-
@contextlib.contextmanager
|
3390
|
-
def defer(fn: ta.Callable, *args: ta.Any, **kwargs: ta.Any) -> ta.Generator[ta.Callable, None, None]:
|
3391
|
-
if args or kwargs:
|
3392
|
-
fn = functools.partial(fn, *args, **kwargs)
|
3393
|
-
try:
|
3394
|
-
yield fn
|
3395
|
-
finally:
|
3396
|
-
fn()
|
3578
|
+
i += 1
|
3397
3579
|
|
3580
|
+
return i
|
3398
3581
|
|
3399
|
-
|
3400
|
-
|
3401
|
-
|
3402
|
-
yield fn
|
3403
|
-
finally:
|
3404
|
-
await fn
|
3582
|
+
def read_until(self, delim: bytes = b'\n') -> ta.Optional[bytes]:
|
3583
|
+
r = self.read_until_(delim)
|
3584
|
+
return r if isinstance(r, bytes) else None
|
3405
3585
|
|
3406
3586
|
|
3407
3587
|
##
|
3408
3588
|
|
3409
3589
|
|
3410
|
-
|
3411
|
-
def
|
3412
|
-
|
3413
|
-
|
3414
|
-
|
3415
|
-
|
3416
|
-
|
3417
|
-
|
3418
|
-
else:
|
3419
|
-
yield default
|
3420
|
-
finally:
|
3421
|
-
if orig is not_set:
|
3422
|
-
delattr(obj, attr)
|
3423
|
-
else:
|
3424
|
-
setattr(obj, attr, orig)
|
3590
|
+
class IncrementalWriteBuffer:
|
3591
|
+
def __init__(
|
3592
|
+
self,
|
3593
|
+
data: bytes,
|
3594
|
+
*,
|
3595
|
+
write_size: int = 0x10000,
|
3596
|
+
) -> None:
|
3597
|
+
super().__init__()
|
3425
3598
|
|
3599
|
+
check.not_empty(data)
|
3600
|
+
self._len = len(data)
|
3601
|
+
self._write_size = write_size
|
3426
3602
|
|
3427
|
-
|
3603
|
+
self._lst = [
|
3604
|
+
data[i:i + write_size]
|
3605
|
+
for i in range(0, len(data), write_size)
|
3606
|
+
]
|
3607
|
+
self._pos = 0
|
3428
3608
|
|
3609
|
+
@property
|
3610
|
+
def rem(self) -> int:
|
3611
|
+
return self._len - self._pos
|
3429
3612
|
|
3430
|
-
|
3431
|
-
|
3432
|
-
self.thing = thing
|
3613
|
+
def write(self, fn: ta.Callable[[bytes], int]) -> int:
|
3614
|
+
lst = check.not_empty(self._lst)
|
3433
3615
|
|
3434
|
-
|
3435
|
-
|
3616
|
+
t = 0
|
3617
|
+
for i, d in enumerate(lst): # noqa
|
3618
|
+
n = fn(check.not_empty(d))
|
3619
|
+
if not n:
|
3620
|
+
break
|
3621
|
+
t += n
|
3436
3622
|
|
3437
|
-
|
3438
|
-
|
3623
|
+
if t:
|
3624
|
+
self._lst = [
|
3625
|
+
*([d[n:]] if n < len(d) else []),
|
3626
|
+
*lst[i + 1:],
|
3627
|
+
]
|
3628
|
+
self._pos += t
|
3629
|
+
|
3630
|
+
return t
|
3439
3631
|
|
3440
3632
|
|
3441
3633
|
########################################
|
@@ -4420,193 +4612,6 @@ class JournalctlMessageBuilder:
|
|
4420
4612
|
return ret
|
4421
4613
|
|
4422
4614
|
|
4423
|
-
########################################
|
4424
|
-
# ../../../../threadworkers.py
|
4425
|
-
"""
|
4426
|
-
FIXME:
|
4427
|
-
- group is racy af - meditate on has_started, etc
|
4428
|
-
|
4429
|
-
TODO:
|
4430
|
-
- overhaul stop lol
|
4431
|
-
- group -> 'context'? :|
|
4432
|
-
- shared stop_event?
|
4433
|
-
"""
|
4434
|
-
|
4435
|
-
|
4436
|
-
##
|
4437
|
-
|
4438
|
-
|
4439
|
-
class ThreadWorker(ExitStacked, abc.ABC):
|
4440
|
-
def __init__(
|
4441
|
-
self,
|
4442
|
-
*,
|
4443
|
-
stop_event: ta.Optional[threading.Event] = None,
|
4444
|
-
worker_groups: ta.Optional[ta.Iterable['ThreadWorkerGroup']] = None,
|
4445
|
-
) -> None:
|
4446
|
-
super().__init__()
|
4447
|
-
|
4448
|
-
if stop_event is None:
|
4449
|
-
stop_event = threading.Event()
|
4450
|
-
self._stop_event = stop_event
|
4451
|
-
|
4452
|
-
self._lock = threading.RLock()
|
4453
|
-
self._thread: ta.Optional[threading.Thread] = None
|
4454
|
-
self._last_heartbeat: ta.Optional[float] = None
|
4455
|
-
|
4456
|
-
for g in worker_groups or []:
|
4457
|
-
g.add(self)
|
4458
|
-
|
4459
|
-
#
|
4460
|
-
|
4461
|
-
@contextlib.contextmanager
|
4462
|
-
def _exit_stacked_init_wrapper(self) -> ta.Iterator[None]:
|
4463
|
-
with self._lock:
|
4464
|
-
yield
|
4465
|
-
|
4466
|
-
#
|
4467
|
-
|
4468
|
-
def should_stop(self) -> bool:
|
4469
|
-
return self._stop_event.is_set()
|
4470
|
-
|
4471
|
-
class Stopping(Exception): # noqa
|
4472
|
-
pass
|
4473
|
-
|
4474
|
-
#
|
4475
|
-
|
4476
|
-
@property
|
4477
|
-
def last_heartbeat(self) -> ta.Optional[float]:
|
4478
|
-
return self._last_heartbeat
|
4479
|
-
|
4480
|
-
def _heartbeat(
|
4481
|
-
self,
|
4482
|
-
*,
|
4483
|
-
no_stop_check: bool = False,
|
4484
|
-
) -> None:
|
4485
|
-
self._last_heartbeat = time.time()
|
4486
|
-
|
4487
|
-
if not no_stop_check and self.should_stop():
|
4488
|
-
log.info('Stopping: %s', self)
|
4489
|
-
raise ThreadWorker.Stopping
|
4490
|
-
|
4491
|
-
#
|
4492
|
-
|
4493
|
-
def has_started(self) -> bool:
|
4494
|
-
return self._thread is not None
|
4495
|
-
|
4496
|
-
def is_alive(self) -> bool:
|
4497
|
-
return (thr := self._thread) is not None and thr.is_alive()
|
4498
|
-
|
4499
|
-
def start(self) -> None:
|
4500
|
-
with self._lock:
|
4501
|
-
if self._thread is not None:
|
4502
|
-
raise RuntimeError('Thread already started: %r', self)
|
4503
|
-
|
4504
|
-
thr = threading.Thread(target=self.__thread_main)
|
4505
|
-
self._thread = thr
|
4506
|
-
thr.start()
|
4507
|
-
|
4508
|
-
#
|
4509
|
-
|
4510
|
-
def __thread_main(self) -> None:
|
4511
|
-
try:
|
4512
|
-
self._run()
|
4513
|
-
except ThreadWorker.Stopping:
|
4514
|
-
log.exception('Thread worker stopped: %r', self)
|
4515
|
-
except Exception: # noqa
|
4516
|
-
log.exception('Error in worker thread: %r', self)
|
4517
|
-
raise
|
4518
|
-
|
4519
|
-
@abc.abstractmethod
|
4520
|
-
def _run(self) -> None:
|
4521
|
-
raise NotImplementedError
|
4522
|
-
|
4523
|
-
#
|
4524
|
-
|
4525
|
-
def stop(self) -> None:
|
4526
|
-
self._stop_event.set()
|
4527
|
-
|
4528
|
-
def join(
|
4529
|
-
self,
|
4530
|
-
timeout: ta.Optional[float] = None,
|
4531
|
-
*,
|
4532
|
-
unless_not_started: bool = False,
|
4533
|
-
) -> None:
|
4534
|
-
with self._lock:
|
4535
|
-
if self._thread is None:
|
4536
|
-
if not unless_not_started:
|
4537
|
-
raise RuntimeError('Thread not started: %r', self)
|
4538
|
-
return
|
4539
|
-
self._thread.join(timeout)
|
4540
|
-
|
4541
|
-
|
4542
|
-
##
|
4543
|
-
|
4544
|
-
|
4545
|
-
class ThreadWorkerGroup:
|
4546
|
-
@dc.dataclass()
|
4547
|
-
class _State:
|
4548
|
-
worker: ThreadWorker
|
4549
|
-
|
4550
|
-
last_heartbeat: ta.Optional[float] = None
|
4551
|
-
|
4552
|
-
def __init__(self) -> None:
|
4553
|
-
super().__init__()
|
4554
|
-
|
4555
|
-
self._lock = threading.RLock()
|
4556
|
-
self._states: ta.Dict[ThreadWorker, ThreadWorkerGroup._State] = {}
|
4557
|
-
self._last_heartbeat_check: ta.Optional[float] = None
|
4558
|
-
|
4559
|
-
#
|
4560
|
-
|
4561
|
-
def add(self, *workers: ThreadWorker) -> 'ThreadWorkerGroup':
|
4562
|
-
with self._lock:
|
4563
|
-
for w in workers:
|
4564
|
-
if w in self._states:
|
4565
|
-
raise KeyError(w)
|
4566
|
-
self._states[w] = ThreadWorkerGroup._State(w)
|
4567
|
-
|
4568
|
-
return self
|
4569
|
-
|
4570
|
-
#
|
4571
|
-
|
4572
|
-
def start_all(self) -> None:
|
4573
|
-
thrs = list(self._states)
|
4574
|
-
with self._lock:
|
4575
|
-
for thr in thrs:
|
4576
|
-
if not thr.has_started():
|
4577
|
-
thr.start()
|
4578
|
-
|
4579
|
-
def stop_all(self) -> None:
|
4580
|
-
for w in reversed(list(self._states)):
|
4581
|
-
if w.has_started():
|
4582
|
-
w.stop()
|
4583
|
-
|
4584
|
-
def join_all(self, timeout: ta.Optional[float] = None) -> None:
|
4585
|
-
for w in reversed(list(self._states)):
|
4586
|
-
if w.has_started():
|
4587
|
-
w.join(timeout, unless_not_started=True)
|
4588
|
-
|
4589
|
-
#
|
4590
|
-
|
4591
|
-
def get_dead(self) -> ta.List[ThreadWorker]:
|
4592
|
-
with self._lock:
|
4593
|
-
return [thr for thr in self._states if not thr.is_alive()]
|
4594
|
-
|
4595
|
-
def check_heartbeats(self) -> ta.Dict[ThreadWorker, float]:
|
4596
|
-
with self._lock:
|
4597
|
-
dct: ta.Dict[ThreadWorker, float] = {}
|
4598
|
-
for thr, st in self._states.items():
|
4599
|
-
if not thr.has_started():
|
4600
|
-
continue
|
4601
|
-
hb = thr.last_heartbeat
|
4602
|
-
if hb is None:
|
4603
|
-
hb = time.time()
|
4604
|
-
st.last_heartbeat = hb
|
4605
|
-
dct[st.worker] = time.time() - hb
|
4606
|
-
self._last_heartbeat_check = time.time()
|
4607
|
-
return dct
|
4608
|
-
|
4609
|
-
|
4610
4615
|
########################################
|
4611
4616
|
# ../../../../../omlish/lite/configs.py
|
4612
4617
|
|