omdev 0.0.0.dev440__py3-none-any.whl → 0.0.0.dev495__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.

Potentially problematic release.


This version of omdev might be problematic. Click here for more details.

Files changed (148) hide show
  1. omdev/.omlish-manifests.json +18 -30
  2. omdev/README.md +51 -0
  3. omdev/__about__.py +11 -7
  4. omdev/amalg/gen/gen.py +49 -6
  5. omdev/amalg/gen/imports.py +1 -1
  6. omdev/amalg/gen/manifests.py +1 -1
  7. omdev/amalg/gen/resources.py +1 -1
  8. omdev/amalg/gen/srcfiles.py +13 -3
  9. omdev/amalg/gen/strip.py +1 -1
  10. omdev/amalg/gen/types.py +1 -1
  11. omdev/amalg/gen/typing.py +1 -1
  12. omdev/amalg/info.py +32 -0
  13. omdev/cache/data/actions.py +1 -1
  14. omdev/cache/data/specs.py +1 -1
  15. omdev/cexts/_boilerplate.cc +2 -3
  16. omdev/cexts/cmake.py +4 -1
  17. omdev/ci/cli.py +2 -3
  18. omdev/cli/clicli.py +37 -7
  19. omdev/cmdlog/cli.py +1 -2
  20. omdev/dataclasses/_dumping.py +1960 -0
  21. omdev/dataclasses/_template.py +22 -0
  22. omdev/dataclasses/cli.py +7 -2
  23. omdev/dataclasses/codegen.py +340 -60
  24. omdev/dataclasses/dumping.py +200 -0
  25. omdev/interp/cli.py +1 -1
  26. omdev/interp/types.py +3 -2
  27. omdev/interp/uv/provider.py +37 -0
  28. omdev/interp/venvs.py +1 -0
  29. omdev/irc/messages/base.py +50 -0
  30. omdev/irc/messages/formats.py +92 -0
  31. omdev/irc/messages/messages.py +775 -0
  32. omdev/irc/messages/parsing.py +99 -0
  33. omdev/irc/numerics/__init__.py +0 -0
  34. omdev/irc/numerics/formats.py +97 -0
  35. omdev/irc/numerics/numerics.py +865 -0
  36. omdev/irc/numerics/types.py +59 -0
  37. omdev/irc/protocol/LICENSE +11 -0
  38. omdev/irc/protocol/__init__.py +61 -0
  39. omdev/irc/protocol/consts.py +6 -0
  40. omdev/irc/protocol/errors.py +30 -0
  41. omdev/irc/protocol/message.py +21 -0
  42. omdev/irc/protocol/nuh.py +55 -0
  43. omdev/irc/protocol/parsing.py +158 -0
  44. omdev/irc/protocol/rendering.py +153 -0
  45. omdev/irc/protocol/tags.py +102 -0
  46. omdev/irc/protocol/utils.py +30 -0
  47. omdev/manifests/_dumping.py +125 -25
  48. omdev/manifests/main.py +1 -1
  49. omdev/markdown/__init__.py +0 -0
  50. omdev/markdown/incparse.py +116 -0
  51. omdev/markdown/tokens.py +51 -0
  52. omdev/packaging/marshal.py +8 -8
  53. omdev/packaging/requires.py +6 -6
  54. omdev/packaging/revisions.py +1 -1
  55. omdev/packaging/specifiers.py +2 -1
  56. omdev/packaging/versions.py +4 -4
  57. omdev/packaging/wheelfile.py +2 -0
  58. omdev/precheck/blanklines.py +66 -0
  59. omdev/precheck/caches.py +1 -1
  60. omdev/precheck/imports.py +14 -1
  61. omdev/precheck/main.py +4 -3
  62. omdev/precheck/unicode.py +39 -15
  63. omdev/py/asts/__init__.py +0 -0
  64. omdev/py/asts/parents.py +28 -0
  65. omdev/py/asts/toplevel.py +123 -0
  66. omdev/py/asts/visitors.py +18 -0
  67. omdev/py/attrdocs.py +1 -1
  68. omdev/py/bracepy.py +12 -4
  69. omdev/py/reprs.py +32 -0
  70. omdev/py/srcheaders.py +1 -1
  71. omdev/py/tokens/__init__.py +0 -0
  72. omdev/py/tools/mkrelimp.py +1 -1
  73. omdev/py/tools/pipdepup.py +686 -0
  74. omdev/pyproject/cli.py +1 -1
  75. omdev/pyproject/pkg.py +190 -45
  76. omdev/pyproject/reqs.py +31 -9
  77. omdev/pyproject/tools/__init__.py +0 -0
  78. omdev/pyproject/tools/aboutdeps.py +60 -0
  79. omdev/pyproject/venvs.py +8 -1
  80. omdev/rs/__init__.py +0 -0
  81. omdev/scripts/ci.py +752 -98
  82. omdev/scripts/interp.py +232 -39
  83. omdev/scripts/lib/inject.py +74 -27
  84. omdev/scripts/lib/logs.py +187 -43
  85. omdev/scripts/lib/marshal.py +67 -25
  86. omdev/scripts/pyproject.py +1369 -143
  87. omdev/tools/git/cli.py +10 -0
  88. omdev/tools/json/formats.py +2 -0
  89. omdev/tools/json/processing.py +5 -2
  90. omdev/tools/jsonview/cli.py +49 -65
  91. omdev/tools/jsonview/resources/jsonview.html.j2 +43 -0
  92. omdev/tools/pawk/README.md +195 -0
  93. omdev/tools/pawk/pawk.py +2 -2
  94. omdev/tools/pip.py +8 -0
  95. omdev/tui/__init__.py +0 -0
  96. omdev/tui/apps/__init__.py +0 -0
  97. omdev/tui/apps/edit/__init__.py +0 -0
  98. omdev/tui/apps/edit/main.py +167 -0
  99. omdev/tui/apps/irc/__init__.py +0 -0
  100. omdev/tui/apps/irc/__main__.py +4 -0
  101. omdev/tui/apps/irc/app.py +286 -0
  102. omdev/tui/apps/irc/client.py +187 -0
  103. omdev/tui/apps/irc/commands.py +175 -0
  104. omdev/tui/apps/irc/main.py +26 -0
  105. omdev/tui/apps/markdown/__init__.py +0 -0
  106. omdev/tui/apps/markdown/__main__.py +11 -0
  107. omdev/{ptk → tui/apps}/markdown/cli.py +5 -7
  108. omdev/tui/rich/__init__.py +46 -0
  109. omdev/tui/rich/console2.py +20 -0
  110. omdev/tui/rich/markdown2.py +186 -0
  111. omdev/tui/textual/__init__.py +265 -0
  112. omdev/tui/textual/app2.py +16 -0
  113. omdev/tui/textual/autocomplete/LICENSE +21 -0
  114. omdev/tui/textual/autocomplete/__init__.py +33 -0
  115. omdev/tui/textual/autocomplete/matching.py +226 -0
  116. omdev/tui/textual/autocomplete/paths.py +202 -0
  117. omdev/tui/textual/autocomplete/widget.py +612 -0
  118. omdev/tui/textual/debug/__init__.py +10 -0
  119. omdev/tui/textual/debug/dominfo.py +151 -0
  120. omdev/tui/textual/debug/screen.py +24 -0
  121. omdev/tui/textual/devtools.py +187 -0
  122. omdev/tui/textual/drivers2.py +55 -0
  123. omdev/tui/textual/logging2.py +20 -0
  124. omdev/tui/textual/types.py +45 -0
  125. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/METADATA +15 -9
  126. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/RECORD +135 -80
  127. omdev/ptk/__init__.py +0 -103
  128. omdev/ptk/apps/ncdu.py +0 -167
  129. omdev/ptk/confirm.py +0 -60
  130. omdev/ptk/markdown/LICENSE +0 -22
  131. omdev/ptk/markdown/__init__.py +0 -10
  132. omdev/ptk/markdown/__main__.py +0 -11
  133. omdev/ptk/markdown/border.py +0 -94
  134. omdev/ptk/markdown/markdown.py +0 -390
  135. omdev/ptk/markdown/parser.py +0 -42
  136. omdev/ptk/markdown/styles.py +0 -29
  137. omdev/ptk/markdown/tags.py +0 -299
  138. omdev/ptk/markdown/utils.py +0 -366
  139. omdev/pyproject/cexts.py +0 -110
  140. /omdev/{ptk/apps → irc}/__init__.py +0 -0
  141. /omdev/{tokens → irc/messages}/__init__.py +0 -0
  142. /omdev/{tokens → py/tokens}/all.py +0 -0
  143. /omdev/{tokens → py/tokens}/tokenizert.py +0 -0
  144. /omdev/{tokens → py/tokens}/utils.py +0 -0
  145. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/WHEEL +0 -0
  146. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/entry_points.txt +0 -0
  147. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/licenses/LICENSE +0 -0
  148. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1960 @@
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 inspect
20
+ import json
21
+ import os.path
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
+ def __omlish_amalg__(): # noqa
38
+ return dict(
39
+ src_files=[
40
+ dict(path='../../omlish/lite/abstract.py', sha1='a2fc3f3697fa8de5247761e9d554e70176f37aac'),
41
+ dict(path='../../omlish/lite/check.py', sha1='bb6b6b63333699b84462951a854d99ae83195b94'),
42
+ dict(path='../../omlish/lite/json.py', sha1='57eeddc4d23a17931e00284ffa5cb6e3ce089486'),
43
+ dict(path='../../omlish/lite/objects.py', sha1='9566bbf3530fd71fcc56321485216b592fae21e9'),
44
+ dict(path='../../omlish/lite/reflect.py', sha1='c4fec44bf144e9d93293c996af06f6c65fc5e63d'),
45
+ dict(path='../../omlish/lite/strings.py', sha1='89831ecbc34ad80e118a865eceb390ed399dc4d6'),
46
+ dict(path='../../omlish/lite/marshal.py', sha1='96348f5f2a26dc27d842d33cc3927e9da163436b'),
47
+ dict(path='dumping.py', sha1='982d4f7a46f40e48da4daadb082b7e66acb34ae9'),
48
+ ],
49
+ )
50
+
51
+
52
+ ########################################
53
+
54
+
55
+ # ../../omlish/lite/abstract.py
56
+ T = ta.TypeVar('T')
57
+
58
+ # ../../omlish/lite/check.py
59
+ SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
60
+ CheckMessage = ta.Union[str, ta.Callable[..., ta.Optional[str]], None] # ta.TypeAlias
61
+ CheckLateConfigureFn = ta.Callable[['Checks'], None] # ta.TypeAlias
62
+ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
63
+ CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
64
+ CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
65
+
66
+
67
+ ########################################
68
+ # ../../../omlish/lite/abstract.py
69
+
70
+
71
+ ##
72
+
73
+
74
+ _ABSTRACT_METHODS_ATTR = '__abstractmethods__'
75
+ _IS_ABSTRACT_METHOD_ATTR = '__isabstractmethod__'
76
+
77
+
78
+ def is_abstract_method(obj: ta.Any) -> bool:
79
+ return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
80
+
81
+
82
+ def compute_abstract_methods(cls: type) -> ta.FrozenSet[str]:
83
+ # ~> https://github.com/python/cpython/blob/f3476c6507381ca860eec0989f53647b13517423/Modules/_abc.c#L358
84
+
85
+ # Stage 1: direct abstract methods
86
+
87
+ abstracts = {
88
+ a
89
+ # Get items as a list to avoid mutation issues during iteration
90
+ for a, v in list(cls.__dict__.items())
91
+ if is_abstract_method(v)
92
+ }
93
+
94
+ # Stage 2: inherited abstract methods
95
+
96
+ for base in cls.__bases__:
97
+ # Get __abstractmethods__ from base if it exists
98
+ if (base_abstracts := getattr(base, _ABSTRACT_METHODS_ATTR, None)) is None:
99
+ continue
100
+
101
+ # Iterate over abstract methods in base
102
+ for key in base_abstracts:
103
+ # Check if this class has an attribute with this name
104
+ try:
105
+ value = getattr(cls, key)
106
+ except AttributeError:
107
+ # Attribute not found in this class, skip
108
+ continue
109
+
110
+ # Check if it's still abstract
111
+ if is_abstract_method(value):
112
+ abstracts.add(key)
113
+
114
+ return frozenset(abstracts)
115
+
116
+
117
+ def update_abstracts(cls: ta.Type[T], *, force: bool = False) -> ta.Type[T]:
118
+ if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
119
+ # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
120
+ # implementation (especially during testing), and we want to handle both cases.
121
+ return cls
122
+
123
+ abstracts = compute_abstract_methods(cls)
124
+ setattr(cls, _ABSTRACT_METHODS_ATTR, abstracts)
125
+ return cls
126
+
127
+
128
+ #
129
+
130
+
131
+ class AbstractTypeError(TypeError):
132
+ pass
133
+
134
+
135
+ _FORCE_ABSTRACT_ATTR = '__forceabstract__'
136
+
137
+
138
+ class Abstract:
139
+ """
140
+ Different from, but interoperable with, abc.ABC / abc.ABCMeta:
141
+
142
+ - This raises AbstractTypeError during class creation, not instance instantiation - unless Abstract or abc.ABC are
143
+ explicitly present in the class's direct bases.
144
+ - This will forbid instantiation of classes with Abstract in their direct bases even if there are no
145
+ abstractmethods left on the class.
146
+ - This is a mixin, not a metaclass.
147
+ - As it is not an ABCMeta, this does not support virtual base classes. As a result, operations like `isinstance`
148
+ and `issubclass` are ~7x faster.
149
+ - It additionally enforces a base class order of (Abstract, abc.ABC) to preemptively prevent common mro conflicts.
150
+
151
+ If not mixed-in with an ABCMeta, it will update __abstractmethods__ itself.
152
+ """
153
+
154
+ __slots__ = ()
155
+
156
+ __abstractmethods__: ta.ClassVar[ta.FrozenSet[str]] = frozenset()
157
+
158
+ #
159
+
160
+ def __forceabstract__(self):
161
+ raise TypeError
162
+
163
+ # This is done manually, rather than through @abc.abstractmethod, to mask it from static analysis.
164
+ setattr(__forceabstract__, _IS_ABSTRACT_METHOD_ATTR, True)
165
+
166
+ #
167
+
168
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
169
+ setattr(
170
+ cls,
171
+ _FORCE_ABSTRACT_ATTR,
172
+ getattr(Abstract, _FORCE_ABSTRACT_ATTR) if Abstract in cls.__bases__ else False,
173
+ )
174
+
175
+ super().__init_subclass__(**kwargs)
176
+
177
+ if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
178
+ if ams := compute_abstract_methods(cls):
179
+ amd = {
180
+ a: mcls
181
+ for mcls in cls.__mro__[::-1]
182
+ for a in ams
183
+ if a in mcls.__dict__
184
+ }
185
+
186
+ raise AbstractTypeError(
187
+ f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
188
+ ', '.join(sorted([
189
+ '.'.join([
190
+ *([
191
+ *([m] if (m := getattr(c, '__module__')) else []),
192
+ getattr(c, '__qualname__', getattr(c, '__name__')),
193
+ ] if c is not None else '?'),
194
+ a,
195
+ ])
196
+ for a in ams
197
+ for c in [amd.get(a)]
198
+ ])),
199
+ )
200
+
201
+ xbi = (Abstract, abc.ABC) # , ta.Generic ?
202
+ bis = [(cls.__bases__.index(b), b) for b in xbi if b in cls.__bases__]
203
+ if bis != sorted(bis):
204
+ raise TypeError(
205
+ f'Abstract subclass {cls.__name__} must have proper base class order of '
206
+ f'({", ".join(getattr(b, "__name__") for b in xbi)}), got: '
207
+ f'({", ".join(getattr(b, "__name__") for _, b in sorted(bis))})',
208
+ )
209
+
210
+ if not isinstance(cls, abc.ABCMeta):
211
+ update_abstracts(cls, force=True)
212
+
213
+
214
+ ########################################
215
+ # ../../../omlish/lite/check.py
216
+ """
217
+ TODO:
218
+ - def maybe(v: lang.Maybe[T])
219
+ - def not_ ?
220
+ - ** class @dataclass Raise - user message should be able to be an exception type or instance or factory
221
+ """
222
+
223
+
224
+ ##
225
+
226
+
227
+ class Checks:
228
+ def __init__(self) -> None:
229
+ super().__init__()
230
+
231
+ self._config_lock = threading.RLock()
232
+ self._on_raise_fns: ta.Sequence[CheckOnRaiseFn] = []
233
+ self._exception_factory: CheckExceptionFactory = Checks.default_exception_factory
234
+ self._args_renderer: ta.Optional[CheckArgsRenderer] = None
235
+ self._late_configure_fns: ta.Sequence[CheckLateConfigureFn] = []
236
+
237
+ @staticmethod
238
+ def default_exception_factory(exc_cls: ta.Type[Exception], *args, **kwargs) -> Exception:
239
+ return exc_cls(*args, **kwargs) # noqa
240
+
241
+ #
242
+
243
+ def register_on_raise(self, fn: CheckOnRaiseFn) -> None:
244
+ with self._config_lock:
245
+ self._on_raise_fns = [*self._on_raise_fns, fn]
246
+
247
+ def unregister_on_raise(self, fn: CheckOnRaiseFn) -> None:
248
+ with self._config_lock:
249
+ self._on_raise_fns = [e for e in self._on_raise_fns if e != fn]
250
+
251
+ #
252
+
253
+ def register_on_raise_breakpoint_if_env_var_set(self, key: str) -> None:
254
+ import os
255
+
256
+ def on_raise(exc: Exception) -> None: # noqa
257
+ if key in os.environ:
258
+ breakpoint() # noqa
259
+
260
+ self.register_on_raise(on_raise)
261
+
262
+ #
263
+
264
+ def set_exception_factory(self, factory: CheckExceptionFactory) -> None:
265
+ self._exception_factory = factory
266
+
267
+ def set_args_renderer(self, renderer: ta.Optional[CheckArgsRenderer]) -> None:
268
+ self._args_renderer = renderer
269
+
270
+ #
271
+
272
+ def register_late_configure(self, fn: CheckLateConfigureFn) -> None:
273
+ with self._config_lock:
274
+ self._late_configure_fns = [*self._late_configure_fns, fn]
275
+
276
+ def _late_configure(self) -> None:
277
+ if not self._late_configure_fns:
278
+ return
279
+
280
+ with self._config_lock:
281
+ if not (lc := self._late_configure_fns):
282
+ return
283
+
284
+ for fn in lc:
285
+ fn(self)
286
+
287
+ self._late_configure_fns = []
288
+
289
+ #
290
+
291
+ class _ArgsKwargs:
292
+ def __init__(self, *args, **kwargs):
293
+ self.args = args
294
+ self.kwargs = kwargs
295
+
296
+ def _raise(
297
+ self,
298
+ exception_type: ta.Type[Exception],
299
+ default_message: str,
300
+ message: CheckMessage,
301
+ ak: _ArgsKwargs = _ArgsKwargs(),
302
+ *,
303
+ render_fmt: ta.Optional[str] = None,
304
+ ) -> ta.NoReturn:
305
+ exc_args = ()
306
+ if callable(message):
307
+ message = ta.cast(ta.Callable, message)(*ak.args, **ak.kwargs)
308
+ if isinstance(message, tuple):
309
+ message, *exc_args = message # type: ignore
310
+
311
+ if message is None:
312
+ message = default_message
313
+
314
+ self._late_configure()
315
+
316
+ if render_fmt is not None and (af := self._args_renderer) is not None:
317
+ rendered_args = af(render_fmt, *ak.args)
318
+ if rendered_args is not None:
319
+ message = f'{message} : {rendered_args}'
320
+
321
+ exc = self._exception_factory(
322
+ exception_type,
323
+ message,
324
+ *exc_args,
325
+ *ak.args,
326
+ **ak.kwargs,
327
+ )
328
+
329
+ for fn in self._on_raise_fns:
330
+ fn(exc)
331
+
332
+ raise exc
333
+
334
+ #
335
+
336
+ def _unpack_isinstance_spec(self, spec: ta.Any) -> tuple:
337
+ if isinstance(spec, type):
338
+ return (spec,)
339
+ if not isinstance(spec, tuple):
340
+ spec = (spec,)
341
+ if None in spec:
342
+ spec = tuple(filter(None, spec)) + (None.__class__,) # noqa
343
+ if ta.Any in spec:
344
+ spec = (object,)
345
+ return spec
346
+
347
+ @ta.overload
348
+ def isinstance(self, v: ta.Any, spec: ta.Type[T], msg: CheckMessage = None) -> T:
349
+ ...
350
+
351
+ @ta.overload
352
+ def isinstance(self, v: ta.Any, spec: ta.Any, msg: CheckMessage = None) -> ta.Any:
353
+ ...
354
+
355
+ def isinstance(self, v, spec, msg=None):
356
+ if not isinstance(v, self._unpack_isinstance_spec(spec)):
357
+ self._raise(
358
+ TypeError,
359
+ 'Must be instance',
360
+ msg,
361
+ Checks._ArgsKwargs(v, spec),
362
+ render_fmt='not isinstance(%s, %s)',
363
+ )
364
+
365
+ return v
366
+
367
+ @ta.overload
368
+ def of_isinstance(self, spec: ta.Type[T], msg: CheckMessage = None) -> ta.Callable[[ta.Any], T]:
369
+ ...
370
+
371
+ @ta.overload
372
+ def of_isinstance(self, spec: ta.Any, msg: CheckMessage = None) -> ta.Callable[[ta.Any], ta.Any]:
373
+ ...
374
+
375
+ def of_isinstance(self, spec, msg=None):
376
+ def inner(v):
377
+ return self.isinstance(v, self._unpack_isinstance_spec(spec), msg)
378
+
379
+ return inner
380
+
381
+ def cast(self, v: ta.Any, cls: ta.Type[T], msg: CheckMessage = None) -> T:
382
+ if not isinstance(v, cls):
383
+ self._raise(
384
+ TypeError,
385
+ 'Must be instance',
386
+ msg,
387
+ Checks._ArgsKwargs(v, cls),
388
+ )
389
+
390
+ return v
391
+
392
+ def of_cast(self, cls: ta.Type[T], msg: CheckMessage = None) -> ta.Callable[[T], T]:
393
+ def inner(v):
394
+ return self.cast(v, cls, msg)
395
+
396
+ return inner
397
+
398
+ def not_isinstance(self, v: T, spec: ta.Any, msg: CheckMessage = None) -> T: # noqa
399
+ if isinstance(v, self._unpack_isinstance_spec(spec)):
400
+ self._raise(
401
+ TypeError,
402
+ 'Must not be instance',
403
+ msg,
404
+ Checks._ArgsKwargs(v, spec),
405
+ render_fmt='isinstance(%s, %s)',
406
+ )
407
+
408
+ return v
409
+
410
+ def of_not_isinstance(self, spec: ta.Any, msg: CheckMessage = None) -> ta.Callable[[T], T]:
411
+ def inner(v):
412
+ return self.not_isinstance(v, self._unpack_isinstance_spec(spec), msg)
413
+
414
+ return inner
415
+
416
+ ##
417
+
418
+ def issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]: # noqa
419
+ if not issubclass(v, spec):
420
+ self._raise(
421
+ TypeError,
422
+ 'Must be subclass',
423
+ msg,
424
+ Checks._ArgsKwargs(v, spec),
425
+ render_fmt='not issubclass(%s, %s)',
426
+ )
427
+
428
+ return v
429
+
430
+ def not_issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]:
431
+ if issubclass(v, spec):
432
+ self._raise(
433
+ TypeError,
434
+ 'Must not be subclass',
435
+ msg,
436
+ Checks._ArgsKwargs(v, spec),
437
+ render_fmt='issubclass(%s, %s)',
438
+ )
439
+
440
+ return v
441
+
442
+ #
443
+
444
+ def in_(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
445
+ if v not in c:
446
+ self._raise(
447
+ ValueError,
448
+ 'Must be in',
449
+ msg,
450
+ Checks._ArgsKwargs(v, c),
451
+ render_fmt='%s not in %s',
452
+ )
453
+
454
+ return v
455
+
456
+ def not_in(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
457
+ if v in c:
458
+ self._raise(
459
+ ValueError,
460
+ 'Must not be in',
461
+ msg,
462
+ Checks._ArgsKwargs(v, c),
463
+ render_fmt='%s in %s',
464
+ )
465
+
466
+ return v
467
+
468
+ def empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
469
+ if len(v) != 0:
470
+ self._raise(
471
+ ValueError,
472
+ 'Must be empty',
473
+ msg,
474
+ Checks._ArgsKwargs(v),
475
+ render_fmt='%s',
476
+ )
477
+
478
+ return v
479
+
480
+ def iterempty(self, v: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
481
+ it = iter(v)
482
+ try:
483
+ next(it)
484
+ except StopIteration:
485
+ pass
486
+ else:
487
+ self._raise(
488
+ ValueError,
489
+ 'Must be empty',
490
+ msg,
491
+ Checks._ArgsKwargs(v),
492
+ render_fmt='%s',
493
+ )
494
+
495
+ return v
496
+
497
+ def not_empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
498
+ if len(v) == 0:
499
+ self._raise(
500
+ ValueError,
501
+ 'Must not be empty',
502
+ msg,
503
+ Checks._ArgsKwargs(v),
504
+ render_fmt='%s',
505
+ )
506
+
507
+ return v
508
+
509
+ def unique(self, it: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
510
+ dupes = [e for e, c in collections.Counter(it).items() if c > 1]
511
+ if dupes:
512
+ self._raise(
513
+ ValueError,
514
+ 'Must be unique',
515
+ msg,
516
+ Checks._ArgsKwargs(it, dupes),
517
+ )
518
+
519
+ return it
520
+
521
+ def single(self, obj: ta.Iterable[T], msg: CheckMessage = None) -> T:
522
+ try:
523
+ [value] = obj
524
+ except ValueError:
525
+ self._raise(
526
+ ValueError,
527
+ 'Must be single',
528
+ msg,
529
+ Checks._ArgsKwargs(obj),
530
+ render_fmt='%s',
531
+ )
532
+
533
+ return value
534
+
535
+ def opt_single(self, obj: ta.Iterable[T], msg: CheckMessage = None) -> ta.Optional[T]:
536
+ it = iter(obj)
537
+ try:
538
+ value = next(it)
539
+ except StopIteration:
540
+ return None
541
+
542
+ try:
543
+ next(it)
544
+ except StopIteration:
545
+ return value # noqa
546
+
547
+ self._raise(
548
+ ValueError,
549
+ 'Must be empty or single',
550
+ msg,
551
+ Checks._ArgsKwargs(obj),
552
+ render_fmt='%s',
553
+ )
554
+
555
+ raise RuntimeError # noqa
556
+
557
+ #
558
+
559
+ def none(self, v: ta.Any, msg: CheckMessage = None) -> None:
560
+ if v is not None:
561
+ self._raise(
562
+ ValueError,
563
+ 'Must be None',
564
+ msg,
565
+ Checks._ArgsKwargs(v),
566
+ render_fmt='%s',
567
+ )
568
+
569
+ def not_none(self, v: ta.Optional[T], msg: CheckMessage = None) -> T:
570
+ if v is None:
571
+ self._raise(
572
+ ValueError,
573
+ 'Must not be None',
574
+ msg,
575
+ Checks._ArgsKwargs(v),
576
+ render_fmt='%s',
577
+ )
578
+
579
+ return v
580
+
581
+ #
582
+
583
+ def equal(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
584
+ if o != v:
585
+ self._raise(
586
+ ValueError,
587
+ 'Must be equal',
588
+ msg,
589
+ Checks._ArgsKwargs(v, o),
590
+ render_fmt='%s != %s',
591
+ )
592
+
593
+ return v
594
+
595
+ def not_equal(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
596
+ if o == v:
597
+ self._raise(
598
+ ValueError,
599
+ 'Must not be equal',
600
+ msg,
601
+ Checks._ArgsKwargs(v, o),
602
+ render_fmt='%s == %s',
603
+ )
604
+
605
+ return v
606
+
607
+ def is_(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
608
+ if o is not v:
609
+ self._raise(
610
+ ValueError,
611
+ 'Must be the same',
612
+ msg,
613
+ Checks._ArgsKwargs(v, o),
614
+ render_fmt='%s is not %s',
615
+ )
616
+
617
+ return v
618
+
619
+ def is_not(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
620
+ if o is v:
621
+ self._raise(
622
+ ValueError,
623
+ 'Must not be the same',
624
+ msg,
625
+ Checks._ArgsKwargs(v, o),
626
+ render_fmt='%s is %s',
627
+ )
628
+
629
+ return v
630
+
631
+ def callable(self, v: T, msg: CheckMessage = None) -> T: # noqa
632
+ if not callable(v):
633
+ self._raise(
634
+ TypeError,
635
+ 'Must be callable',
636
+ msg,
637
+ Checks._ArgsKwargs(v),
638
+ render_fmt='%s',
639
+ )
640
+
641
+ return v
642
+
643
+ def non_empty_str(self, v: ta.Optional[str], msg: CheckMessage = None) -> str:
644
+ if not isinstance(v, str) or not v:
645
+ self._raise(
646
+ ValueError,
647
+ 'Must be non-empty str',
648
+ msg,
649
+ Checks._ArgsKwargs(v),
650
+ render_fmt='%s',
651
+ )
652
+
653
+ return v
654
+
655
+ def replacing(self, expected: ta.Any, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
656
+ if old != expected:
657
+ self._raise(
658
+ ValueError,
659
+ 'Must be replacing',
660
+ msg,
661
+ Checks._ArgsKwargs(expected, old, new),
662
+ render_fmt='%s -> %s -> %s',
663
+ )
664
+
665
+ return new
666
+
667
+ def replacing_none(self, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
668
+ if old is not None:
669
+ self._raise(
670
+ ValueError,
671
+ 'Must be replacing None',
672
+ msg,
673
+ Checks._ArgsKwargs(old, new),
674
+ render_fmt='%s -> %s',
675
+ )
676
+
677
+ return new
678
+
679
+ #
680
+
681
+ def arg(self, v: bool, msg: CheckMessage = None) -> None:
682
+ if not v:
683
+ self._raise(
684
+ RuntimeError,
685
+ 'Argument condition not met',
686
+ msg,
687
+ Checks._ArgsKwargs(v),
688
+ render_fmt='%s',
689
+ )
690
+
691
+ def state(self, v: bool, msg: CheckMessage = None) -> None:
692
+ if not v:
693
+ self._raise(
694
+ RuntimeError,
695
+ 'State condition not met',
696
+ msg,
697
+ Checks._ArgsKwargs(v),
698
+ render_fmt='%s',
699
+ )
700
+
701
+
702
+ check = Checks()
703
+
704
+
705
+ ########################################
706
+ # ../../../omlish/lite/json.py
707
+
708
+
709
+ ##
710
+
711
+
712
+ JSON_PRETTY_INDENT = 2
713
+
714
+ JSON_PRETTY_KWARGS: ta.Mapping[str, ta.Any] = dict(
715
+ indent=JSON_PRETTY_INDENT,
716
+ )
717
+
718
+ json_dump_pretty: ta.Callable[..., None] = functools.partial(json.dump, **JSON_PRETTY_KWARGS)
719
+ json_dumps_pretty: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_PRETTY_KWARGS)
720
+
721
+
722
+ ##
723
+
724
+
725
+ JSON_COMPACT_SEPARATORS = (',', ':')
726
+
727
+ JSON_COMPACT_KWARGS: ta.Mapping[str, ta.Any] = dict(
728
+ indent=None,
729
+ separators=JSON_COMPACT_SEPARATORS,
730
+ )
731
+
732
+ json_dump_compact: ta.Callable[..., None] = functools.partial(json.dump, **JSON_COMPACT_KWARGS)
733
+ json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_COMPACT_KWARGS)
734
+
735
+
736
+ ########################################
737
+ # ../../../omlish/lite/objects.py
738
+
739
+
740
+ ##
741
+
742
+
743
+ def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
744
+ seen = set()
745
+ todo = list(reversed(cls.__subclasses__()))
746
+ while todo:
747
+ cur = todo.pop()
748
+ if cur in seen:
749
+ continue
750
+ seen.add(cur)
751
+ yield cur
752
+ todo.extend(reversed(cur.__subclasses__()))
753
+
754
+
755
+ ##
756
+
757
+
758
+ def mro_owner_dict(
759
+ instance_cls: type,
760
+ owner_cls: ta.Optional[type] = None,
761
+ *,
762
+ bottom_up_key_order: bool = False,
763
+ sort_keys: bool = False,
764
+ ) -> ta.Mapping[str, ta.Tuple[type, ta.Any]]:
765
+ if owner_cls is None:
766
+ owner_cls = instance_cls
767
+
768
+ mro = instance_cls.__mro__[-2::-1]
769
+ try:
770
+ pos = mro.index(owner_cls)
771
+ except ValueError:
772
+ raise TypeError(f'Owner class {owner_cls} not in mro of instance class {instance_cls}') from None
773
+
774
+ dct: ta.Dict[str, ta.Tuple[type, ta.Any]] = {}
775
+ if not bottom_up_key_order:
776
+ for cur_cls in mro[:pos + 1][::-1]:
777
+ for k, v in cur_cls.__dict__.items():
778
+ if k not in dct:
779
+ dct[k] = (cur_cls, v)
780
+
781
+ else:
782
+ for cur_cls in mro[:pos + 1]:
783
+ dct.update({k: (cur_cls, v) for k, v in cur_cls.__dict__.items()})
784
+
785
+ if sort_keys:
786
+ dct = dict(sorted(dct.items(), key=lambda t: t[0]))
787
+
788
+ return dct
789
+
790
+
791
+ def mro_dict(
792
+ instance_cls: type,
793
+ owner_cls: ta.Optional[type] = None,
794
+ *,
795
+ bottom_up_key_order: bool = False,
796
+ sort_keys: bool = False,
797
+ ) -> ta.Mapping[str, ta.Any]:
798
+ return {
799
+ k: v
800
+ for k, (o, v) in mro_owner_dict(
801
+ instance_cls,
802
+ owner_cls,
803
+ bottom_up_key_order=bottom_up_key_order,
804
+ sort_keys=sort_keys,
805
+ ).items()
806
+ }
807
+
808
+
809
+ def dir_dict(o: ta.Any) -> ta.Dict[str, ta.Any]:
810
+ return {
811
+ a: getattr(o, a)
812
+ for a in dir(o)
813
+ }
814
+
815
+
816
+ ########################################
817
+ # ../../../omlish/lite/reflect.py
818
+
819
+
820
+ ##
821
+
822
+
823
+ _GENERIC_ALIAS_TYPES = (
824
+ ta._GenericAlias, # type: ignore # noqa
825
+ *([ta._SpecialGenericAlias] if hasattr(ta, '_SpecialGenericAlias') else []), # noqa
826
+ )
827
+
828
+
829
+ def is_generic_alias(obj: ta.Any, *, origin: ta.Any = None) -> bool:
830
+ return (
831
+ isinstance(obj, _GENERIC_ALIAS_TYPES) and
832
+ (origin is None or ta.get_origin(obj) is origin)
833
+ )
834
+
835
+
836
+ is_callable_alias = functools.partial(is_generic_alias, origin=ta.Callable)
837
+
838
+
839
+ ##
840
+
841
+
842
+ _UNION_ALIAS_ORIGINS = frozenset([
843
+ ta.get_origin(ta.Optional[int]),
844
+ *(
845
+ [
846
+ ta.get_origin(int | None),
847
+ ta.get_origin(getattr(ta, 'TypeVar')('_T') | None),
848
+ ] if sys.version_info >= (3, 10) else ()
849
+ ),
850
+ ])
851
+
852
+
853
+ def is_union_alias(obj: ta.Any) -> bool:
854
+ return ta.get_origin(obj) in _UNION_ALIAS_ORIGINS
855
+
856
+
857
+ #
858
+
859
+
860
+ def is_optional_alias(spec: ta.Any) -> bool:
861
+ return (
862
+ is_union_alias(spec) and
863
+ len(ta.get_args(spec)) == 2 and
864
+ any(a in (None, type(None)) for a in ta.get_args(spec))
865
+ )
866
+
867
+
868
+ def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
869
+ [it] = [it for it in ta.get_args(spec) if it not in (None, type(None))]
870
+ return it
871
+
872
+
873
+ ##
874
+
875
+
876
+ def is_new_type(spec: ta.Any) -> bool:
877
+ if isinstance(ta.NewType, type):
878
+ return isinstance(spec, ta.NewType)
879
+ else:
880
+ # Before https://github.com/python/cpython/commit/c2f33dfc83ab270412bf243fb21f724037effa1a
881
+ return isinstance(spec, types.FunctionType) and spec.__code__ is ta.NewType.__code__.co_consts[1] # type: ignore # noqa
882
+
883
+
884
+ def get_new_type_supertype(spec: ta.Any) -> ta.Any:
885
+ return spec.__supertype__
886
+
887
+
888
+ ##
889
+
890
+
891
+ def is_literal_type(spec: ta.Any) -> bool:
892
+ if hasattr(ta, '_LiteralGenericAlias'):
893
+ return isinstance(spec, ta._LiteralGenericAlias) # noqa
894
+ else:
895
+ return (
896
+ isinstance(spec, ta._GenericAlias) and # type: ignore # noqa
897
+ spec.__origin__ is ta.Literal
898
+ )
899
+
900
+
901
+ def get_literal_type_args(spec: ta.Any) -> ta.Iterable[ta.Any]:
902
+ return spec.__args__
903
+
904
+
905
+ ########################################
906
+ # ../../../omlish/lite/strings.py
907
+
908
+
909
+ ##
910
+
911
+
912
+ def camel_case(name: str, *, lower: bool = False) -> str:
913
+ if not name:
914
+ return ''
915
+ s = ''.join(map(str.capitalize, name.split('_'))) # noqa
916
+ if lower:
917
+ s = s[0].lower() + s[1:]
918
+ return s
919
+
920
+
921
+ def snake_case(name: str) -> str:
922
+ uppers: list[int | None] = [i for i, c in enumerate(name) if c.isupper()]
923
+ return '_'.join([name[l:r].lower() for l, r in zip([None, *uppers], [*uppers, None])]).strip('_')
924
+
925
+
926
+ ##
927
+
928
+
929
+ def is_dunder(name: str) -> bool:
930
+ return (
931
+ name[:2] == name[-2:] == '__' and
932
+ name[2:3] != '_' and
933
+ name[-3:-2] != '_' and
934
+ len(name) > 4
935
+ )
936
+
937
+
938
+ def is_sunder(name: str) -> bool:
939
+ return (
940
+ name[0] == name[-1] == '_' and
941
+ name[1:2] != '_' and
942
+ name[-2:-1] != '_' and
943
+ len(name) > 2
944
+ )
945
+
946
+
947
+ ##
948
+
949
+
950
+ def strip_with_newline(s: str) -> str:
951
+ if not s:
952
+ return ''
953
+ return s.strip() + '\n'
954
+
955
+
956
+ @ta.overload
957
+ def split_keep_delimiter(s: str, d: str) -> str:
958
+ ...
959
+
960
+
961
+ @ta.overload
962
+ def split_keep_delimiter(s: bytes, d: bytes) -> bytes:
963
+ ...
964
+
965
+
966
+ def split_keep_delimiter(s, d):
967
+ ps = []
968
+ i = 0
969
+ while i < len(s):
970
+ if (n := s.find(d, i)) < i:
971
+ ps.append(s[i:])
972
+ break
973
+ ps.append(s[i:n + 1])
974
+ i = n + 1
975
+ return ps
976
+
977
+
978
+ ##
979
+
980
+
981
+ FORMAT_NUM_BYTES_SUFFIXES: ta.Sequence[str] = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB']
982
+
983
+
984
+ def format_num_bytes(num_bytes: int) -> str:
985
+ for i, suffix in enumerate(FORMAT_NUM_BYTES_SUFFIXES):
986
+ value = num_bytes / 1024 ** i
987
+ if num_bytes < 1024 ** (i + 1):
988
+ if value.is_integer():
989
+ return f'{int(value)}{suffix}'
990
+ else:
991
+ return f'{value:.2f}{suffix}'
992
+
993
+ return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
994
+
995
+
996
+ ########################################
997
+ # ../../../omlish/lite/marshal.py
998
+ """
999
+ TODO:
1000
+ - pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
1001
+ - Options.sequence_cls = list, mapping_cls = dict, ... - def with_mutable_containers() -> Options
1002
+ """
1003
+
1004
+
1005
+ ##
1006
+
1007
+
1008
+ @dc.dataclass(frozen=True)
1009
+ class ObjMarshalOptions:
1010
+ raw_bytes: bool = False
1011
+ non_strict_fields: bool = False
1012
+
1013
+
1014
+ class ObjMarshaler(Abstract):
1015
+ @abc.abstractmethod
1016
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1017
+ raise NotImplementedError
1018
+
1019
+ @abc.abstractmethod
1020
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1021
+ raise NotImplementedError
1022
+
1023
+
1024
+ class NopObjMarshaler(ObjMarshaler):
1025
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1026
+ return o
1027
+
1028
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1029
+ return o
1030
+
1031
+
1032
+ class ProxyObjMarshaler(ObjMarshaler):
1033
+ def __init__(self, m: ta.Optional[ObjMarshaler] = None) -> None:
1034
+ super().__init__()
1035
+
1036
+ self._m = m
1037
+
1038
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1039
+ return check.not_none(self._m).marshal(o, ctx)
1040
+
1041
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1042
+ return check.not_none(self._m).unmarshal(o, ctx)
1043
+
1044
+
1045
+ class CastObjMarshaler(ObjMarshaler):
1046
+ def __init__(self, ty: type) -> None:
1047
+ super().__init__()
1048
+
1049
+ self._ty = ty
1050
+
1051
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1052
+ return o
1053
+
1054
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1055
+ return self._ty(o)
1056
+
1057
+
1058
+ class DynamicObjMarshaler(ObjMarshaler):
1059
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1060
+ return ctx.manager.marshal_obj(o, opts=ctx.options)
1061
+
1062
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1063
+ return o
1064
+
1065
+
1066
+ class Base64ObjMarshaler(ObjMarshaler):
1067
+ def __init__(self, ty: type) -> None:
1068
+ super().__init__()
1069
+
1070
+ self._ty = ty
1071
+
1072
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1073
+ return base64.b64encode(o).decode('ascii')
1074
+
1075
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1076
+ return self._ty(base64.b64decode(o))
1077
+
1078
+
1079
+ class BytesSwitchedObjMarshaler(ObjMarshaler):
1080
+ def __init__(self, m: ObjMarshaler) -> None:
1081
+ super().__init__()
1082
+
1083
+ self._m = m
1084
+
1085
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1086
+ if ctx.options.raw_bytes:
1087
+ return o
1088
+ return self._m.marshal(o, ctx)
1089
+
1090
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1091
+ if ctx.options.raw_bytes:
1092
+ return o
1093
+ return self._m.unmarshal(o, ctx)
1094
+
1095
+
1096
+ class EnumObjMarshaler(ObjMarshaler):
1097
+ def __init__(self, ty: type) -> None:
1098
+ super().__init__()
1099
+
1100
+ self._ty = ty
1101
+
1102
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1103
+ return o.name
1104
+
1105
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1106
+ return self._ty.__members__[o] # type: ignore
1107
+
1108
+
1109
+ class OptionalObjMarshaler(ObjMarshaler):
1110
+ def __init__(self, item: ObjMarshaler) -> None:
1111
+ super().__init__()
1112
+
1113
+ self._item = item
1114
+
1115
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1116
+ if o is None:
1117
+ return None
1118
+ return self._item.marshal(o, ctx)
1119
+
1120
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1121
+ if o is None:
1122
+ return None
1123
+ return self._item.unmarshal(o, ctx)
1124
+
1125
+
1126
+ class PrimitiveUnionObjMarshaler(ObjMarshaler):
1127
+ def __init__(
1128
+ self,
1129
+ pt: ta.Tuple[type, ...],
1130
+ x: ta.Optional[ObjMarshaler] = None,
1131
+ ) -> None:
1132
+ super().__init__()
1133
+
1134
+ self._pt = pt
1135
+ self._x = x
1136
+
1137
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1138
+ if isinstance(o, self._pt):
1139
+ return o
1140
+ elif self._x is not None:
1141
+ return self._x.marshal(o, ctx)
1142
+ else:
1143
+ raise TypeError(o)
1144
+
1145
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1146
+ if isinstance(o, self._pt):
1147
+ return o
1148
+ elif self._x is not None:
1149
+ return self._x.unmarshal(o, ctx)
1150
+ else:
1151
+ raise TypeError(o)
1152
+
1153
+
1154
+ class LiteralObjMarshaler(ObjMarshaler):
1155
+ def __init__(
1156
+ self,
1157
+ item: ObjMarshaler,
1158
+ vs: frozenset,
1159
+ ) -> None:
1160
+ super().__init__()
1161
+
1162
+ self._item = item
1163
+ self._vs = vs
1164
+
1165
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1166
+ return self._item.marshal(check.in_(o, self._vs), ctx)
1167
+
1168
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1169
+ return check.in_(self._item.unmarshal(o, ctx), self._vs)
1170
+
1171
+
1172
+ class MappingObjMarshaler(ObjMarshaler):
1173
+ def __init__(
1174
+ self,
1175
+ ty: type,
1176
+ km: ObjMarshaler,
1177
+ vm: ObjMarshaler,
1178
+ ) -> None:
1179
+ super().__init__()
1180
+
1181
+ self._ty = ty
1182
+ self._km = km
1183
+ self._vm = vm
1184
+
1185
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1186
+ return {self._km.marshal(k, ctx): self._vm.marshal(v, ctx) for k, v in o.items()}
1187
+
1188
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1189
+ return self._ty((self._km.unmarshal(k, ctx), self._vm.unmarshal(v, ctx)) for k, v in o.items())
1190
+
1191
+
1192
+ class IterableObjMarshaler(ObjMarshaler):
1193
+ def __init__(
1194
+ self,
1195
+ ty: type,
1196
+ item: ObjMarshaler,
1197
+ ) -> None:
1198
+ super().__init__()
1199
+
1200
+ self._ty = ty
1201
+ self._item = item
1202
+
1203
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1204
+ return [self._item.marshal(e, ctx) for e in o]
1205
+
1206
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1207
+ return self._ty(self._item.unmarshal(e, ctx) for e in o)
1208
+
1209
+
1210
+ class FieldsObjMarshaler(ObjMarshaler):
1211
+ @dc.dataclass(frozen=True)
1212
+ class Field:
1213
+ att: str
1214
+ key: str
1215
+ m: ObjMarshaler
1216
+
1217
+ omit_if_none: bool = False
1218
+
1219
+ def __init__(
1220
+ self,
1221
+ ty: type,
1222
+ fs: ta.Sequence[Field],
1223
+ *,
1224
+ non_strict: bool = False,
1225
+ ) -> None:
1226
+ super().__init__()
1227
+
1228
+ self._ty = ty
1229
+ self._fs = fs
1230
+ self._non_strict = non_strict
1231
+
1232
+ fs_by_att: dict = {}
1233
+ fs_by_key: dict = {}
1234
+ for f in self._fs:
1235
+ check.not_in(check.non_empty_str(f.att), fs_by_att)
1236
+ check.not_in(check.non_empty_str(f.key), fs_by_key)
1237
+ fs_by_att[f.att] = f
1238
+ fs_by_key[f.key] = f
1239
+
1240
+ self._fs_by_att: ta.Mapping[str, FieldsObjMarshaler.Field] = fs_by_att
1241
+ self._fs_by_key: ta.Mapping[str, FieldsObjMarshaler.Field] = fs_by_key
1242
+
1243
+ @property
1244
+ def ty(self) -> type:
1245
+ return self._ty
1246
+
1247
+ @property
1248
+ def fs(self) -> ta.Sequence[Field]:
1249
+ return self._fs
1250
+
1251
+ #
1252
+
1253
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1254
+ d = {}
1255
+ for f in self._fs:
1256
+ mv = f.m.marshal(getattr(o, f.att), ctx)
1257
+ if mv is None and f.omit_if_none:
1258
+ continue
1259
+ d[f.key] = mv
1260
+ return d
1261
+
1262
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1263
+ kw = {}
1264
+ for k, v in o.items():
1265
+ if (f := self._fs_by_key.get(k)) is None:
1266
+ if not (self._non_strict or ctx.options.non_strict_fields):
1267
+ raise KeyError(k)
1268
+ continue
1269
+ kw[f.att] = f.m.unmarshal(v, ctx)
1270
+ return self._ty(**kw)
1271
+
1272
+
1273
+ class SingleFieldObjMarshaler(ObjMarshaler):
1274
+ def __init__(
1275
+ self,
1276
+ ty: type,
1277
+ fld: str,
1278
+ ) -> None:
1279
+ super().__init__()
1280
+
1281
+ self._ty = ty
1282
+ self._fld = fld
1283
+
1284
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1285
+ return getattr(o, self._fld)
1286
+
1287
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1288
+ return self._ty(**{self._fld: o})
1289
+
1290
+
1291
+ class PolymorphicObjMarshaler(ObjMarshaler):
1292
+ class Impl(ta.NamedTuple):
1293
+ ty: type
1294
+ tag: str
1295
+ m: ObjMarshaler
1296
+
1297
+ def __init__(
1298
+ self,
1299
+ impls_by_ty: ta.Mapping[type, Impl],
1300
+ impls_by_tag: ta.Mapping[str, Impl],
1301
+ ) -> None:
1302
+ super().__init__()
1303
+
1304
+ self._impls_by_ty = impls_by_ty
1305
+ self._impls_by_tag = impls_by_tag
1306
+
1307
+ @classmethod
1308
+ def of(cls, impls: ta.Iterable[Impl]) -> 'PolymorphicObjMarshaler':
1309
+ return cls(
1310
+ {i.ty: i for i in impls},
1311
+ {i.tag: i for i in impls},
1312
+ )
1313
+
1314
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1315
+ impl = self._impls_by_ty[type(o)]
1316
+ return {impl.tag: impl.m.marshal(o, ctx)}
1317
+
1318
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1319
+ [(t, v)] = o.items()
1320
+ impl = self._impls_by_tag[t]
1321
+ return impl.m.unmarshal(v, ctx)
1322
+
1323
+
1324
+ class DatetimeObjMarshaler(ObjMarshaler):
1325
+ def __init__(
1326
+ self,
1327
+ ty: type,
1328
+ ) -> None:
1329
+ super().__init__()
1330
+
1331
+ self._ty = ty
1332
+
1333
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1334
+ return o.isoformat()
1335
+
1336
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1337
+ return self._ty.fromisoformat(o) # type: ignore
1338
+
1339
+
1340
+ class DecimalObjMarshaler(ObjMarshaler):
1341
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1342
+ return str(check.isinstance(o, decimal.Decimal))
1343
+
1344
+ def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1345
+ return decimal.Decimal(check.isinstance(v, str))
1346
+
1347
+
1348
+ class FractionObjMarshaler(ObjMarshaler):
1349
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1350
+ fr = check.isinstance(o, fractions.Fraction)
1351
+ return [fr.numerator, fr.denominator]
1352
+
1353
+ def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1354
+ num, denom = check.isinstance(v, list)
1355
+ return fractions.Fraction(num, denom)
1356
+
1357
+
1358
+ class UuidObjMarshaler(ObjMarshaler):
1359
+ def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1360
+ return str(o)
1361
+
1362
+ def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1363
+ return uuid.UUID(o)
1364
+
1365
+
1366
+ ##
1367
+
1368
+
1369
+ _DEFAULT_OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
1370
+ **{t: NopObjMarshaler() for t in (type(None),)},
1371
+ **{t: CastObjMarshaler(t) for t in (int, float, str, bool)},
1372
+ **{t: BytesSwitchedObjMarshaler(Base64ObjMarshaler(t)) for t in (bytes, bytearray)},
1373
+ **{t: IterableObjMarshaler(t, DynamicObjMarshaler()) for t in (list, tuple, set, frozenset)},
1374
+ **{t: MappingObjMarshaler(t, DynamicObjMarshaler(), DynamicObjMarshaler()) for t in (dict,)},
1375
+
1376
+ **{t: DynamicObjMarshaler() for t in (ta.Any, object)},
1377
+
1378
+ **{t: DatetimeObjMarshaler(t) for t in (datetime.date, datetime.time, datetime.datetime)},
1379
+ decimal.Decimal: DecimalObjMarshaler(),
1380
+ fractions.Fraction: FractionObjMarshaler(),
1381
+ uuid.UUID: UuidObjMarshaler(),
1382
+ }
1383
+
1384
+ _OBJ_MARSHALER_GENERIC_MAPPING_TYPES: ta.Dict[ta.Any, type] = {
1385
+ **{t: t for t in (dict,)},
1386
+ **{t: dict for t in (collections.abc.Mapping, collections.abc.MutableMapping)}, # noqa
1387
+ }
1388
+
1389
+ _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES: ta.Dict[ta.Any, type] = {
1390
+ **{t: t for t in (list, tuple, set, frozenset)},
1391
+ collections.abc.Set: frozenset,
1392
+ collections.abc.MutableSet: set,
1393
+ collections.abc.Sequence: tuple,
1394
+ collections.abc.MutableSequence: list,
1395
+ }
1396
+
1397
+ _OBJ_MARSHALER_PRIMITIVE_TYPES: ta.Set[type] = {
1398
+ int,
1399
+ float,
1400
+ bool,
1401
+ str,
1402
+ }
1403
+
1404
+
1405
+ ##
1406
+
1407
+
1408
+ _REGISTERED_OBJ_MARSHALERS_BY_TYPE: ta.MutableMapping[type, ObjMarshaler] = weakref.WeakKeyDictionary()
1409
+
1410
+
1411
+ def register_type_obj_marshaler(ty: type, om: ObjMarshaler) -> None:
1412
+ _REGISTERED_OBJ_MARSHALERS_BY_TYPE[ty] = om
1413
+
1414
+
1415
+ def register_single_field_type_obj_marshaler(fld, ty=None):
1416
+ def inner(ty): # noqa
1417
+ register_type_obj_marshaler(ty, SingleFieldObjMarshaler(ty, fld))
1418
+ return ty
1419
+
1420
+ if ty is not None:
1421
+ return inner(ty)
1422
+ else:
1423
+ return inner
1424
+
1425
+
1426
+ ##
1427
+
1428
+
1429
+ class ObjMarshalerFieldMetadata:
1430
+ def __new__(cls, *args, **kwargs): # noqa
1431
+ raise TypeError
1432
+
1433
+
1434
+ class OBJ_MARSHALER_FIELD_KEY(ObjMarshalerFieldMetadata): # noqa
1435
+ pass
1436
+
1437
+
1438
+ class OBJ_MARSHALER_OMIT_IF_NONE(ObjMarshalerFieldMetadata): # noqa
1439
+ pass
1440
+
1441
+
1442
+ ##
1443
+
1444
+
1445
+ class ObjMarshalerManager(Abstract):
1446
+ @abc.abstractmethod
1447
+ def make_obj_marshaler(
1448
+ self,
1449
+ ty: ta.Any,
1450
+ rec: ta.Callable[[ta.Any], ObjMarshaler],
1451
+ *,
1452
+ non_strict_fields: bool = False,
1453
+ ) -> ObjMarshaler:
1454
+ raise NotImplementedError
1455
+
1456
+ @abc.abstractmethod
1457
+ def set_obj_marshaler(
1458
+ self,
1459
+ ty: ta.Any,
1460
+ m: ObjMarshaler,
1461
+ *,
1462
+ override: bool = False,
1463
+ ) -> None:
1464
+ raise NotImplementedError
1465
+
1466
+ @abc.abstractmethod
1467
+ def get_obj_marshaler(
1468
+ self,
1469
+ ty: ta.Any,
1470
+ *,
1471
+ no_cache: bool = False,
1472
+ **kwargs: ta.Any,
1473
+ ) -> ObjMarshaler:
1474
+ raise NotImplementedError
1475
+
1476
+ @abc.abstractmethod
1477
+ def make_context(self, opts: ta.Optional[ObjMarshalOptions]) -> 'ObjMarshalContext':
1478
+ raise NotImplementedError
1479
+
1480
+ #
1481
+
1482
+ def marshal_obj(
1483
+ self,
1484
+ o: ta.Any,
1485
+ ty: ta.Any = None,
1486
+ opts: ta.Optional[ObjMarshalOptions] = None,
1487
+ ) -> ta.Any:
1488
+ m = self.get_obj_marshaler(ty if ty is not None else type(o))
1489
+ return m.marshal(o, self.make_context(opts))
1490
+
1491
+ def unmarshal_obj(
1492
+ self,
1493
+ o: ta.Any,
1494
+ ty: ta.Union[ta.Type[T], ta.Any],
1495
+ opts: ta.Optional[ObjMarshalOptions] = None,
1496
+ ) -> T:
1497
+ m = self.get_obj_marshaler(ty)
1498
+ return m.unmarshal(o, self.make_context(opts))
1499
+
1500
+ def roundtrip_obj(
1501
+ self,
1502
+ o: ta.Any,
1503
+ ty: ta.Any = None,
1504
+ opts: ta.Optional[ObjMarshalOptions] = None,
1505
+ ) -> ta.Any:
1506
+ if ty is None:
1507
+ ty = type(o)
1508
+ m: ta.Any = self.marshal_obj(o, ty, opts)
1509
+ u: ta.Any = self.unmarshal_obj(m, ty, opts)
1510
+ return u
1511
+
1512
+
1513
+ #
1514
+
1515
+
1516
+ class ObjMarshalerManagerImpl(ObjMarshalerManager):
1517
+ def __init__(
1518
+ self,
1519
+ *,
1520
+ default_options: ObjMarshalOptions = ObjMarshalOptions(),
1521
+
1522
+ default_obj_marshalers: ta.Dict[ta.Any, ObjMarshaler] = _DEFAULT_OBJ_MARSHALERS, # noqa
1523
+ generic_mapping_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_MAPPING_TYPES, # noqa
1524
+ generic_iterable_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES, # noqa
1525
+
1526
+ registered_obj_marshalers: ta.Mapping[type, ObjMarshaler] = _REGISTERED_OBJ_MARSHALERS_BY_TYPE,
1527
+ ) -> None:
1528
+ super().__init__()
1529
+
1530
+ self._default_options = default_options
1531
+
1532
+ self._obj_marshalers = dict(default_obj_marshalers)
1533
+ self._generic_mapping_types = generic_mapping_types
1534
+ self._generic_iterable_types = generic_iterable_types
1535
+ self._registered_obj_marshalers = registered_obj_marshalers
1536
+
1537
+ self._lock = threading.RLock()
1538
+ self._marshalers: ta.Dict[ta.Any, ObjMarshaler] = dict(_DEFAULT_OBJ_MARSHALERS)
1539
+ self._proxies: ta.Dict[ta.Any, ProxyObjMarshaler] = {}
1540
+
1541
+ #
1542
+
1543
+ @classmethod
1544
+ def _is_abstract(cls, ty: type) -> bool:
1545
+ return abc.ABC in ty.__bases__ or Abstract in ty.__bases__
1546
+
1547
+ #
1548
+
1549
+ def make_obj_marshaler(
1550
+ self,
1551
+ ty: ta.Any,
1552
+ rec: ta.Callable[[ta.Any], ObjMarshaler],
1553
+ *,
1554
+ non_strict_fields: bool = False,
1555
+ ) -> ObjMarshaler:
1556
+ if isinstance(ty, type):
1557
+ if (reg := self._registered_obj_marshalers.get(ty)) is not None:
1558
+ return reg
1559
+
1560
+ if self._is_abstract(ty):
1561
+ tn = ty.__name__
1562
+ impls: ta.List[ta.Tuple[type, str]] = [ # type: ignore[var-annotated]
1563
+ (ity, ity.__name__)
1564
+ for ity in deep_subclasses(ty)
1565
+ if not self._is_abstract(ity)
1566
+ ]
1567
+
1568
+ if all(itn.endswith(tn) for _, itn in impls):
1569
+ impls = [
1570
+ (ity, snake_case(itn[:-len(tn)]))
1571
+ for ity, itn in impls
1572
+ ]
1573
+
1574
+ dupe_tns = sorted(
1575
+ dn
1576
+ for dn, dc in collections.Counter(itn for _, itn in impls).items()
1577
+ if dc > 1
1578
+ )
1579
+ if dupe_tns:
1580
+ raise KeyError(f'Duplicate impl names for {ty}: {dupe_tns}')
1581
+
1582
+ return PolymorphicObjMarshaler.of([
1583
+ PolymorphicObjMarshaler.Impl(
1584
+ ity,
1585
+ itn,
1586
+ rec(ity),
1587
+ )
1588
+ for ity, itn in impls
1589
+ ])
1590
+
1591
+ if issubclass(ty, enum.Enum):
1592
+ return EnumObjMarshaler(ty)
1593
+
1594
+ if dc.is_dataclass(ty):
1595
+ return FieldsObjMarshaler(
1596
+ ty,
1597
+ [
1598
+ FieldsObjMarshaler.Field(
1599
+ att=f.name,
1600
+ key=check.non_empty_str(fk),
1601
+ m=rec(f.type),
1602
+ omit_if_none=check.isinstance(f.metadata.get(OBJ_MARSHALER_OMIT_IF_NONE, False), bool),
1603
+ )
1604
+ for f in dc.fields(ty)
1605
+ if (fk := f.metadata.get(OBJ_MARSHALER_FIELD_KEY, f.name)) is not None
1606
+ ],
1607
+ non_strict=non_strict_fields,
1608
+ )
1609
+
1610
+ if issubclass(ty, tuple) and hasattr(ty, '_fields'):
1611
+ return FieldsObjMarshaler(
1612
+ ty,
1613
+ [
1614
+ FieldsObjMarshaler.Field(
1615
+ att=p.name,
1616
+ key=p.name,
1617
+ m=rec(p.annotation),
1618
+ )
1619
+ for p in inspect.signature(ty).parameters.values()
1620
+ ],
1621
+ non_strict=non_strict_fields,
1622
+ )
1623
+
1624
+ if is_new_type(ty):
1625
+ return rec(get_new_type_supertype(ty))
1626
+
1627
+ if is_literal_type(ty):
1628
+ lvs = frozenset(get_literal_type_args(ty))
1629
+ if None in lvs:
1630
+ is_opt = True
1631
+ lvs -= frozenset([None])
1632
+ else:
1633
+ is_opt = False
1634
+ lty = check.single(set(map(type, lvs)))
1635
+ lm: ObjMarshaler = LiteralObjMarshaler(rec(lty), lvs)
1636
+ if is_opt:
1637
+ lm = OptionalObjMarshaler(lm)
1638
+ return lm
1639
+
1640
+ if is_generic_alias(ty):
1641
+ try:
1642
+ mt = self._generic_mapping_types[ta.get_origin(ty)]
1643
+ except KeyError:
1644
+ pass
1645
+ else:
1646
+ k, v = ta.get_args(ty)
1647
+ return MappingObjMarshaler(mt, rec(k), rec(v))
1648
+
1649
+ try:
1650
+ st = self._generic_iterable_types[ta.get_origin(ty)]
1651
+ except KeyError:
1652
+ pass
1653
+ else:
1654
+ [e] = ta.get_args(ty)
1655
+ return IterableObjMarshaler(st, rec(e))
1656
+
1657
+ if is_union_alias(ty):
1658
+ uts = frozenset(ta.get_args(ty))
1659
+ if None in uts or type(None) in uts:
1660
+ is_opt = True
1661
+ uts = frozenset(ut for ut in uts if ut not in (None, type(None)))
1662
+ else:
1663
+ is_opt = False
1664
+
1665
+ um: ObjMarshaler
1666
+ if not uts:
1667
+ raise TypeError(ty)
1668
+ elif len(uts) == 1:
1669
+ um = rec(check.single(uts))
1670
+ else:
1671
+ pt = tuple({ut for ut in uts if ut in _OBJ_MARSHALER_PRIMITIVE_TYPES})
1672
+ np_uts = {ut for ut in uts if ut not in _OBJ_MARSHALER_PRIMITIVE_TYPES}
1673
+ if not np_uts:
1674
+ um = PrimitiveUnionObjMarshaler(pt)
1675
+ elif len(np_uts) == 1:
1676
+ um = PrimitiveUnionObjMarshaler(pt, x=rec(check.single(np_uts)))
1677
+ else:
1678
+ raise TypeError(ty)
1679
+
1680
+ if is_opt:
1681
+ um = OptionalObjMarshaler(um)
1682
+ return um
1683
+
1684
+ raise TypeError(ty)
1685
+
1686
+ #
1687
+
1688
+ def set_obj_marshaler(
1689
+ self,
1690
+ ty: ta.Any,
1691
+ m: ObjMarshaler,
1692
+ *,
1693
+ override: bool = False,
1694
+ ) -> None:
1695
+ with self._lock:
1696
+ if not override and ty in self._obj_marshalers:
1697
+ raise KeyError(ty)
1698
+ self._obj_marshalers[ty] = m
1699
+
1700
+ def get_obj_marshaler(
1701
+ self,
1702
+ ty: ta.Any,
1703
+ *,
1704
+ no_cache: bool = False,
1705
+ **kwargs: ta.Any,
1706
+ ) -> ObjMarshaler:
1707
+ with self._lock:
1708
+ if not no_cache:
1709
+ try:
1710
+ return self._obj_marshalers[ty]
1711
+ except KeyError:
1712
+ pass
1713
+
1714
+ try:
1715
+ return self._proxies[ty]
1716
+ except KeyError:
1717
+ pass
1718
+
1719
+ rec = functools.partial(
1720
+ self.get_obj_marshaler,
1721
+ no_cache=no_cache,
1722
+ **kwargs,
1723
+ )
1724
+
1725
+ p = ProxyObjMarshaler()
1726
+ self._proxies[ty] = p
1727
+ try:
1728
+ m = self.make_obj_marshaler(ty, rec, **kwargs)
1729
+ finally:
1730
+ del self._proxies[ty]
1731
+ p._m = m # noqa
1732
+
1733
+ if not no_cache:
1734
+ self._obj_marshalers[ty] = m
1735
+ return m
1736
+
1737
+ def make_context(self, opts: ta.Optional[ObjMarshalOptions]) -> 'ObjMarshalContext':
1738
+ return ObjMarshalContext(
1739
+ options=opts or self._default_options,
1740
+ manager=self,
1741
+ )
1742
+
1743
+
1744
+ def new_obj_marshaler_manager(**kwargs: ta.Any) -> ObjMarshalerManager:
1745
+ return ObjMarshalerManagerImpl(**kwargs)
1746
+
1747
+
1748
+ ##
1749
+
1750
+
1751
+ @dc.dataclass(frozen=True)
1752
+ class ObjMarshalContext:
1753
+ options: ObjMarshalOptions
1754
+ manager: ObjMarshalerManager
1755
+
1756
+
1757
+ ##
1758
+
1759
+
1760
+ OBJ_MARSHALER_MANAGER = new_obj_marshaler_manager()
1761
+
1762
+ set_obj_marshaler = OBJ_MARSHALER_MANAGER.set_obj_marshaler
1763
+ get_obj_marshaler = OBJ_MARSHALER_MANAGER.get_obj_marshaler
1764
+
1765
+ marshal_obj = OBJ_MARSHALER_MANAGER.marshal_obj
1766
+ unmarshal_obj = OBJ_MARSHALER_MANAGER.unmarshal_obj
1767
+
1768
+
1769
+ ########################################
1770
+ # dumping.py
1771
+
1772
+
1773
+ ##
1774
+
1775
+
1776
+ @dc.dataclass(frozen=True)
1777
+ class DumpedDataclassCodegen:
1778
+ mod_name: str
1779
+
1780
+ cls_module: str
1781
+ cls_qualname: str
1782
+
1783
+ plan_repr: str
1784
+
1785
+ fn_name: str
1786
+ fn_params: ta.Sequence[str]
1787
+
1788
+ hdr_lines: ta.Sequence[str]
1789
+ fn_lines: ta.Sequence[str]
1790
+
1791
+ @dc.dataclass(frozen=True)
1792
+ class Ref:
1793
+ kind: ta.Literal['op', 'global']
1794
+ ident: str
1795
+
1796
+ refs: ta.Sequence[Ref]
1797
+
1798
+
1799
+ @dc.dataclass(frozen=True)
1800
+ class DataclassCodegenDumperOutput:
1801
+ init_file_path: str
1802
+ out_file_path: str
1803
+
1804
+ processed_modules: ta.Sequence[str]
1805
+ import_errors: ta.Mapping[str, str]
1806
+
1807
+ dumped: ta.Sequence[DumpedDataclassCodegen]
1808
+
1809
+
1810
+ ##
1811
+
1812
+
1813
+ class _DataclassCodegenDumper:
1814
+ def __call__(
1815
+ self,
1816
+ *,
1817
+ init_file_path: str,
1818
+ out_file_path: str,
1819
+ ) -> None:
1820
+ from omlish.dataclasses.impl.configs import PACKAGE_CONFIG_CACHE # noqa
1821
+ from omlish.dataclasses.impl.generation.compilation import OpCompiler # noqa
1822
+ from omlish.dataclasses.impl.generation.globals import FnGlobal # noqa
1823
+ from omlish.dataclasses.impl.generation.ops import OpRef # noqa
1824
+ from omlish.dataclasses.impl.generation.processor import Codegen # noqa
1825
+ from omlish.dataclasses.impl.generation.processor import GeneratorProcessor # noqa
1826
+ from omlish.dataclasses.impl.processing.base import ProcessingContext # noqa
1827
+ from omlish.dataclasses.impl.processing.driving import processing_options_context # noqa
1828
+
1829
+ cur_module: ta.Optional[str] = None
1830
+
1831
+ dumped: ta.List[DumpedDataclassCodegen] = []
1832
+
1833
+ def callback(
1834
+ ctx: ProcessingContext,
1835
+ prepared: GeneratorProcessor.Prepared,
1836
+ comp: OpCompiler.CompileResult,
1837
+ ) -> None:
1838
+ d_refs: ta.List[DumpedDataclassCodegen.Ref] = []
1839
+ for ref in comp.refs:
1840
+ if isinstance(ref, OpRef):
1841
+ d_refs.append(DumpedDataclassCodegen.Ref(
1842
+ kind='op',
1843
+ ident=ref.ident(),
1844
+ ))
1845
+ elif isinstance(ref, FnGlobal):
1846
+ d_refs.append(DumpedDataclassCodegen.Ref(
1847
+ kind='global',
1848
+ ident=ref.ident,
1849
+ ))
1850
+ else:
1851
+ raise TypeError(ref)
1852
+
1853
+ dumped.append(DumpedDataclassCodegen(
1854
+ mod_name=check.not_none(cur_module),
1855
+
1856
+ cls_module=ctx.cls.__module__,
1857
+ cls_qualname=ctx.cls.__qualname__,
1858
+
1859
+ plan_repr=repr(prepared.plans),
1860
+
1861
+ fn_name=comp.fn_name,
1862
+ fn_params=comp.fn_params,
1863
+
1864
+ hdr_lines=comp.hdr_lines,
1865
+ fn_lines=comp.fn_lines,
1866
+
1867
+ refs=d_refs,
1868
+ ))
1869
+
1870
+ #
1871
+
1872
+ processed_modules: ta.List[str] = []
1873
+
1874
+ import_errors: ta.Dict[str, str] = {}
1875
+
1876
+ def process_module(spec: str) -> None:
1877
+ nonlocal cur_module
1878
+ check.none(cur_module)
1879
+ cur_module = spec
1880
+
1881
+ try:
1882
+ processed_modules.append(spec)
1883
+
1884
+ try:
1885
+ __import__(spec)
1886
+ except Exception as e: # noqa
1887
+ import_errors[spec] = repr(e)
1888
+
1889
+ finally:
1890
+ cur_module = None
1891
+
1892
+ def process_dir(dir_path: str) -> None:
1893
+ spec = '.'.join(dir_path.split(os.path.sep))
1894
+
1895
+ process_module(spec)
1896
+
1897
+ pkg_cfg = PACKAGE_CONFIG_CACHE.get(spec)
1898
+ if pkg_cfg is not None and not pkg_cfg.cfg.codegen:
1899
+ return
1900
+
1901
+ #
1902
+
1903
+ dns: ta.List[str] = []
1904
+ fns: ta.List[str] = []
1905
+ for n in os.listdir(dir_path):
1906
+ np = os.path.join(dir_path, n)
1907
+ if os.path.isdir(np):
1908
+ dns.append(n)
1909
+ elif os.path.isfile(np):
1910
+ fns.append(n)
1911
+
1912
+ for fn in sorted(fns):
1913
+ if not fn.endswith('.py') or fn in ('conftest.py', '_dataclasses.py'):
1914
+ continue
1915
+
1916
+ fp = os.path.join(dir_path, fn)
1917
+
1918
+ fpp = fp.split(os.path.sep)
1919
+ check.state(fpp[-1].endswith('.py'))
1920
+ fpp[-1] = fpp[-1][:-3]
1921
+ if fpp[-1] == '__init__':
1922
+ fpp.pop()
1923
+ spec = '.'.join(fpp)
1924
+
1925
+ process_module(spec)
1926
+
1927
+ for dn in sorted(dns):
1928
+ if dn == 'tests':
1929
+ continue
1930
+
1931
+ dp = os.path.join(dir_path, dn)
1932
+
1933
+ if not os.path.isfile(os.path.join(dp, '__init__.py')):
1934
+ continue
1935
+
1936
+ process_dir(dp)
1937
+
1938
+ #
1939
+
1940
+ with processing_options_context(Codegen(
1941
+ style='aot',
1942
+ force=True,
1943
+ callback=callback,
1944
+ )):
1945
+ process_dir(os.path.dirname(init_file_path))
1946
+
1947
+ #
1948
+
1949
+ output = DataclassCodegenDumperOutput(
1950
+ init_file_path=init_file_path,
1951
+ out_file_path=out_file_path,
1952
+
1953
+ processed_modules=processed_modules,
1954
+ import_errors=import_errors,
1955
+
1956
+ dumped=dumped,
1957
+ )
1958
+
1959
+ with open(out_file_path, 'w') as f:
1960
+ f.write(json_dumps_pretty(marshal_obj(output)))