omdev 0.0.0.dev392__py3-none-any.whl → 0.0.0.dev394__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.
@@ -0,0 +1,1586 @@
1
+ #!/usr/bin/env python3
2
+ # noinspection DuplicatedCode
3
+ # @omlish-lite
4
+ # @omlish-script
5
+ # @omlish-generated
6
+ # @omlish-amalg-output dumping.py
7
+ # @omlish-git-diff-omit
8
+ # ruff: noqa: UP006 UP007 UP036 UP037 UP045
9
+ import abc
10
+ import base64
11
+ import collections
12
+ import collections.abc
13
+ import dataclasses as dc
14
+ import datetime
15
+ import decimal
16
+ import enum
17
+ import fractions
18
+ import functools
19
+ import importlib
20
+ import inspect
21
+ import json
22
+ import sys
23
+ import threading
24
+ import types
25
+ import typing as ta
26
+ import uuid
27
+ import weakref
28
+
29
+
30
+ ########################################
31
+
32
+
33
+ if sys.version_info < (3, 8):
34
+ raise OSError(f'Requires python (3, 8), got {sys.version_info} from {sys.executable}') # noqa
35
+
36
+
37
+ ########################################
38
+
39
+
40
+ # ../../omlish/lite/cached.py
41
+ T = ta.TypeVar('T')
42
+ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
43
+
44
+ # ../../omlish/lite/check.py
45
+ SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
46
+ CheckMessage = ta.Union[str, ta.Callable[..., ta.Optional[str]], None] # ta.TypeAlias
47
+ CheckLateConfigureFn = ta.Callable[['Checks'], None] # ta.TypeAlias
48
+ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
49
+ CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
50
+ CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
51
+
52
+
53
+ ########################################
54
+ # ../../../omlish/lite/cached.py
55
+
56
+
57
+ ##
58
+
59
+
60
+ class _AbstractCachedNullary:
61
+ def __init__(self, fn):
62
+ super().__init__()
63
+ self._fn = fn
64
+ self._value = self._missing = object()
65
+ functools.update_wrapper(self, fn)
66
+
67
+ def __call__(self, *args, **kwargs): # noqa
68
+ raise TypeError
69
+
70
+ def __get__(self, instance, owner): # noqa
71
+ bound = instance.__dict__[self._fn.__name__] = self.__class__(self._fn.__get__(instance, owner))
72
+ return bound
73
+
74
+
75
+ ##
76
+
77
+
78
+ class _CachedNullary(_AbstractCachedNullary):
79
+ def __call__(self, *args, **kwargs): # noqa
80
+ if self._value is self._missing:
81
+ self._value = self._fn()
82
+ return self._value
83
+
84
+
85
+ def cached_nullary(fn: CallableT) -> CallableT:
86
+ return _CachedNullary(fn) # type: ignore
87
+
88
+
89
+ def static_init(fn: CallableT) -> CallableT:
90
+ fn = cached_nullary(fn)
91
+ fn()
92
+ return fn
93
+
94
+
95
+ ##
96
+
97
+
98
+ class _AsyncCachedNullary(_AbstractCachedNullary):
99
+ async def __call__(self, *args, **kwargs):
100
+ if self._value is self._missing:
101
+ self._value = await self._fn()
102
+ return self._value
103
+
104
+
105
+ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
106
+ return _AsyncCachedNullary(fn)
107
+
108
+
109
+ ########################################
110
+ # ../../../omlish/lite/check.py
111
+ """
112
+ TODO:
113
+ - def maybe(v: lang.Maybe[T])
114
+ - def not_ ?
115
+ - ** class @dataclass Raise - user message should be able to be an exception type or instance or factory
116
+ """
117
+
118
+
119
+ ##
120
+
121
+
122
+ class Checks:
123
+ def __init__(self) -> None:
124
+ super().__init__()
125
+
126
+ self._config_lock = threading.RLock()
127
+ self._on_raise_fns: ta.Sequence[CheckOnRaiseFn] = []
128
+ self._exception_factory: CheckExceptionFactory = Checks.default_exception_factory
129
+ self._args_renderer: ta.Optional[CheckArgsRenderer] = None
130
+ self._late_configure_fns: ta.Sequence[CheckLateConfigureFn] = []
131
+
132
+ @staticmethod
133
+ def default_exception_factory(exc_cls: ta.Type[Exception], *args, **kwargs) -> Exception:
134
+ return exc_cls(*args, **kwargs) # noqa
135
+
136
+ #
137
+
138
+ def register_on_raise(self, fn: CheckOnRaiseFn) -> None:
139
+ with self._config_lock:
140
+ self._on_raise_fns = [*self._on_raise_fns, fn]
141
+
142
+ def unregister_on_raise(self, fn: CheckOnRaiseFn) -> None:
143
+ with self._config_lock:
144
+ self._on_raise_fns = [e for e in self._on_raise_fns if e != fn]
145
+
146
+ #
147
+
148
+ def register_on_raise_breakpoint_if_env_var_set(self, key: str) -> None:
149
+ import os
150
+
151
+ def on_raise(exc: Exception) -> None: # noqa
152
+ if key in os.environ:
153
+ breakpoint() # noqa
154
+
155
+ self.register_on_raise(on_raise)
156
+
157
+ #
158
+
159
+ def set_exception_factory(self, factory: CheckExceptionFactory) -> None:
160
+ self._exception_factory = factory
161
+
162
+ def set_args_renderer(self, renderer: ta.Optional[CheckArgsRenderer]) -> None:
163
+ self._args_renderer = renderer
164
+
165
+ #
166
+
167
+ def register_late_configure(self, fn: CheckLateConfigureFn) -> None:
168
+ with self._config_lock:
169
+ self._late_configure_fns = [*self._late_configure_fns, fn]
170
+
171
+ def _late_configure(self) -> None:
172
+ if not self._late_configure_fns:
173
+ return
174
+
175
+ with self._config_lock:
176
+ if not (lc := self._late_configure_fns):
177
+ return
178
+
179
+ for fn in lc:
180
+ fn(self)
181
+
182
+ self._late_configure_fns = []
183
+
184
+ #
185
+
186
+ class _ArgsKwargs:
187
+ def __init__(self, *args, **kwargs):
188
+ self.args = args
189
+ self.kwargs = kwargs
190
+
191
+ def _raise(
192
+ self,
193
+ exception_type: ta.Type[Exception],
194
+ default_message: str,
195
+ message: CheckMessage,
196
+ ak: _ArgsKwargs = _ArgsKwargs(),
197
+ *,
198
+ render_fmt: ta.Optional[str] = None,
199
+ ) -> ta.NoReturn:
200
+ exc_args = ()
201
+ if callable(message):
202
+ message = ta.cast(ta.Callable, message)(*ak.args, **ak.kwargs)
203
+ if isinstance(message, tuple):
204
+ message, *exc_args = message # type: ignore
205
+
206
+ if message is None:
207
+ message = default_message
208
+
209
+ self._late_configure()
210
+
211
+ if render_fmt is not None and (af := self._args_renderer) is not None:
212
+ rendered_args = af(render_fmt, *ak.args)
213
+ if rendered_args is not None:
214
+ message = f'{message} : {rendered_args}'
215
+
216
+ exc = self._exception_factory(
217
+ exception_type,
218
+ message,
219
+ *exc_args,
220
+ *ak.args,
221
+ **ak.kwargs,
222
+ )
223
+
224
+ for fn in self._on_raise_fns:
225
+ fn(exc)
226
+
227
+ raise exc
228
+
229
+ #
230
+
231
+ def _unpack_isinstance_spec(self, spec: ta.Any) -> tuple:
232
+ if isinstance(spec, type):
233
+ return (spec,)
234
+ if not isinstance(spec, tuple):
235
+ spec = (spec,)
236
+ if None in spec:
237
+ spec = tuple(filter(None, spec)) + (None.__class__,) # noqa
238
+ if ta.Any in spec:
239
+ spec = (object,)
240
+ return spec
241
+
242
+ @ta.overload
243
+ def isinstance(self, v: ta.Any, spec: ta.Type[T], msg: CheckMessage = None) -> T:
244
+ ...
245
+
246
+ @ta.overload
247
+ def isinstance(self, v: ta.Any, spec: ta.Any, msg: CheckMessage = None) -> ta.Any:
248
+ ...
249
+
250
+ def isinstance(self, v, spec, msg=None):
251
+ if not isinstance(v, self._unpack_isinstance_spec(spec)):
252
+ self._raise(
253
+ TypeError,
254
+ 'Must be instance',
255
+ msg,
256
+ Checks._ArgsKwargs(v, spec),
257
+ render_fmt='not isinstance(%s, %s)',
258
+ )
259
+
260
+ return v
261
+
262
+ @ta.overload
263
+ def of_isinstance(self, spec: ta.Type[T], msg: CheckMessage = None) -> ta.Callable[[ta.Any], T]:
264
+ ...
265
+
266
+ @ta.overload
267
+ def of_isinstance(self, spec: ta.Any, msg: CheckMessage = None) -> ta.Callable[[ta.Any], ta.Any]:
268
+ ...
269
+
270
+ def of_isinstance(self, spec, msg=None):
271
+ def inner(v):
272
+ return self.isinstance(v, self._unpack_isinstance_spec(spec), msg)
273
+
274
+ return inner
275
+
276
+ def cast(self, v: ta.Any, cls: ta.Type[T], msg: CheckMessage = None) -> T:
277
+ if not isinstance(v, cls):
278
+ self._raise(
279
+ TypeError,
280
+ 'Must be instance',
281
+ msg,
282
+ Checks._ArgsKwargs(v, cls),
283
+ )
284
+
285
+ return v
286
+
287
+ def of_cast(self, cls: ta.Type[T], msg: CheckMessage = None) -> ta.Callable[[T], T]:
288
+ def inner(v):
289
+ return self.cast(v, cls, msg)
290
+
291
+ return inner
292
+
293
+ def not_isinstance(self, v: T, spec: ta.Any, msg: CheckMessage = None) -> T: # noqa
294
+ if isinstance(v, self._unpack_isinstance_spec(spec)):
295
+ self._raise(
296
+ TypeError,
297
+ 'Must not be instance',
298
+ msg,
299
+ Checks._ArgsKwargs(v, spec),
300
+ render_fmt='isinstance(%s, %s)',
301
+ )
302
+
303
+ return v
304
+
305
+ def of_not_isinstance(self, spec: ta.Any, msg: CheckMessage = None) -> ta.Callable[[T], T]:
306
+ def inner(v):
307
+ return self.not_isinstance(v, self._unpack_isinstance_spec(spec), msg)
308
+
309
+ return inner
310
+
311
+ ##
312
+
313
+ def issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]: # noqa
314
+ if not issubclass(v, spec):
315
+ self._raise(
316
+ TypeError,
317
+ 'Must be subclass',
318
+ msg,
319
+ Checks._ArgsKwargs(v, spec),
320
+ render_fmt='not issubclass(%s, %s)',
321
+ )
322
+
323
+ return v
324
+
325
+ def not_issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]:
326
+ if issubclass(v, spec):
327
+ self._raise(
328
+ TypeError,
329
+ 'Must not be subclass',
330
+ msg,
331
+ Checks._ArgsKwargs(v, spec),
332
+ render_fmt='issubclass(%s, %s)',
333
+ )
334
+
335
+ return v
336
+
337
+ #
338
+
339
+ def in_(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
340
+ if v not in c:
341
+ self._raise(
342
+ ValueError,
343
+ 'Must be in',
344
+ msg,
345
+ Checks._ArgsKwargs(v, c),
346
+ render_fmt='%s not in %s',
347
+ )
348
+
349
+ return v
350
+
351
+ def not_in(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
352
+ if v in c:
353
+ self._raise(
354
+ ValueError,
355
+ 'Must not be in',
356
+ msg,
357
+ Checks._ArgsKwargs(v, c),
358
+ render_fmt='%s in %s',
359
+ )
360
+
361
+ return v
362
+
363
+ def empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
364
+ if len(v) != 0:
365
+ self._raise(
366
+ ValueError,
367
+ 'Must be empty',
368
+ msg,
369
+ Checks._ArgsKwargs(v),
370
+ render_fmt='%s',
371
+ )
372
+
373
+ return v
374
+
375
+ def iterempty(self, v: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
376
+ it = iter(v)
377
+ try:
378
+ next(it)
379
+ except StopIteration:
380
+ pass
381
+ else:
382
+ self._raise(
383
+ ValueError,
384
+ 'Must be empty',
385
+ msg,
386
+ Checks._ArgsKwargs(v),
387
+ render_fmt='%s',
388
+ )
389
+
390
+ return v
391
+
392
+ def not_empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
393
+ if len(v) == 0:
394
+ self._raise(
395
+ ValueError,
396
+ 'Must not be empty',
397
+ msg,
398
+ Checks._ArgsKwargs(v),
399
+ render_fmt='%s',
400
+ )
401
+
402
+ return v
403
+
404
+ def unique(self, it: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
405
+ dupes = [e for e, c in collections.Counter(it).items() if c > 1]
406
+ if dupes:
407
+ self._raise(
408
+ ValueError,
409
+ 'Must be unique',
410
+ msg,
411
+ Checks._ArgsKwargs(it, dupes),
412
+ )
413
+
414
+ return it
415
+
416
+ def single(self, obj: ta.Iterable[T], msg: CheckMessage = None) -> T:
417
+ try:
418
+ [value] = obj
419
+ except ValueError:
420
+ self._raise(
421
+ ValueError,
422
+ 'Must be single',
423
+ msg,
424
+ Checks._ArgsKwargs(obj),
425
+ render_fmt='%s',
426
+ )
427
+
428
+ return value
429
+
430
+ def opt_single(self, obj: ta.Iterable[T], msg: CheckMessage = None) -> ta.Optional[T]:
431
+ it = iter(obj)
432
+ try:
433
+ value = next(it)
434
+ except StopIteration:
435
+ return None
436
+
437
+ try:
438
+ next(it)
439
+ except StopIteration:
440
+ return value # noqa
441
+
442
+ self._raise(
443
+ ValueError,
444
+ 'Must be empty or single',
445
+ msg,
446
+ Checks._ArgsKwargs(obj),
447
+ render_fmt='%s',
448
+ )
449
+
450
+ raise RuntimeError # noqa
451
+
452
+ #
453
+
454
+ def none(self, v: ta.Any, msg: CheckMessage = None) -> None:
455
+ if v is not None:
456
+ self._raise(
457
+ ValueError,
458
+ 'Must be None',
459
+ msg,
460
+ Checks._ArgsKwargs(v),
461
+ render_fmt='%s',
462
+ )
463
+
464
+ def not_none(self, v: ta.Optional[T], msg: CheckMessage = None) -> T:
465
+ if v is None:
466
+ self._raise(
467
+ ValueError,
468
+ 'Must not be None',
469
+ msg,
470
+ Checks._ArgsKwargs(v),
471
+ render_fmt='%s',
472
+ )
473
+
474
+ return v
475
+
476
+ #
477
+
478
+ def equal(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
479
+ if o != v:
480
+ self._raise(
481
+ ValueError,
482
+ 'Must be equal',
483
+ msg,
484
+ Checks._ArgsKwargs(v, o),
485
+ render_fmt='%s != %s',
486
+ )
487
+
488
+ return v
489
+
490
+ def not_equal(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
491
+ if o == v:
492
+ self._raise(
493
+ ValueError,
494
+ 'Must not be equal',
495
+ msg,
496
+ Checks._ArgsKwargs(v, o),
497
+ render_fmt='%s == %s',
498
+ )
499
+
500
+ return v
501
+
502
+ def is_(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
503
+ if o is not v:
504
+ self._raise(
505
+ ValueError,
506
+ 'Must be the same',
507
+ msg,
508
+ Checks._ArgsKwargs(v, o),
509
+ render_fmt='%s is not %s',
510
+ )
511
+
512
+ return v
513
+
514
+ def is_not(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
515
+ if o is v:
516
+ self._raise(
517
+ ValueError,
518
+ 'Must not be the same',
519
+ msg,
520
+ Checks._ArgsKwargs(v, o),
521
+ render_fmt='%s is %s',
522
+ )
523
+
524
+ return v
525
+
526
+ def callable(self, v: T, msg: CheckMessage = None) -> T: # noqa
527
+ if not callable(v):
528
+ self._raise(
529
+ TypeError,
530
+ 'Must be callable',
531
+ msg,
532
+ Checks._ArgsKwargs(v),
533
+ render_fmt='%s',
534
+ )
535
+
536
+ return v
537
+
538
+ def non_empty_str(self, v: ta.Optional[str], msg: CheckMessage = None) -> str:
539
+ if not isinstance(v, str) or not v:
540
+ self._raise(
541
+ ValueError,
542
+ 'Must be non-empty str',
543
+ msg,
544
+ Checks._ArgsKwargs(v),
545
+ render_fmt='%s',
546
+ )
547
+
548
+ return v
549
+
550
+ def replacing(self, expected: ta.Any, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
551
+ if old != expected:
552
+ self._raise(
553
+ ValueError,
554
+ 'Must be replacing',
555
+ msg,
556
+ Checks._ArgsKwargs(expected, old, new),
557
+ render_fmt='%s -> %s -> %s',
558
+ )
559
+
560
+ return new
561
+
562
+ def replacing_none(self, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
563
+ if old is not None:
564
+ self._raise(
565
+ ValueError,
566
+ 'Must be replacing None',
567
+ msg,
568
+ Checks._ArgsKwargs(old, new),
569
+ render_fmt='%s -> %s',
570
+ )
571
+
572
+ return new
573
+
574
+ #
575
+
576
+ def arg(self, v: bool, msg: CheckMessage = None) -> None:
577
+ if not v:
578
+ self._raise(
579
+ RuntimeError,
580
+ 'Argument condition not met',
581
+ msg,
582
+ Checks._ArgsKwargs(v),
583
+ render_fmt='%s',
584
+ )
585
+
586
+ def state(self, v: bool, msg: CheckMessage = None) -> None:
587
+ if not v:
588
+ self._raise(
589
+ RuntimeError,
590
+ 'State condition not met',
591
+ msg,
592
+ Checks._ArgsKwargs(v),
593
+ render_fmt='%s',
594
+ )
595
+
596
+
597
+ check = Checks()
598
+
599
+
600
+ ########################################
601
+ # ../../../omlish/lite/reflect.py
602
+
603
+
604
+ ##
605
+
606
+
607
+ _GENERIC_ALIAS_TYPES = (
608
+ ta._GenericAlias, # type: ignore # noqa
609
+ *([ta._SpecialGenericAlias] if hasattr(ta, '_SpecialGenericAlias') else []), # noqa
610
+ )
611
+
612
+
613
+ def is_generic_alias(obj, *, origin: ta.Any = None) -> bool:
614
+ return (
615
+ isinstance(obj, _GENERIC_ALIAS_TYPES) and
616
+ (origin is None or ta.get_origin(obj) is origin)
617
+ )
618
+
619
+
620
+ is_union_alias = functools.partial(is_generic_alias, origin=ta.Union)
621
+ is_callable_alias = functools.partial(is_generic_alias, origin=ta.Callable)
622
+
623
+
624
+ ##
625
+
626
+
627
+ def is_optional_alias(spec: ta.Any) -> bool:
628
+ return (
629
+ isinstance(spec, _GENERIC_ALIAS_TYPES) and # noqa
630
+ ta.get_origin(spec) is ta.Union and
631
+ len(ta.get_args(spec)) == 2 and
632
+ any(a in (None, type(None)) for a in ta.get_args(spec))
633
+ )
634
+
635
+
636
+ def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
637
+ [it] = [it for it in ta.get_args(spec) if it not in (None, type(None))]
638
+ return it
639
+
640
+
641
+ ##
642
+
643
+
644
+ def is_new_type(spec: ta.Any) -> bool:
645
+ if isinstance(ta.NewType, type):
646
+ return isinstance(spec, ta.NewType)
647
+ else:
648
+ # Before https://github.com/python/cpython/commit/c2f33dfc83ab270412bf243fb21f724037effa1a
649
+ return isinstance(spec, types.FunctionType) and spec.__code__ is ta.NewType.__code__.co_consts[1] # type: ignore # noqa
650
+
651
+
652
+ def get_new_type_supertype(spec: ta.Any) -> ta.Any:
653
+ return spec.__supertype__
654
+
655
+
656
+ ##
657
+
658
+
659
+ def is_literal_type(spec: ta.Any) -> bool:
660
+ if hasattr(ta, '_LiteralGenericAlias'):
661
+ return isinstance(spec, ta._LiteralGenericAlias) # noqa
662
+ else:
663
+ return (
664
+ isinstance(spec, ta._GenericAlias) and # type: ignore # noqa
665
+ spec.__origin__ is ta.Literal
666
+ )
667
+
668
+
669
+ def get_literal_type_args(spec: ta.Any) -> ta.Iterable[ta.Any]:
670
+ return spec.__args__
671
+
672
+
673
+ ##
674
+
675
+
676
+ def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
677
+ seen = set()
678
+ todo = list(reversed(cls.__subclasses__()))
679
+ while todo:
680
+ cur = todo.pop()
681
+ if cur in seen:
682
+ continue
683
+ seen.add(cur)
684
+ yield cur
685
+ todo.extend(reversed(cur.__subclasses__()))
686
+
687
+
688
+ ########################################
689
+ # ../../../omlish/lite/strings.py
690
+
691
+
692
+ ##
693
+
694
+
695
+ def camel_case(name: str, *, lower: bool = False) -> str:
696
+ if not name:
697
+ return ''
698
+ s = ''.join(map(str.capitalize, name.split('_'))) # noqa
699
+ if lower:
700
+ s = s[0].lower() + s[1:]
701
+ return s
702
+
703
+
704
+ def snake_case(name: str) -> str:
705
+ uppers: list[int | None] = [i for i, c in enumerate(name) if c.isupper()]
706
+ return '_'.join([name[l:r].lower() for l, r in zip([None, *uppers], [*uppers, None])]).strip('_')
707
+
708
+
709
+ ##
710
+
711
+
712
+ def is_dunder(name: str) -> bool:
713
+ return (
714
+ name[:2] == name[-2:] == '__' and
715
+ name[2:3] != '_' and
716
+ name[-3:-2] != '_' and
717
+ len(name) > 4
718
+ )
719
+
720
+
721
+ def is_sunder(name: str) -> bool:
722
+ return (
723
+ name[0] == name[-1] == '_' and
724
+ name[1:2] != '_' and
725
+ name[-2:-1] != '_' and
726
+ len(name) > 2
727
+ )
728
+
729
+
730
+ ##
731
+
732
+
733
+ def strip_with_newline(s: str) -> str:
734
+ if not s:
735
+ return ''
736
+ return s.strip() + '\n'
737
+
738
+
739
+ @ta.overload
740
+ def split_keep_delimiter(s: str, d: str) -> str:
741
+ ...
742
+
743
+
744
+ @ta.overload
745
+ def split_keep_delimiter(s: bytes, d: bytes) -> bytes:
746
+ ...
747
+
748
+
749
+ def split_keep_delimiter(s, d):
750
+ ps = []
751
+ i = 0
752
+ while i < len(s):
753
+ if (n := s.find(d, i)) < i:
754
+ ps.append(s[i:])
755
+ break
756
+ ps.append(s[i:n + 1])
757
+ i = n + 1
758
+ return ps
759
+
760
+
761
+ ##
762
+
763
+
764
+ def attr_repr(obj: ta.Any, *attrs: str) -> str:
765
+ return f'{type(obj).__name__}({", ".join(f"{attr}={getattr(obj, attr)!r}" for attr in attrs)})'
766
+
767
+
768
+ ##
769
+
770
+
771
+ FORMAT_NUM_BYTES_SUFFIXES: ta.Sequence[str] = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB']
772
+
773
+
774
+ def format_num_bytes(num_bytes: int) -> str:
775
+ for i, suffix in enumerate(FORMAT_NUM_BYTES_SUFFIXES):
776
+ value = num_bytes / 1024 ** i
777
+ if num_bytes < 1024 ** (i + 1):
778
+ if value.is_integer():
779
+ return f'{int(value)}{suffix}'
780
+ else:
781
+ return f'{value:.2f}{suffix}'
782
+
783
+ return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
784
+
785
+
786
+ ########################################
787
+ # ../../../omlish/lite/marshal.py
788
+ """
789
+ TODO:
790
+ - pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
791
+ - Options.sequence_cls = list, mapping_cls = dict, ... - def with_mutable_containers() -> Options
792
+ """
793
+
794
+
795
+ ##
796
+
797
+
798
+ @dc.dataclass(frozen=True)
799
+ class ObjMarshalOptions:
800
+ raw_bytes: bool = False
801
+ non_strict_fields: bool = False
802
+
803
+
804
+ class ObjMarshaler(abc.ABC):
805
+ @abc.abstractmethod
806
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
807
+ raise NotImplementedError
808
+
809
+ @abc.abstractmethod
810
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
811
+ raise NotImplementedError
812
+
813
+
814
+ class NopObjMarshaler(ObjMarshaler):
815
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
816
+ return o
817
+
818
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
819
+ return o
820
+
821
+
822
+ @dc.dataclass()
823
+ class ProxyObjMarshaler(ObjMarshaler):
824
+ m: ta.Optional[ObjMarshaler] = None
825
+
826
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
827
+ return check.not_none(self.m).marshal(o, ctx)
828
+
829
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
830
+ return check.not_none(self.m).unmarshal(o, ctx)
831
+
832
+
833
+ @dc.dataclass(frozen=True)
834
+ class CastObjMarshaler(ObjMarshaler):
835
+ ty: type
836
+
837
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
838
+ return o
839
+
840
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
841
+ return self.ty(o)
842
+
843
+
844
+ class DynamicObjMarshaler(ObjMarshaler):
845
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
846
+ return ctx.manager.marshal_obj(o, opts=ctx.options)
847
+
848
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
849
+ return o
850
+
851
+
852
+ @dc.dataclass(frozen=True)
853
+ class Base64ObjMarshaler(ObjMarshaler):
854
+ ty: type
855
+
856
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
857
+ return base64.b64encode(o).decode('ascii')
858
+
859
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
860
+ return self.ty(base64.b64decode(o))
861
+
862
+
863
+ @dc.dataclass(frozen=True)
864
+ class BytesSwitchedObjMarshaler(ObjMarshaler):
865
+ m: ObjMarshaler
866
+
867
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
868
+ if ctx.options.raw_bytes:
869
+ return o
870
+ return self.m.marshal(o, ctx)
871
+
872
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
873
+ if ctx.options.raw_bytes:
874
+ return o
875
+ return self.m.unmarshal(o, ctx)
876
+
877
+
878
+ @dc.dataclass(frozen=True)
879
+ class EnumObjMarshaler(ObjMarshaler):
880
+ ty: type
881
+
882
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
883
+ return o.name
884
+
885
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
886
+ return self.ty.__members__[o] # type: ignore
887
+
888
+
889
+ @dc.dataclass(frozen=True)
890
+ class OptionalObjMarshaler(ObjMarshaler):
891
+ item: ObjMarshaler
892
+
893
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
894
+ if o is None:
895
+ return None
896
+ return self.item.marshal(o, ctx)
897
+
898
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
899
+ if o is None:
900
+ return None
901
+ return self.item.unmarshal(o, ctx)
902
+
903
+
904
+ @dc.dataclass(frozen=True)
905
+ class PrimitiveUnionObjMarshaler(ObjMarshaler):
906
+ pt: ta.Tuple[type, ...]
907
+ x: ta.Optional[ObjMarshaler] = None
908
+
909
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
910
+ if isinstance(o, self.pt):
911
+ return o
912
+ elif self.x is not None:
913
+ return self.x.marshal(o, ctx)
914
+ else:
915
+ raise TypeError(o)
916
+
917
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
918
+ if isinstance(o, self.pt):
919
+ return o
920
+ elif self.x is not None:
921
+ return self.x.unmarshal(o, ctx)
922
+ else:
923
+ raise TypeError(o)
924
+
925
+
926
+ @dc.dataclass(frozen=True)
927
+ class LiteralObjMarshaler(ObjMarshaler):
928
+ item: ObjMarshaler
929
+ vs: frozenset
930
+
931
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
932
+ return self.item.marshal(check.in_(o, self.vs), ctx)
933
+
934
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
935
+ return check.in_(self.item.unmarshal(o, ctx), self.vs)
936
+
937
+
938
+ @dc.dataclass(frozen=True)
939
+ class MappingObjMarshaler(ObjMarshaler):
940
+ ty: type
941
+ km: ObjMarshaler
942
+ vm: ObjMarshaler
943
+
944
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
945
+ return {self.km.marshal(k, ctx): self.vm.marshal(v, ctx) for k, v in o.items()}
946
+
947
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
948
+ return self.ty((self.km.unmarshal(k, ctx), self.vm.unmarshal(v, ctx)) for k, v in o.items())
949
+
950
+
951
+ @dc.dataclass(frozen=True)
952
+ class IterableObjMarshaler(ObjMarshaler):
953
+ ty: type
954
+ item: ObjMarshaler
955
+
956
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
957
+ return [self.item.marshal(e, ctx) for e in o]
958
+
959
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
960
+ return self.ty(self.item.unmarshal(e, ctx) for e in o)
961
+
962
+
963
+ @dc.dataclass(frozen=True)
964
+ class FieldsObjMarshaler(ObjMarshaler):
965
+ ty: type
966
+
967
+ @dc.dataclass(frozen=True)
968
+ class Field:
969
+ att: str
970
+ key: str
971
+ m: ObjMarshaler
972
+
973
+ omit_if_none: bool = False
974
+
975
+ fs: ta.Sequence[Field]
976
+
977
+ non_strict: bool = False
978
+
979
+ #
980
+
981
+ _fs_by_att: ta.ClassVar[ta.Mapping[str, Field]]
982
+ _fs_by_key: ta.ClassVar[ta.Mapping[str, Field]]
983
+
984
+ def __post_init__(self) -> None:
985
+ fs_by_att: dict = {}
986
+ fs_by_key: dict = {}
987
+ for f in self.fs:
988
+ check.not_in(check.non_empty_str(f.att), fs_by_att)
989
+ check.not_in(check.non_empty_str(f.key), fs_by_key)
990
+ fs_by_att[f.att] = f
991
+ fs_by_key[f.key] = f
992
+ self.__dict__['_fs_by_att'] = fs_by_att
993
+ self.__dict__['_fs_by_key'] = fs_by_key
994
+
995
+ #
996
+
997
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
998
+ d = {}
999
+ for f in self.fs:
1000
+ mv = f.m.marshal(getattr(o, f.att), ctx)
1001
+ if mv is None and f.omit_if_none:
1002
+ continue
1003
+ d[f.key] = mv
1004
+ return d
1005
+
1006
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1007
+ kw = {}
1008
+ for k, v in o.items():
1009
+ if (f := self._fs_by_key.get(k)) is None:
1010
+ if not (self.non_strict or ctx.options.non_strict_fields):
1011
+ raise KeyError(k)
1012
+ continue
1013
+ kw[f.att] = f.m.unmarshal(v, ctx)
1014
+ return self.ty(**kw)
1015
+
1016
+
1017
+ @dc.dataclass(frozen=True)
1018
+ class SingleFieldObjMarshaler(ObjMarshaler):
1019
+ ty: type
1020
+ fld: str
1021
+
1022
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1023
+ return getattr(o, self.fld)
1024
+
1025
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1026
+ return self.ty(**{self.fld: o})
1027
+
1028
+
1029
+ @dc.dataclass(frozen=True)
1030
+ class PolymorphicObjMarshaler(ObjMarshaler):
1031
+ class Impl(ta.NamedTuple):
1032
+ ty: type
1033
+ tag: str
1034
+ m: ObjMarshaler
1035
+
1036
+ impls_by_ty: ta.Mapping[type, Impl]
1037
+ impls_by_tag: ta.Mapping[str, Impl]
1038
+
1039
+ @classmethod
1040
+ def of(cls, impls: ta.Iterable[Impl]) -> 'PolymorphicObjMarshaler':
1041
+ return cls(
1042
+ {i.ty: i for i in impls},
1043
+ {i.tag: i for i in impls},
1044
+ )
1045
+
1046
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1047
+ impl = self.impls_by_ty[type(o)]
1048
+ return {impl.tag: impl.m.marshal(o, ctx)}
1049
+
1050
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1051
+ [(t, v)] = o.items()
1052
+ impl = self.impls_by_tag[t]
1053
+ return impl.m.unmarshal(v, ctx)
1054
+
1055
+
1056
+ @dc.dataclass(frozen=True)
1057
+ class DatetimeObjMarshaler(ObjMarshaler):
1058
+ ty: type
1059
+
1060
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1061
+ return o.isoformat()
1062
+
1063
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1064
+ return self.ty.fromisoformat(o) # type: ignore
1065
+
1066
+
1067
+ class DecimalObjMarshaler(ObjMarshaler):
1068
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1069
+ return str(check.isinstance(o, decimal.Decimal))
1070
+
1071
+ def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1072
+ return decimal.Decimal(check.isinstance(v, str))
1073
+
1074
+
1075
+ class FractionObjMarshaler(ObjMarshaler):
1076
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1077
+ fr = check.isinstance(o, fractions.Fraction)
1078
+ return [fr.numerator, fr.denominator]
1079
+
1080
+ def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1081
+ num, denom = check.isinstance(v, list)
1082
+ return fractions.Fraction(num, denom)
1083
+
1084
+
1085
+ class UuidObjMarshaler(ObjMarshaler):
1086
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1087
+ return str(o)
1088
+
1089
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1090
+ return uuid.UUID(o)
1091
+
1092
+
1093
+ ##
1094
+
1095
+
1096
+ _DEFAULT_OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
1097
+ **{t: NopObjMarshaler() for t in (type(None),)},
1098
+ **{t: CastObjMarshaler(t) for t in (int, float, str, bool)},
1099
+ **{t: BytesSwitchedObjMarshaler(Base64ObjMarshaler(t)) for t in (bytes, bytearray)},
1100
+ **{t: IterableObjMarshaler(t, DynamicObjMarshaler()) for t in (list, tuple, set, frozenset)},
1101
+ **{t: MappingObjMarshaler(t, DynamicObjMarshaler(), DynamicObjMarshaler()) for t in (dict,)},
1102
+
1103
+ **{t: DynamicObjMarshaler() for t in (ta.Any, object)},
1104
+
1105
+ **{t: DatetimeObjMarshaler(t) for t in (datetime.date, datetime.time, datetime.datetime)},
1106
+ decimal.Decimal: DecimalObjMarshaler(),
1107
+ fractions.Fraction: FractionObjMarshaler(),
1108
+ uuid.UUID: UuidObjMarshaler(),
1109
+ }
1110
+
1111
+ _OBJ_MARSHALER_GENERIC_MAPPING_TYPES: ta.Dict[ta.Any, type] = {
1112
+ **{t: t for t in (dict,)},
1113
+ **{t: dict for t in (collections.abc.Mapping, collections.abc.MutableMapping)}, # noqa
1114
+ }
1115
+
1116
+ _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES: ta.Dict[ta.Any, type] = {
1117
+ **{t: t for t in (list, tuple, set, frozenset)},
1118
+ collections.abc.Set: frozenset,
1119
+ collections.abc.MutableSet: set,
1120
+ collections.abc.Sequence: tuple,
1121
+ collections.abc.MutableSequence: list,
1122
+ }
1123
+
1124
+ _OBJ_MARSHALER_PRIMITIVE_TYPES: ta.Set[type] = {
1125
+ int,
1126
+ float,
1127
+ bool,
1128
+ str,
1129
+ }
1130
+
1131
+
1132
+ ##
1133
+
1134
+
1135
+ _REGISTERED_OBJ_MARSHALERS_BY_TYPE: ta.MutableMapping[type, ObjMarshaler] = weakref.WeakKeyDictionary()
1136
+
1137
+
1138
+ def register_type_obj_marshaler(ty: type, om: ObjMarshaler) -> None:
1139
+ _REGISTERED_OBJ_MARSHALERS_BY_TYPE[ty] = om
1140
+
1141
+
1142
+ def register_single_field_type_obj_marshaler(fld, ty=None):
1143
+ def inner(ty): # noqa
1144
+ register_type_obj_marshaler(ty, SingleFieldObjMarshaler(ty, fld))
1145
+ return ty
1146
+
1147
+ if ty is not None:
1148
+ return inner(ty)
1149
+ else:
1150
+ return inner
1151
+
1152
+
1153
+ ##
1154
+
1155
+
1156
+ class ObjMarshalerFieldMetadata:
1157
+ def __new__(cls, *args, **kwargs): # noqa
1158
+ raise TypeError
1159
+
1160
+
1161
+ class OBJ_MARSHALER_FIELD_KEY(ObjMarshalerFieldMetadata): # noqa
1162
+ pass
1163
+
1164
+
1165
+ class OBJ_MARSHALER_OMIT_IF_NONE(ObjMarshalerFieldMetadata): # noqa
1166
+ pass
1167
+
1168
+
1169
+ ##
1170
+
1171
+
1172
+ class ObjMarshalerManager:
1173
+ def __init__(
1174
+ self,
1175
+ *,
1176
+ default_options: ObjMarshalOptions = ObjMarshalOptions(),
1177
+
1178
+ default_obj_marshalers: ta.Dict[ta.Any, ObjMarshaler] = _DEFAULT_OBJ_MARSHALERS, # noqa
1179
+ generic_mapping_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_MAPPING_TYPES, # noqa
1180
+ generic_iterable_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES, # noqa
1181
+
1182
+ registered_obj_marshalers: ta.Mapping[type, ObjMarshaler] = _REGISTERED_OBJ_MARSHALERS_BY_TYPE,
1183
+ ) -> None:
1184
+ super().__init__()
1185
+
1186
+ self._default_options = default_options
1187
+
1188
+ self._obj_marshalers = dict(default_obj_marshalers)
1189
+ self._generic_mapping_types = generic_mapping_types
1190
+ self._generic_iterable_types = generic_iterable_types
1191
+ self._registered_obj_marshalers = registered_obj_marshalers
1192
+
1193
+ self._lock = threading.RLock()
1194
+ self._marshalers: ta.Dict[ta.Any, ObjMarshaler] = dict(_DEFAULT_OBJ_MARSHALERS)
1195
+ self._proxies: ta.Dict[ta.Any, ProxyObjMarshaler] = {}
1196
+
1197
+ #
1198
+
1199
+ def make_obj_marshaler(
1200
+ self,
1201
+ ty: ta.Any,
1202
+ rec: ta.Callable[[ta.Any], ObjMarshaler],
1203
+ *,
1204
+ non_strict_fields: bool = False,
1205
+ ) -> ObjMarshaler:
1206
+ if isinstance(ty, type):
1207
+ if (reg := self._registered_obj_marshalers.get(ty)) is not None:
1208
+ return reg
1209
+
1210
+ if abc.ABC in ty.__bases__:
1211
+ tn = ty.__name__
1212
+ impls: ta.List[ta.Tuple[type, str]] = [ # type: ignore[var-annotated]
1213
+ (ity, ity.__name__)
1214
+ for ity in deep_subclasses(ty)
1215
+ if abc.ABC not in ity.__bases__
1216
+ ]
1217
+
1218
+ if all(itn.endswith(tn) for _, itn in impls):
1219
+ impls = [
1220
+ (ity, snake_case(itn[:-len(tn)]))
1221
+ for ity, itn in impls
1222
+ ]
1223
+
1224
+ dupe_tns = sorted(
1225
+ dn
1226
+ for dn, dc in collections.Counter(itn for _, itn in impls).items()
1227
+ if dc > 1
1228
+ )
1229
+ if dupe_tns:
1230
+ raise KeyError(f'Duplicate impl names for {ty}: {dupe_tns}')
1231
+
1232
+ return PolymorphicObjMarshaler.of([
1233
+ PolymorphicObjMarshaler.Impl(
1234
+ ity,
1235
+ itn,
1236
+ rec(ity),
1237
+ )
1238
+ for ity, itn in impls
1239
+ ])
1240
+
1241
+ if issubclass(ty, enum.Enum):
1242
+ return EnumObjMarshaler(ty)
1243
+
1244
+ if dc.is_dataclass(ty):
1245
+ return FieldsObjMarshaler(
1246
+ ty,
1247
+ [
1248
+ FieldsObjMarshaler.Field(
1249
+ att=f.name,
1250
+ key=check.non_empty_str(fk),
1251
+ m=rec(f.type),
1252
+ omit_if_none=check.isinstance(f.metadata.get(OBJ_MARSHALER_OMIT_IF_NONE, False), bool),
1253
+ )
1254
+ for f in dc.fields(ty)
1255
+ if (fk := f.metadata.get(OBJ_MARSHALER_FIELD_KEY, f.name)) is not None
1256
+ ],
1257
+ non_strict=non_strict_fields,
1258
+ )
1259
+
1260
+ if issubclass(ty, tuple) and hasattr(ty, '_fields'):
1261
+ return FieldsObjMarshaler(
1262
+ ty,
1263
+ [
1264
+ FieldsObjMarshaler.Field(
1265
+ att=p.name,
1266
+ key=p.name,
1267
+ m=rec(p.annotation),
1268
+ )
1269
+ for p in inspect.signature(ty).parameters.values()
1270
+ ],
1271
+ non_strict=non_strict_fields,
1272
+ )
1273
+
1274
+ if is_new_type(ty):
1275
+ return rec(get_new_type_supertype(ty))
1276
+
1277
+ if is_literal_type(ty):
1278
+ lvs = frozenset(get_literal_type_args(ty))
1279
+ if None in lvs:
1280
+ is_opt = True
1281
+ lvs -= frozenset([None])
1282
+ else:
1283
+ is_opt = False
1284
+ lty = check.single(set(map(type, lvs)))
1285
+ lm: ObjMarshaler = LiteralObjMarshaler(rec(lty), lvs)
1286
+ if is_opt:
1287
+ lm = OptionalObjMarshaler(lm)
1288
+ return lm
1289
+
1290
+ if is_generic_alias(ty):
1291
+ try:
1292
+ mt = self._generic_mapping_types[ta.get_origin(ty)]
1293
+ except KeyError:
1294
+ pass
1295
+ else:
1296
+ k, v = ta.get_args(ty)
1297
+ return MappingObjMarshaler(mt, rec(k), rec(v))
1298
+
1299
+ try:
1300
+ st = self._generic_iterable_types[ta.get_origin(ty)]
1301
+ except KeyError:
1302
+ pass
1303
+ else:
1304
+ [e] = ta.get_args(ty)
1305
+ return IterableObjMarshaler(st, rec(e))
1306
+
1307
+ if is_union_alias(ty):
1308
+ uts = frozenset(ta.get_args(ty))
1309
+ if None in uts or type(None) in uts:
1310
+ is_opt = True
1311
+ uts = frozenset(ut for ut in uts if ut not in (None, type(None)))
1312
+ else:
1313
+ is_opt = False
1314
+
1315
+ um: ObjMarshaler
1316
+ if not uts:
1317
+ raise TypeError(ty)
1318
+ elif len(uts) == 1:
1319
+ um = rec(check.single(uts))
1320
+ else:
1321
+ pt = tuple({ut for ut in uts if ut in _OBJ_MARSHALER_PRIMITIVE_TYPES})
1322
+ np_uts = {ut for ut in uts if ut not in _OBJ_MARSHALER_PRIMITIVE_TYPES}
1323
+ if not np_uts:
1324
+ um = PrimitiveUnionObjMarshaler(pt)
1325
+ elif len(np_uts) == 1:
1326
+ um = PrimitiveUnionObjMarshaler(pt, x=rec(check.single(np_uts)))
1327
+ else:
1328
+ raise TypeError(ty)
1329
+
1330
+ if is_opt:
1331
+ um = OptionalObjMarshaler(um)
1332
+ return um
1333
+
1334
+ raise TypeError(ty)
1335
+
1336
+ #
1337
+
1338
+ def set_obj_marshaler(
1339
+ self,
1340
+ ty: ta.Any,
1341
+ m: ObjMarshaler,
1342
+ *,
1343
+ override: bool = False,
1344
+ ) -> None:
1345
+ with self._lock:
1346
+ if not override and ty in self._obj_marshalers:
1347
+ raise KeyError(ty)
1348
+ self._obj_marshalers[ty] = m
1349
+
1350
+ def get_obj_marshaler(
1351
+ self,
1352
+ ty: ta.Any,
1353
+ *,
1354
+ no_cache: bool = False,
1355
+ **kwargs: ta.Any,
1356
+ ) -> ObjMarshaler:
1357
+ with self._lock:
1358
+ if not no_cache:
1359
+ try:
1360
+ return self._obj_marshalers[ty]
1361
+ except KeyError:
1362
+ pass
1363
+
1364
+ try:
1365
+ return self._proxies[ty]
1366
+ except KeyError:
1367
+ pass
1368
+
1369
+ rec = functools.partial(
1370
+ self.get_obj_marshaler,
1371
+ no_cache=no_cache,
1372
+ **kwargs,
1373
+ )
1374
+
1375
+ p = ProxyObjMarshaler()
1376
+ self._proxies[ty] = p
1377
+ try:
1378
+ m = self.make_obj_marshaler(ty, rec, **kwargs)
1379
+ finally:
1380
+ del self._proxies[ty]
1381
+ p.m = m
1382
+
1383
+ if not no_cache:
1384
+ self._obj_marshalers[ty] = m
1385
+ return m
1386
+
1387
+ #
1388
+
1389
+ def _make_context(self, opts: ta.Optional[ObjMarshalOptions]) -> 'ObjMarshalContext':
1390
+ return ObjMarshalContext(
1391
+ options=opts or self._default_options,
1392
+ manager=self,
1393
+ )
1394
+
1395
+ def marshal_obj(
1396
+ self,
1397
+ o: ta.Any,
1398
+ ty: ta.Any = None,
1399
+ opts: ta.Optional[ObjMarshalOptions] = None,
1400
+ ) -> ta.Any:
1401
+ m = self.get_obj_marshaler(ty if ty is not None else type(o))
1402
+ return m.marshal(o, self._make_context(opts))
1403
+
1404
+ def unmarshal_obj(
1405
+ self,
1406
+ o: ta.Any,
1407
+ ty: ta.Union[ta.Type[T], ta.Any],
1408
+ opts: ta.Optional[ObjMarshalOptions] = None,
1409
+ ) -> T:
1410
+ m = self.get_obj_marshaler(ty)
1411
+ return m.unmarshal(o, self._make_context(opts))
1412
+
1413
+ def roundtrip_obj(
1414
+ self,
1415
+ o: ta.Any,
1416
+ ty: ta.Any = None,
1417
+ opts: ta.Optional[ObjMarshalOptions] = None,
1418
+ ) -> ta.Any:
1419
+ if ty is None:
1420
+ ty = type(o)
1421
+ m: ta.Any = self.marshal_obj(o, ty, opts)
1422
+ u: ta.Any = self.unmarshal_obj(m, ty, opts)
1423
+ return u
1424
+
1425
+
1426
+ @dc.dataclass(frozen=True)
1427
+ class ObjMarshalContext:
1428
+ options: ObjMarshalOptions
1429
+ manager: ObjMarshalerManager
1430
+
1431
+
1432
+ ##
1433
+
1434
+
1435
+ OBJ_MARSHALER_MANAGER = ObjMarshalerManager()
1436
+
1437
+ set_obj_marshaler = OBJ_MARSHALER_MANAGER.set_obj_marshaler
1438
+ get_obj_marshaler = OBJ_MARSHALER_MANAGER.get_obj_marshaler
1439
+
1440
+ marshal_obj = OBJ_MARSHALER_MANAGER.marshal_obj
1441
+ unmarshal_obj = OBJ_MARSHALER_MANAGER.unmarshal_obj
1442
+
1443
+
1444
+ ########################################
1445
+ # dumping.py
1446
+
1447
+
1448
+ ##
1449
+
1450
+
1451
+ class _ModuleManifestDumper:
1452
+ def __init__(
1453
+ self,
1454
+ spec: str,
1455
+ *,
1456
+ output: ta.Optional[ta.Callable[[str], None]] = None,
1457
+ ) -> None:
1458
+ super().__init__()
1459
+
1460
+ self._spec = spec
1461
+ if output is None:
1462
+ output = print
1463
+ self._output = output
1464
+
1465
+ #
1466
+
1467
+ @cached_nullary
1468
+ def _mod(self) -> ta.Any:
1469
+ return importlib.import_module(self._spec)
1470
+
1471
+ #
1472
+
1473
+ def _build_manifest_dct(self, manifest: ta.Any) -> ta.Mapping[str, ta.Any]:
1474
+ manifest_json = json.dumps(marshal_obj(manifest))
1475
+ manifest_dct = json.loads(manifest_json)
1476
+
1477
+ rt_manifest: ta.Any = unmarshal_obj(manifest_dct, type(manifest))
1478
+ rt_manifest_json: ta.Any = json.dumps(marshal_obj(rt_manifest))
1479
+ rt_manifest_dct: ta.Any = json.loads(rt_manifest_json)
1480
+ if rt_manifest_dct != manifest_dct:
1481
+ raise Exception(
1482
+ f'Manifest failed to roundtrip: '
1483
+ f'{manifest} => {manifest_dct} != {rt_manifest} => {rt_manifest_dct}',
1484
+ )
1485
+
1486
+ return manifest_dct
1487
+
1488
+ #
1489
+
1490
+ def _load_attr_manifest(self, target: dict) -> dict:
1491
+ attr = target['attr']
1492
+ manifest = getattr(self._mod(), attr)
1493
+
1494
+ if dc.is_dataclass(manifest):
1495
+ # Support static dataclasses
1496
+ if isinstance(manifest, type):
1497
+ manifest = manifest()
1498
+
1499
+ manifest_dct = self._build_manifest_dct(manifest)
1500
+
1501
+ cls = type(manifest)
1502
+ key = f'${cls.__module__}.{cls.__qualname__}'
1503
+
1504
+ return {key: manifest_dct}
1505
+
1506
+ elif isinstance(manifest, collections.abc.Mapping):
1507
+ [(key, manifest_dct)] = manifest.items()
1508
+ if not key.startswith('$'): # noqa
1509
+ raise Exception(f'Bad key: {key}')
1510
+
1511
+ if not isinstance(manifest_dct, collections.abc.Mapping):
1512
+ raise Exception(f'Bad value: {manifest_dct}')
1513
+
1514
+ manifest_json = json.dumps(manifest_dct)
1515
+
1516
+ rt_manifest_dct = json.loads(manifest_json)
1517
+ if rt_manifest_dct != manifest_dct:
1518
+ raise Exception(f'Manifest failed to roundtrip: {manifest_dct} != {rt_manifest_dct}')
1519
+
1520
+ return {key: manifest_dct}
1521
+
1522
+ else:
1523
+ raise TypeError(f'Manifest must be dataclass or mapping: {manifest!r}')
1524
+
1525
+ #
1526
+
1527
+ class _LazyGlobals(dict):
1528
+ def __init__(self, get_missing: ta.Callable[[str], ta.Any]) -> None:
1529
+ super().__init__()
1530
+
1531
+ self.__get_missing = get_missing
1532
+
1533
+ def __missing__(self, key):
1534
+ return self.__get_missing(key)
1535
+
1536
+ def _load_inline_manifest(self, target: dict) -> dict:
1537
+ cls: ta.Any = importlib.import_module(target['cls_mod_name'])
1538
+ for p in target['cls_qualname'].split('.'):
1539
+ cls = getattr(cls, p)
1540
+ if not isinstance(cls, type) or not dc.is_dataclass(cls):
1541
+ raise TypeError(cls)
1542
+
1543
+ cls_fac = functools.partial(cls, **target['kwargs'])
1544
+ eval_attr_name = '__manifest_factory__'
1545
+
1546
+ inl_glo = self._LazyGlobals(lambda k: getattr(self._mod(), k))
1547
+ inl_glo.update({
1548
+ eval_attr_name: cls_fac,
1549
+ })
1550
+
1551
+ inl_src = eval_attr_name + target['init_src']
1552
+ inl_code = compile(inl_src, '<magic>', 'eval')
1553
+
1554
+ manifest = eval(inl_code, inl_glo) # noqa
1555
+
1556
+ manifest_dct = self._build_manifest_dct(manifest)
1557
+
1558
+ key = f'${cls.__module__}.{cls.__qualname__}'
1559
+ return {key: manifest_dct}
1560
+
1561
+ #
1562
+
1563
+ def __call__(
1564
+ self,
1565
+ *targets: dict, # .build.ManifestDumperTarget
1566
+ ) -> None:
1567
+ out = []
1568
+ for target in targets:
1569
+ origin = target['origin']
1570
+
1571
+ if target['kind'] == 'attr':
1572
+ out_value = self._load_attr_manifest(target)
1573
+
1574
+ elif target['kind'] == 'inline':
1575
+ out_value = self._load_inline_manifest(target)
1576
+
1577
+ else:
1578
+ raise ValueError(target)
1579
+
1580
+ out.append({
1581
+ **origin,
1582
+ 'value': out_value,
1583
+ })
1584
+
1585
+ out_json = json.dumps(out, indent=None, separators=(',', ':'))
1586
+ self._output(out_json)