omlish 0.0.0.dev229__py3-none-any.whl → 0.0.0.dev231__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.
- omlish/__about__.py +2 -2
- omlish/collections/__init__.py +15 -15
- omlish/collections/frozen.py +0 -2
- omlish/collections/identity.py +0 -3
- omlish/collections/indexed.py +0 -2
- omlish/collections/mappings.py +0 -3
- omlish/collections/ordered.py +0 -2
- omlish/collections/persistent/__init__.py +0 -0
- omlish/collections/sorted/__init__.py +0 -0
- omlish/collections/{skiplist.py → sorted/skiplist.py} +0 -1
- omlish/collections/{sorted.py → sorted/sorted.py} +2 -5
- omlish/collections/unmodifiable.py +0 -2
- omlish/daemons/__init__.py +0 -0
- omlish/daemons/daemon.py +200 -0
- omlish/daemons/reparent.py +16 -0
- omlish/daemons/spawning.py +166 -0
- omlish/daemons/targets.py +89 -0
- omlish/daemons/waiting.py +95 -0
- omlish/dispatch/dispatch.py +4 -1
- omlish/dispatch/methods.py +4 -0
- omlish/formats/json/__init__.py +5 -0
- omlish/io/compress/brotli.py +3 -3
- omlish/io/fdio/__init__.py +3 -0
- omlish/lang/__init__.py +0 -2
- omlish/lang/contextmanagers.py +15 -10
- omlish/libc.py +2 -4
- omlish/lite/timeouts.py +1 -1
- omlish/marshal/__init__.py +10 -10
- omlish/marshal/base.py +1 -1
- omlish/marshal/standard.py +2 -2
- omlish/marshal/trivial/__init__.py +0 -0
- omlish/marshal/{forbidden.py → trivial/forbidden.py} +7 -7
- omlish/marshal/{nop.py → trivial/nop.py} +5 -5
- omlish/os/deathpacts/__init__.py +15 -0
- omlish/os/deathpacts/base.py +76 -0
- omlish/os/deathpacts/heartbeatfile.py +85 -0
- omlish/os/{death.py → deathpacts/pipe.py} +20 -90
- omlish/os/forkhooks.py +55 -31
- omlish/os/pidfiles/manager.py +11 -44
- omlish/os/pidfiles/pidfile.py +18 -1
- omlish/reflect/__init__.py +1 -0
- omlish/reflect/inspect.py +43 -0
- omlish/sql/queries/__init__.py +4 -4
- omlish/sql/queries/rendering2.py +248 -0
- omlish/text/parts.py +26 -23
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/RECORD +57 -45
- omlish/formats/json/delimted.py +0 -4
- /omlish/collections/{persistent.py → persistent/persistent.py} +0 -0
- /omlish/collections/{treap.py → persistent/treap.py} +0 -0
- /omlish/collections/{treapmap.py → persistent/treapmap.py} +0 -0
- /omlish/marshal/{utils.py → proxy.py} +0 -0
- /omlish/marshal/{singular → trivial}/any.py +0 -0
- /omlish/sql/queries/{building.py → std.py} +0 -0
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/top_level.txt +0 -0
@@ -1,84 +1,11 @@
|
|
1
|
-
import abc
|
2
1
|
import os
|
3
|
-
import signal
|
4
|
-
import sys
|
5
|
-
import time
|
6
2
|
import typing as ta
|
7
3
|
import weakref
|
8
4
|
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
|
13
|
-
|
14
|
-
##
|
15
|
-
|
16
|
-
|
17
|
-
class Deathpact(abc.ABC):
|
18
|
-
@abc.abstractmethod
|
19
|
-
def poll(self) -> None:
|
20
|
-
raise NotImplementedError
|
21
|
-
|
22
|
-
|
23
|
-
class NopDeathpact(Deathpact):
|
24
|
-
def poll(self) -> None:
|
25
|
-
pass
|
26
|
-
|
27
|
-
|
28
|
-
##
|
29
|
-
|
30
|
-
|
31
|
-
class BaseDeathpact(Deathpact, abc.ABC):
|
32
|
-
def __init__(
|
33
|
-
self,
|
34
|
-
*,
|
35
|
-
interval_s: float = .5,
|
36
|
-
signal: int | None = signal.SIGTERM,
|
37
|
-
output: ta.Literal['stdout', 'stderr'] | None = 'stderr',
|
38
|
-
on_die: ta.Callable[[], None] | None = None,
|
39
|
-
) -> None:
|
40
|
-
super().__init__()
|
41
|
-
|
42
|
-
self._interval_s = interval_s
|
43
|
-
self._signal = signal
|
44
|
-
self._output = output
|
45
|
-
self._on_die = on_die
|
46
|
-
|
47
|
-
self._last_check_t: float | None = None
|
48
|
-
|
49
|
-
def _print(self, msg: str) -> None:
|
50
|
-
match self._output:
|
51
|
-
case 'stdout':
|
52
|
-
f = sys.stdout
|
53
|
-
case 'stderr':
|
54
|
-
f = sys.stderr
|
55
|
-
case _:
|
56
|
-
return
|
57
|
-
print(f'{self} pid={os.getpid()}: {msg}', file=f)
|
58
|
-
|
59
|
-
def die(self) -> None:
|
60
|
-
self._print('Triggered! Process terminating!')
|
61
|
-
|
62
|
-
if self._on_die is not None:
|
63
|
-
self._on_die()
|
64
|
-
|
65
|
-
if self._signal is not None:
|
66
|
-
os.kill(os.getpid(), self._signal)
|
67
|
-
|
68
|
-
sys.exit(1)
|
69
|
-
|
70
|
-
@abc.abstractmethod
|
71
|
-
def should_die(self) -> bool:
|
72
|
-
raise NotImplementedError
|
73
|
-
|
74
|
-
def maybe_die(self) -> None:
|
75
|
-
if self.should_die():
|
76
|
-
self.die()
|
77
|
-
|
78
|
-
def poll(self) -> None:
|
79
|
-
if self._last_check_t is None or (time.monotonic() - self._last_check_t) >= self._interval_s:
|
80
|
-
self.maybe_die()
|
81
|
-
self._last_check_t = time.monotonic()
|
5
|
+
from ... import check
|
6
|
+
from ..forkhooks import ForkHook
|
7
|
+
from ..forkhooks import ProcessOriginTracker
|
8
|
+
from .base import BaseDeathpact
|
82
9
|
|
83
10
|
|
84
11
|
##
|
@@ -92,16 +19,13 @@ class PipeDeathpact(BaseDeathpact):
|
|
92
19
|
handle such cases.
|
93
20
|
"""
|
94
21
|
|
95
|
-
_COOKIE: ta.ClassVar[bytes] = os.urandom(16)
|
96
|
-
|
97
22
|
def __init__(self, **kwargs: ta.Any) -> None:
|
98
23
|
super().__init__(**kwargs)
|
99
24
|
|
100
25
|
self._rfd: int | None = None
|
101
26
|
self._wfd: int | None = None
|
102
27
|
|
103
|
-
self.
|
104
|
-
self._fork_depth: int | None = get_fork_depth()
|
28
|
+
self._process_origin = ProcessOriginTracker()
|
105
29
|
|
106
30
|
def __repr__(self) -> str:
|
107
31
|
return f'{self.__class__.__name__}(rfd={self._rfd}, wfd={self._wfd})'
|
@@ -111,11 +35,13 @@ class PipeDeathpact(BaseDeathpact):
|
|
111
35
|
return check.not_none(self._rfd)
|
112
36
|
|
113
37
|
def is_parent(self) -> bool:
|
114
|
-
return
|
38
|
+
return self._process_origin.is_in_origin_process()
|
115
39
|
|
116
40
|
#
|
117
41
|
|
118
42
|
def __enter__(self) -> ta.Self:
|
43
|
+
check.state(self.is_parent())
|
44
|
+
|
119
45
|
check.none(self._rfd)
|
120
46
|
check.none(self._wfd)
|
121
47
|
|
@@ -125,18 +51,24 @@ class PipeDeathpact(BaseDeathpact):
|
|
125
51
|
|
126
52
|
return self
|
127
53
|
|
128
|
-
def
|
54
|
+
def close(self) -> None:
|
55
|
+
if self._rfd is not None:
|
56
|
+
os.close(self._rfd)
|
57
|
+
self._rfd = None
|
58
|
+
|
129
59
|
if self._wfd is not None:
|
130
|
-
if
|
60
|
+
if self.is_parent():
|
131
61
|
os.close(check.not_none(self._wfd))
|
132
62
|
self._wfd = None
|
133
63
|
|
134
64
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
135
|
-
|
136
|
-
os.close(self._rfd)
|
137
|
-
self._rfd = None
|
65
|
+
self.close()
|
138
66
|
|
139
|
-
|
67
|
+
def _close_wfd_if_not_parent(self) -> None:
|
68
|
+
if self._wfd is not None:
|
69
|
+
if not self.is_parent():
|
70
|
+
os.close(check.not_none(self._wfd))
|
71
|
+
self._wfd = None
|
140
72
|
|
141
73
|
#
|
142
74
|
|
@@ -145,8 +77,6 @@ class PipeDeathpact(BaseDeathpact):
|
|
145
77
|
**self.__dict__,
|
146
78
|
**dict(
|
147
79
|
_wfd=None,
|
148
|
-
_cookie=None,
|
149
|
-
_fork_depth=None,
|
150
80
|
),
|
151
81
|
}
|
152
82
|
|
omlish/os/forkhooks.py
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
|
-
"""
|
4
|
-
TODO:
|
5
|
-
- ForkHook base class? all classmethods? prevents pickling
|
6
|
-
"""
|
7
3
|
import abc
|
8
4
|
import os
|
9
5
|
import threading
|
@@ -29,31 +25,6 @@ class _ForkHookManager:
|
|
29
25
|
|
30
26
|
#
|
31
27
|
|
32
|
-
_installed: ta.ClassVar[bool] = False
|
33
|
-
|
34
|
-
@classmethod
|
35
|
-
def _install(cls) -> bool:
|
36
|
-
if cls._installed:
|
37
|
-
return False
|
38
|
-
|
39
|
-
check.state(not cls._installed)
|
40
|
-
|
41
|
-
os.register_at_fork(
|
42
|
-
before=cls._before_fork,
|
43
|
-
after_in_parent=cls._after_fork_in_parent,
|
44
|
-
after_in_child=cls._after_fork_in_child,
|
45
|
-
)
|
46
|
-
|
47
|
-
cls._installed = True
|
48
|
-
return True
|
49
|
-
|
50
|
-
@classmethod
|
51
|
-
def install(cls) -> bool:
|
52
|
-
with cls._lock:
|
53
|
-
return cls._install()
|
54
|
-
|
55
|
-
#
|
56
|
-
|
57
28
|
class Hook(ta.NamedTuple):
|
58
29
|
key: ta.Any
|
59
30
|
priority: int
|
@@ -74,8 +45,8 @@ class _ForkHookManager:
|
|
74
45
|
def _rebuild_hook_collections(cls) -> None:
|
75
46
|
cls._hook_keys = frozenset(cls._hooks_by_key)
|
76
47
|
|
77
|
-
# Uses
|
78
|
-
# depended upon for usecase correctness.
|
48
|
+
# Uses dict order preservation to retain insertion-order of hooks of equal priority (although that shouldn't be
|
49
|
+
# depended upon for usecase correctness).
|
79
50
|
cls._priority_ordered_hooks = sorted(cls._hooks_by_key.values(), key=lambda h: h.priority)
|
80
51
|
|
81
52
|
#
|
@@ -121,6 +92,31 @@ class _ForkHookManager:
|
|
121
92
|
|
122
93
|
#
|
123
94
|
|
95
|
+
_installed: ta.ClassVar[bool] = False
|
96
|
+
|
97
|
+
@classmethod
|
98
|
+
def _install(cls) -> bool:
|
99
|
+
if cls._installed:
|
100
|
+
return False
|
101
|
+
|
102
|
+
check.state(not cls._installed)
|
103
|
+
|
104
|
+
os.register_at_fork(
|
105
|
+
before=cls._before_fork,
|
106
|
+
after_in_parent=cls._after_fork_in_parent,
|
107
|
+
after_in_child=cls._after_fork_in_child,
|
108
|
+
)
|
109
|
+
|
110
|
+
cls._installed = True
|
111
|
+
return True
|
112
|
+
|
113
|
+
@classmethod
|
114
|
+
def install(cls) -> bool:
|
115
|
+
with cls._lock:
|
116
|
+
return cls._install()
|
117
|
+
|
118
|
+
#
|
119
|
+
|
124
120
|
@classmethod
|
125
121
|
def _before_fork(cls) -> None:
|
126
122
|
cls._lock.acquire()
|
@@ -213,3 +209,31 @@ class _ForkDepthTracker(ForkHook):
|
|
213
209
|
|
214
210
|
def get_fork_depth() -> int:
|
215
211
|
return _ForkDepthTracker.get_fork_depth()
|
212
|
+
|
213
|
+
|
214
|
+
##
|
215
|
+
|
216
|
+
|
217
|
+
class ProcessOriginTracker:
|
218
|
+
_PROCESS_COOKIE: ta.ClassVar[bytes] = os.urandom(16)
|
219
|
+
|
220
|
+
def __init__(self, **kwargs: ta.Any) -> None:
|
221
|
+
super().__init__(**kwargs)
|
222
|
+
|
223
|
+
self._process_cookie: bytes | None = self._PROCESS_COOKIE
|
224
|
+
self._fork_depth: int | None = get_fork_depth()
|
225
|
+
|
226
|
+
def is_in_origin_process(self) -> bool:
|
227
|
+
return (self._PROCESS_COOKIE, get_fork_depth()) == (self._process_cookie, self._fork_depth)
|
228
|
+
|
229
|
+
def __getstate__(self):
|
230
|
+
return {
|
231
|
+
**self.__dict__,
|
232
|
+
**dict(
|
233
|
+
_cookie=None,
|
234
|
+
_fork_depth=None,
|
235
|
+
),
|
236
|
+
}
|
237
|
+
|
238
|
+
def __setstate__(self, state):
|
239
|
+
self.__dict__.update(state)
|
omlish/os/pidfiles/manager.py
CHANGED
@@ -7,71 +7,38 @@ import typing as ta
|
|
7
7
|
import weakref
|
8
8
|
|
9
9
|
from ...lite.check import check
|
10
|
+
from ..forkhooks import ForkHook
|
10
11
|
from .pidfile import Pidfile
|
11
12
|
|
12
13
|
|
13
14
|
##
|
14
15
|
|
15
16
|
|
16
|
-
class _PidfileManager:
|
17
|
+
class _PidfileManager(ForkHook):
|
17
18
|
"""
|
18
19
|
Manager for controlled inheritance of Pidfiles across forks in the presence of multiple threads. There is of course
|
19
20
|
no safe or correct way to mix the use of fork and multiple active threads, and one should never write code which
|
20
21
|
does so, but in the Real World one may still find oneself in such a situation outside of their control (such as when
|
21
22
|
running under Pycharm's debugger which forces the use of forked multiprocessing).
|
22
|
-
|
23
|
-
Not implemented as an instantiated class as there is no way to unregister at_fork listeners, and because Pidfiles
|
24
|
-
may be pickled and there must be no possibility of accidentally unpickling and instantiating a new instance of the
|
25
|
-
manager.
|
26
23
|
"""
|
27
24
|
|
28
|
-
def __new__(cls, *args, **kwargs): # noqa
|
29
|
-
raise TypeError
|
30
|
-
|
31
25
|
_lock: ta.ClassVar[threading.Lock] = threading.Lock()
|
32
|
-
_installed: ta.ClassVar[bool] = False
|
33
26
|
_pidfile_threads: ta.ClassVar[ta.MutableMapping[Pidfile, threading.Thread]] = weakref.WeakKeyDictionary()
|
27
|
+
_num_kills: ta.ClassVar[int] = 0
|
34
28
|
|
35
29
|
@classmethod
|
36
|
-
def
|
37
|
-
cls.
|
38
|
-
|
39
|
-
@classmethod
|
40
|
-
def _after_fork_in_parent(cls) -> None:
|
41
|
-
cls._lock.release()
|
30
|
+
def num_kills(cls) -> int:
|
31
|
+
return cls._num_kills
|
42
32
|
|
43
33
|
@classmethod
|
44
34
|
def _after_fork_in_child(cls) -> None:
|
45
|
-
th = threading.current_thread()
|
46
|
-
for pf, pf_th in list(cls._pidfile_threads.items()):
|
47
|
-
if pf_th is not th:
|
48
|
-
pf.close()
|
49
|
-
del cls._pidfile_threads[pf]
|
50
|
-
|
51
|
-
cls._lock.release()
|
52
|
-
|
53
|
-
#
|
54
|
-
|
55
|
-
@classmethod
|
56
|
-
def _install(cls) -> None:
|
57
|
-
check.state(not cls._installed)
|
58
|
-
|
59
|
-
os.register_at_fork(
|
60
|
-
before=cls._before_fork,
|
61
|
-
after_in_parent=cls._after_fork_in_parent,
|
62
|
-
after_in_child=cls._after_fork_in_child,
|
63
|
-
)
|
64
|
-
|
65
|
-
cls._installed = True
|
66
|
-
|
67
|
-
@classmethod
|
68
|
-
def install(cls) -> bool:
|
69
35
|
with cls._lock:
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
36
|
+
th = threading.current_thread()
|
37
|
+
for pf, pf_th in list(cls._pidfile_threads.items()):
|
38
|
+
if pf_th is not th:
|
39
|
+
pf.close()
|
40
|
+
del cls._pidfile_threads[pf]
|
41
|
+
cls._num_kills += 1
|
75
42
|
|
76
43
|
@classmethod
|
77
44
|
@contextlib.contextmanager
|
omlish/os/pidfiles/pidfile.py
CHANGED
@@ -56,8 +56,25 @@ class Pidfile:
|
|
56
56
|
|
57
57
|
#
|
58
58
|
|
59
|
+
_fd_to_dup: int
|
60
|
+
|
61
|
+
def dup(self) -> 'Pidfile':
|
62
|
+
fd = self._f.fileno()
|
63
|
+
dup = Pidfile(
|
64
|
+
self._path,
|
65
|
+
inheritable=self._inheritable,
|
66
|
+
)
|
67
|
+
dup._fd_to_dup = fd # noqa
|
68
|
+
return dup
|
69
|
+
|
70
|
+
#
|
71
|
+
|
59
72
|
def __enter__(self) -> 'Pidfile':
|
60
|
-
|
73
|
+
if hasattr(self, '_fd_to_dup'):
|
74
|
+
fd = os.dup(self._fd_to_dup)
|
75
|
+
del self._fd_to_dup
|
76
|
+
else:
|
77
|
+
fd = os.open(self._path, os.O_RDWR | os.O_CREAT, 0o600)
|
61
78
|
|
62
79
|
try:
|
63
80
|
if self._inheritable:
|
omlish/reflect/__init__.py
CHANGED
omlish/reflect/inspect.py
CHANGED
@@ -4,6 +4,7 @@ TODO:
|
|
4
4
|
"""
|
5
5
|
import typing as ta
|
6
6
|
|
7
|
+
from .. import check
|
7
8
|
from .. import lang
|
8
9
|
|
9
10
|
|
@@ -16,8 +17,50 @@ else:
|
|
16
17
|
annotationlib = lang.lazy_import('annotationlib', optional=True, cache_failure=True)
|
17
18
|
|
18
19
|
|
20
|
+
##
|
21
|
+
|
22
|
+
|
19
23
|
def get_annotations(obj: ta.Any) -> ta.Mapping[str, ta.Any]:
|
20
24
|
if (al := annotationlib()) is not None:
|
21
25
|
return al.get_annotations(obj, format=al.Format.FORWARDREF) # noqa
|
22
26
|
else:
|
23
27
|
return inspect.get_annotations(obj)
|
28
|
+
|
29
|
+
|
30
|
+
##
|
31
|
+
|
32
|
+
|
33
|
+
class _TypeHintsTarget:
|
34
|
+
def __init__(self, obj, annotations):
|
35
|
+
super().__init__()
|
36
|
+
|
37
|
+
self.obj = obj
|
38
|
+
self.__annotations__ = annotations
|
39
|
+
self.__globals__ = obj.__globals__
|
40
|
+
self.__type_params__ = obj.__type_params__
|
41
|
+
|
42
|
+
|
43
|
+
def get_filtered_type_hints(
|
44
|
+
obj: ta.Any,
|
45
|
+
*,
|
46
|
+
include: ta.Container[str] | None = None,
|
47
|
+
exclude: ta.Container[str] | None = None,
|
48
|
+
) -> ta.Mapping[str, ta.Any]:
|
49
|
+
"""
|
50
|
+
Gross hack: get_type_hints doesn't support recursive types, but it's nice to support functions with, at least,
|
51
|
+
recursive return types.
|
52
|
+
|
53
|
+
This is consciously constrained in what it supports - basically just functions.
|
54
|
+
"""
|
55
|
+
|
56
|
+
check.not_isinstance(include, str)
|
57
|
+
check.not_isinstance(exclude, str)
|
58
|
+
|
59
|
+
anns = {
|
60
|
+
k: v
|
61
|
+
for k, v in obj.__annotations__.items()
|
62
|
+
if (include is None or k in include)
|
63
|
+
and not (exclude is not None and k in exclude)
|
64
|
+
}
|
65
|
+
|
66
|
+
return ta.get_type_hints(_TypeHintsTarget(obj, anns))
|
omlish/sql/queries/__init__.py
CHANGED
@@ -11,10 +11,6 @@ from .binary import ( # noqa
|
|
11
11
|
BinaryOps,
|
12
12
|
)
|
13
13
|
|
14
|
-
from .building import ( # noqa
|
15
|
-
StdBuilder,
|
16
|
-
)
|
17
|
-
|
18
14
|
from .exprs import ( # noqa
|
19
15
|
CanExpr,
|
20
16
|
CanLiteral,
|
@@ -76,6 +72,10 @@ from .selects import ( # noqa
|
|
76
72
|
SelectItem,
|
77
73
|
)
|
78
74
|
|
75
|
+
from .std import ( # noqa
|
76
|
+
StdBuilder,
|
77
|
+
)
|
78
|
+
|
79
79
|
from .stmts import ( # noqa
|
80
80
|
CanExpr,
|
81
81
|
ExprStmt,
|