omlish 0.0.0.dev225__py3-none-any.whl → 0.0.0.dev226__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.dev225'
2
- __revision__ = '99eb1740a6c8647c20c80278b6d0bedff0f7c103'
1
+ __version__ = '0.0.0.dev226'
2
+ __revision__ = '1f3419dac98e9db37df48a9a952f780d17bf31ed'
3
3
 
4
4
 
5
5
  #
@@ -10,6 +10,7 @@ import sys
10
10
  import typing as ta
11
11
 
12
12
  from ...lite.check import check
13
+ from ...lite.timeouts import TimeoutLike
13
14
  from ...subprocesses.async_ import AbstractAsyncSubprocesses
14
15
  from ...subprocesses.run import SubprocessRun
15
16
  from ...subprocesses.run import SubprocessRunOutput
@@ -130,7 +131,7 @@ class AsyncioProcessCommunicator:
130
131
  async def communicate(
131
132
  self,
132
133
  input: ta.Any = None, # noqa
133
- timeout: ta.Optional[float] = None,
134
+ timeout: ta.Optional[TimeoutLike] = None,
134
135
  ) -> Communication:
135
136
  return await asyncio_maybe_timeout(self._communicate(input), timeout)
136
137
 
@@ -143,7 +144,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
143
144
  self,
144
145
  proc: asyncio.subprocess.Process,
145
146
  input: ta.Any = None, # noqa
146
- timeout: ta.Optional[float] = None,
147
+ timeout: ta.Optional[TimeoutLike] = None,
147
148
  ) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
148
149
  return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
149
150
 
@@ -154,7 +155,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
154
155
  self,
155
156
  *cmd: str,
156
157
  shell: bool = False,
157
- timeout: ta.Optional[float] = None,
158
+ timeout: ta.Optional[TimeoutLike] = None,
158
159
  **kwargs: ta.Any,
159
160
  ) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
160
161
  with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
@@ -3,14 +3,17 @@
3
3
  import asyncio
4
4
  import typing as ta
5
5
 
6
+ from ...lite.timeouts import Timeout
7
+ from ...lite.timeouts import TimeoutLike
8
+
6
9
 
7
10
  AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
8
11
 
9
12
 
10
13
  def asyncio_maybe_timeout(
11
14
  fut: AwaitableT,
12
- timeout: ta.Optional[float] = None,
15
+ timeout: ta.Optional[TimeoutLike] = None,
13
16
  ) -> AwaitableT:
14
17
  if timeout is not None:
15
- fut = asyncio.wait_for(fut, timeout) # type: ignore
18
+ fut = asyncio.wait_for(fut, Timeout.of(timeout)()) # type: ignore
16
19
  return fut
