omlish 0.0.0.dev244__py3-none-any.whl → 0.0.0.dev246__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 (33) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/cached.py +4 -3
  3. omlish/collections/__init__.py +0 -1
  4. omlish/collections/frozen.py +2 -2
  5. omlish/collections/hasheq.py +1 -2
  6. omlish/collections/identity.py +1 -2
  7. omlish/collections/mappings.py +0 -16
  8. omlish/collections/sorted/sorted.py +1 -2
  9. omlish/daemons/services.py +7 -0
  10. omlish/dataclasses/__init__.py +3 -0
  11. omlish/dataclasses/utils.py +14 -0
  12. omlish/http/handlers.py +16 -0
  13. omlish/lang/__init__.py +46 -16
  14. omlish/lang/attrs.py +161 -0
  15. omlish/lang/cached/__init__.py +0 -0
  16. omlish/lang/{cached.py → cached/function.py} +125 -83
  17. omlish/lang/cached/property.py +118 -0
  18. omlish/lang/classes/__init__.py +0 -41
  19. omlish/lang/collections.py +50 -0
  20. omlish/marshal/__init__.py +23 -0
  21. omlish/marshal/composite/wrapped.py +26 -0
  22. omlish/marshal/objects/dataclasses.py +36 -11
  23. omlish/marshal/objects/namedtuples.py +9 -9
  24. omlish/marshal/polymorphism/marshal.py +16 -2
  25. omlish/marshal/polymorphism/unmarshal.py +16 -2
  26. omlish/os/pidfiles/pidfile.py +20 -4
  27. omlish/os/signals.py +5 -1
  28. {omlish-0.0.0.dev244.dist-info → omlish-0.0.0.dev246.dist-info}/METADATA +1 -1
  29. {omlish-0.0.0.dev244.dist-info → omlish-0.0.0.dev246.dist-info}/RECORD +33 -28
  30. {omlish-0.0.0.dev244.dist-info → omlish-0.0.0.dev246.dist-info}/WHEEL +1 -1
  31. {omlish-0.0.0.dev244.dist-info → omlish-0.0.0.dev246.dist-info}/LICENSE +0 -0
  32. {omlish-0.0.0.dev244.dist-info → omlish-0.0.0.dev246.dist-info}/entry_points.txt +0 -0
  33. {omlish-0.0.0.dev244.dist-info → omlish-0.0.0.dev246.dist-info}/top_level.txt +0 -0
@@ -2,37 +2,43 @@
2
2
  TODO:
3
3
  - integrate / expose with collections.cache
4
4
  - weakrefs (selectable by arg)
