omlish 0.0.0.dev228__py3-none-any.whl → 0.0.0.dev230__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omlish/__about__.py +2 -2
- omlish/collections/__init__.py +15 -15
- omlish/collections/frozen.py +0 -2
- omlish/collections/identity.py +0 -3
- omlish/collections/indexed.py +0 -2
- omlish/collections/mappings.py +0 -3
- omlish/collections/ordered.py +0 -2
- omlish/collections/persistent/__init__.py +0 -0
- omlish/collections/sorted/__init__.py +0 -0
- omlish/collections/{skiplist.py → sorted/skiplist.py} +0 -1
- omlish/collections/{sorted.py → sorted/sorted.py} +2 -5
- omlish/collections/unmodifiable.py +0 -2
- omlish/dispatch/dispatch.py +4 -1
- omlish/dispatch/methods.py +4 -0
- omlish/formats/json/__init__.py +5 -0
- omlish/io/compress/brotli.py +3 -3
- omlish/io/fdio/__init__.py +3 -0
- omlish/marshal/__init__.py +10 -10
- omlish/marshal/base.py +1 -1
- omlish/marshal/standard.py +2 -2
- omlish/marshal/trivial/__init__.py +0 -0
- omlish/marshal/{forbidden.py → trivial/forbidden.py} +7 -7
- omlish/marshal/{nop.py → trivial/nop.py} +5 -5
- omlish/os/deathpacts/__init__.py +15 -0
- omlish/os/deathpacts/base.py +76 -0
- omlish/os/deathpacts/heartbeatfile.py +85 -0
- omlish/os/{death.py → deathpacts/pipe.py} +25 -93
- omlish/os/forkhooks.py +55 -31
- omlish/os/pidfiles/manager.py +11 -44
- omlish/reflect/__init__.py +1 -0
- omlish/reflect/inspect.py +43 -0
- omlish/sql/queries/__init__.py +4 -4
- omlish/sql/queries/rendering2.py +248 -0
- omlish/text/parts.py +26 -23
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/RECORD +46 -40
- omlish/formats/json/delimted.py +0 -4
- /omlish/collections/{persistent.py → persistent/persistent.py} +0 -0
- /omlish/collections/{treap.py → persistent/treap.py} +0 -0
- /omlish/collections/{treapmap.py → persistent/treapmap.py} +0 -0
- /omlish/marshal/{utils.py → proxy.py} +0 -0
- /omlish/marshal/{singular → trivial}/any.py +0 -0
- /omlish/sql/queries/{building.py → std.py} +0 -0
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/collections/__init__.py
CHANGED
@@ -76,39 +76,39 @@ from .ordered import ( # noqa
|
|
76
76
|
OrderedSet,
|
77
77
|
)
|
78
78
|
|
79
|
-
from .persistent import ( # noqa
|
79
|
+
from .persistent.persistent import ( # noqa
|
80
80
|
PersistentMap,
|
81
81
|
)
|
82
82
|
|
83
83
|
if _ta.TYPE_CHECKING:
|
84
|
-
from .
|
84
|
+
from .persistent.treapmap import ( # noqa
|
85
|
+
TreapMap,
|
86
|
+
new_treap_map,
|
87
|
+
)
|
88
|
+
else:
|
89
|
+
_lang.proxy_init(globals(), '.persistent.treapmap', [
|
90
|
+
'TreapMap',
|
91
|
+
'new_treap_map',
|
92
|
+
])
|
93
|
+
|
94
|
+
if _ta.TYPE_CHECKING:
|
95
|
+
from .sorted.skiplist import ( # noqa
|
85
96
|
SkipList,
|
86
97
|
SkipListDict,
|
87
98
|
)
|
88
99
|
else:
|
89
|
-
_lang.proxy_init(globals(), '.skiplist', [
|
100
|
+
_lang.proxy_init(globals(), '.sorted.skiplist', [
|
90
101
|
'SkipList',
|
91
102
|
'SkipListDict',
|
92
103
|
])
|
93
104
|
|
94
|
-
from .sorted import ( # noqa
|
105
|
+
from .sorted.sorted import ( # noqa
|
95
106
|
SortedCollection,
|
96
107
|
SortedListDict,
|
97
108
|
SortedMapping,
|
98
109
|
SortedMutableMapping,
|
99
110
|
)
|
100
111
|
|
101
|
-
if _ta.TYPE_CHECKING:
|
102
|
-
from .treapmap import ( # noqa
|
103
|
-
TreapMap,
|
104
|
-
new_treap_map,
|
105
|
-
)
|
106
|
-
else:
|
107
|
-
_lang.proxy_init(globals(), '.treapmap', [
|
108
|
-
'TreapMap',
|
109
|
-
'new_treap_map',
|
110
|
-
])
|
111
|
-
|
112
112
|
from .unmodifiable import ( # noqa
|
113
113
|
Unmodifiable,
|
114
114
|
UnmodifiableMapping,
|
omlish/collections/frozen.py
CHANGED
@@ -16,7 +16,6 @@ class Frozen(ta.Hashable, abc.ABC):
|
|
16
16
|
|
17
17
|
|
18
18
|
class FrozenDict(ta.Mapping[K, V], Frozen):
|
19
|
-
|
20
19
|
def __new__(cls, *args: ta.Any, **kwargs: ta.Any) -> 'FrozenDict[K, V]': # noqa
|
21
20
|
if len(args) == 1 and Frozen in type(args[0]).__bases__:
|
22
21
|
return args[0]
|
@@ -73,7 +72,6 @@ class FrozenDict(ta.Mapping[K, V], Frozen):
|
|
73
72
|
|
74
73
|
|
75
74
|
class FrozenList(ta.Sequence[T], Frozen):
|
76
|
-
|
77
75
|
def __init__(self, it: ta.Iterable[T] | None = None) -> None:
|
78
76
|
super().__init__()
|
79
77
|
|
omlish/collections/identity.py
CHANGED
@@ -13,7 +13,6 @@ V = ta.TypeVar('V')
|
|
13
13
|
|
14
14
|
|
15
15
|
class IdentityWrapper(ta.Generic[T]):
|
16
|
-
|
17
16
|
def __init__(self, value: T) -> None:
|
18
17
|
super().__init__()
|
19
18
|
self._value = value
|
@@ -36,7 +35,6 @@ class IdentityWrapper(ta.Generic[T]):
|
|
36
35
|
|
37
36
|
|
38
37
|
class IdentityKeyDict(ta.MutableMapping[K, V]):
|
39
|
-
|
40
38
|
def __init__(self, *args, **kwargs) -> None:
|
41
39
|
super().__init__()
|
42
40
|
self._dict: dict[int, tuple[K, V]] = {}
|
@@ -70,7 +68,6 @@ class IdentityKeyDict(ta.MutableMapping[K, V]):
|
|
70
68
|
|
71
69
|
|
72
70
|
class IdentitySet(ta.MutableSet[T]):
|
73
|
-
|
74
71
|
def __init__(self, init: ta.Iterable[T] | None = None) -> None:
|
75
72
|
super().__init__()
|
76
73
|
self._dict: dict[int, T] = {}
|
omlish/collections/indexed.py
CHANGED
@@ -8,7 +8,6 @@ T = ta.TypeVar('T')
|
|
8
8
|
|
9
9
|
|
10
10
|
class IndexedSeq(ta.Sequence[T]):
|
11
|
-
|
12
11
|
def __init__(self, it: ta.Iterable[T], *, identity: bool = False) -> None:
|
13
12
|
super().__init__()
|
14
13
|
|
@@ -42,7 +41,6 @@ class IndexedSeq(ta.Sequence[T]):
|
|
42
41
|
|
43
42
|
|
44
43
|
class IndexedSetSeq(ta.Sequence[ta.AbstractSet[T]]):
|
45
|
-
|
46
44
|
def __init__(self, it: ta.Iterable[ta.Iterable[T]], *, identity: bool = False) -> None:
|
47
45
|
super().__init__()
|
48
46
|
|
omlish/collections/mappings.py
CHANGED
@@ -49,7 +49,6 @@ def yield_dict_init(*args, **kwargs) -> ta.Iterable[tuple[ta.Any, ta.Any]]:
|
|
49
49
|
|
50
50
|
|
51
51
|
class TypeMap(ta.Generic[T]):
|
52
|
-
|
53
52
|
def __init__(self, items: ta.Iterable[T] = ()) -> None:
|
54
53
|
super().__init__()
|
55
54
|
|
@@ -79,7 +78,6 @@ class TypeMap(ta.Generic[T]):
|
|
79
78
|
|
80
79
|
|
81
80
|
class DynamicTypeMap(ta.Generic[V]):
|
82
|
-
|
83
81
|
def __init__(self, items: ta.Iterable[V] = (), *, weak: bool = False) -> None:
|
84
82
|
super().__init__()
|
85
83
|
|
@@ -115,7 +113,6 @@ class DynamicTypeMap(ta.Generic[V]):
|
|
115
113
|
|
116
114
|
|
117
115
|
class MissingDict(dict[K, V]):
|
118
|
-
|
119
116
|
def __init__(self, missing_fn: ta.Callable[[K], V]) -> None:
|
120
117
|
if not callable(missing_fn):
|
121
118
|
raise TypeError(missing_fn)
|
omlish/collections/ordered.py
CHANGED
@@ -5,7 +5,6 @@ T = ta.TypeVar('T')
|
|
5
5
|
|
6
6
|
|
7
7
|
class OrderedSet(ta.MutableSet[T]):
|
8
|
-
|
9
8
|
def __init__(self, iterable: ta.Iterable[T] | None = None) -> None:
|
10
9
|
super().__init__()
|
11
10
|
self._dct: dict[T, ta.Any] = {}
|
@@ -56,7 +55,6 @@ class OrderedSet(ta.MutableSet[T]):
|
|
56
55
|
|
57
56
|
|
58
57
|
class OrderedFrozenSet(ta.FrozenSet[T]): # noqa
|
59
|
-
|
60
58
|
_list: ta.Sequence[T]
|
61
59
|
|
62
60
|
def __new__(cls, items: ta.Iterable[T]) -> frozenset[T]: # type: ignore
|
File without changes
|
File without changes
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import abc
|
2
2
|
import typing as ta
|
3
3
|
|
4
|
-
from
|
5
|
-
from
|
4
|
+
from ... import lang
|
5
|
+
from ..mappings import yield_dict_init
|
6
6
|
|
7
7
|
|
8
8
|
T = ta.TypeVar('T')
|
@@ -12,7 +12,6 @@ V = ta.TypeVar('V')
|
|
12
12
|
|
13
13
|
|
14
14
|
class SortedCollection(lang.Abstract, ta.Collection[T]):
|
15
|
-
|
16
15
|
Comparator = ta.Callable[[U, U], int]
|
17
16
|
|
18
17
|
@staticmethod
|
@@ -55,7 +54,6 @@ class SortedCollection(lang.Abstract, ta.Collection[T]):
|
|
55
54
|
|
56
55
|
|
57
56
|
class SortedMapping(ta.Mapping[K, V]):
|
58
|
-
|
59
57
|
@abc.abstractmethod
|
60
58
|
def items(self) -> ta.Iterator[tuple[K, V]]: # type: ignore
|
61
59
|
raise NotImplementedError
|
@@ -78,7 +76,6 @@ class SortedMutableMapping(ta.MutableMapping[K, V], SortedMapping[K, V]):
|
|
78
76
|
|
79
77
|
|
80
78
|
class SortedListDict(SortedMutableMapping[K, V]):
|
81
|
-
|
82
79
|
@staticmethod
|
83
80
|
def _item_comparator(a: tuple[K, V], b: tuple[K, V]) -> int:
|
84
81
|
return SortedCollection.default_comparator(a[0], b[0])
|
@@ -13,7 +13,6 @@ class Unmodifiable(lang.Abstract):
|
|
13
13
|
|
14
14
|
|
15
15
|
class UnmodifiableSequence(ta.Sequence[T], Unmodifiable, lang.Final):
|
16
|
-
|
17
16
|
def __init__(self, target: ta.Sequence[T]) -> None:
|
18
17
|
super().__init__()
|
19
18
|
|
@@ -65,7 +64,6 @@ class UnmodifiableSequence(ta.Sequence[T], Unmodifiable, lang.Final):
|
|
65
64
|
|
66
65
|
|
67
66
|
class UnmodifiableSet(ta.AbstractSet[T], Unmodifiable, lang.Final):
|
68
|
-
|
69
67
|
def __init__(self, target: ta.AbstractSet[T]) -> None:
|
70
68
|
super().__init__()
|
71
69
|
|
omlish/dispatch/dispatch.py
CHANGED
@@ -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
|
-
|
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)
|
omlish/dispatch/methods.py
CHANGED
@@ -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]
|
omlish/formats/json/__init__.py
CHANGED
omlish/io/compress/brotli.py
CHANGED
@@ -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(
|
31
|
-
**(dict(
|
32
|
-
**(dict(
|
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:
|
omlish/io/fdio/__init__.py
CHANGED
omlish/marshal/__init__.py
CHANGED
@@ -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
|
|
omlish/marshal/standard.py
CHANGED
@@ -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
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
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
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
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,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
|