omlish 0.0.0.dev102__py3-none-any.whl → 0.0.0.dev104__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,293 @@
1
+ """
2
+ TODO:
3
+ - https://docs.python.org/3/library/zlib.html#zlib.compressobj
4
+ """
5
+ import abc
6
+ import contextlib
7
+ import io
8
+ import typing as ta
9
+
10
+ from .. import check
11
+ from .. import lang
12
+ from ..concurrent import threadlets as tls
13
+ from ..sync import ConditionDeque
14
+
15
+
16
+ if ta.TYPE_CHECKING:
17
+ import threading
18
+
19
+ from . import pyio # noqa
20
+
21
+ else:
22
+ threading = lang.proxy_import('threading')
23
+
24
+ pyio = lang.proxy_import('.pyio', __package__)
25
+
26
+
27
+ T = ta.TypeVar('T')
28
+
29
+ BytesLike: ta.TypeAlias = ta.Any
30
+
31
+ BufferedReader = io.BufferedReader
32
+ # BufferedReader = pyio.BufferedReader
33
+
34
+
35
+ ##
36
+
37
+
38
+ class ProxyReadFile:
39
+ def __init__(self, read: ta.Callable[[int], bytes | BaseException]) -> None:
40
+ super().__init__()
41
+
42
+ self._read = read
43
+ self._eof = False
44
+ self._closed = False
45
+
46
+ #
47
+
48
+ def read(self, n: int, /) -> bytes:
49
+ if self._closed:
50
+ raise RuntimeError('Closed')
51
+ if self._eof:
52
+ return b''
53
+ d = self._read(n)
54
+ if isinstance(d, BaseException):
55
+ raise d
56
+ if not d:
57
+ self._eof = True
58
+ return d
59
+
60
+ def readall(self, *args, **kwargs):
61
+ raise TypeError # FIXME
62
+
63
+ def readinto(self, b: BytesLike) -> int | None:
64
+ d = self.read(len(b))
65
+ if d:
66
+ b[:len(d)] = d
67
+ return len(d)
68
+
69
+ #
70
+
71
+ def readable(self) -> bool:
72
+ return not (self._eof or self._closed)
73
+
74
+ @property
75
+ def closed(self) -> bool:
76
+ return self._closed
77
+
78
+ def close(self) -> None:
79
+ self._closed = True
80
+
81
+ #
82
+
83
+ def flush(self) -> None:
84
+ pass
85
+
86
+ def seekable(self) -> bool:
87
+ return False
88
+
89
+ def seek(self, n: int, /) -> ta.Any:
90
+ raise TypeError
91
+
92
+
93
+ ##
94
+
95
+
96
+ class NeedMore(lang.Marker):
97
+ pass
98
+
99
+
100
+ class Exited(lang.Marker):
101
+ pass
102
+
103
+
104
+ class Shutdown(BaseException): # noqa
105
+ pass
106
+
107
+
108
+ IoTrampolineTarget: ta.TypeAlias = ta.Callable[[io.BufferedReader], ta.ContextManager[ta.Callable[[], bytes]]]
109
+
110
+
111
+ class IoTrampoline(lang.Abstract):
112
+ def __init__(
113
+ self,
114
+ target: IoTrampolineTarget,
115
+ *,
116
+ buffer_size: int | None = None,
117
+ ) -> None:
118
+ super().__init__()
119
+
120
+ self._target = target
121
+ self._buffer_size = buffer_size
122
+
123
+ def _make_buffered_reader(self, raw: ta.Any) -> io.BufferedReader:
124
+ return io.BufferedReader(
125
+ raw,
126
+ **(dict(buffer_size=self._buffer_size) if self._buffer_size is not None else {}),
127
+ )
128
+
129
+ @abc.abstractmethod
130
+ def close(self, timeout: float | None = None) -> None:
131
+ raise NotImplementedError
132
+
133
+ @abc.abstractmethod
134
+ def __enter__(self) -> ta.Self:
135
+ raise NotImplementedError
136
+
137
+ @abc.abstractmethod
138
+ def __exit__(self, exc_type, exc_val, exc_tb):
139
+ raise NotImplementedError
140
+
141
+ @abc.abstractmethod
142
+ def feed(self, *data: bytes) -> ta.Iterable[bytes]:
143
+ raise NotImplementedError
144
+
145
+
146
+ #
147
+
148
+
149
+ class ThreadIoTrampoline(IoTrampoline):
150
+ def __init__(self, target: IoTrampolineTarget, **kwargs: ta.Any) -> None:
151
+ super().__init__(target, **kwargs)
152
+
153
+ self._in: ConditionDeque[bytes | BaseException] = ConditionDeque()
154
+ self._out: ConditionDeque[
155
+ bytes | # noqa
156
+ type[NeedMore] |
157
+ type[Exited] |
158
+ BaseException
159
+ ] = ConditionDeque()
160
+
161
+ self._proxy = ProxyReadFile(self._read)
162
+
163
+ self._thread = threading.Thread(target=self._thread_proc, daemon=True)
164
+
165
+ #
166
+
167
+ def close(self, timeout: float | None = None) -> None:
168
+ if not self._thread.is_alive():
169
+ return
170
+ self._out.push(Shutdown())
171
+ self._thread.join(timeout)
172
+ if self._thread.is_alive():
173
+ if timeout is not None:
174
+ raise TimeoutError
175
+ else:
176
+ raise RuntimeError('Failed to join thread')
177
+
178
+ #
179
+
180
+ def __enter__(self) -> ta.Self:
181
+ self._thread.start()
182
+ return self
183
+
184
+ def __exit__(self, exc_type, exc_val, exc_tb):
185
+ self.close()
186
+
187
+ #
188
+
189
+ def _read(self, n: int, /) -> bytes | BaseException:
190
+ return self._in.pop(if_empty=lambda: self._out.push(NeedMore))
191
+
192
+ def _thread_proc(self) -> None:
193
+ try:
194
+ with contextlib.closing(self._make_buffered_reader(self._proxy)) as bf: # noqa
195
+ with self._target(bf) as read:
196
+ while out := read():
197
+ self._out.push(out)
198
+ self._out.push(out)
199
+ except BaseException as e:
200
+ self._out.push(e)
201
+ raise
202
+ finally:
203
+ self._out.push(Exited)
204
+
205
+ def feed(self, *data: bytes) -> ta.Iterable[bytes]:
206
+ self._in.push(*data)
207
+ while True:
208
+ e = self._out.pop()
209
+ if e is NeedMore:
210
+ break
211
+ elif isinstance(e, BaseException):
212
+ raise e
213
+ elif e is Exited:
214
+ raise RuntimeError('IO thread exited')
215
+ elif isinstance(e, bytes):
216
+ yield e
217
+ if not e:
218
+ return
219
+ else:
220
+ raise TypeError(e)
221
+
222
+
223
+ #
224
+
225
+
226
+ class ThreadletIoTrampoline(IoTrampoline):
227
+ def __init__(
228
+ self,
229
+ target: IoTrampolineTarget,
230
+ threadlets: tls.Threadlets = tls.GREENLET_THREADLETS,
231
+ ** kwargs: ta.Any,
232
+ ) -> None:
233
+ super().__init__(target, **kwargs)
234
+
235
+ self._proxy = ProxyReadFile(self._read)
236
+ self._tl: tls.Threadlet = threadlets.spawn(self._g_proc)
237
+
238
+ #
239
+
240
+ def close(self, timeout: float | None = None) -> None:
241
+ if self._tl.dead:
242
+ return
243
+ out = self._tl.switch(Shutdown())
244
+ if out is not Exited or not self._tl.dead:
245
+ raise RuntimeError
246
+
247
+ #
248
+
249
+ def __enter__(self) -> ta.Self:
250
+ out = self._tl.switch()
251
+ if out is not NeedMore:
252
+ raise RuntimeError
253
+ return self
254
+
255
+ def __exit__(self, exc_type, exc_val, exc_tb):
256
+ self.close()
257
+
258
+ #
259
+
260
+ def _read(self, n: int, /) -> bytes:
261
+ out = check.not_none(self._tl.parent).switch(NeedMore)
262
+ return out
263
+
264
+ def _g_proc(self) -> ta.Any:
265
+ try:
266
+ with contextlib.closing(self._make_buffered_reader(self._proxy)) as bf: # noqa
267
+ with self._target(bf) as read:
268
+ while out := read():
269
+ e = check.not_none(self._tl.parent).switch(out)
270
+ if e is not NeedMore:
271
+ raise TypeError(e) # noqa
272
+ e = check.not_none(self._tl.parent).switch(out)
273
+ if not isinstance(e, Shutdown):
274
+ raise TypeError(e) # noqa
275
+ return Exited
276
+ except BaseException as e:
277
+ check.not_none(self._tl.parent).throw(e)
278
+ raise
279
+
280
+ def feed(self, *data: bytes) -> ta.Iterable[bytes]:
281
+ i: bytes | type[NeedMore]
282
+ for i in data:
283
+ while True:
284
+ e = self._tl.switch(i)
285
+ i = NeedMore
286
+ if e is NeedMore:
287
+ break
288
+ elif isinstance(e, bytes):
289
+ yield e
290
+ if not e:
291
+ return
292
+ else:
293
+ raise TypeError(e)
omlish/lite/cached.py CHANGED
@@ -1,7 +1,11 @@
1
1
  import functools