5
- - locks
5
+ - more rigorous descriptor pickling
6
+ - must support free functions (which have no instance nor owner)
7
+ - 'staticmethod' or effective equiv - which must resolve to the shared instance
8
+ - and must be transient?
9
+ - use __transient_dict__ to support common state nuking
6
10
  """
7
11
  import dataclasses as dc
8
12
  import functools
9
13
  import inspect
10
14
  import typing as ta
11
15
 
12
- from .contextmanagers import DefaultLockable
13
- from .contextmanagers import default_lock
14
- from .descriptors import unwrap_func
15
- from .descriptors import unwrap_func_with_partials
16
+ from ..classes.abstract import Abstract
17
+ from ..contextmanagers import DefaultLockable
18
+ from ..contextmanagers import default_lock
19
+ from ..descriptors import unwrap_func
20
+ from ..descriptors import unwrap_func_with_partials
16
21
 
17
22
 
18
23
  P = ta.ParamSpec('P')
19
24
  T = ta.TypeVar('T')
20
25
  CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
21
26
 
22
- _IGNORE = object()
27
+
28
+ ##
23
29
 
24
30
 
25
- def _nullary_cache_keyer():
31
+ def _nullary_cache_key_maker():
26
32
  return ()
27
33
 
28
34
 
29
- def _simple_cache_keyer(*args, **kwargs):
35
+ def _simple_cache_key_maker(*args, **kwargs):
30
36
  return (args, tuple(sorted(kwargs.items())))
31
37
 
32
38
 
33
- def _make_cache_keyer(fn, *, simple=False, bound=False):
39
+ def _make_cache_key_maker(fn, *, simple=False, bound=False):
34
40
  if simple:
35
- return _simple_cache_keyer
41
+ return _simple_cache_key_maker
36
42
 
37
43
  fn, partials = unwrap_func_with_partials(fn)
38
44
 
@@ -42,7 +48,7 @@ def _make_cache_keyer(fn, *, simple=False, bound=False):
42
48
  sig = inspect.signature(fn)
43
49
  sig_params = list(sig.parameters.values())[1 if bound else 0:]
44
50
  if not sig_params:
45
- return _nullary_cache_keyer
51
+ return _nullary_cache_key_maker
46
52
 
47
53
  ns = {}
48
54
  src_params = []
@@ -92,19 +98,23 @@ def _make_cache_keyer(fn, *, simple=False, bound=False):
92
98
  return kfn
93
99
 
94
100
 
95
- class _CachedFunction(ta.Generic[T]):
101
+ ##
102
+
103
+
104
+ class _CachedFunction(ta.Generic[T], Abstract):
96
105
  @dc.dataclass(frozen=True)
97
106
  class Opts:
98
107
  map_maker: ta.Callable[[], ta.MutableMapping] = dict
99
108
  simple_key: bool = False
100
109
  lock: DefaultLockable = None
110
+ transient: bool = False
101
111
 
102
112
  def __init__(
103
113
  self,
104
114
  fn: ta.Callable[P, T],
105
115
  *,
106
116
  opts: Opts = Opts(),
107
- keyer: ta.Callable[..., tuple] | None = None,
117
+ key_maker: ta.Callable[..., tuple] | None = None,
108
118
  values: ta.MutableMapping | None = None,
109
119
  value_fn: ta.Callable[P, T] | None = None,
110
120
  ) -> None:
@@ -112,7 +122,7 @@ class _CachedFunction(ta.Generic[T]):
112
122
 
113
123
  self._fn = (fn,)
114
124
  self._opts = opts
115
- self._keyer = keyer if keyer is not None else _make_cache_keyer(fn, simple=opts.simple_key)
125
+ self._key_maker = key_maker if key_maker is not None else _make_cache_key_maker(fn, simple=opts.simple_key)
116
126
 
117
127
  self._lock = default_lock(opts.lock, False)() if opts.lock is not None else None
118
128
  self._values = values if values is not None else opts.map_maker()
@@ -134,7 +144,7 @@ class _CachedFunction(ta.Generic[T]):
134
144
  raise TypeError
135
145
 
136
146
  def __call__(self, *args, **kwargs) -> T:
137
- k = self._keyer(*args, **kwargs)
147
+ k = self._key_maker(*args, **kwargs)
138
148
 
139
149
  try:
140
150
  return self._values[k]
@@ -157,11 +167,42 @@ class _CachedFunction(ta.Generic[T]):
157
167
  return value
158
168
 
159
169
 
160
- class _CachedFunctionDescriptor(_CachedFunction[T]):
170
+ #
171
+
172
+
173
+ class _FreeCachedFunction(_CachedFunction[T]):
174
+ @classmethod
175
+ def _unpickle(
176
+ cls,
177
+ fn,
178
+ opts,
179
+ values,
180
+ ):
181
+ return cls(
182
+ fn,
183
+ opts=opts,
184
+ values=values,
185
+ )
186
+
187
+ def __reduce__(self):
188
+ return (
189
+ _FreeCachedFunction._unpickle,
190
+ (
191
+ self._fn,
192
+ self._opts,
193
+ self._values if not self._opts.transient else None,
194
+ ),
195
+ )
196
+
197
+
198
+ #
199
+
200
+
201
+ class _DescriptorCachedFunction(_CachedFunction[T]):
161
202
  def __init__(
162
203
  self,
163
204
  fn: ta.Callable[P, T],
164
- scope: ta.Any,
205
+ scope: ta.Any, # classmethod | None
165
206
  *,
166
207
  instance: ta.Any = None,
167
208
  owner: ta.Any = None,
@@ -174,9 +215,61 @@ class _CachedFunctionDescriptor(_CachedFunction[T]):
174
215
  self._instance = instance
175
216
  self._owner = owner
176
217
  self._name = name if name is not None else unwrap_func(fn).__name__
177
- self._bound_keyer = None
218
+ self._bound_key_maker = None
178
219
 
179
- def __get__(self, instance, owner=None):
220
+ @classmethod
221
+ def _unpickle(
222
+ cls,
223
+ scope,
224
+ instance,
225
+ owner,
226
+ name,
227
+ values,
228
+ ):
229
+ if scope is not None:
230
+ raise NotImplementedError
231
+
232
+ if instance is None:
233
+ raise RuntimeError
234
+ obj = type(instance)
235
+
236
+ desc: _DescriptorCachedFunction = object.__getattribute__(obj, name)
237
+ if not isinstance(desc, cls):
238
+ raise TypeError(desc)
239
+ if (desc._instance is not None or desc._owner is not None):
240
+ raise RuntimeError
241
+
242
+ return desc._bind(
243
+ instance,
244
+ owner,
245
+ values=values,
246
+ )
247
+
248
+ def __reduce__(self):
249
+ if self._scope is not None:
250
+ raise NotImplementedError
251
+
252
+ if not (self._instance is not None or self._owner is not None):
253
+ raise RuntimeError
254
+
255
+ return (
256
+ _DescriptorCachedFunction._unpickle,
257
+ (
258
+ self._scope,
259
+ self._instance,
260
+ self._owner,
261
+ self._name,
262
+ self._values if not self._opts.transient else None,
263
+ ),
264
+ )
265
+
266
+ def _bind(
267
+ self,
268
+ instance,
269
+ owner=None,
270
+ *,
271
+ values: ta.MutableMapping | None = None,
272
+ ):
180
273
  scope = self._scope
181
274
  if owner is self._owner and (instance is self._instance or scope is classmethod):
182
275
  return self
@@ -184,8 +277,8 @@ class _CachedFunctionDescriptor(_CachedFunction[T]):
184
277
  fn, = self._fn
185
278
  name = self._name
186
279
  bound_fn = fn.__get__(instance, owner)
187
- if self._bound_keyer is None:
188
- self._bound_keyer = _make_cache_keyer(fn, simple=self._opts.simple_key, bound=True)
280
+ if self._bound_key_maker is None:
281
+ self._bound_key_maker = _make_cache_key_maker(fn, simple=self._opts.simple_key, bound=True)
189
282
 
190
283
  bound = self.__class__(
191
284
  fn,
@@ -194,8 +287,9 @@ class _CachedFunctionDescriptor(_CachedFunction[T]):
194
287
  instance=instance,
195
288
  owner=owner,
196
289
  name=name,
197
- keyer=self._bound_keyer,
198
- # values=None if scope is classmethod else self._values,
290
+ key_maker=self._bound_key_maker,
291
+ # values=None if scope is classmethod else self._values, # FIXME: ?
292
+ values=values,
199
293
  value_fn=bound_fn,
200
294
  )
201
295
 
@@ -206,76 +300,24 @@ class _CachedFunctionDescriptor(_CachedFunction[T]):
206
300
 
207
301
  return bound
208
302
 
303
+ def __get__(self, instance, owner=None):
304
+ return self._bind(instance, owner)
305
+
306
+
307
+ #
308
+
209
309
 
210
310
  def cached_function(fn=None, **kwargs): # noqa
211
311
  if fn is None:
212
312
  return functools.partial(cached_function, **kwargs)
213
313
  opts = _CachedFunction.Opts(**kwargs)
214
314
  if isinstance(fn, staticmethod):
215
- return _CachedFunction(fn, opts=opts, value_fn=unwrap_func(fn))
315
+ return _FreeCachedFunction(fn, opts=opts, value_fn=unwrap_func(fn))
216
316
  scope = classmethod if isinstance(fn, classmethod) else None
217
- return _CachedFunctionDescriptor(fn, scope, opts=opts)
317
+ return _DescriptorCachedFunction(fn, scope, opts=opts)
218
318
 
219
319
 
220
320
  def static_init(fn: CallableT) -> CallableT:
221
321
  fn = cached_function(fn)
222
322
  fn()
223
323
  return fn
224
-
225
-
226
- ##
227
-
228
-
229
- class _CachedProperty(property):
230
- def __init__(
231
- self,
232
- fn,
233
- *,
234
- name=None,
235
- ignore_if=lambda _: False,
236
- clear_on_init=False,
237
- ):
238
- if isinstance(fn, property):
239
- fn = fn.fget
240
- super().__init__(fn)
241
- self._fn = fn
242
- self._ignore_if = ignore_if
243
- self._name = name
244
- self._clear_on_init = clear_on_init
245
-
246
- def __set_name__(self, owner, name):
247
- if self._name is None:
248
- self._name = name
249
-
250
- def __get__(self, instance, owner=None):
251
- if instance is None:
252
- return self
253
- if self._name is None:
254
- raise TypeError(self)
255
-
256
- try:
257
- return instance.__dict__[self._name]
258
- except KeyError:
259
- pass
260
-
261
- value = self._fn.__get__(instance, owner)()
262
- if value is _IGNORE:
263
- return None
264
- instance.__dict__[self._name] = value
265
- return value
266
-
267
- def __set__(self, instance, value):
268
- if self._ignore_if(value):
269
- return
270
- if instance.__dict__[self._name] == value:
271
- return
272
- raise TypeError(self._name)
273
-
274
- def __delete__(self, instance):
275
- raise TypeError
276
-
277
-
278
- def cached_property(fn=None, **kwargs): # noqa
279
- if fn is None:
280
- return functools.partial(cached_property, **kwargs)
281
- return _CachedProperty(fn, **kwargs)
@@ -0,0 +1,118 @@
1
+ import abc
2
+ import functools
3
+ import typing as ta
4
+
5
+ from ..attrs import transient_getattr
6
+ from ..attrs import transient_setattr
7
+ from ..classes.abstract import Abstract
8
+
9
+
10
+ _IGNORE = object()
11
+
12
+
13
+ ##
14
+
15
+
16
+ class _CachedProperty(property, Abstract):
17
+ def __init__(
18
+ self,
19
+ fn,
20
+ *,
21
+ name=None,
22
+ ignore_if=lambda _: False,
23
+ ):
24
+ if isinstance(fn, property):
25
+ fn = fn.fget
26
+
27
+ super().__init__(fn)
28
+
29
+ self._fn = fn
30
+ self._ignore_if = ignore_if
31
+
32
+ self._name = name
33
+
34
+ def __set_name__(self, owner, name):
35
+ if self._name is None:
36
+ self._name = name
37
+
38
+ @abc.abstractmethod
39
+ def _instance_get(self, instance: ta.Any) -> tuple[ta.Any, bool]:
40
+ raise NotImplementedError
41
+
42
+ @abc.abstractmethod
43
+ def _instance_set(self, instance: ta.Any, value: ta.Any) -> None:
44
+ raise NotImplementedError
45
+
46
+ def __get__(self, instance, owner=None):
47
+ if instance is None:
48
+ return self
49
+ if self._name is None:
50
+ raise TypeError(self)
51
+
52
+ value, ok = self._instance_get(instance)
53
+ if ok:
54
+ return value
55
+
56
+ value = self._fn.__get__(instance, owner)()
57
+ if value is _IGNORE:
58
+ return None
59
+
60
+ self._instance_set(instance, value)
61
+ return value
62
+
63
+ def __set__(self, instance, value):
64
+ if self._ignore_if(value):
65
+ return
66
+
67
+ ev, ok = self._instance_get(instance)
68
+ if ok and ev == value:
69
+ return
70
+
71
+ raise TypeError(self._name)
72
+
73
+ def __delete__(self, instance):
74
+ raise TypeError
75
+
76
+
77
+ #
78
+
79
+
80
+ class _DictCachedProperty(_CachedProperty):
81
+ def _instance_get(self, instance: ta.Any) -> tuple[ta.Any, bool]:
82
+ try:
83
+ value = instance.__dict__[self._name]
84
+ except KeyError:
85
+ return None, False
86
+ else:
87
+ return value, True
88
+
89
+ def _instance_set(self, instance: ta.Any, value: ta.Any) -> None:
90
+ instance.__dict__[self._name] = value
91
+
92
+
93
+ #
94
+
95
+
96
+ class _TransientCachedProperty(_CachedProperty):
97
+ def _instance_get(self, instance: ta.Any) -> tuple[ta.Any, bool]:
98
+ try:
99
+ value = transient_getattr(instance, self._name)
100
+ except AttributeError:
101
+ return None, False
102
+ else:
103
+ return value, True
104
+
105
+ def _instance_set(self, instance: ta.Any, value: ta.Any) -> None:
106
+ transient_setattr(instance, self._name, value)
107
+
108
+
109
+ #
110
+
111
+
112
+ def cached_property(fn=None, *, transient=False, **kwargs): # noqa
113
+ if fn is None:
114
+ return functools.partial(cached_property, transient=transient, **kwargs)
115
+ if transient:
116
+ return _TransientCachedProperty(fn, **kwargs)
117
+ else:
118
+ return _DictCachedProperty(fn, **kwargs)
@@ -1,41 +0,0 @@
1
- from .abstract import ( # noqa
2
- Abstract,
3
- AbstractTypeError,
4
- get_abstract_methods,
5
- is_abstract,
6
- is_abstract_class,
7
- is_abstract_method,
8
- make_abstract,
9
- unabstract_class,
10
- )
11
-
12
- from .restrict import ( # noqa
13
- AnySensitive,
14
- Final,
15
- FinalTypeError,
16
- NoBool,
17
- NotInstantiable,
18
- NotPicklable,
19
- PackageSealed,
20
- SENSITIVE_ATTR,
21
- Sealed,
22
- SealedError,
23
- Sensitive,
24
- no_bool,
25
- )
26
-
27
- from .simple import ( # noqa
28
- LazySingleton,
29
- Marker,
30
- Namespace,
31
- SimpleMetaDict,
32
- Singleton,
33
- )
34
-
35
- from .virtual import ( # noqa
36
- Callable,
37
- Descriptor,
38
- Picklable,
39
- Virtual,
40
- virtual_check,
41
- )
@@ -0,0 +1,50 @@
1
+ import collections.abc
2
+ import typing as ta
3
+
4
+
5
+ K = ta.TypeVar('K')
6
+ V = ta.TypeVar('V')
7
+
8
+
9
+ ##
10
+
11
+
12
+ def yield_dict_init(*args: ta.Any, **kwargs: ta.Any) -> ta.Iterable[tuple[ta.Any, ta.Any]]:
13
+ if len(args) > 1:
14
+ raise TypeError
15
+ if args:
16
+ [src] = args
17
+ if isinstance(src, collections.abc.Mapping):
18
+ for k in src:
19
+ yield (k, src[k])
20
+ else:
21
+ for k, v in src:
22
+ yield (k, v)
23
+ for k, v in kwargs.items():
24
+ yield (k, v)
25
+
26
+
27
+ def merge_dicts(
28
+ *dcts: ta.Mapping[K, V],
29
+ pair_fn: ta.Callable[[K, V], tuple[K, V] | None] | None = None,
30
+ conflict_fn: ta.Callable[[K, V, V], tuple[K, V] | None] | None = None,
31
+ ) -> dict[K, V]:
32
+ out: dict[K, V] = {}
33
+
34
+ for d in dcts:
35
+ for k_v in d.items():
36
+ if pair_fn is not None and (k_v := pair_fn(*k_v)) is None: # type: ignore[assignment]
37
+ continue
38
+
39
+ k, v = k_v
40
+ if k in out:
41
+ if conflict_fn is None:
42
+ raise KeyError(k)
43
+
44
+ if (k_v := conflict_fn(k, out[k], v)) is None: # type: ignore[assignment]
45
+ continue
46
+ k, v = k_v
47
+
48
+ out[k] = v
49
+
50
+ return out
@@ -28,6 +28,16 @@ from .base import ( # noqa
28
28
  ReflectOverride,
29
29
  )
30
30
 
31
+ from .composite.iterables import ( # noqa
32
+ IterableMarshaler,
33
+ IterableUnmarshaler,
34
+ )
35
+
36
+ from .composite.wrapped import ( # noqa
37
+ WrappedMarshaler,
38
+ WrappedUnmarshaler,
39
+ )
40
+
31
41
  from .exceptions import ( # noqa
32
42
  ForbiddenTypeError,
33
43
  MarshalError,
@@ -49,6 +59,14 @@ from .naming import ( # noqa
49
59
  translate_name,
50
60
  )
51
61
 
62
+ from .objects.dataclasses import ( # noqa
63
+ AbstractDataclassFactory,
64
+ DataclassMarshalerFactory,
65
+ DataclassUnmarshalerFactory,
66
+ get_dataclass_field_infos,
67
+ get_dataclass_metadata,
68
+ )
69
+
52
70
  from .objects.helpers import ( # noqa
53
71
  update_field_metadata,
54
72
  update_fields_metadata,
@@ -75,14 +93,18 @@ from .objects.unmarshal import ( # noqa
75
93
  )
76
94
 
77
95
  from .polymorphism.marshal import ( # noqa
96
+ PolymorphismMarshaler,
78
97
  PolymorphismMarshalerFactory,
79
98
  make_polymorphism_marshaler,
80
99
  )
81
100
 
82
101
  from .polymorphism.metadata import ( # noqa
102
+ FieldTypeTagging,
83
103
  Impl,
84
104
  Impls,
85
105
  Polymorphism,
106
+ TypeTagging,
107
+ WrapperTypeTagging,
86
108
  polymorphism_from_subclasses,
87
109
  )
88
110
 
@@ -97,6 +119,7 @@ from .polymorphism.unions import ( # noqa
97
119
  )
98
120
 
99
121
  from .polymorphism.unmarshal import ( # noqa
122
+ PolymorphismUnmarshaler,
100
123
  PolymorphismUnmarshalerFactory,
101
124
  make_polymorphism_unmarshaler,
102
125
  )
@@ -0,0 +1,26 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ..base import MarshalContext
5
+ from ..base import Marshaler
6
+ from ..base import UnmarshalContext
7
+ from ..base import Unmarshaler
8
+ from ..values import Value
9
+
10
+
11
+ @dc.dataclass(frozen=True)
12
+ class WrappedMarshaler(Marshaler):
13
+ wrapper: ta.Callable[[MarshalContext, ta.Any], ta.Any]
14
+ m: Marshaler
15
+
16
+ def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
17
+ return self.m.marshal(ctx, self.wrapper(ctx, o))
18
+
19
+
20
+ @dc.dataclass(frozen=True)
21
+ class WrappedUnmarshaler(Unmarshaler):
22
+ unwrapper: ta.Callable[[UnmarshalContext, ta.Any], ta.Any]
23
+ u: Unmarshaler
24
+
25
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
26
+ return self.unwrapper(ctx, self.u.unmarshal(ctx, v))