omlish/diag/lslocks.py ADDED
@@ -0,0 +1,64 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ https://github.com/util-linux/util-linux/blob/a4436c7bf07f98a6381c7dfa2ab3f9a415f9c479/misc-utils/lslocks.c
5
+ """
6
+ import dataclasses as dc
7
+ import json
8
+ import typing as ta
9
+
10
+ from ..lite.check import check
11
+ from ..lite.marshal import OBJ_MARSHALER_FIELD_KEY
12
+ from ..lite.marshal import unmarshal_obj
13
+ from ..subprocesses.run import SubprocessRun
14
+ from ..subprocesses.run import SubprocessRunnable
15
+ from ..subprocesses.run import SubprocessRunOutput
16
+
17
+
18
+ ##
19
+
20
+
21
+ @dc.dataclass(frozen=True)
22
+ class LsLocksItem:
23
+ """https://manpages.ubuntu.com/manpages/lunar/man8/lslocks.8.html"""
24
+
25
+ command: str
26
+ pid: int
27
+ type: str # POSIX | FLOCK | OFDLCK
28
+ size: ta.Optional[int]
29
+ mode: str # READ | WRITE
30
+ mandatory: bool = dc.field(metadata={OBJ_MARSHALER_FIELD_KEY: 'm'})
31
+ start: int
32
+ end: int
33
+ path: str
34
+ blocker: ta.Optional[int] = None
35
+
36
+
37
+ ##
38
+
39
+
40
+ @dc.dataclass(frozen=True)
41
+ class LsLocksCommand(SubprocessRunnable):
42
+ pid: ta.Optional[int] = None
43
+ no_inaccessible: bool = False
44
+
45
+ def make_run(self) -> SubprocessRun:
46
+ return SubprocessRun.of(
47
+ 'lslocks',
48
+ '--json',
49
+ '--bytes',
50
+ '--notruncate',
51
+ *(['--pid', str(self.pid)] if self.pid is not None else []),
52
+ *(['--noinaccessible'] if self.no_inaccessible else []),
53
+
54
+ check=True,
55
+ stdout='pipe',
56
+ stderr='devnull',
57
+ )
58
+
59
+ def handle_run_output(self, output: SubprocessRunOutput) -> ta.List[LsLocksItem]:
60
+ buf = check.not_none(output.stdout).decode().strip()
61
+ if not buf:
62
+ return []
63
+ obj = json.loads(buf)
64
+ return unmarshal_obj(obj['locks'], ta.List[LsLocksItem])
omlish/diag/lsof.py ADDED
@@ -0,0 +1,264 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ https://man7.org/linux/man-pages/man8/lsof.8.html
5
+
6
+ Included in the process set are fields that identify the command, the process group IDentification (PGID) number, the
7
+ task (thread) ID (TID), and the user ID (UID) number or login name.
8
+ """
9
+ import dataclasses as dc
10
+ import enum
11
+ import typing as ta
12
+
13
+ from ..lite.check import check
14
+ from ..lite.dataclasses import dataclass_repr_omit_falsey
15
+ from ..lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
16
+ from ..subprocesses.run import SubprocessRun
17
+ from ..subprocesses.run import SubprocessRunnable
18
+ from ..subprocesses.run import SubprocessRunOutput
19
+
20
+
21
+ ##
22
+
23
+
24
+ # FIXME: ??
25
+ # https://unix.stackexchange.com/a/86011
26
+ class LsofItemMode(enum.Enum):
27
+ SOLARIS_NFS_LOCK = 'N'
28
+ READ_LOCK_PARTIAL = 'r'
29
+ READ_LOCK_FULL = 'R'
30
+ WRITE_LOCK_PARTIAL = 'w'
31
+ WRITE_LOCK_FULL = 'W'
32
+ READ_WRITE_LOCK = 'u'
33
+ UNKNOWN_LOCK_TYPE = 'U'
34
+ XENIX_LOCK_PARTIAL = 'x'
35
+ XENIX_LOCK_FULL = 'X'
36
+
37
+
38
+ @dc.dataclass(frozen=True)
39
+ class _LsofFieldMeta:
40
+ prefix: str
41
+ process: bool = False
42
+ variadic: bool = False
43
+
44
+ @classmethod
45
+ def make(cls, *args, **kwargs):
46
+ return {
47
+ cls: cls(*args, **kwargs),
48
+ OBJ_MARSHALER_OMIT_IF_NONE: True,
49
+ }
50
+
51
+
52
+ @dc.dataclass(frozen=True)
53
+ class LsofItem:
54
+ __repr__ = dataclass_repr_omit_falsey
55
+
56
+ class _PREFIX:
57
+ def __new__(cls, *args, **kwargs): # noqa
58
+ raise TypeError
59
+
60
+ class _PROCESS:
61
+ def __new__(cls, *args, **kwargs): # noqa
62
+ raise TypeError
63
+
64
+ pid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('p', process=True))
65
+ pgid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('g', process=True))
66
+ ppid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('R', process=True))
67
+ cmd: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('c', process=True))
68
+
69
+ uid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('u', process=True))
70
+ login_name: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('L', process=True))
71
+
72
+ # FD is the File Descriptor number of the file or:
73
+ # cwd current working directory;
74
+ # Lnn library references (AIX);
75
+ # ctty character tty;
76
+ # DEL deleted file;
77
+ # err FD information error (see NAME column);
78
+ # fp. Fileport (Darwin);
79
+ # jld jail directory (FreeBSD);
80
+ # ltx shared library text (code and data);
81
+ # Mxx hex memory-mapped type number xx.
82
+ # m86 DOS Merge mapped file;
83
+ # mem memory-mapped file;
84
+ # mmap memory-mapped device;
85
+ # NOFD for a Linux /proc/<PID>/fd directory that can't be opened -- the directory path appears in the NAME column,
86
+ # followed by an error message;
87
+ # pd parent directory;
88
+ # Rnn unknown pregion number (HP-UX);
89
+ # rtd root directory;
90
+ # twd per task current working directory;
91
+ # txt program text (code and data);
92
+ # v86 VP/ix mapped file;
93
+ #
94
+ # FD is followed by one of these characters, describing the mode under which the file is open:
95
+ # r for read access;
96
+ # w for write access;
97
+ # u for read and write access;
98
+ # space if mode unknown and no lock
99
+ # character follows;
100
+ # `-' if mode unknown and lock
101
+ # character follows.
102
+ #
103
+ # The mode character is followed by one of these lock characters, describing the type of lock applied to the file:
104
+ # N for a Solaris NFS lock of unknown type;
105
+ # r for read lock on part of the file;
106
+ # R for a read lock on the entire file;
107
+ # w for a write lock on part of the file;
108
+ # W for a write lock on the entire file;
109
+ # u for a read and write lock of any length;
110
+ # U for a lock of unknown type;
111
+ # x for an SCO OpenServer Xenix lock on part of the file;
112
+ # X for an SCO OpenServer Xenix lock on the entire file;
113
+ # space if there is no lock.
114
+ #
115
+ fd: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('f'))
116
+
117
+ inode: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('i'))
118
+ name: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('n'))
119
+
120
+ # r = read; w = write; u = read/write
121
+ access: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('a'))
122
+
123
+ # r/R = read; w/W = write; u = read/write
124
+ lock: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('l'))
125
+
126
+ file_flags: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('G'))
127
+ file_type: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('t'))
128
+ file_size: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('s'))
129
+ file_offset: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('o')) # as 0t<dec> or 0x<hex>
130
+
131
+ device_character_code: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('d'))
132
+ device_number: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('D')) # as 0x<hex>
133
+ raw_device_number: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('r')) # as 0x<hex>
134
+
135
+ share_count: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('C'))
136
+ link_count: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('k'))
137
+
138
+ stream_info: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('S'))
139
+ protocol_name: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('P'))
140
+
141
+ tcp_tpi_info: ta.Optional[ta.Sequence[str]] = dc.field(
142
+ default=None,
143
+ metadata=_LsofFieldMeta.make('T', variadic=True),
144
+ )
145
+
146
+ # 'K': 'task_id', # unknown field
147
+ # 'M': 'task_command_name', # unknown field
148
+
149
+ _FIELDS_BY_PREFIX: ta.ClassVar[ta.Mapping[str, dc.Field]]
150
+ _DEFAULT_PREFIXES: ta.ClassVar[str]
151
+
152
+ @classmethod
153
+ def from_prefixes(cls, dct: ta.Mapping[str, ta.Any]) -> 'LsofItem':
154
+ print(dct)
155
+ kw: ta.Dict[str, ta.Any] = {
156
+ fld.name: val
157
+ for pfx, val in dct.items()
158
+ if (fld := cls._FIELDS_BY_PREFIX.get(pfx)) is not None
159
+ }
160
+
161
+ return cls(**kw)
162
+
163
+ @classmethod
164
+ def from_prefix_lines(cls, lines: ta.Iterable[str]) -> ta.List['LsofItem']:
165
+ proc_dct: ta.Dict[str, ta.Any] = {}
166
+ file_dct: ta.Dict[str, ta.Any] = {}
167
+ out: ta.List[LsofItem] = []
168
+
169
+ def emit() -> None:
170
+ if file_dct:
171
+ out.append(cls.from_prefixes({**proc_dct, **file_dct}))
172
+
173
+ for line in lines:
174
+ pfx, val = line[0], line[1:]
175
+ try:
176
+ fld = cls._FIELDS_BY_PREFIX[pfx]
177
+ except KeyError:
178
+ continue
179
+ meta: _LsofFieldMeta = fld.metadata[_LsofFieldMeta]
180
+
181
+ if pfx == 'p':
182
+ emit()
183
+ proc_dct = {}
184
+ file_dct = {}
185
+ elif pfx == 'f':
186
+ emit()
187
+ file_dct = {}
188
+
189
+ if meta.process:
190
+ dct = proc_dct
191
+ else:
192
+ dct = file_dct
193
+
194
+ if meta.variadic:
195
+ dct.setdefault(pfx, []).append(val)
196
+ else:
197
+ if pfx in dct:
198
+ raise KeyError(pfx)
199
+ dct[pfx] = val
200
+
201
+ emit()
202
+
203
+ return out
204
+
205
+
206
+ LsofItem._FIELDS_BY_PREFIX = { # noqa
207
+ meta.prefix: fld
208
+ for fld in dc.fields(LsofItem) # noqa
209
+ if (meta := fld.metadata.get(_LsofFieldMeta)) is not None # noqa
210
+ }
211
+
212
+ LsofItem._DEFAULT_PREFIXES = ''.join(LsofItem._FIELDS_BY_PREFIX) # noqa
213
+
214
+
215
+ ##
216
+
217
+
218
+ @dc.dataclass(frozen=True)
219
+ class LsofCommand(SubprocessRunnable[ta.List[LsofItem]]):
220
+ pid: ta.Optional[int] = None
221
+ file: ta.Optional[str] = None
222
+
223
+ prefixes: ta.Optional[ta.Sequence[str]] = None
224
+
225
+ def make_run(self) -> SubprocessRun:
226
+ if (prefixes := self.prefixes) is None:
227
+ prefixes = LsofItem._DEFAULT_PREFIXES # noqa
228
+
229
+ return SubprocessRun.of(
230
+ 'lsof',
231
+ '-F', ''.join(prefixes),
232
+ *(['-p', str(self.pid)] if self.pid is not None else []),
233
+ *([self.file] if self.file is not None else []),
234
+
235
+ stdout='pipe',
236
+ stderr='devnull',
237
+ check=True,
238
+ )
239
+
240
+ def handle_run_output(self, output: SubprocessRunOutput) -> ta.List[LsofItem]:
241
+ lines = [s for l in check.not_none(output.stdout).decode().splitlines() if (s := l.strip())]
242
+ return LsofItem.from_prefix_lines(lines)
243
+
244
+
245
+ if __name__ == '__main__':
246
+ def _main() -> None:
247
+ argparse = __import__('argparse')
248
+ parser = argparse.ArgumentParser()
249
+ parser.add_argument('--pid', '-p', type=int)
250
+ parser.add_argument('file', nargs='?')
251
+ args = parser.parse_args()
252
+
253
+ importlib = __import__('importlib')
254
+ subprocesses = importlib.import_module('..subprocesses.sync', package=__package__).subprocesses
255
+ items = LsofCommand(
256
+ pid=args.pid,
257
+ file=args.file,
258
+ ).run(subprocesses)
259
+
260
+ json = __import__('json')
261
+ marshal_obj = importlib.import_module('..lite.marshal', package=__package__).marshal_obj
262
+ print(json.dumps(marshal_obj(items), indent=2))
263
+
264
+ _main()
omlish/diag/ps.py CHANGED
@@ -1,8 +1,15 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
1
3
  import dataclasses as dc
2
4
  import os
3
- import subprocess
5
+ import typing as ta
4
6
 
5
- from .. import lang
7
+ from ..lite.check import check
8
+ from ..lite.timeouts import Timeout
9
+ from ..subprocesses.run import SubprocessRun
10
+ from ..subprocesses.run import SubprocessRunnable
11
+ from ..subprocesses.run import SubprocessRunOutput
12
+ from ..subprocesses.sync import subprocesses
6
13
 
7
14
 
8
15
  @dc.dataclass(frozen=True)
@@ -12,23 +19,36 @@ class PsItem:
12
19
  cmd: str
13
20
 
14
21
 
15
- def get_ps_item(pid: int, timeout: lang.Timeout | None = None) -> PsItem:
16
- timeout = lang.timeout(timeout)
17
- out = subprocess.check_output(
18
- ['ps', '-o', 'pid=,ppid=,command=', str(int(pid))],
19
- timeout=timeout.or_(None),
20
- ).decode().strip()
21
- opid, _, rest = out.partition(' ')
22
- ppid, _, cmd = rest.strip().partition(' ')
23
- return PsItem(
24
- int(opid),
25
- int(ppid),
26
- cmd.strip(),
27
- )
22
+ @dc.dataclass(frozen=True)
23
+ class PsCommand(SubprocessRunnable):
24
+ pid: ta.Optional[int] = None
25
+
26
+ def make_run(self) -> SubprocessRun:
27
+ return SubprocessRun.of(
28
+ 'ps',
29
+ '-o', 'pid=,ppid=,command=',
30
+ *([str(int(self.pid))] if self.pid is not None else []),
31
+
32
+ check=True,
33
+ stdout='pipe',
34
+ stderr='devnull',
35
+ )
36
+
37
+ def handle_run_output(self, output: SubprocessRunOutput) -> PsItem:
38
+ opid, ppid, cmd = check.not_none(output.stdout).decode().split(maxsplit=2)
39
+ return PsItem(
40
+ int(opid),
41
+ int(ppid),
42
+ cmd.strip(),
43
+ )
44
+
28
45
 
46
+ def get_ps_item(pid: int, timeout: ta.Optional[Timeout] = None) -> PsItem:
47
+ return PsCommand(pid).run(subprocesses, timeout=timeout)
29
48
 
30
- def get_ps_lineage(pid: int, timeout: lang.Timeout | None = None) -> list[PsItem]:
31
- timeout = lang.timeout(timeout)
49
+
50
+ def get_ps_lineage(pid: int, timeout: ta.Optional[Timeout] = None) -> ta.List[PsItem]:
51
+ timeout = Timeout.of(timeout)
32
52
  ret: list[PsItem] = []
33
53
  while True:
34
54
  cur = get_ps_item(pid, timeout)
@@ -39,9 +59,8 @@ def get_ps_lineage(pid: int, timeout: lang.Timeout | None = None) -> list[PsItem
39
59
  return ret
40
60
 
41
61
 
42
- def _main() -> None:
43
- print(get_ps_lineage(os.getpid()))
44
-
45
-
46
62
  if __name__ == '__main__':
63
+ def _main() -> None:
64
+ print(get_ps_lineage(os.getpid()))
65
+
47
66
  _main()
omlish/lang/__init__.py CHANGED
@@ -226,14 +226,6 @@ from .sys import ( # noqa
226
226
  is_gil_enabled,
227
227
  )
228
228
 
229
- from .timeouts import ( # noqa
230
- DeadlineTimeout,
231
- InfiniteTimeout,
232
- Timeout,
233
- TimeoutLike,
234
- timeout,
235
- )
236
-
237
229
  from .typing import ( # noqa
238
230
  BytesLike,
239
231
  SequenceNotStr,
@@ -244,6 +236,13 @@ from .typing import ( # noqa
244
236
 
245
237
  ##
246
238
 
239
+ from ..lite.timeouts import ( # noqa
240
+ DeadlineTimeout,
241
+ InfiniteTimeout,
242
+ Timeout,
243
+ TimeoutLike,
244
+ )
245
+
247
246
  from ..lite.types import ( # noqa
248
247
  BUILTIN_SCALAR_ITERABLE_TYPES,
249
248
  )
@@ -42,3 +42,26 @@ def dataclass_maybe_post_init(sup: ta.Any) -> bool:
42
42
  return False
43
43
  fn()
44
44
  return True
45
+
46
+
47
+ def dataclass_repr_filtered(
48
+ obj: ta.Any,
49
+ fn: ta.Callable[[ta.Any, dc.Field, ta.Any], bool],
50
+ ) -> str:
51
+ return (
52
+ f'{obj.__class__.__qualname__}(' +
53
+ ', '.join([
54
+ f'{f.name}={v!r}'
55
+ for f in dc.fields(obj)
56
+ if fn(obj, f, v := getattr(obj, f.name))
57
+ ]) +
58
+ ')'
59
+ )
60
+
61
+
62
+ def dataclass_repr_omit_none(obj: ta.Any) -> str:
63
+ return dataclass_repr_filtered(obj, lambda o, f, v: v is not None)
64
+
65
+
66
+ def dataclass_repr_omit_falsey(obj: ta.Any) -> str:
67
+ return dataclass_repr_filtered(obj, lambda o, f, v: bool(v))
@@ -0,0 +1,202 @@
1
+ # ruff: noqa: UP006 UP007
2
+ """
3
+ TODO:
4
+ - Event (/ Predicate)
5
+ """
6
+ import abc
7
+ import time
8
+ import typing as ta
9
+
10
+
11
+ TimeoutLike = ta.Union['Timeout', 'Timeout.Default', ta.Iterable['TimeoutLike'], float] # ta.TypeAlias
12
+
13
+
14
+ ##
15
+
16
+
17
+ class Timeout(abc.ABC):
18
+ @property
19
+ @abc.abstractmethod
20
+ def can_expire(self) -> bool:
21
+ """Indicates whether or not this timeout will ever expire."""
22
+
23
+ raise NotImplementedError
24
+
25
+ @abc.abstractmethod
26
+ def expired(self) -> bool:
27
+ """Return whether or not this timeout has expired."""
28
+
29
+ raise NotImplementedError
30
+
31
+ @abc.abstractmethod
32
+ def remaining(self) -> float:
33
+ """Returns the time (in seconds) remaining until the timeout expires. May be negative and/or infinite."""
34
+
35
+ raise NotImplementedError
36
+
37
+ @abc.abstractmethod
38
+ def __call__(self) -> float:
39
+ """Returns the time (in seconds) remaining until the timeout expires, or raises if the timeout has expired."""
40
+
41
+ raise NotImplementedError
42
+
43
+ @abc.abstractmethod
44
+ def or_(self, o: ta.Any) -> ta.Any:
45
+ """Evaluates time remaining via remaining() if this timeout can expire, otherwise returns `o`."""
46
+
47
+ raise NotImplementedError
48
+
49
+ #
50
+
51
+ @classmethod
52
+ def _now(cls) -> float:
53
+ return time.time()
54
+
55
+ #
56
+
57
+ class Default:
58
+ def __new__(cls, *args, **kwargs): # noqa
59
+ raise TypeError
60
+
61
+ class _NOT_SPECIFIED: # noqa
62
+ def __new__(cls, *args, **kwargs): # noqa
63
+ raise TypeError
64
+
65
+ @classmethod
66
+ def of(
67
+ cls,
68
+ obj: ta.Optional[TimeoutLike],
69
+ default: ta.Union[TimeoutLike, ta.Type[_NOT_SPECIFIED]] = _NOT_SPECIFIED,
70
+ ) -> 'Timeout':
71
+ if obj is None:
72
+ return InfiniteTimeout()
73
+
74
+ elif isinstance(obj, Timeout):
75
+ return obj
76
+
77
+ elif isinstance(obj, (float, int)):
78
+ return DeadlineTimeout(cls._now() + obj)
79
+
80
+ elif isinstance(obj, ta.Iterable):
81
+ return CompositeTimeout(*[Timeout.of(c) for c in obj])
82
+
83
+ elif obj is Timeout.Default:
84
+ if default is Timeout._NOT_SPECIFIED or default is Timeout.Default:
85
+ raise RuntimeError('Must specify a default timeout')
86
+
87
+ else:
88
+ return Timeout.of(default) # type: ignore[arg-type]
89
+
90
+ else:
91
+ raise TypeError(obj)
92
+
93
+ @classmethod
94
+ def of_deadline(cls, deadline: float) -> 'DeadlineTimeout':
95
+ return DeadlineTimeout(deadline)
96
+
97
+ @classmethod
98
+ def of_predicate(cls, expired_fn: ta.Callable[[], bool]) -> 'PredicateTimeout':
99
+ return PredicateTimeout(expired_fn)
100
+
101
+
102
+ class DeadlineTimeout(Timeout):
103
+ def __init__(
104
+ self,
105
+ deadline: float,
106
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
107
+ ) -> None:
108
+ super().__init__()
109
+
110
+ self.deadline = deadline
111
+ self.exc = exc
112
+
113
+ @property
114
+ def can_expire(self) -> bool:
115
+ return True
116
+
117
+ def expired(self) -> bool:
118
+ return not (self.remaining() > 0)
119
+
120
+ def remaining(self) -> float:
121
+ return self.deadline - self._now()
122
+
123
+ def __call__(self) -> float:
124
+ if (rem := self.remaining()) > 0:
125
+ return rem
126
+ raise self.exc
127
+
128
+ def or_(self, o: ta.Any) -> ta.Any:
129
+ return self()
130
+
131
+
132
+ class InfiniteTimeout(Timeout):
133
+ @property
134
+ def can_expire(self) -> bool:
135
+ return False
136
+
137
+ def expired(self) -> bool:
138
+ return False
139
+
140
+ def remaining(self) -> float:
141
+ return float('inf')
142
+
143
+ def __call__(self) -> float:
144
+ return float('inf')
145
+
146
+ def or_(self, o: ta.Any) -> ta.Any:
147
+ return o
148
+
149
+
150
+ class CompositeTimeout(Timeout):
151
+ def __init__(self, *children: Timeout) -> None:
152
+ super().__init__()
153
+
154
+ self.children = children
155
+
156
+ @property
157
+ def can_expire(self) -> bool:
158
+ return any(c.can_expire for c in self.children)
159
+
160
+ def expired(self) -> bool:
161
+ return any(c.expired() for c in self.children)
162
+
163
+ def remaining(self) -> float:
164
+ return min(c.remaining() for c in self.children)
165
+
166
+ def __call__(self) -> float:
167
+ return min(c() for c in self.children)
168
+
169
+ def or_(self, o: ta.Any) -> ta.Any:
170
+ if self.can_expire:
171
+ return self()
172
+ return o
173
+
174
+
175
+ class PredicateTimeout(Timeout):
176
+ def __init__(
177
+ self,
178
+ expired_fn: ta.Callable[[], bool],
179
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
180
+ ) -> None:
181
+ super().__init__()
182
+
183
+ self.expired_fn = expired_fn
184
+ self.exc = exc
185
+
186
+ @property
187
+ def can_expire(self) -> bool:
188
+ return True
189
+
190
+ def expired(self) -> bool:
191
+ return self.expired_fn()
192
+
193
+ def remaining(self) -> float:
194
+ return float('inf')
195
+
196
+ def __call__(self) -> float:
197
+ if not self.expired_fn():
198
+ return float('inf')
199
+ raise self.exc
200
+
201
+ def or_(self, o: ta.Any) -> ta.Any:
202
+ return self()
omlish/os/fcntl.py ADDED
@@ -0,0 +1,59 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import dataclasses as dc
4
+ import os
5
+ import struct
6
+ import sys
7
+ import typing as ta
8
+
9
+
10
+ @dc.dataclass(frozen=True)
11
+ class FcntlLockData:
12
+ # cmd = {F_SETLK, F_SETLKW, F_GETLK}
13
+
14
+ type: int # {F_RDLCK, F_WRLCK, F_UNLCK}
15
+ whence: int = os.SEEK_SET
16
+ start: int = 0
17
+ len: int = 0
18
+ pid: int = 0
19
+
20
+ #
21
+
22
+ _STRUCT_PACK_BY_PLATFORM: ta.ClassVar[ta.Mapping[str, ta.Sequence[ta.Tuple[str, str]]]] = {
23
+ 'linux': [
24
+ ('type', 'h'),
25
+ ('whence', 'h'),
26
+ ('start', 'q'),
27
+ ('len', 'q'),
28
+ ('pid', 'i'),
29
+ ],
30
+ 'darwin': [
31
+ ('start', 'q'),
32
+ ('len', 'q'),
33
+ ('pid', 'i'),
34
+ ('type', 'h'),
35
+ ('whence', 'h'),
36
+ ],
37
+ }
38
+
39
+ def pack(self) -> bytes:
40
+ try:
41
+ pack = self._STRUCT_PACK_BY_PLATFORM[sys.platform]
42
+ except KeyError:
43
+ raise OSError from None
44
+
45
+ fmt = ''.join(f for _, f in pack)
46
+ tup = [getattr(self, a) for a, _ in pack]
47
+ return struct.pack(fmt, *tup)
48
+
49
+ @classmethod
50
+ def unpack(cls, data: bytes) -> 'FcntlLockData':
51
+ try:
52
+ pack = cls._STRUCT_PACK_BY_PLATFORM[sys.platform]
53
+ except KeyError:
54
+ raise OSError from None
55
+
56
+ fmt = ''.join(f for _, f in pack)
57
+ tup = struct.unpack(fmt, data)
58
+ kw = {a: v for (a, _), v in zip(pack, tup)}
59
+ return FcntlLockData(**kw)
@@ -14,6 +14,14 @@ from .pidfile import Pidfile
14
14
 
15
15
 
16
16
  class _PidfileManager:
17
+ """
18
+ Manager for controlled inheritance of Pidfiles across forks.
19
+
20
+ Not implemented as an instantiated class as there is no way to unregister at_fork listeners, and because Pidfiles
21
+ may be pickled and there must be no possibility of accidentally unpickling and instantiating a new instance of the
22
+ manager.
23
+ """
24
+
17
25
  def __new__(cls, *args, **kwargs): # noqa
18
26
  raise TypeError
19
27
 
@@ -71,6 +79,11 @@ class _PidfileManager:
71
79
  inheritable: bool = True,
72
80
  **kwargs: ta.Any,
73
81
  ) -> ta.Iterator[Pidfile]:
82
+ """
83
+ A contextmanager for creating and managing a Pidfile which will only be inherited by forks of the calling /
84
+ creating thread.
85
+ """
86
+
74
87
  check.arg(inheritable)
75
88
 
76
89
  cls.install()
@@ -84,6 +97,7 @@ class _PidfileManager:
84
97
  with cls._lock:
85
98
  cls._pidfile_threads[pf] = threading.current_thread()
86
99
  try:
100
+
87
101
  with pf:
88
102
  os.set_inheritable(check.not_none(pf.fileno()), True)
89
103
  yield pf
@@ -10,6 +10,7 @@ TODO:
10
10
  - 3) recheck current pid of flock holder == that pid
11
11
  - racy as to if it's a different actual process as initial check, just with same pid, but due to 'identity' / semantic
12
12
  meaning of the named pidfile the processes are considered equivalent
13
+ - read_checked(), contextmanager
13
14
  """