2
+ import typing as ta
2
3
 
3
4
 
4
- class cached_nullary: # noqa
5
+ T = ta.TypeVar('T')
6
+
7
+
8
+ class _cached_nullary: # noqa
5
9
  def __init__(self, fn):
6
10
  super().__init__()
7
11
  self._fn = fn
@@ -16,3 +20,7 @@ class cached_nullary: # noqa
16
20
  def __get__(self, instance, owner): # noqa
17
21
  bound = instance.__dict__[self._fn.__name__] = self.__class__(self._fn.__get__(instance, owner))
18
22
  return bound
23
+
24
+
25
+ def cached_nullary(fn: ta.Callable[..., T]) -> ta.Callable[..., T]:
26
+ return _cached_nullary(fn)
@@ -1,4 +1,38 @@
1
+ # ruff: noqa: UP007
1
2
  import contextlib
3
+ import typing as ta
4
+
5
+ from .check import check_not_none
6
+ from .check import check_state
7
+
8
+
9
+ T = ta.TypeVar('T')
10
+ ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
11
+
12
+
13
+ ##
14
+
15
+
16
+ class ExitStacked:
17
+ _exit_stack: ta.Optional[contextlib.ExitStack] = None
18
+
19
+ def __enter__(self: ExitStackedT) -> ExitStackedT:
20
+ check_state(self._exit_stack is None)
21
+ es = self._exit_stack = contextlib.ExitStack()
22
+ es.__enter__()
23
+ return self
24
+
25
+ def __exit__(self, exc_type, exc_val, exc_tb):
26
+ if (es := self._exit_stack) is None:
27
+ return None
28
+ return es.__exit__(exc_type, exc_val, exc_tb)
29
+
30
+ def _enter_context(self, cm: ta.ContextManager[T]) -> T:
31
+ es = check_not_none(self._exit_stack)
32
+ return es.enter_context(cm)
33
+
34
+
35
+ ##
2
36
 
