omlish 0.0.0.dev230__py3-none-any.whl → 0.0.0.dev231__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev230'
2
- __revision__ = 'ed2672ad6a2fb4718c57e281c5ce91fdc7998a6f'
1
+ __version__ = '0.0.0.dev231'
2
+ __revision__ = '2eb4140ac802af2814bc4b4d5a8d37613083ae32'
3
3
 
4
4
 
5
5
  #
File without changes
@@ -0,0 +1,200 @@
1
+ """
2
+ TODO:
3
+ - OK.. this is useful for non-system-daemons too... which have no pidfile..
4
+ - split out pidfile concern to.. identity?
5
+ - async[io] support, really just waiting
6
+ - helpers for http, json, etc
7
+ - heartbeat? status checks? heartbeat file?
8
+ - zmq
9
+ - cli, cfg reload
10
+ - bootstrap
11
+ - rsrc limit
12
+ - logs
13
+ - https://github.com/Homebrew/homebrew-services
14
+ - deathpacts
15
+ - timebomb
16
+ - pickle protocol, revision / venv check, multiprocessing manager support
17
+ """
18
+ import contextlib
19
+ import functools
20
+ import logging
21
+ import os.path
22
+ import threading
23
+ import time
24
+ import typing as ta
25
+
26
+ from .. import check
27
+ from .. import dataclasses as dc
28
+ from .. import lang
29
+ from ..os.pidfiles.manager import _PidfileManager # noqa
30
+ from ..os.pidfiles.manager import open_inheritable_pidfile
31
+ from ..os.pidfiles.pidfile import Pidfile
32
+ from .reparent import reparent_process
33
+ from .spawning import InProcessSpawner
34
+ from .spawning import Spawn
35
+ from .spawning import Spawner
36
+ from .spawning import Spawning
37
+ from .spawning import spawner_for
38
+ from .targets import Target
39
+ from .targets import target_runner_for
40
+ from .waiting import Wait
41
+ from .waiting import waiter_for
42
+
43
+
44
+ log = logging.getLogger(__name__)
45
+
46
+
47
+ ##
48
+
49
+
50
+ class Daemon:
51
+ @dc.dataclass(frozen=True, kw_only=True)
52
+ class Config:
53
+ target: Target
54
+ spawning: Spawning
55
+
56
+ #
57
+
58
+ reparent_process: bool = False
59
+
60
+ pid_file: str | None = None
61
+
62
+ #
63
+
64
+ wait: Wait | None = None
65
+
66
+ wait_timeout: lang.TimeoutLike = 10.
67
+ wait_sleep_s: float = .1
68
+
69
+ launched_timeout_s: float = 5.
70
+
71
+ #
72
+
73
+ def __post_init__(self) -> None:
74
+ check.isinstance(self.pid_file, (str, None))
75
+
76
+ def __init__(self, config: Config) -> None:
77
+ super().__init__()
78
+
79
+ self._config = config
80
+
81
+ @property
82
+ def config(self) -> Config:
83
+ return self._config
84
+
85
+ #
86
+
87
+ @property
88
+ def has_pidfile(self) -> bool:
89
+ return self._config.pid_file is not None
90
+
91
+ def _non_inheritable_pidfile(self) -> Pidfile:
92
+ check.state(self.has_pidfile)
93
+ return Pidfile(
94
+ check.non_empty_str(self._config.pid_file),
95
+ inheritable=False,
96
+ )
97
+
98
+ def is_running(self) -> bool:
99
+ check.state(self.has_pidfile)
100
+
101
+ if not os.path.isfile(check.non_empty_str(self._config.pid_file)):
102
+ return False
103
+
104
+ with self._non_inheritable_pidfile() as pf:
105
+ return not pf.try_acquire_lock()
106
+
107
+ #
108
+
109
+ def _inner_launch(
110
+ self,
111
+ *,
112
+ pidfile_manager: ta.ContextManager | None,
113
+ launched_callback: ta.Callable[[], None] | None = None,
114
+ ) -> None:
115
+ try:
116
+ if self._config.reparent_process:
117
+ log.info('Reparenting')
118
+ reparent_process()
119
+
120
+ with contextlib.ExitStack() as es:
121
+ pidfile: Pidfile | None = None # noqa
122
+ if pidfile_manager is not None:
123
+ pidfile = check.isinstance(es.enter_context(pidfile_manager), Pidfile)
124
+ pidfile.write()
125
+
126
+ if launched_callback is not None:
127
+ launched_callback()
128
+
129
+ runner = target_runner_for(self._config.target)
130
+ runner.run()
131
+
132
+ finally:
133
+ if launched_callback is not None:
134
+ launched_callback()
135
+
136
+ def launch_no_wait(self) -> bool:
137
+ with contextlib.ExitStack() as es:
138
+ spawner: Spawner = es.enter_context(spawner_for(self._config.spawning))
139
+
140
+ #
141
+
142
+ inherit_fds: set[int] = set()
143
+ launched_event: threading.Event | None = None
144
+
145
+ pidfile: Pidfile | None = None # noqa
146
+ pidfile_manager: ta.ContextManager | None = None
147
+
148
+ if (pid_file := self._config.pid_file) is not None:
149
+ if not isinstance(spawner, InProcessSpawner):
150
+ pidfile = es.enter_context(open_inheritable_pidfile(pid_file))
151
+ pidfile_manager = lang.NopContextManager(pidfile)
152
+
153
+ else:
154
+ check.state(not self._config.reparent_process)
155
+ pidfile = es.enter_context(Pidfile(pid_file))
156
+ pidfile_manager = pidfile.dup()
157
+ launched_event = threading.Event()
158
+
159
+ if not pidfile.try_acquire_lock():
160
+ return False
161
+
162
+ inherit_fds.add(check.isinstance(pidfile.fileno(), int))
163
+
164
+ #
165
+
166
+ spawner.spawn(Spawn(
167
+ functools.partial(
168
+ self._inner_launch,
169
+ pidfile_manager=pidfile_manager,
170
+ launched_callback=launched_event.set if launched_event is not None else None,
171
+ ),
172
+ inherit_fds=inherit_fds,
173
+ ))
174
+
175
+ if launched_event is not None:
176
+ check.state(launched_event.wait(timeout=self._config.launched_timeout_s))
177
+
178
+ return True
179
+
180
+ #
181
+
182
+ def wait_sync(self, timeout: lang.TimeoutLike = lang.Timeout.Default) -> None:
183
+ if self._config.wait is None:
184
+ return
185
+
186
+ timeout = lang.Timeout.of(timeout, self._config.wait_timeout)
187
+ waiter = waiter_for(self._config.wait)
188
+ while not waiter.do_wait():
189
+ timeout()
190
+ time.sleep(self._config.wait_sleep_s or 0.)
191
+
192
+ #
193
+
194
+ class _NOT_SET(lang.Marker): # noqa
195
+ pass
196
+
197
+ def launch(self, timeout: lang.TimeoutLike = lang.Timeout.Default) -> None:
198
+ self.launch_no_wait()
199
+
200
+ self.wait_sync(timeout)
@@ -0,0 +1,16 @@
1
+ import os
2
+ import sys
3
+
4
+
5
+ def reparent_process() -> None:
6
+ if (pid := os.fork()): # noqa
7
+ sys.exit(0)
8
+ raise RuntimeError('Unreachable') # noqa
9
+
10
+ os.setsid()
11
+
12
+ if (pid := os.fork()): # noqa
13
+ sys.exit(0)
14
+
15
+ sys.stdout.flush()
16
+ sys.stderr.flush()
@@ -0,0 +1,166 @@
1
+ import abc
2
+ import functools
3
+ import os
4
+ import sys
5
+ import threading
6
+ import typing as ta
7
+
8
+ from .. import check
9
+ from .. import dataclasses as dc
10
+ from .. import lang
11
+ from ..diag import pydevd
12
+
13
+
14
+ if ta.TYPE_CHECKING:
15
+ import multiprocessing as mp
16
+ import multiprocessing.context
17
+ import multiprocessing.process # noqa
18
+
19
+ from ..multiprocessing import spawn as omp_spawn
20
+
21
+ else:
22
+ mp = lang.proxy_import('multiprocessing', extras=['context', 'process'])
23
+ subprocess = lang.proxy_import('subprocess')
24
+
25
+ omp_spawn = lang.proxy_import('..multiprocessing.spawn', __package__)
26
+
27
+
28
+ ##
29
+
30
+
31
+ class Spawning(dc.Case):
32
+ pass
33
+
34
+
35
+ class Spawn(dc.Frozen, final=True):
36
+ fn: ta.Callable[[], None]
37
+
38
+ _: dc.KW_ONLY
39
+
40
+ inherit_fds: ta.Collection[int] | None = None
41
+
42
+
43
+ class Spawner(lang.ContextManaged, abc.ABC):
44
+ @abc.abstractmethod
45
+ def spawn(self, spawn: Spawn) -> None:
46
+ raise NotImplementedError
47
+
48
+
49
+ class InProcessSpawner(Spawner, abc.ABC):
50
+ pass
51
+
52
+
53
+ @functools.singledispatch
54
+ def spawner_for(spawning: Spawning) -> Spawner:
55
+ raise TypeError(spawning)
56
+
57
+
58
+ ##
59
+
60
+
61
+ class MultiprocessingSpawning(Spawning, kw_only=True):
62
+ # Defaults to 'fork' if under pydevd, else 'spawn'
63
+ start_method: str | None = None
64
+
65
+ non_daemon: bool = False
66
+
67
+
68
+ class MultiprocessingSpawner(Spawner):
69
+ def __init__(self, spawning: MultiprocessingSpawning) -> None:
70
+ super().__init__()
71
+
72
+ self._spawning = spawning
73
+ self._process: ta.Optional['mp.process.BaseProcess'] = None # noqa
74
+
75
+ def _process_cls(self, spawn: Spawn) -> type['mp.process.BaseProcess']:
76
+ if (start_method := self._spawning.start_method) is None:
77
+ # Unfortunately, pydevd forces the use of the 'fork' start_method, which cannot be mixed with 'spawn':
78
+ # https://github.com/python/cpython/blob/a7427f2db937adb4c787754deb4c337f1894fe86/Lib/multiprocessing/spawn.py#L102 # noqa
79
+ if pydevd.is_running():
80
+ start_method = 'fork'
81
+ else:
82
+ start_method = 'spawn'
83
+
84
+ ctx: 'mp.context.BaseContext' # noqa
85
+ if start_method == 'fork':
86
+ ctx = mp.get_context(check.non_empty_str(start_method))
87
+
88
+ elif start_method == 'spawn':
89
+ ctx = omp_spawn.ExtrasSpawnContext(omp_spawn.SpawnExtras(
90
+ pass_fds=frozenset(spawn.inherit_fds) if spawn.inherit_fds is not None else None,
91
+ ))
92
+
93
+ else:
94
+ raise ValueError(start_method)
95
+
96
+ return ctx.Process # type: ignore
97
+
98
+ def spawn(self, spawn: Spawn) -> None:
99
+ check.none(self._process)
100
+ self._process = self._process_cls(spawn)(
101
+ target=spawn.fn,
102
+ daemon=not self._spawning.non_daemon,
103
+ )
104
+ self._process.start()
105
+
106
+
107
+ @spawner_for.register
108
+ def _(spawning: MultiprocessingSpawning) -> MultiprocessingSpawner:
109
+ return MultiprocessingSpawner(spawning)
110
+
111
+
112
+ ##
113
+
114
+
115
+ class ForkSpawning(Spawning):
116
+ pass
117
+
118
+
119
+ class ForkSpawner(Spawner, dc.Frozen):
120
+ spawning: ForkSpawning
121
+
122
+ def spawn(self, spawn: Spawn) -> None:
123
+ if (pid := os.fork()): # noqa
124
+ return
125
+
126
+ try:
127
+ spawn.fn()
128
+ except BaseException: # noqa
129
+ sys.exit(1)
130
+ else:
131
+ sys.exit(0)
132
+
133
+ raise RuntimeError('Unreachable') # noqa
134
+
135
+
136
+ @spawner_for.register
137
+ def _(spawning: ForkSpawning) -> ForkSpawner:
138
+ return ForkSpawner(spawning)
139
+
140
+
141
+ ##
142
+
143
+
144
+ class ThreadSpawning(Spawning, kw_only=True):
145
+ non_daemon: bool = False
146
+
147
+
148
+ class ThreadSpawner(InProcessSpawner):
149
+ def __init__(self, spawning: ThreadSpawning) -> None:
150
+ super().__init__()
151
+
152
+ self._spawning = spawning
153
+ self._thread: threading.Thread | None = None
154
+
155
+ def spawn(self, spawn: Spawn) -> None:
156
+ check.none(self._thread)
157
+ self._thread = threading.Thread(
158
+ target=spawn.fn,
159
+ daemon=not self._spawning.non_daemon,
160
+ )
161
+ self._thread.start()
162
+
163
+
164
+ @spawner_for.register
165
+ def _(spawning: ThreadSpawning) -> ThreadSpawner:
166
+ return ThreadSpawner(spawning)
@@ -0,0 +1,89 @@
1
+ import abc
2
+ import functools
3
+ import os
4
+ import typing as ta
5
+
6
+ from .. import check
7
+ from .. import dataclasses as dc
8
+ from .. import lang
9
+
10
+
11
+ if ta.TYPE_CHECKING:
12
+ import runpy
13
+ else:
14
+ runpy = lang.proxy_import('runpy')
15
+
16
+
17
+ ##
18
+
19
+
20
+ class Target(dc.Case):
21
+ pass
22
+
23
+
24
+ class TargetRunner(abc.ABC):
25
+ @abc.abstractmethod
26
+ def run(self) -> None:
27
+ raise NotImplementedError
28
+
29
+
30
+ @functools.singledispatch
31
+ def target_runner_for(target: Target) -> TargetRunner:
32
+ raise TypeError(target)
33
+
34
+
35
+ ##
36
+
37
+
38
+ class FnTarget(Target):
39
+ fn: ta.Callable[[], None]
40
+
41
+
42
+ class FnTargetRunner(TargetRunner, dc.Frozen):
43
+ target: FnTarget
44
+
45
+ def run(self) -> None:
46
+ self.target.fn()
47
+
48
+
49
+ @target_runner_for.register
50
+ def _(target: FnTarget) -> FnTargetRunner:
51
+ return FnTargetRunner(target)
52
+
53
+
54
+ ##
55
+
56
+
57
+ class NameTarget(Target):
58
+ name: str
59
+
60
+
61
+ class NameTargetRunner(TargetRunner, dc.Frozen):
62
+ target: NameTarget
63
+
64
+ def run(self) -> None:
65
+ name = self.target.name
66
+ if lang.can_import(name):
67
+ runpy._run_module_as_main(name) # type: ignore # noqa
68
+ else:
69
+ obj = lang.import_attr(self.target.name)
70
+ obj()
71
+
72
+
73
+ ##
74
+
75
+
76
+ class ExecTarget(Target):
77
+ cmd: ta.Sequence[str] = dc.xfield(coerce=check.of_not_isinstance(str))
78
+
79
+
80
+ class ExecTargetRunner(TargetRunner, dc.Frozen):
81
+ target: ExecTarget
82
+
83
+ def run(self) -> None:
84
+ os.execl(*self.target.cmd)
85
+
86
+
87
+ @target_runner_for.register
88
+ def _(target: ExecTarget) -> ExecTargetRunner:
89
+ return ExecTargetRunner(target)
@@ -0,0 +1,95 @@
1
+ import abc
2
+ import functools
3
+ import socket
4
+ import typing as ta
5
+
6
+ from .. import dataclasses as dc
7
+
8
+
9
+ ##
10
+
11
+
12
+ class Wait(dc.Case):
13
+ pass
14
+
15
+
16
+ class Waiter(abc.ABC):
17
+ @abc.abstractmethod
18
+ def do_wait(self) -> bool:
19
+ raise NotImplementedError
20
+
21
+
22
+ @functools.singledispatch
23
+ def waiter_for(wait: Wait) -> Waiter:
24
+ raise TypeError(wait)
25
+
26
+
27
+ ##
28
+
29
+
30
+ class SequentialWait(Wait):
31
+ waits: ta.Sequence[Wait]
32
+
33
+
34
+ class SequentialWaiter(Waiter):
35
+ def __init__(self, waiters: ta.Sequence[Waiter]) -> None:
36
+ super().__init__()
37
+
38
+ self._waiters = waiters
39
+ self._idx = 0
40
+
41
+ def do_wait(self) -> bool:
42
+ while self._idx < len(self._waiters):
43
+ if not self._waiters[self._idx].do_wait():
44
+ return False
45
+ self._idx += 1
46
+ return True
47
+
48
+
49
+ @waiter_for.register
50
+ def _(wait: SequentialWait) -> SequentialWaiter:
51
+ return SequentialWaiter([waiter_for(c) for c in wait.waits])
52
+
53
+
54
+ ##
55
+
56
+
57
+ class FnWait(Wait):
58
+ fn: ta.Callable[[], bool]
59
+
60
+
61
+ class FnWaiter(Waiter, dc.Frozen):
62
+ wait: FnWait
63
+
64
+ def do_wait(self) -> bool:
65
+ return self.wait.fn()
66
+
67
+
68
+ @waiter_for.register
69
+ def _(wait: FnWait) -> FnWaiter:
70
+ return FnWaiter(wait)
71
+
72
+
73
+ ##
74
+
75
+
76
+ class ConnectWait(Wait):
77
+ address: ta.Any
78
+
79
+
80
+ class ConnectWaiter(Waiter, dc.Frozen):
81
+ wait: ConnectWait
82
+
83
+ def do_wait(self) -> bool:
84
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
85
+ try:
86
+ s.connect(self.wait.address)
87
+ except ConnectionRefusedError:
88
+ return False
89
+ else:
90
+ return True
91
+
92
+
93
+ @waiter_for.register
94
+ def _(wait: ConnectWait) -> ConnectWaiter:
95
+ return ConnectWaiter(wait)
omlish/lang/__init__.py CHANGED
@@ -61,9 +61,7 @@ from .contextmanagers import ( # noqa
61
61
  DefaultLockable,
62
62
  ExitStacked,
63
63
  Lockable,
64
- NOP_CONTEXT_MANAGED,
65
64
  NOP_CONTEXT_MANAGER,
66
- NopContextManaged,
67
65
  NopContextManager,
68
66
  Timer,
69
67
  a_defer,
@@ -18,6 +18,11 @@ T = ta.TypeVar('T')
18
18
  ##
19
19
 
20
20
 
21
+ class _NOT_SET: # noqa
22
+ def __new__(cls, *args, **kwargs): # noqa
23
+ raise TypeError
24
+
25
+
21
26
  class ContextManaged:
22
27
  def __enter__(self) -> ta.Self:
23
28
  return self
@@ -31,21 +36,21 @@ class ContextManaged:
31
36
  return None
32
37
 
33
38
 
34
- class NopContextManaged(ContextManaged):
35
- def __init_subclass__(cls, **kwargs: ta.Any) -> None:
36
- raise TypeError
37
-
39
+ class NopContextManager(ContextManaged):
40
+ def __init__(self, /, value: ta.Any = _NOT_SET) -> None:
41
+ super().__init__()
38
42
 
39
- NOP_CONTEXT_MANAGED = NopContextManaged()
43
+ self._value = value
40
44
 
45
+ def __enter__(self):
46
+ if (value := self._value) is _NOT_SET:
47
+ return self
48
+ else:
49
+ return value
41
50
 
42
- class NopContextManager:
43
51
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
44
52
  raise TypeError
45
53
 
46
- def __call__(self, *args, **kwargs):
47
- return NOP_CONTEXT_MANAGED
48
-
49
54
 
50
55
  NOP_CONTEXT_MANAGER = NopContextManager()
51
56
 
@@ -350,7 +355,7 @@ def default_lock(value: DefaultLockable, default: DefaultLockable = None) -> Loc
350
355
  return lambda: lock
351
356
 
352
357
  elif value is False or value is None:
353
- return NOP_CONTEXT_MANAGER
358
+ return lambda: NOP_CONTEXT_MANAGER
354
359
 
355
360
  elif callable(value):
356
361
  return value
omlish/libc.py CHANGED
@@ -62,7 +62,6 @@ libc.free.argtypes = [ct.c_void_p]
62
62
 
63
63
 
64
64
  class Malloc:
65
-
66
65
  def __init__(self, sz: int) -> None:
67
66
  super().__init__()
68
67
 
@@ -174,7 +173,6 @@ if LINUX:
174
173
 
175
174
 
176
175
  class Mmap:
177
-
178
176
  def __init__(
179
177
  self,
180
178
  length: int,
@@ -537,14 +535,14 @@ elif DARWIN:
537
535
 
538
536
 
539
537
  if LINUX:
540
- def gettid():
538
+ def gettid() -> int:
541
539
  syscalls = {
542
540
  'i386': 224, # unistd_32.h: #define __NR_gettid 224
543
541
  'x86_64': 186, # unistd_64.h: #define __NR_gettid 186
544
542
  'aarch64': 178, # asm-generic/unistd.h: #define __NR_gettid 178
545
543
  }
546
544
  try:
547
- tid = ct.CDLL('libc.so.6').syscall(syscalls[platform.machine()])
545
+ tid = libc.syscall(syscalls[platform.machine()])
548
546
  except Exception: # noqa
549
547
  tid = -1
550
548
  return tid
omlish/lite/timeouts.py CHANGED
@@ -8,7 +8,7 @@ import time
8
8
  import typing as ta
9
9
 
10
10
 
11
- TimeoutLike = ta.Union['Timeout', 'Timeout.Default', ta.Iterable['TimeoutLike'], float] # ta.TypeAlias
11
+ TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.Default'], ta.Iterable['TimeoutLike'], float] # ta.TypeAlias
12
12
 
13
13
 
14
14
  ##
@@ -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
- fd = os.open(self._path, os.O_RDWR | os.O_CREAT, 0o600)
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: omlish
3
- Version: 0.0.0.dev230
3
+ Version: 0.0.0.dev231
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=YGmAnUBszmosQQ_7Hh2wwtDiYdYZ4unNKYzOtALuels,7968
2
- omlish/__about__.py,sha256=5Ic1ejTV8SWSaVGgOg2_WDgpVTfT-KBLADFDeHHanA4,3380
2
+ omlish/__about__.py,sha256=ZBkJyvFWxZFNvvD7zkGGThXCoE7JjItJdlIadSjnLho,3380
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
5
5
  omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
@@ -7,7 +7,7 @@ omlish/check.py,sha256=THqm6jD1a0skAO5EC8SOVg58yq96Vk5wcuruBkCYxyU,2016
7
7
  omlish/datetimes.py,sha256=HajeM1kBvwlTa-uR1TTZHmZ3zTPnnUr1uGGQhiO1XQ0,2152
8
8
  omlish/defs.py,sha256=9uUjJuVIbCBL3g14fyzAp-9gH935MFofvlfOGwcBIaM,4913
9
9
  omlish/dynamic.py,sha256=kIZokHHid8a0pIAPXMNiXrVJvJJyBnY49WP1a2m-HUQ,6525
10
- omlish/libc.py,sha256=20YdP3Sh45d-c13OMV0Od6FEAIxqKvU3d5kCOIW9xCQ,15650
10
+ omlish/libc.py,sha256=8K4c66YV1ziJerl5poAAYCmsV-VSsHkT3EHhPW04ufg,15639
11
11
  omlish/outcome.py,sha256=ABIE0zjjTyTNtn-ZqQ_9_mUzLiBQ3sDAyqc9JVD8N2k,7852
12
12
  omlish/runmodule.py,sha256=PWvuAaJ9wQQn6bx9ftEL3_d04DyotNn8dR_twm2pgw0,700
13
13
  omlish/shlex.py,sha256=bsW2XUD8GiMTUTDefJejZ5AyqT1pTgWMPD0BMoF02jE,248
@@ -175,6 +175,12 @@ omlish/configs/processing/matching.py,sha256=R64RxpPB1uX5Ztvvk2dQ2xi_xwlaxkxQgZw
175
175
  omlish/configs/processing/names.py,sha256=weHmaTclzgM9lUn3aBtw-kwZ3mc2N-CZlFg3Kd_UsKo,1093
176
176
  omlish/configs/processing/rewriting.py,sha256=v7PfHtuTn5v_5Y6Au7oMN2Z0nxAMy1iYyO5CXnTvZhs,4226
177
177
  omlish/configs/processing/strings.py,sha256=qFS2oh6z02IaM_q4lTKLdufzkJqAJ6J-Qjrz5S-QJoM,826
178
+ omlish/daemons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
179
+ omlish/daemons/daemon.py,sha256=rK5T_ZbQPwwF-yLgS03fmJ0cZsVGSjTF5M4xSq_YiM0,5534
180
+ omlish/daemons/reparent.py,sha256=UaG2X6VJHJPOlUwHPNRH3aWGgF0Fg771jjO9IRPLlyY,280
181
+ omlish/daemons/spawning.py,sha256=cx00xeqSrfhlFbjCtKqaBHvMuHwB9hdjuKNHzAAo_dw,4030
182
+ omlish/daemons/targets.py,sha256=scq6BYgzi0H2apfgD74U0D8_msQuYAel3Qiij74YWVo,1501
183
+ omlish/daemons/waiting.py,sha256=RfgD1L33QQVbD2431dkKZGE4w6DUcGvYeRXXi8puAP4,1676
178
184
  omlish/dataclasses/__init__.py,sha256=D7I6ZJjEFeLN2re8oOZ_7JKWiG2plrPnaUFq3iEXYmQ,1553
179
185
  omlish/dataclasses/utils.py,sha256=N2seT8cJtfOv-41D7F3E-q4us-FCTQmnxxPv3dt1OcI,3796
180
186
  omlish/dataclasses/impl/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
@@ -385,11 +391,11 @@ omlish/iterators/iterators.py,sha256=ghI4dO6WPyyFOLTIIMaHQ_IOy2xXaFpGPqveZ5YGIBU
385
391
  omlish/iterators/recipes.py,sha256=53mkexitMhkwXQZbL6DrhpT0WePQ_56uXd5Jaw3DfzI,467
386
392
  omlish/iterators/tools.py,sha256=Pi4ybXytUXVZ3xwK89xpPImQfYYId9p1vIFQvVqVLqA,2551
387
393
  omlish/iterators/unique.py,sha256=0jAX3kwzVfRNhe0Tmh7kVP_Q2WBIn8POo_O-rgFV0rQ,1390
388
- omlish/lang/__init__.py,sha256=_DddR5PNgjqr51AB5DPUq0iKVqJrKz55WMJnBm97LL0,4127
394
+ omlish/lang/__init__.py,sha256=bE8ra_QM5M4k8v3qFtreTjQswaZClIOaqXt9fE43MD0,4079
389
395
  omlish/lang/cached.py,sha256=tQaqMu1LID0q4NSTk5vPXsgxIBWSFAmjs5AhQoEHoCQ,7833
390
396
  omlish/lang/clsdct.py,sha256=sJYadm-fwzti-gsi98knR5qQUxriBmOqQE_qz3RopNk,1743
391
397
  omlish/lang/cmp.py,sha256=5vbzWWbqdzDmNKAGL19z6ZfUKe5Ci49e-Oegf9f4BsE,1346
392
- omlish/lang/contextmanagers.py,sha256=V-XRwj1Em17Pr92i9mSYeeElQs9pmfM_B4jTF732qIg,10400
398
+ omlish/lang/contextmanagers.py,sha256=Mrn8NJ3pP0Zxi-IoGqSjZDdWUctsyee2vrZ2FtZvNmo,10529
393
399
  omlish/lang/datetimes.py,sha256=ehI_DhQRM-bDxAavnp470XcekbbXc4Gdw9y1KpHDJT0,223
394
400
  omlish/lang/descriptors.py,sha256=njkYDS1gn5p4-3v1jr-s_srauC7tvvt571RjE7Q4LXE,6616
395
401
  omlish/lang/exceptions.py,sha256=qJBo3NU1mOWWm-NhQUHCY5feYXR3arZVyEHinLsmRH4,47
@@ -434,7 +440,7 @@ omlish/lite/resources.py,sha256=YNSmX1Ohck1aoWRs55a-o5ChVbFJIQhtbqE-XwF55Oc,326
434
440
  omlish/lite/runtime.py,sha256=XQo408zxTdJdppUZqOWHyeUR50VlCpNIExNGHz4U6O4,459
435
441
  omlish/lite/secrets.py,sha256=3Mz3V2jf__XU9qNHcH56sBSw95L3U2UPL24bjvobG0c,816
436
442
  omlish/lite/strings.py,sha256=QGxT1Yh4oI8ycsfeobxnjEhvDob_GiAKLeIhZwo1j24,1986
437
- omlish/lite/timeouts.py,sha256=wZ6PP1D3B8jpAI7-cQm7wkGTh2sE366XIn6FSMH6DZ4,4959
443
+ omlish/lite/timeouts.py,sha256=lhXo0zwpLM7nr2-AliBRui2BO9jh7B8ALxetwOq6hYI,4968
438
444
  omlish/lite/timing.py,sha256=aVu3hEDB_jyTF_ryZI7iU-xg4q8CNwqpp9Apfru_iwY,196
439
445
  omlish/lite/types.py,sha256=fP5EMyBdEp2LmDxcHjUDtwAMdR06ISr9lKOL7smWfHM,140
440
446
  omlish/lite/typing.py,sha256=U3-JaEnkDSYxK4tsu_MzUn3RP6qALBe5FXQXpD-licE,1090
@@ -520,7 +526,7 @@ omlish/os/deathpacts/heartbeatfile.py,sha256=OybdvhM2kxBTuoJWOJJ5LcX-0lg3jTOvvD2
520
526
  omlish/os/deathpacts/pipe.py,sha256=ZH-l-fIKyurocCehqOgvaYRurxIEMWe8D7l2dsJeGws,3214
521
527
  omlish/os/pidfiles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
522
528
  omlish/os/pidfiles/manager.py,sha256=qSEwNaWT1KOAnU0KxliwvU_uowme5jyf1FyIPsGwnTY,2391
523
- omlish/os/pidfiles/pidfile.py,sha256=WmZt_c8fvztPgZQnYHhcQCKWgHqAEsaI3Ggz6Wqgkc8,3748
529
+ omlish/os/pidfiles/pidfile.py,sha256=wrttUQB7rk2SJ7MqtaRlqVGii5Kg6LWm3eV2auHb3zA,4125
524
530
  omlish/os/pidfiles/pinning.py,sha256=_AwYjJc1UGX7mdCOk4mItJJcsOJo3RW2ebBOm2noW5Y,6359
525
531
  omlish/reflect/__init__.py,sha256=Er2yBHibVO16hFNA1szQF2_f43Y3HRCBWtS-fjsOIYc,798
526
532
  omlish/reflect/inspect.py,sha256=WCo2YpBYauKw6k758FLlZ_H4Q05rgVPs96fEv9w6zHQ,1538
@@ -695,9 +701,9 @@ omlish/text/indent.py,sha256=YjtJEBYWuk8--b9JU_T6q4yxV85_TR7VEVr5ViRCFwk,1336
695
701
  omlish/text/minja.py,sha256=jZC-fp3Xuhx48ppqsf2Sf1pHbC0t8XBB7UpUUoOk2Qw,5751
696
702
  omlish/text/parts.py,sha256=JkNZpyR2tv2CNcTaWJJhpQ9E4F0yPR8P_YfDbZfMtwQ,6182
697
703
  omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
698
- omlish-0.0.0.dev230.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
699
- omlish-0.0.0.dev230.dist-info/METADATA,sha256=9A2Cqst8XnecLQcMoWK5ElLcgfKuHa_CQqsM5YCaeUc,4176
700
- omlish-0.0.0.dev230.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
701
- omlish-0.0.0.dev230.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
702
- omlish-0.0.0.dev230.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
703
- omlish-0.0.0.dev230.dist-info/RECORD,,
704
+ omlish-0.0.0.dev231.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
705
+ omlish-0.0.0.dev231.dist-info/METADATA,sha256=glvhjWJ8XJlRwe1JuGQQYJBpwdsJhjPFHEWn7Z985oE,4176
706
+ omlish-0.0.0.dev231.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
707
+ omlish-0.0.0.dev231.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
708
+ omlish-0.0.0.dev231.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
709
+ omlish-0.0.0.dev231.dist-info/RECORD,,