14
15
  import fcntl
15
16
  import os
@@ -32,6 +33,14 @@ class Pidfile:
32
33
  self._path = path
33
34
  self._inheritable = inheritable
34
35
 
36
+ @property
37
+ def path(self) -> str:
38
+ return self._path
39
+
40
+ @property
41
+ def inheritable(self) -> bool:
42
+ return self._inheritable
43
+
35
44
  def __repr__(self) -> str:
36
45
  return f'{self.__class__.__name__}({self._path!r})'
37
46
 
@@ -75,6 +84,7 @@ class Pidfile:
75
84
  state = self.__dict__.copy()
76
85
 
77
86
  if '_f' in state:
87
+ # self._inheritable may be decoupled from actual file inheritability - for example when using the manager.
78
88
  if os.get_inheritable(fd := state.pop('_f').fileno()):
79
89
  state['__fd'] = fd
80
90
 
@@ -135,8 +145,8 @@ class Pidfile:
135
145
  raise RuntimeError('Got lock')
136
146
 
137
147
  self._f.seek(0)
138
- return int(self._f.read()) # FIXME: could be empty or hold old value, race w proc start
148
+ return int(self._f.read())
139
149
 
140
150
  def kill(self, sig: int = signal.SIGTERM) -> None:
141
151
  pid = self.read()