3
37
 
4
38
  @contextlib.contextmanager
omlish/lite/marshal.py CHANGED
@@ -12,6 +12,8 @@ import datetime
12
12
  import decimal
13
13
  import enum
14
14
  import fractions
15
+ import functools
16
+ import threading
15
17
  import typing as ta
16
18
  import uuid
17
19
  import weakref # noqa
@@ -150,7 +152,7 @@ class DataclassObjMarshaler(ObjMarshaler):
150
152
  return {k: m.marshal(getattr(o, k)) for k, m in self.fs.items()}
151
153
 
152
154
  def unmarshal(self, o: ta.Any) -> ta.Any:
153
- return self.ty(**{k: self.fs[k].unmarshal(v) for k, v in o.items() if self.nonstrict or k in self.fs})
155
+ return self.ty(**{k: self.fs[k].unmarshal(v) for k, v in o.items() if not self.nonstrict or k in self.fs})
154
156
 
155
157
 
156
158
  @dc.dataclass(frozen=True)
@@ -210,7 +212,10 @@ class UuidObjMarshaler(ObjMarshaler):
210
212
  return uuid.UUID(o)
211
213
 
212
214
 
213
- _OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
215
+ ##
216
+
217
+
218
+ _DEFAULT_OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
214
219
  **{t: NopObjMarshaler() for t in (type(None),)},
215
220
  **{t: CastObjMarshaler(t) for t in (int, float, str, bool)},
216
221
  **{t: Base64ObjMarshaler(t) for t in (bytes, bytearray)},
@@ -239,20 +244,19 @@ _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES: ta.Dict[ta.Any, type] = {
239
244
  }
240
245
 
241
246
 
