omlish 0.0.0.dev230__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 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,,