142
- os.kill(pid, sig) # FIXME: Still racy - pidfd_send_signal?
152
+ os.kill(pid, sig)
@@ -4,6 +4,7 @@ import abc
4
4
  import sys
5
5
  import typing as ta
6
6
 
7
+ from ..lite.timeouts import TimeoutLike
7
8
  from .base import BaseSubprocesses
8
9
  from .run import SubprocessRun
9
10
  from .run import SubprocessRunOutput
@@ -21,7 +22,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
21
22
  self,
22
23
  *cmd: str,
23
24
  input: ta.Any = None, # noqa
24
- timeout: ta.Optional[float] = None,
25
+ timeout: ta.Optional[TimeoutLike] = None,
25
26
  check: bool = False,
26
27
  capture_output: ta.Optional[bool] = None,
27
28
  **kwargs: ta.Any,
@@ -8,6 +8,7 @@ import subprocess
8
8
  import time
9
9
  import typing as ta
10
10
 
11
+ from ..lite.timeouts import Timeout
11
12
  from .wrap import subprocess_maybe_shell_wrap_exec
12
13
 
13
14
 
@@ -111,6 +112,11 @@ class BaseSubprocesses(abc.ABC): # noqa
111
112
 
112
113
  #
113
114
 
115
+ if 'timeout' in kwargs:
116
+ kwargs['timeout'] = Timeout.of(kwargs['timeout']).or_(None)
117
+
118
+ #
119
+
114
120
  return cmd, dict(
115
121
  env=env,
116
122
  shell=shell,
@@ -5,6 +5,7 @@ import dataclasses as dc
5
5
  import typing as ta
6
6
 
7
7
  from ..lite.check import check
8
+ from ..lite.timeouts import TimeoutLike
8
9
 
9
10
 
10
11
  T = ta.TypeVar('T')
@@ -30,17 +31,43 @@ class SubprocessRunOutput(ta.Generic[T]):
30
31
  class SubprocessRun:
31
32
  cmd: ta.Sequence[str]
32
33
  input: ta.Any = None
33
- timeout: ta.Optional[float] = None
34
+ timeout: ta.Optional[TimeoutLike] = None
34
35
  check: bool = False
35
36
  capture_output: ta.Optional[bool] = None
36
37
  kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
37
38
 
39
+ #
40
+
41
+ _FIELD_NAMES: ta.ClassVar[ta.FrozenSet[str]]
42
+
43
+ def replace(self, **kwargs: ta.Any) -> 'SubprocessRun':
44
+ if not kwargs:
45
+ return self
46
+
47
+ field_kws = {}
48
+ extra_kws = {}
49
+ for k, v in kwargs.items():
50
+ if k in self._FIELD_NAMES:
51
+ field_kws[k] = v
52
+ else:
53
+ extra_kws[k] = v
54
+
55
+ return dc.replace(self, **{
56
+ **dict(kwargs={
57
+ **(self.kwargs or {}),
58
+ **extra_kws,
59
+ }),
60
+ **field_kws, # passing a kwarg named 'kwargs' intentionally clobbers
61
+ })
62
+
63
+ #
64
+
38
65
  @classmethod
39
66
  def of(
40
67
  cls,
41
68
  *cmd: str,
42
69
  input: ta.Any = None, # noqa
43
- timeout: ta.Optional[float] = None,
70
+ timeout: ta.Optional[TimeoutLike] = None,
44
71
  check: bool = False, # noqa
45
72
  capture_output: ta.Optional[bool] = None,
46
73
  **kwargs: ta.Any,
@@ -61,20 +88,25 @@ class SubprocessRun:
61
88
  def run(
62
89
  self,
63
90
  subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
91
+ **kwargs: ta.Any,
64
92
  ) -> SubprocessRunOutput:
65
93
  if subprocesses is None:
66
94
  subprocesses = self._DEFAULT_SUBPROCESSES
67
- return check.not_none(subprocesses).run_(self) # type: ignore[attr-defined]
95
+ return check.not_none(subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
68
96
 
69
97
  _DEFAULT_ASYNC_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractAsyncSubprocesses
70
98
 
71
99
  async def async_run(
72
100
  self,
73
101
  async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
102
+ **kwargs: ta.Any,
74
103
  ) -> SubprocessRunOutput:
75
104
  if async_subprocesses is None:
76
105
  async_subprocesses = self._DEFAULT_ASYNC_SUBPROCESSES
77
- return await check.not_none(async_subprocesses).run_(self) # type: ignore[attr-defined]
106
+ return await check.not_none(async_subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
107
+
108
+
109
+ SubprocessRun._FIELD_NAMES = frozenset(fld.name for fld in dc.fields(SubprocessRun)) # noqa
78
110
 
79
111
 
80
112
  ##
@@ -91,8 +123,16 @@ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
91
123
 
92
124
  #
93
125
 
94
- def run(self, subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractSubprocesses
95
- return self.handle_run_output(self.make_run().run(subprocesses))
126
+ def run(
127
+ self,
128
+ subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
129
+ **kwargs: ta.Any,
130
+ ) -> T:
131
+ return self.handle_run_output(self.make_run().run(subprocesses, **kwargs))
96
132
 
97
- async def async_run(self, async_subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractAsyncSubprocesses
98
- return self.handle_run_output(await self.make_run().async_run(async_subprocesses))
133
+ async def async_run(
134
+ self,
135
+ async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
136
+ **kwargs: ta.Any,
137
+ ) -> T:
138
+ return self.handle_run_output(await self.make_run().async_run(async_subprocesses, **kwargs))
@@ -1,10 +1,16 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  # @omlish-lite
3
+ """
4
+ TODO:
5
+ - popen
6
+ - route check_calls through run_?
7
+ """
3
8
  import abc
4
9
  import subprocess
5
10
  import sys
6
11
  import typing as ta
7
12
 
13
+ from ..lite.timeouts import TimeoutLike
8
14
  from .base import BaseSubprocesses
9
15
  from .run import SubprocessRun
10
16
  from .run import SubprocessRunOutput
@@ -22,7 +28,7 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
22
28
  self,
23
29
  *cmd: str,
24
30
  input: ta.Any = None, # noqa
25
- timeout: ta.Optional[float] = None,
31
+ timeout: ta.Optional[TimeoutLike] = None,
26
32
  check: bool = False,
27
33
  capture_output: ta.Optional[bool] = None,
28
34
  **kwargs: ta.Any,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: omlish
3
- Version: 0.0.0.dev225
3
+ Version: 0.0.0.dev226
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=gehJaunJejmNajD_3gJSN6_vRuZdXjgTLUMaCAxAmJA,3380
2
+ omlish/__about__.py,sha256=QR1QmjxqAJ538XauJdEuHH505rJ9QF67p3blNgi6kwc,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
@@ -101,8 +101,8 @@ omlish/asyncs/asyncio/all.py,sha256=KpTanEtpTlc3rqv5SyiJaJGb_DXBzp_WKLhlRq6lrhY,
101
101
  omlish/asyncs/asyncio/asyncio.py,sha256=3BMhVIF-QTjsFRGDtNYlRbBqKPCA3_AwJsjJoIWdM8k,1783
102
102
  omlish/asyncs/asyncio/channels.py,sha256=ZbmsEmdK1fV96liHdcVpRqA2dAMkXJt4Q3rFAg3YOIw,1074
103
103
  omlish/asyncs/asyncio/streams.py,sha256=Uc9PCWSfBqrK2kdVtfjjQU1eaYTWYmZm8QISDj2xiuw,1004
104
- omlish/asyncs/asyncio/subprocesses.py,sha256=0NXHmCXeONwOiJlPfWop6WPfSiQH_keiyHT_pnfZm7U,6872
105
- omlish/asyncs/asyncio/timeouts.py,sha256=Rj5OU9BIAPcVZZKp74z7SzUXF5xokh4dgsWkUqOy1aE,355
104
+ omlish/asyncs/asyncio/subprocesses.py,sha256=f30-wi-3n9R5dftm4CMrzp23EEa4GX283bORixm1_UU,6931
105
+ omlish/asyncs/asyncio/timeouts.py,sha256=hokhi7jZSAtBv0ME3qL1cO5eshNA9ViEH7BLafNCBpQ,454
106
106
  omlish/asyncs/bluelet/LICENSE,sha256=VHf3oPQihOHnWyIR8LcXX0dpONa1lgyJnjWC2qVuRR0,559
107
107
  omlish/asyncs/bluelet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
108
  omlish/asyncs/bluelet/all.py,sha256=aUV6PwnR8DqnEBS9wsZuPW_UtP6G9M8_KY-mmxZeVG0,1516
@@ -203,9 +203,11 @@ omlish/dataclasses/impl/utils.py,sha256=aER2iL3UAtgS1BdLuEvTr9Tr2wC28wk1kiOeO-jI
203
203
  omlish/diag/__init__.py,sha256=4S8v0myJM4Zld6_FV6cPe_nSv0aJb6kXftEit0HkiGE,1141
204
204
  omlish/diag/asts.py,sha256=BveUUNUcaAm4Hg55f4ZxGSI313E4L8cCZ5XjHpEkKVI,3325
205
205
  omlish/diag/debug.py,sha256=ClED7kKXeVMyKrjGIxcq14kXk9kvUJfytBQwK9y7c4Q,1637
206
+ omlish/diag/lslocks.py,sha256=7I_u-Vx0Dz0KN59Y6dk9nBZynHa6usvKIXVSywGCiWE,1722
207
+ omlish/diag/lsof.py,sha256=gMtNmfBo1AlYmvZWBB51u4NfbfC6yJyEVwfr1HgktP8,9328
206
208
  omlish/diag/procfs.py,sha256=KaGTAA2Gj8eEEp7MjClRe4aimwzd-HDABThFzvq2cBQ,9684
207
209
  omlish/diag/procstats.py,sha256=UkqxREqfd-38xPYZ9T1SIJISz5ARQCEhTtOZrxtm2dE,777
208
- omlish/diag/ps.py,sha256=1JWxZen3fVG-20R6ZZ8BtO_gpzw_5bhHZiKdoHkgxoU,1004
210
+ omlish/diag/ps.py,sha256=mwraPO6Nze_2r9RiC4dyLL0aJ_lHR1_fSN9BlYNBoHw,1651
209
211
  omlish/diag/pycharm.py,sha256=7-r_F-whXt8v-0dehxAX-MeMFPM3iZXX9IfeL0GfUtk,4643
210
212
  omlish/diag/pydevd.py,sha256=UN55ZjkWLCVyHxE2CNRRYamuvSKfzWsn0D5oczRTXO4,7536
211
213
  omlish/diag/threads.py,sha256=1-x02VCDZ407gfbtXm1pWK-ubqhqfePm9PMqkHCVoqk,3642
@@ -382,7 +384,7 @@ omlish/iterators/iterators.py,sha256=ghI4dO6WPyyFOLTIIMaHQ_IOy2xXaFpGPqveZ5YGIBU
382
384
  omlish/iterators/recipes.py,sha256=53mkexitMhkwXQZbL6DrhpT0WePQ_56uXd5Jaw3DfzI,467
383
385
  omlish/iterators/tools.py,sha256=Pi4ybXytUXVZ3xwK89xpPImQfYYId9p1vIFQvVqVLqA,2551
384
386
  omlish/iterators/unique.py,sha256=0jAX3kwzVfRNhe0Tmh7kVP_Q2WBIn8POo_O-rgFV0rQ,1390
385
- omlish/lang/__init__.py,sha256=9H6fElLMlA5MwT3tIW2CpojG5chn-hAvKW9tuA8REDQ,4134
387
+ omlish/lang/__init__.py,sha256=_DddR5PNgjqr51AB5DPUq0iKVqJrKz55WMJnBm97LL0,4127
386
388
  omlish/lang/cached.py,sha256=tQaqMu1LID0q4NSTk5vPXsgxIBWSFAmjs5AhQoEHoCQ,7833
387
389
  omlish/lang/clsdct.py,sha256=sJYadm-fwzti-gsi98knR5qQUxriBmOqQE_qz3RopNk,1743
388
390
  omlish/lang/cmp.py,sha256=5vbzWWbqdzDmNKAGL19z6ZfUKe5Ci49e-Oegf9f4BsE,1346
@@ -400,7 +402,6 @@ omlish/lang/resolving.py,sha256=OuN2mDTPNyBUbcrswtvFKtj4xgH4H4WglgqSKv3MTy0,1606
400
402
  omlish/lang/resources.py,sha256=N64KeVE-rYMxqBBRp91qzgVqpOVR2uX7k1WlS_bo5hM,2681
401
403
  omlish/lang/strings.py,sha256=egdv8PxLNG40-5V93agP5j2rBUDIsahCx048zV7uEbU,4690
402
404
  omlish/lang/sys.py,sha256=UoZz_PJYVKLQAKqYxxn-LHz1okK_38I__maZgnXMcxU,406
403
- omlish/lang/timeouts.py,sha256=vECdWYhc_IZgcal1Ng1Y42wf2FV3KAx-i8As-MgGHIQ,1186
404
405
  omlish/lang/typing.py,sha256=Zdad9Zv0sa-hIaUXPrzPidT7sDVpRcussAI7D-j-I1c,3296
405
406
  omlish/lang/classes/__init__.py,sha256=HY9UD3Tg8_sH59emZUKgP1Kb8-8r0GCw5hUOumxwKlM,658
406
407
  omlish/lang/classes/abstract.py,sha256=bIcuAetV_aChhpSURypmjjcqP07xi20uVYPKh1kvQNU,3710
@@ -420,7 +421,7 @@ omlish/lite/cached.py,sha256=O7ozcoDNFm1Hg2wtpHEqYSp_i_nCLNOP6Ueq_Uk-7mU,1300
420
421
  omlish/lite/check.py,sha256=OLwtE2x6nlbGx4vS3Rda7zMHpgqzDSLJminTAX2lqLA,13529
421
422
  omlish/lite/configs.py,sha256=Ev_19sbII67pTWzInYjYqa9VyTiZBvyjhZqyG8TtufE,908
422
423
  omlish/lite/contextmanagers.py,sha256=ciaMl0D3QDHToM7M28-kwZ-Q48LtwgCxiud3nekgutA,2863
423
- omlish/lite/dataclasses.py,sha256=k1588egoF3h_Kb21tuQb8WyJ9wxhT06EztFvaKOHzNU,1115
424
+ omlish/lite/dataclasses.py,sha256=sjJnZ4tZXMzKRkr-gymMbhuMxJAY8parJ_sopEqBc6M,1704
424
425
  omlish/lite/inject.py,sha256=qBUftFeXMiRgANYbNS2e7TePMYyFAcuLgsJiLyMTW5o,28769
425
426
  omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
426
427
  omlish/lite/logs.py,sha256=CWFG0NKGhqNeEgryF5atN2gkPYbUdTINEw_s1phbINM,51
@@ -432,6 +433,7 @@ omlish/lite/resources.py,sha256=YNSmX1Ohck1aoWRs55a-o5ChVbFJIQhtbqE-XwF55Oc,326
432
433
  omlish/lite/runtime.py,sha256=XQo408zxTdJdppUZqOWHyeUR50VlCpNIExNGHz4U6O4,459
433
434
  omlish/lite/secrets.py,sha256=3Mz3V2jf__XU9qNHcH56sBSw95L3U2UPL24bjvobG0c,816
434
435
  omlish/lite/strings.py,sha256=QGxT1Yh4oI8ycsfeobxnjEhvDob_GiAKLeIhZwo1j24,1986
436
+ omlish/lite/timeouts.py,sha256=Bd_XaEmaGTsKcORfPt-AJECmf8Bt9XwjBfjkMacpEdM,4954
435
437
  omlish/lite/timing.py,sha256=aVu3hEDB_jyTF_ryZI7iU-xg4q8CNwqpp9Apfru_iwY,196
436
438
  omlish/lite/types.py,sha256=fP5EMyBdEp2LmDxcHjUDtwAMdR06ISr9lKOL7smWfHM,140
437
439
  omlish/lite/typing.py,sha256=U3-JaEnkDSYxK4tsu_MzUn3RP6qALBe5FXQXpD-licE,1090
@@ -495,6 +497,7 @@ omlish/os/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
495
497
  omlish/os/atomics.py,sha256=KhWNeh4mzU3M-TF0v8uR6hUqMfZJW42MeyIK9Jl6R0k,5246
496
498
  omlish/os/death.py,sha256=_JV88yxwYIalamIMuqmGTQ4KYd0t-UHLjVkdoliPWL8,2943
497
499
  omlish/os/deathsig.py,sha256=hk9Yq2kyDdI-cI7OQH7mOfpRbOKzY_TfPKEqgrjVYbA,641
500
+ omlish/os/fcntl.py,sha256=hSlCqzMu5iqEsxlp9me6KtoG9FK7g3sdjZIpDba-kLQ,1458
498
501
  omlish/os/files.py,sha256=O1Um1iCLeIzDSKQq2zByVC_4NZ3tLMqv6vICyU21U3Q,861
499
502
  omlish/os/journald.py,sha256=2nI8Res1poXkbLc31--MPUlzYMESnCcPUkIxDOCjZW0,3903
500
503
  omlish/os/linux.py,sha256=whJ6scwMKSFBdXiVhJW0BCpJV4jOGMr-a_a3Bhwz6Ls,18938
@@ -502,8 +505,8 @@ omlish/os/paths.py,sha256=hqPiyg_eYaRoIVPdAeX4oeLEV4Kpln_XsH0tHvbOf8Q,844
502
505
  omlish/os/sizes.py,sha256=ohkALLvqSqBX4iR-7DMKJ4pfOCRdZXV8htH4QywUNM0,152
503
506
  omlish/os/temp.py,sha256=P97KiVeNB7rfGn4tlgU5ro86JUxAsiphLMlxsjQgfB0,1198
504
507
  omlish/os/pidfiles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
505
- omlish/os/pidfiles/manager.py,sha256=u8N7z6fSMHeE4aEHhkYShIymos1B6k1hxMd9sP6IuKE,2330
506
- omlish/os/pidfiles/pidfile.py,sha256=NHpt4721J_VsCKaInG6u4sjFkHiZaor7lDneTTykr8w,3177
508
+ omlish/os/pidfiles/manager.py,sha256=ssnxCvSoL3OapzcMmxSgiy9o1NogR6PTX4LLTJXuPkM,2830
509
+ omlish/os/pidfiles/pidfile.py,sha256=833aPCV7cQqFqYnxwma8np0eoy0CJzVHRskn-YSQwis,3378
507
510
  omlish/reflect/__init__.py,sha256=JBWwxKwP4IEaomkK0PTju02STU1BVXT14SCrShT1Sm0,769
508
511
  omlish/reflect/inspect.py,sha256=veJ424-9oZrqyvhVpvxOi7hcKW-PDBkdYL2yjrFlk4o,495
509
512
  omlish/reflect/ops.py,sha256=RJ6jzrM4ieFsXzWyNXWV43O_WgzEaUvlHSc5N2ezW2A,2044
@@ -627,10 +630,10 @@ omlish/sql/tabledefs/lower.py,sha256=YQf8gl1kxD5Fm-vOxV6G0Feh_D9PP1pYwz_vz6XjTPQ
627
630
  omlish/sql/tabledefs/marshal.py,sha256=j-Rz1HsiXmABv39-2VoJdzSSB3kbxqaVevbdkZWMyG8,504
628
631
  omlish/sql/tabledefs/tabledefs.py,sha256=lIhvlt0pk6G7RZAtDFsFXm5j0l9BvRfnP7vNGeydHtE,816
629
632
  omlish/subprocesses/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
630
- omlish/subprocesses/async_.py,sha256=Ic9luphqOGdOsBCnH76msy5SCrudMYqvUD1bw-l8ABY,2327
631
- omlish/subprocesses/base.py,sha256=tWh5ivsPCpz6e3qayhfnapxuiFWO3nNvgnbYbzSXxAs,5981
632
- omlish/subprocesses/run.py,sha256=iRYiGqoLpB6aQ7a3t-GiJ_6XFLljr6PrZZER7XvibXM,2663
633
- omlish/subprocesses/sync.py,sha256=wAJeA4awo53AlrF6kzRM4usj9gMy7bOYIHnMcr93ETg,3567
633
+ omlish/subprocesses/async_.py,sha256=hPQTWFa3k5CE_s9p1JTY4KdTPOsqLJtq3lGMRznrVpY,2373
634
+ omlish/subprocesses/base.py,sha256=W6El-PUKKF9KLAks5LB6kzqs_n3FfkblJ-JOv6NFQbY,6133
635
+ omlish/subprocesses/run.py,sha256=3jwSnQJvFMDMHmJvtAkrrK5D-i7_8cw12vX84EWTuJo,3668
636
+ omlish/subprocesses/sync.py,sha256=HKmKM99_Y7tkJRg_n5onXrw41IZt5M5fqU0281LY-mo,3671
634
637
  omlish/subprocesses/utils.py,sha256=MJb6hvKhZceTmBeFVqlc5oM7rDxWkUzSzK9nKvbIvM8,396
635
638
  omlish/subprocesses/wrap.py,sha256=v7vrbDhl_FVWESnXIOJ3oGtE3Y-OHQa5sH3MTtFTBUE,504
636
639
  omlish/term/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -676,9 +679,9 @@ omlish/text/indent.py,sha256=YjtJEBYWuk8--b9JU_T6q4yxV85_TR7VEVr5ViRCFwk,1336
676
679
  omlish/text/minja.py,sha256=jZC-fp3Xuhx48ppqsf2Sf1pHbC0t8XBB7UpUUoOk2Qw,5751
677
680
  omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
678
681
  omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
679
- omlish-0.0.0.dev225.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
680
- omlish-0.0.0.dev225.dist-info/METADATA,sha256=8NMIcuuTxgyCnPF4F5NHPdeRYbajQPP030dNBorRANg,4176
681
- omlish-0.0.0.dev225.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
682
- omlish-0.0.0.dev225.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
683
- omlish-0.0.0.dev225.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
684
- omlish-0.0.0.dev225.dist-info/RECORD,,
682
+ omlish-0.0.0.dev226.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
683
+ omlish-0.0.0.dev226.dist-info/METADATA,sha256=0HcF368bfjlzkGzprdbRX4GJagL5c9PDPRUqiBYWIlY,4176
684
+ omlish-0.0.0.dev226.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
685
+ omlish-0.0.0.dev226.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
686
+ omlish-0.0.0.dev226.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
687
+ omlish-0.0.0.dev226.dist-info/RECORD,,
omlish/lang/timeouts.py DELETED
@@ -1,53 +0,0 @@
1
- import abc
2
- import time
3
- import typing as ta
4
-
5
-
6
- TimeoutLike: ta.TypeAlias = ta.Union['Timeout', float]
7
-
8
-
9
- class Timeout(abc.ABC):
10
- @abc.abstractmethod
11
- def __call__(self) -> float:
12
- raise NotImplementedError
13
-
14
- @abc.abstractmethod
15
- def or_(self, o: ta.Any) -> ta.Any:
16
- raise NotImplementedError
17
-
18
-
19
- class DeadlineTimeout(Timeout):
20
- def __init__(
21
- self,
22
- deadline: float,
23
- exc: type[BaseException] | BaseException = TimeoutError,
24
- ) -> None:
25
- super().__init__()
26
- self.deadline = deadline
27
- self.exc = exc
28
-
29
- def __call__(self) -> float:
30
- if (rem := self.deadline - time.time()) > 0:
31
- return rem
32
- raise self.exc
33
-
34
- def or_(self, o: ta.Any) -> ta.Any:
35
- return self()
36
-
37
-
38
- class InfiniteTimeout(Timeout):
39
- def __call__(self) -> float:
40
- return float('inf')
41
-
42
- def or_(self, o: ta.Any) -> ta.Any:
43
- return o
44
-
45
-
46
- def timeout(t: TimeoutLike | None) -> Timeout:
47
- if t is None:
48
- return InfiniteTimeout()
49
- if isinstance(t, Timeout):
50
- return t
51
- if isinstance(t, (float, int)):
52
- return DeadlineTimeout(time.time() + t)
53
- raise TypeError(t)