242
- def register_opj_marshaler(ty: ta.Any, m: ObjMarshaler) -> None:
243
- if ty in _OBJ_MARSHALERS:
244
- raise KeyError(ty)
245
- _OBJ_MARSHALERS[ty] = m
246
-
247
-
248
- def _make_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
247
+ def _make_obj_marshaler(
248
+ ty: ta.Any,
249
+ rec: ta.Callable[[ta.Any], ObjMarshaler],
250
+ *,
251
+ nonstrict_dataclasses: bool = False,
252
+ ) -> ObjMarshaler:
249
253
  if isinstance(ty, type):
250
254
  if abc.ABC in ty.__bases__:
251
255
  impls = [ # type: ignore
252
256
  PolymorphicObjMarshaler.Impl(
253
257
  ity,
254
258
  ity.__qualname__,
255
- get_obj_marshaler(ity),
259
+ rec(ity),
256
260
  )
257
261
  for ity in deep_subclasses(ty)
258
262
  if abc.ABC not in ity.__bases__
@@ -268,7 +272,8 @@ def _make_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
268
272
  if dc.is_dataclass(ty):
269
273
  return DataclassObjMarshaler(
270
274
  ty,
271
- {f.name: get_obj_marshaler(f.type) for f in dc.fields(ty)},
275
+ {f.name: rec(f.type) for f in dc.fields(ty)},
276
+ nonstrict=nonstrict_dataclasses,
272
277
  )
273
278
 
274
279
  if is_generic_alias(ty):
@@ -278,7 +283,7 @@ def _make_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
278
283
  pass
279
284
  else:
280
285
  k, v = ta.get_args(ty)
281
- return MappingObjMarshaler(mt, get_obj_marshaler(k), get_obj_marshaler(v))
286
+ return MappingObjMarshaler(mt, rec(k), rec(v))
282
287
 
283
288
  try:
284
289
  st = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES[ta.get_origin(ty)]
@@ -286,33 +291,71 @@ def _make_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
286
291
  pass
287
292
  else:
288
293
  [e] = ta.get_args(ty)
289
- return IterableObjMarshaler(st, get_obj_marshaler(e))
294
+ return IterableObjMarshaler(st, rec(e))
290
295
 
291
296
  if is_union_alias(ty):
292
- return OptionalObjMarshaler(get_obj_marshaler(get_optional_alias_arg(ty)))
297
+ return OptionalObjMarshaler(rec(get_optional_alias_arg(ty)))
293
298
 
294
299
  raise TypeError(ty)
295
300
 
296
301
 
297
- def get_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
298
- try:
299
- return _OBJ_MARSHALERS[ty]
300
- except KeyError:
301
- pass
302
-
303
- p = ProxyObjMarshaler()
304
- _OBJ_MARSHALERS[ty] = p
305
- try:
306
- m = _make_obj_marshaler(ty)
307
- except Exception:
308
- del _OBJ_MARSHALERS[ty]
309
- raise
310
- else:
311
- p.m = m
302
+ ##
303
+
304
+
305
+ _OBJ_MARSHALERS_LOCK = threading.RLock()
306
+
307
+ _OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = dict(_DEFAULT_OBJ_MARSHALERS)
308
+
309
+ _OBJ_MARSHALER_PROXIES: ta.Dict[ta.Any, ProxyObjMarshaler] = {}
310
+
311
+
312
+ def register_opj_marshaler(ty: ta.Any, m: ObjMarshaler) -> None:
313
+ with _OBJ_MARSHALERS_LOCK:
314
+ if ty in _OBJ_MARSHALERS:
315
+ raise KeyError(ty)
312
316
  _OBJ_MARSHALERS[ty] = m
317
+
318
+
319
+ def get_obj_marshaler(
320
+ ty: ta.Any,
321
+ *,
322
+ no_cache: bool = False,
323
+ **kwargs: ta.Any,
324
+ ) -> ObjMarshaler:
325
+ with _OBJ_MARSHALERS_LOCK:
326
+ if not no_cache:
327
+ try:
328
+ return _OBJ_MARSHALERS[ty]
329
+ except KeyError:
330
+ pass
331
+
332
+ try:
333
+ return _OBJ_MARSHALER_PROXIES[ty]
334
+ except KeyError:
335
+ pass
336
+
337
+ rec = functools.partial(
338
+ get_obj_marshaler,
339
+ no_cache=no_cache,
340
+ **kwargs,
341
+ )
342
+
343
+ p = ProxyObjMarshaler()
344
+ _OBJ_MARSHALER_PROXIES[ty] = p
345
+ try:
346
+ m = _make_obj_marshaler(ty, rec, **kwargs)
347
+ finally:
348
+ del _OBJ_MARSHALER_PROXIES[ty]
349
+ p.m = m
350
+
351
+ if not no_cache:
352
+ _OBJ_MARSHALERS[ty] = m
313
353
  return m
314
354
 
315
355
 
356
+ ##
357
+
358
+
316
359
  def marshal_obj(o: ta.Any, ty: ta.Any = None) -> ta.Any:
317
360
  return get_obj_marshaler(ty if ty is not None else type(o)).marshal(o)
318
361
 
omlish/lite/pidfile.py CHANGED
@@ -30,7 +30,7 @@ class Pidfile:
30
30
  return self
31
31
 
32
32
  def __exit__(self, exc_type, exc_val, exc_tb):
33
- if self._f is not None:
33
+ if hasattr(self, '_f'):
34
34
  self._f.close()
35
35
  del self._f
36
36
 
@@ -110,3 +110,21 @@ def subprocess_try_output(
110
110
  def subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
111
111
  out = subprocess_try_output(*args, **kwargs)
112
112
  return out.decode().strip() if out is not None else None
113
+
114
+
115
+ ##
116
+
117
+
118
+ def subprocess_close(
119
+ proc: subprocess.Popen,
120
+ timeout: ta.Optional[float] = None,
121
+ ) -> None:
122
+ # TODO: terminate, sleep, kill
123
+ if proc.stdout:
124
+ proc.stdout.close()
125
+ if proc.stderr:
126
+ proc.stderr.close()
127
+ if proc.stdin:
128
+ proc.stdin.close()
129
+
130
+ proc.wait(timeout)
omlish/specs/__init__.py CHANGED
@@ -4,6 +4,8 @@ TODO:
4
4
  - iceberg
5
5
  - jsonnet
6
6
  - jsonpatch
7
+ - jsonpath
7
8
  - jsonpointer.py
8
9
  - protobuf
10
+ - irc? or in net?
9
11
  """
omlish/sync.py CHANGED
@@ -3,6 +3,7 @@ TODO:
3
3
  - sync (lol) w/ asyncs.anyio
4
4
  - atomics
5
5
  """
6
+ import collections
6
7
  import threading
7
8
  import typing as ta
8
9
 
@@ -68,3 +69,57 @@ class LazyFn(ta.Generic[T]):
68
69
  self._v = lang.just(self._fn())
69
70
  self._once.do(do)
70
71
  return self._v.must()
72
+
73
+
74
+ class ConditionDeque(ta.Generic[T]):
75
+ def __init__(
76
+ self,
77
+ *,
78
+ cond: ta.Optional['threading.Condition'] = None,
79
+ deque: collections.deque[T] | None = None,
80
+
81
+ lock: ta.Optional['threading.RLock'] = None,
82
+ maxlen: int | None = None,
83
+ init: ta.Iterable[T] | None = None,
84
+ ) -> None:
85
+ super().__init__()
86
+
87
+ if cond is None:
88
+ cond = threading.Condition(lock=lock)
89
+ if deque is None:
90
+ deque = collections.deque(maxlen=maxlen)
91
+ if init:
92
+ deque.extend(init)
93
+
94
+ self._cond = cond
95
+ self._deque = deque
96
+
97
+ @property
98
+ def cond(self) -> 'threading.Condition':
99
+ return self._cond
100
+
101
+ @property
102
+ def deque(self) -> collections.deque[T]:
103
+ return self._deque
104
+
105
+ def push(
106
+ self,
107
+ *items: T,
108
+ n: int = 1,
109
+ ) -> None:
110
+ with self.cond:
111
+ self.deque.extend(items)
112
+ self.cond.notify(n)
113
+
114
+ def pop(
115
+ self,
116
+ timeout: float | None = None,
117
+ *,
118
+ if_empty: ta.Callable[[], None] | None = None,
119
+ ) -> T:
120
+ with self.cond:
121
+ if not self.deque and if_empty is not None:
122
+ if_empty()
123
+ while not self.deque:
124
+ self.cond.wait(timeout)
125
+ return self.deque.popleft()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev102
3
+ Version: 0.0.0.dev104
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause