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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/collections/__init__.py +15 -15
  3. omlish/collections/frozen.py +0 -2
  4. omlish/collections/identity.py +0 -3
  5. omlish/collections/indexed.py +0 -2
  6. omlish/collections/mappings.py +0 -3
  7. omlish/collections/ordered.py +0 -2
  8. omlish/collections/persistent/__init__.py +0 -0
  9. omlish/collections/sorted/__init__.py +0 -0
  10. omlish/collections/{skiplist.py → sorted/skiplist.py} +0 -1
  11. omlish/collections/{sorted.py → sorted/sorted.py} +2 -5
  12. omlish/collections/unmodifiable.py +0 -2
  13. omlish/daemons/__init__.py +0 -0
  14. omlish/daemons/daemon.py +200 -0
  15. omlish/daemons/reparent.py +16 -0
  16. omlish/daemons/spawning.py +166 -0
  17. omlish/daemons/targets.py +89 -0
  18. omlish/daemons/waiting.py +95 -0
  19. omlish/dispatch/dispatch.py +4 -1
  20. omlish/dispatch/methods.py +4 -0
  21. omlish/formats/json/__init__.py +5 -0
  22. omlish/io/compress/brotli.py +3 -3
  23. omlish/io/fdio/__init__.py +3 -0
  24. omlish/lang/__init__.py +0 -2
  25. omlish/lang/contextmanagers.py +15 -10
  26. omlish/libc.py +2 -4
  27. omlish/lite/timeouts.py +1 -1
  28. omlish/marshal/__init__.py +10 -10
  29. omlish/marshal/base.py +1 -1
  30. omlish/marshal/standard.py +2 -2
  31. omlish/marshal/trivial/__init__.py +0 -0
  32. omlish/marshal/{forbidden.py → trivial/forbidden.py} +7 -7
  33. omlish/marshal/{nop.py → trivial/nop.py} +5 -5
  34. omlish/os/deathpacts/__init__.py +15 -0
  35. omlish/os/deathpacts/base.py +76 -0
  36. omlish/os/deathpacts/heartbeatfile.py +85 -0
  37. omlish/os/{death.py → deathpacts/pipe.py} +20 -90
  38. omlish/os/forkhooks.py +55 -31
  39. omlish/os/pidfiles/manager.py +11 -44
  40. omlish/os/pidfiles/pidfile.py +18 -1
  41. omlish/reflect/__init__.py +1 -0
  42. omlish/reflect/inspect.py +43 -0
  43. omlish/sql/queries/__init__.py +4 -4
  44. omlish/sql/queries/rendering2.py +248 -0
  45. omlish/text/parts.py +26 -23
  46. {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/METADATA +1 -1
  47. {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/RECORD +57 -45
  48. omlish/formats/json/delimted.py +0 -4
  49. /omlish/collections/{persistent.py → persistent/persistent.py} +0 -0
  50. /omlish/collections/{treap.py → persistent/treap.py} +0 -0
  51. /omlish/collections/{treapmap.py → persistent/treapmap.py} +0 -0
  52. /omlish/marshal/{utils.py → proxy.py} +0 -0
  53. /omlish/marshal/{singular → trivial}/any.py +0 -0
  54. /omlish/sql/queries/{building.py → std.py} +0 -0
  55. {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/LICENSE +0 -0
  56. {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/WHEEL +0 -0
  57. {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/entry_points.txt +0 -0
  58. {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -31,7 +31,10 @@ def get_impl_func_cls_set(func: ta.Callable) -> frozenset[type]:
31
31
  else:
32
32
  return check.isinstance(a, type)
33
33
 
34
- _, cls = next(iter(ta.get_type_hints(func).items()))
34
+ # Exclude 'return' to support difficult to handle return types - they are unimportant.
35
+ # TODO: only get hints for first arg - requires inspection, which requires chopping off `self`, which can be tricky.
36
+ _, cls = next(iter(rfl.get_filtered_type_hints(func, exclude=['return']).items()))
37
+
35
38
  rty = rfl.type_(cls)
36
39
  if isinstance(rty, rfl.Union):
37
40
  ret = frozenset(erase(arg) for arg in rty.args)
@@ -81,6 +81,10 @@ class Method:
81
81
  mro_dct = lang.build_mro_dict(instance_cls, owner_cls)
82
82
  seen: ta.Mapping[ta.Any, str] = {}
83
83
  for nam, att in mro_dct.items():
84
+ try:
85
+ hash(att)
86
+ except TypeError:
87
+ continue
84
88
  if att in self._impls:
85
89
  try:
86
90
  ex_nam = seen[att]
@@ -1,4 +1,9 @@
1
1
  # ruff: noqa: I001
2
+ """
3
+ TODO:
4
+ - delimited.py / jsonl
5
+ - + record separators ala https://en.wikipedia.org/wiki/JSON_streaming
6
+ """
2
7
  import typing as _ta
3
8
 
4
9
  from ... import lang as _lang
@@ -27,9 +27,9 @@ class BrotliCompression(Compression):
27
27
  return brotli.compress(
28
28
  d,
29
29
  **(dict(mode=self.mode) if self.mode is not None else {}),
30
- **(dict(mode=self.quality) if self.quality is not None else {}),
31
- **(dict(mode=self.lgwin) if self.lgwin is not None else {}),
32
- **(dict(mode=self.lgblock) if self.lgblock is not None else {}),
30
+ **(dict(quality=self.quality) if self.quality is not None else {}),
31
+ **(dict(lgwin=self.lgwin) if self.lgwin is not None else {}),
32
+ **(dict(lgblock=self.lgblock) if self.lgblock is not None else {}),
33
33
  )
34
34
 
35
35
  def decompress(self, d: bytes) -> bytes:
@@ -1 +1,4 @@
1
1
  # @omlish-lite
2
+ """
3
+ Basically the old asyncore system, which still has usecases over asyncio (such as single-threaded, forking code).
4
+ """
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
  ##
@@ -34,11 +34,6 @@ from .exceptions import ( # noqa
34
34
  UnhandledTypeError,
35
35
  )
36
36
 
37
- from .forbidden import ( # noqa
38
- ForbiddenTypeMarshalerFactory,
39
- ForbiddenTypeUnmarshalerFactory,
40
- )
41
-
42
37
  from .global_ import ( # noqa
43
38
  GLOBAL_REGISTRY,
44
39
 
@@ -54,11 +49,6 @@ from .naming import ( # noqa
54
49
  translate_name,
55
50
  )
56
51
 
57
- from .nop import ( # noqa
58
- NOP_MARSHALER_UNMARSHALER,
59
- NopMarshalerUnmarshaler,
60
- )
61
-
62
52
  from .objects.helpers import ( # noqa
63
53
  update_field_metadata,
64
54
  update_fields_metadata,
@@ -127,6 +117,16 @@ from .standard import ( # noqa
127
117
  new_standard_unmarshaler_factory,
128
118
  )
129
119
 
120
+ from .trivial.forbidden import ( # noqa
121
+ ForbiddenTypeMarshalerFactory,
122
+ ForbiddenTypeUnmarshalerFactory,
123
+ )
124
+
125
+ from .trivial.nop import ( # noqa
126
+ NOP_MARSHALER_UNMARSHALER,
127
+ NopMarshalerUnmarshaler,
128
+ )
129
+
130
130
  from .values import ( # noqa
131
131
  Value,
132
132
  )
omlish/marshal/base.py CHANGED
@@ -95,9 +95,9 @@ from .exceptions import UnhandledTypeError
95
95
  from .factories import RecursiveTypeFactory
96
96
  from .factories import TypeCacheFactory
97
97
  from .factories import TypeMapFactory
98
+ from .proxy import _Proxy
98
99
  from .registries import Registry
99
100
  from .registries import RegistryItem
100
- from .utils import _Proxy
101
101
  from .values import Value
102
102
 
103
103
 
@@ -23,8 +23,6 @@ from .objects.namedtuples import NamedtupleMarshalerFactory
23
23
  from .objects.namedtuples import NamedtupleUnmarshalerFactory
24
24
  from .polymorphism.unions import PrimitiveUnionMarshalerFactory
25
25
  from .polymorphism.unions import PrimitiveUnionUnmarshalerFactory
26
- from .singular.any import ANY_MARSHALER_FACTORY
27
- from .singular.any import ANY_UNMARSHALER_FACTORY
28
26
  from .singular.base64 import BASE64_MARSHALER_FACTORY
29
27
  from .singular.base64 import BASE64_UNMARSHALER_FACTORY
30
28
  from .singular.datetimes import DATETIME_MARSHALER_FACTORY
@@ -37,6 +35,8 @@ from .singular.primitives import PRIMITIVE_MARSHALER_FACTORY
37
35
  from .singular.primitives import PRIMITIVE_UNMARSHALER_FACTORY
38
36
  from .singular.uuids import UUID_MARSHALER_FACTORY
39
37
  from .singular.uuids import UUID_UNMARSHALER_FACTORY
38
+ from .trivial.any import ANY_MARSHALER_FACTORY
39
+ from .trivial.any import ANY_UNMARSHALER_FACTORY
40
40
 
41
41
 
42
42
  ##
File without changes
@@ -1,13 +1,13 @@
1
1
  import dataclasses as dc
2
2
  import typing as ta
3
3
 
4
- from .. import reflect as rfl
5
- from ..funcs import match as mfs
6
- from .base import MarshalContext
7
- from .base import Marshaler
8
- from .base import UnmarshalContext
9
- from .base import Unmarshaler
10
- from .exceptions import ForbiddenTypeError
4
+ from ... import reflect as rfl
5
+ from ...funcs import match as mfs
6
+ from ..base import MarshalContext
7
+ from ..base import Marshaler
8
+ from ..base import UnmarshalContext
9
+ from ..base import Unmarshaler
10
+ from ..exceptions import ForbiddenTypeError
11
11
 
12
12
 
13
13
  C = ta.TypeVar('C')
@@ -1,10 +1,10 @@
1
1
  import typing as ta
2
2
 
3
- from .base import MarshalContext
4
- from .base import Marshaler
5
- from .base import UnmarshalContext
6
- from .base import Unmarshaler
7
- from .values import Value
3
+ from ..base import MarshalContext
4
+ from ..base import Marshaler
5
+ from ..base import UnmarshalContext
6
+ from ..base import Unmarshaler
7
+ from ..values import Value
8
8
 
9
9
 
10
10
  class NopMarshalerUnmarshaler(Marshaler, Unmarshaler):
@@ -0,0 +1,15 @@
1
+ from .base import ( # noqa
2
+ Deathpact,
3
+ NopDeathpact,
4
+
5
+ BaseDeathpact,
6
+ )
7
+
8
+ from .heartbeatfile import ( # noqa
9
+ HeartbeatFileDeathpact,
10
+ )
11
+
12
+ from .pipe import ( # noqa
13
+ PipeDeathpact,
14
+ ForkAwarePipeDeathpact,
15
+ )
@@ -0,0 +1,76 @@
1
+ import abc
2
+ import os
3
+ import signal
4
+ import sys
5
+ import time
6
+ import typing as ta
7
+
8
+
9
+ ##
10
+
11
+
12
+ class Deathpact(abc.ABC):
13
+ @abc.abstractmethod
14
+ def poll(self) -> None:
15
+ raise NotImplementedError
16
+
17
+
18
+ class NopDeathpact(Deathpact):
19
+ def poll(self) -> None:
20
+ pass
21
+
22
+
23
+ ##
24
+
25
+
26
+ class BaseDeathpact(Deathpact, abc.ABC):
27
+ def __init__(
28
+ self,
29
+ *,
30
+ interval_s: float = .5,
31
+ signal: int | None = signal.SIGTERM, # noqa
32
+ output: ta.Literal['stdout', 'stderr'] | None = 'stderr',
33
+ on_die: ta.Callable[[], None] | None = None,
34
+ ) -> None:
35
+ super().__init__()
36
+
37
+ self._interval_s = interval_s
38
+ self._signal = signal
39
+ self._output = output
40
+ self._on_die = on_die
41
+
42
+ self._last_check_t: float | None = None
43
+
44
+ def _print(self, msg: str) -> None:
45
+ match self._output:
46
+ case 'stdout':
47
+ f = sys.stdout
48
+ case 'stderr':
49
+ f = sys.stderr
50
+ case _:
51
+ return
52
+ print(f'{self} pid={os.getpid()}: {msg}', file=f)
53
+
54
+ def die(self) -> None:
55
+ self._print('Triggered! Process terminating!')
56
+
57
+ if self._on_die is not None:
58
+ self._on_die()
59
+
60
+ if self._signal is not None:
61
+ os.kill(os.getpid(), self._signal)
62
+
63
+ sys.exit(1)
64
+
65
+ @abc.abstractmethod
66
+ def should_die(self) -> bool:
67
+ raise NotImplementedError
68
+
69
+ def maybe_die(self) -> None:
70
+ if self.should_die():
71
+ self.die()
72
+
73
+ def poll(self) -> None:
74
+ if self._last_check_t is None or (time.monotonic() - self._last_check_t) >= self._interval_s:
75
+ self.maybe_die()
76
+ self._last_check_t = time.monotonic()
@@ -0,0 +1,85 @@
1
+ """
2
+ TODO:
3
+ - chaining
4
+ """
5
+ import os
6
+ import time
7
+ import typing as ta
8
+
9
+ from ... import check
10
+ from ..forkhooks import ProcessOriginTracker
11
+ from ..temp import make_temp_file
12
+ from .base import BaseDeathpact
13
+
14
+
15
+ ##
16
+
17
+
18
+ class HeartbeatFileDeathpact(BaseDeathpact):
19
+ def __init__(
20
+ self,
21
+ path: str,
22
+ ttl_s: float = 10.,
23
+ **kwargs: ta.Any,
24
+ ) -> None:
25
+ super().__init__(**kwargs)
26
+
27
+ self._path = path
28
+ self._ttl_s = ttl_s
29
+
30
+ self._process_origin = ProcessOriginTracker()
31
+
32
+ @property
33
+ def path(self) -> str:
34
+ return self._path
35
+
36
+ def is_parent(self) -> bool:
37
+ return self._process_origin.is_in_origin_process()
38
+
39
+ #
40
+
41
+ def __enter__(self) -> ta.Self:
42
+ check.state(self.is_parent())
43
+
44
+ self.update()
45
+
46
+ return self
47
+
48
+ def close(self) -> None:
49
+ if self.is_parent():
50
+ try:
51
+ os.unlink(self._path)
52
+ except FileNotFoundError:
53
+ pass
54
+
55
+ def __exit__(self, exc_type, exc_val, exc_tb):
56
+ self.close()
57
+
58
+ #
59
+
60
+ @classmethod
61
+ def _now(cls) -> float:
62
+ return time.monotonic()
63
+
64
+ def update(self) -> None:
65
+ check.state(self.is_parent())
66
+
67
+ new = make_temp_file()
68
+ with open(new, 'w') as f:
69
+ f.write(str(self._now()))
70
+
71
+ # FIXME: same filesystem
72
+ os.replace(new, self._path)
73
+
74
+ def read(self) -> float:
75
+ try:
76
+ with open(self._path) as f:
77
+ return float(f.read())
78
+ except FileNotFoundError:
79
+ return float('-inf')
80
+
81
+ def age(self) -> float:
82
+ return self._now() - self.read()
83
+
84
+ def should_die(self) -> bool:
85
+ return self.age() >= self._ttl_s