omlish 0.0.0.dev447__py3-none-any.whl → 0.0.0.dev484__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 omlish might be problematic. Click here for more details.

Files changed (226) hide show
  1. omlish/.omlish-manifests.json +12 -0
  2. omlish/__about__.py +15 -15
  3. omlish/argparse/all.py +17 -9
  4. omlish/argparse/cli.py +16 -3
  5. omlish/argparse/utils.py +21 -0
  6. omlish/asyncs/asyncio/rlock.py +110 -0
  7. omlish/asyncs/asyncio/sync.py +43 -0
  8. omlish/asyncs/asyncio/utils.py +2 -0
  9. omlish/asyncs/sync.py +25 -0
  10. omlish/bootstrap/_marshal.py +1 -1
  11. omlish/bootstrap/diag.py +12 -21
  12. omlish/bootstrap/main.py +2 -5
  13. omlish/bootstrap/sys.py +27 -28
  14. omlish/cexts/__init__.py +0 -0
  15. omlish/cexts/include/omlish/omlish.hh +1 -0
  16. omlish/collections/__init__.py +13 -1
  17. omlish/collections/attrregistry.py +210 -0
  18. omlish/collections/cache/impl.py +1 -0
  19. omlish/collections/identity.py +1 -0
  20. omlish/collections/mappings.py +28 -0
  21. omlish/collections/trie.py +5 -1
  22. omlish/collections/utils.py +77 -0
  23. omlish/concurrent/all.py +2 -1
  24. omlish/concurrent/futures.py +25 -0
  25. omlish/concurrent/threadlets.py +1 -1
  26. omlish/daemons/reparent.py +2 -3
  27. omlish/daemons/spawning.py +2 -3
  28. omlish/dataclasses/__init__.py +2 -0
  29. omlish/dataclasses/impl/api/classes/decorator.py +3 -0
  30. omlish/dataclasses/impl/api/classes/make.py +3 -0
  31. omlish/dataclasses/impl/concerns/repr.py +15 -2
  32. omlish/dataclasses/impl/configs.py +97 -37
  33. omlish/dataclasses/impl/generation/compilation.py +21 -19
  34. omlish/dataclasses/impl/generation/globals.py +1 -0
  35. omlish/dataclasses/impl/generation/ops.py +1 -0
  36. omlish/dataclasses/impl/generation/processor.py +105 -24
  37. omlish/dataclasses/impl/processing/base.py +8 -0
  38. omlish/dataclasses/impl/processing/driving.py +8 -8
  39. omlish/dataclasses/specs.py +1 -0
  40. omlish/dataclasses/tools/modifiers.py +5 -0
  41. omlish/diag/cmds/__init__.py +0 -0
  42. omlish/diag/{lslocks.py → cmds/lslocks.py} +6 -6
  43. omlish/diag/{lsof.py → cmds/lsof.py} +6 -6
  44. omlish/diag/{ps.py → cmds/ps.py} +6 -6
  45. omlish/diag/pycharm.py +16 -2
  46. omlish/diag/pydevd.py +58 -40
  47. omlish/diag/replserver/console.py +1 -1
  48. omlish/dispatch/__init__.py +18 -12
  49. omlish/dispatch/methods.py +50 -140
  50. omlish/dom/rendering.py +1 -1
  51. omlish/formats/dotenv.py +1 -1
  52. omlish/formats/json/stream/__init__.py +13 -0
  53. omlish/funcs/guard.py +225 -0
  54. omlish/graphs/dot/rendering.py +1 -1
  55. omlish/http/all.py +44 -4
  56. omlish/http/clients/asyncs.py +33 -27
  57. omlish/http/clients/base.py +17 -1
  58. omlish/http/clients/coro/__init__.py +0 -0
  59. omlish/http/clients/coro/sync.py +171 -0
  60. omlish/http/clients/default.py +208 -29
  61. omlish/http/clients/executor.py +56 -0
  62. omlish/http/clients/httpx.py +72 -11
  63. omlish/http/clients/middleware.py +181 -0
  64. omlish/http/clients/sync.py +33 -27
  65. omlish/http/clients/syncasync.py +49 -0
  66. omlish/http/clients/urllib.py +6 -3
  67. omlish/http/coro/client/connection.py +15 -6
  68. omlish/http/coro/io.py +2 -0
  69. omlish/http/flasky/__init__.py +40 -0
  70. omlish/http/flasky/_compat.py +2 -0
  71. omlish/http/flasky/api.py +82 -0
  72. omlish/http/flasky/app.py +203 -0
  73. omlish/http/flasky/cvs.py +59 -0
  74. omlish/http/flasky/requests.py +20 -0
  75. omlish/http/flasky/responses.py +23 -0
  76. omlish/http/flasky/routes.py +23 -0
  77. omlish/http/flasky/types.py +15 -0
  78. omlish/http/urls.py +67 -0
  79. omlish/inject/__init__.py +38 -18
  80. omlish/inject/_dataclasses.py +4986 -0
  81. omlish/inject/binder.py +4 -48
  82. omlish/inject/elements.py +27 -0
  83. omlish/inject/helpers/__init__.py +0 -0
  84. omlish/inject/{utils.py → helpers/constfn.py} +3 -3
  85. omlish/inject/{tags.py → helpers/id.py} +2 -2
  86. omlish/inject/helpers/multis.py +143 -0
  87. omlish/inject/helpers/wrappers.py +54 -0
  88. omlish/inject/impl/elements.py +47 -17
  89. omlish/inject/impl/injector.py +20 -19
  90. omlish/inject/impl/inspect.py +10 -1
  91. omlish/inject/impl/maysync.py +3 -4
  92. omlish/inject/impl/multis.py +3 -0
  93. omlish/inject/impl/sync.py +3 -4
  94. omlish/inject/injector.py +31 -2
  95. omlish/inject/inspect.py +35 -0
  96. omlish/inject/maysync.py +2 -4
  97. omlish/inject/multis.py +8 -0
  98. omlish/inject/overrides.py +3 -3
  99. omlish/inject/privates.py +6 -0
  100. omlish/inject/providers.py +3 -2
  101. omlish/inject/sync.py +5 -4
  102. omlish/io/buffers.py +118 -0
  103. omlish/io/readers.py +29 -0
  104. omlish/iterators/transforms.py +2 -2
  105. omlish/lang/__init__.py +178 -97
  106. omlish/lang/_asyncs.cc +186 -0
  107. omlish/lang/asyncs.py +17 -0
  108. omlish/lang/casing.py +11 -0
  109. omlish/lang/contextmanagers.py +28 -4
  110. omlish/lang/functions.py +31 -22
  111. omlish/lang/imports/_capture.cc +11 -9
  112. omlish/lang/imports/capture.py +363 -170
  113. omlish/lang/imports/proxy.py +455 -152
  114. omlish/lang/lazyglobals.py +22 -9
  115. omlish/lang/params.py +17 -0
  116. omlish/lang/recursion.py +0 -1
  117. omlish/lang/sequences.py +124 -0
  118. omlish/lite/abstract.py +54 -24
  119. omlish/lite/asyncs.py +2 -2
  120. omlish/lite/attrops.py +2 -0
  121. omlish/lite/contextmanagers.py +4 -4
  122. omlish/lite/dataclasses.py +44 -0
  123. omlish/lite/maybes.py +8 -0
  124. omlish/lite/maysync.py +1 -5
  125. omlish/lite/pycharm.py +1 -1
  126. omlish/lite/typing.py +6 -0
  127. omlish/logs/all.py +1 -1
  128. omlish/logs/utils.py +1 -1
  129. omlish/manifests/loading.py +2 -1
  130. omlish/marshal/__init__.py +33 -13
  131. omlish/marshal/_dataclasses.py +2774 -0
  132. omlish/marshal/base/configs.py +12 -0
  133. omlish/marshal/base/contexts.py +36 -21
  134. omlish/marshal/base/funcs.py +8 -11
  135. omlish/marshal/base/options.py +8 -0
  136. omlish/marshal/base/registries.py +146 -44
  137. omlish/marshal/base/types.py +40 -16
  138. omlish/marshal/composite/iterables.py +33 -20
  139. omlish/marshal/composite/literals.py +20 -18
  140. omlish/marshal/composite/mappings.py +36 -23
  141. omlish/marshal/composite/maybes.py +29 -19
  142. omlish/marshal/composite/newtypes.py +16 -16
  143. omlish/marshal/composite/optionals.py +14 -14
  144. omlish/marshal/composite/special.py +15 -15
  145. omlish/marshal/composite/unions/__init__.py +0 -0
  146. omlish/marshal/composite/unions/literals.py +93 -0
  147. omlish/marshal/composite/unions/primitives.py +103 -0
  148. omlish/marshal/factories/invalidate.py +18 -68
  149. omlish/marshal/factories/method.py +26 -0
  150. omlish/marshal/factories/moduleimport/factories.py +22 -65
  151. omlish/marshal/factories/multi.py +13 -25
  152. omlish/marshal/factories/recursive.py +42 -56
  153. omlish/marshal/factories/typecache.py +29 -74
  154. omlish/marshal/factories/typemap.py +42 -43
  155. omlish/marshal/objects/dataclasses.py +129 -106
  156. omlish/marshal/objects/marshal.py +18 -14
  157. omlish/marshal/objects/namedtuples.py +48 -42
  158. omlish/marshal/objects/unmarshal.py +19 -15
  159. omlish/marshal/polymorphism/marshal.py +9 -11
  160. omlish/marshal/polymorphism/metadata.py +16 -5
  161. omlish/marshal/polymorphism/standard.py +13 -1
  162. omlish/marshal/polymorphism/unions.py +15 -105
  163. omlish/marshal/polymorphism/unmarshal.py +9 -10
  164. omlish/marshal/singular/enums.py +14 -18
  165. omlish/marshal/standard.py +10 -6
  166. omlish/marshal/trivial/any.py +1 -1
  167. omlish/marshal/trivial/forbidden.py +21 -26
  168. omlish/metadata.py +23 -1
  169. omlish/os/forkhooks.py +4 -0
  170. omlish/os/pidfiles/pinning.py +2 -2
  171. omlish/reflect/types.py +1 -0
  172. omlish/secrets/marshal.py +1 -1
  173. omlish/specs/jmespath/__init__.py +12 -3
  174. omlish/specs/jmespath/_dataclasses.py +2893 -0
  175. omlish/specs/jmespath/ast.py +1 -1
  176. omlish/specs/jsonrpc/__init__.py +13 -0
  177. omlish/specs/jsonrpc/_marshal.py +32 -23
  178. omlish/specs/jsonrpc/conns.py +10 -7
  179. omlish/specs/jsonschema/_marshal.py +1 -1
  180. omlish/specs/jsonschema/keywords/__init__.py +7 -0
  181. omlish/specs/jsonschema/keywords/_dataclasses.py +1644 -0
  182. omlish/specs/openapi/_marshal.py +31 -22
  183. omlish/sql/{tabledefs/alchemy.py → alchemy/tabledefs.py} +2 -2
  184. omlish/sql/queries/_marshal.py +2 -2
  185. omlish/sql/queries/rendering.py +1 -1
  186. omlish/sql/tabledefs/_marshal.py +1 -1
  187. omlish/subprocesses/base.py +4 -0
  188. omlish/subprocesses/editor.py +1 -1
  189. omlish/sync.py +155 -21
  190. omlish/term/alt.py +60 -0
  191. omlish/term/confirm.py +8 -8
  192. omlish/term/pager.py +235 -0
  193. omlish/term/terminfo.py +935 -0
  194. omlish/term/termstate.py +67 -0
  195. omlish/term/vt100/terminal.py +0 -3
  196. omlish/testing/pytest/plugins/asyncs/fixtures.py +4 -1
  197. omlish/testing/pytest/plugins/skips.py +2 -1
  198. omlish/testing/unittest/main.py +3 -3
  199. omlish/text/docwrap/__init__.py +3 -0
  200. omlish/text/docwrap/__main__.py +11 -0
  201. omlish/text/docwrap/api.py +83 -0
  202. omlish/text/docwrap/cli.py +86 -0
  203. omlish/text/docwrap/groups.py +84 -0
  204. omlish/text/docwrap/lists.py +167 -0
  205. omlish/text/docwrap/parts.py +146 -0
  206. omlish/text/docwrap/reflowing.py +106 -0
  207. omlish/text/docwrap/rendering.py +151 -0
  208. omlish/text/docwrap/utils.py +11 -0
  209. omlish/text/docwrap/wrapping.py +59 -0
  210. omlish/text/filecache.py +2 -2
  211. omlish/text/lorem.py +6 -0
  212. omlish/text/parts.py +2 -2
  213. omlish/text/textwrap.py +51 -0
  214. omlish/typedvalues/marshal.py +85 -59
  215. omlish/typedvalues/values.py +2 -1
  216. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/METADATA +29 -28
  217. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/RECORD +222 -171
  218. omlish/dataclasses/impl/generation/mangling.py +0 -18
  219. omlish/funcs/match.py +0 -227
  220. omlish/marshal/factories/match.py +0 -34
  221. omlish/marshal/factories/simple.py +0 -28
  222. /omlish/inject/impl/{providers2.py → providersmap.py} +0 -0
  223. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/WHEEL +0 -0
  224. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/entry_points.txt +0 -0
  225. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/licenses/LICENSE +0 -0
  226. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
+ import threading
1
2
  import typing as ta
2
3
 
3
4
 
@@ -15,6 +16,10 @@ class AmbiguousLazyGlobalsFallbackError(Exception):
15
16
  return f'{self.__class__.__name__}({self.attr!r}, {self.fallbacks!r})'
16
17
 
17
18
 
19
+ _LAZY_GLOBALS_LOCK = threading.RLock()
20
+
21
+
22
+ @ta.final
18
23
  class LazyGlobals:
19
24
  def __init__(
20
25
  self,
@@ -22,8 +27,6 @@ class LazyGlobals:
22
27
  globals: ta.MutableMapping[str, ta.Any] | None = None, # noqa
23
28
  update_globals: bool = False,
24
29
  ) -> None:
25
- super().__init__()
26
-
27
30
  self._globals = globals
28
31
  self._update_globals = update_globals
29
32
 
@@ -37,18 +40,28 @@ class LazyGlobals:
37
40
  except KeyError:
38
41
  pass
39
42
  else:
40
- if not isinstance(xga, cls):
43
+ if xga.__class__ is not cls:
41
44
  raise RuntimeError(f'Module already has __getattr__ hook: {xga}') # noqa
42
45
  return xga
43
46
 
44
- lm = cls(
45
- globals=globals,
46
- update_globals=True,
47
- )
47
+ with _LAZY_GLOBALS_LOCK:
48
+ try:
49
+ xga = globals['__getattr__']
50
+ except KeyError:
51
+ pass
52
+ else:
53
+ if xga.__class__ is not cls:
54
+ raise RuntimeError(f'Module already has __getattr__ hook: {xga}') # noqa
55
+ return xga
56
+
57
+ lm = cls(
58
+ globals=globals,
59
+ update_globals=True,
60
+ )
48
61
 
49
- globals['__getattr__'] = lm
62
+ globals['__getattr__'] = lm
50
63
 
51
- return lm
64
+ return lm
52
65
 
53
66
  def set_fn(self, attr: str, fn: ta.Callable[[], ta.Any]) -> 'LazyGlobals':
54
67
  self._attr_fns[attr] = fn
omlish/lang/params.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """
2
2
  TODO:
3
3
  - check validity
4
+ - signature vs getfullargspec - diff unwrapping + 'self' handling
4
5
  """
5
6
  import dataclasses as dc
6
7
  import enum
@@ -16,6 +17,13 @@ from .classes.restrict import Sealed
16
17
  T = ta.TypeVar('T')
17
18
 
18
19
 
20
+ CanParamSpec: ta.TypeAlias = ta.Union[
21
+ 'ParamSpec',
22
+ inspect.Signature,
23
+ ta.Callable,
24
+ ]
25
+
26
+
19
27
  ##
20
28
 
21
29
 
@@ -101,6 +109,15 @@ class ParamSpec(ta.Sequence[Param], Final):
101
109
 
102
110
  #
103
111
 
112
+ @classmethod
113
+ def of(cls, obj: CanParamSpec) -> 'ParamSpec':
114
+ if isinstance(obj, ParamSpec):
115
+ return obj
116
+ elif isinstance(obj, inspect.Signature):
117
+ return cls.of_signature(obj)
118
+ else:
119
+ return cls.inspect(obj)
120
+
104
121
  @classmethod
105
122
  def of_signature(
106
123
  cls,
omlish/lang/recursion.py CHANGED
@@ -13,7 +13,6 @@ P = ta.ParamSpec('P')
13
13
 
14
14
 
15
15
  _LOCK = threading.RLock()
16
-
17
16
  _LOCAL: threading.local
18
17
 
19
18
 
@@ -0,0 +1,124 @@
1
+ """
2
+ TODO:
3
+ - StrView, BytesView - in lieu of hkt lol
4
+ - cext? even necessary?
5
+ - __eq__, cmp, __hash__
6
+ - __buffer__
7
+ - optimize `slice(None)`, keep as SeqView but fast path ops
8
+ - shorter repr if __len__ > some threshold
9
+ - use materialize()?
10
+ """
11
+ import typing as ta
12
+
13
+
14
+ T = ta.TypeVar('T')
15
+
16
+
17
+ ##
18
+
19
+
20
+ def iterslice(
21
+ seq: ta.Sequence[T],
22
+ slc: slice,
23
+ ) -> ta.Iterator[T]:
24
+ return map(seq.__getitem__, range(*slc.indices(len(seq))))
25
+
26
+
27
+ def iterrange(
28
+ seq: ta.Sequence[T],
29
+ start: int | None = None,
30
+ stop: int | None = None,
31
+ step: int | None = None,
32
+ ) -> ta.Iterator[T]:
33
+ return iterslice(seq, slice(start, stop, step))
34
+
35
+
36
+ ##
37
+
38
+
39
+ @ta.final
40
+ class SeqView(ta.Sequence[T]):
41
+ def __init__(self, data: ta.Sequence[T], slice_: slice = slice(None)) -> None:
42
+ if data.__class__ is SeqView:
43
+ self._data = data._data # type: ignore[attr-defined] # noqa
44
+ self._range = data._range[slice_] # type: ignore[attr-defined] # noqa
45
+ else:
46
+ self._data = data
47
+ self._range = range(*slice_.indices(len(data)))
48
+
49
+ def __init_subclass__(cls, **kwargs):
50
+ raise TypeError
51
+
52
+ _data: ta.Sequence[T]
53
+ _range: range
54
+
55
+ @classmethod
56
+ def _from_range(cls, base: ta.Sequence[T], rng: range) -> 'SeqView[T]':
57
+ self = object.__new__(cls)
58
+ self._data = base
59
+ self._range = rng
60
+ return self
61
+
62
+ def __repr__(self) -> str:
63
+ return f'{self.__class__.__name__}({self._data!r}, {self.slice!r})'
64
+
65
+ #
66
+
67
+ def __len__(self) -> int:
68
+ return len(self._range)
69
+
70
+ def __getitem__(self, key: int | slice) -> ta.Union[T, 'SeqView[T]']: # type: ignore[override]
71
+ if isinstance(key, slice):
72
+ nr = self._range[key]
73
+ return SeqView._from_range(self._data, nr)
74
+ return self._data[self._range[key]]
75
+
76
+ def __iter__(self) -> ta.Iterator[T]:
77
+ return map(self._data.__getitem__, self._range)
78
+
79
+ def __reversed__(self) -> ta.Iterator[T]:
80
+ return map(self._data.__getitem__, reversed(self._range))
81
+
82
+ def count(self, value: ta.Any) -> int:
83
+ c = 0
84
+ for i in self._range:
85
+ if self._data[i] == value:
86
+ c += 1
87
+ return c
88
+
89
+ def index(self, value: ta.Any, start: int = 0, stop: int | None = None) -> int:
90
+ sub = self._range[slice(start, stop, 1)]
91
+ for off, i in enumerate(sub):
92
+ if self._data[i] == value:
93
+ return off
94
+ raise ValueError(f'{value!r} is not in view')
95
+
96
+ #
97
+
98
+ @property
99
+ def data(self) -> ta.Sequence[T]:
100
+ return self._data
101
+
102
+ _slice: slice
103
+
104
+ @property
105
+ def slice(self) -> slice:
106
+ try:
107
+ return self._slice
108
+ except AttributeError:
109
+ pass
110
+
111
+ step = self._range.step
112
+ start = self._range.start
113
+ if len(self._range) == 0:
114
+ stop = start
115
+ else:
116
+ last = start + (len(self._range) - 1) * step
117
+ stop = last + (1 if step > 0 else -1)
118
+ slc = slice(start, stop, step)
119
+
120
+ self._slice = slc
121
+ return slc
122
+
123
+ def materialize(self) -> ta.Sequence[T]:
124
+ return self._data[self.slice]
omlish/lite/abstract.py CHANGED
@@ -3,6 +3,9 @@ import abc
3
3
  import typing as ta
4
4
 
5
5
 
6
+ T = ta.TypeVar('T')
7
+
8
+
6
9
  ##
7
10
 
8
11
 
@@ -14,25 +17,49 @@ def is_abstract_method(obj: ta.Any) -> bool:
14
17
  return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
15
18
 
16
19
 
17
- def update_abstracts(cls, *, force=False):
20
+ def compute_abstract_methods(cls: type) -> ta.FrozenSet[str]:
21
+ # ~> https://github.com/python/cpython/blob/f3476c6507381ca860eec0989f53647b13517423/Modules/_abc.c#L358
22
+
23
+ # Stage 1: direct abstract methods
24
+
25
+ abstracts = {
26
+ a
27
+ # Get items as a list to avoid mutation issues during iteration
28
+ for a, v in list(cls.__dict__.items())
29
+ if is_abstract_method(v)
30
+ }
31
+
32
+ # Stage 2: inherited abstract methods
33
+
34
+ for base in cls.__bases__:
35
+ # Get __abstractmethods__ from base if it exists
36
+ if (base_abstracts := getattr(base, _ABSTRACT_METHODS_ATTR, None)) is None:
37
+ continue
38
+
39
+ # Iterate over abstract methods in base
40
+ for key in base_abstracts:
41
+ # Check if this class has an attribute with this name
42
+ try:
43
+ value = getattr(cls, key)
44
+ except AttributeError:
45
+ # Attribute not found in this class, skip
46
+ continue
47
+
48
+ # Check if it's still abstract
49
+ if is_abstract_method(value):
50
+ abstracts.add(key)
51
+
52
+ return frozenset(abstracts)
53
+
54
+
55
+ def update_abstracts(cls: ta.Type[T], *, force: bool = False) -> ta.Type[T]:
18
56
  if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
19
57
  # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
20
58
  # implementation (especially during testing), and we want to handle both cases.
21
59
  return cls
22
60
 
23
- abstracts: ta.Set[str] = set()
24
-
25
- for scls in cls.__bases__:
26
- for name in getattr(scls, _ABSTRACT_METHODS_ATTR, ()):
27
- value = getattr(cls, name, None)
28
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
29
- abstracts.add(name)
30
-
31
- for name, value in cls.__dict__.items():
32
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
33
- abstracts.add(name)
34
-
35
- setattr(cls, _ABSTRACT_METHODS_ATTR, frozenset(abstracts))
61
+ abstracts = compute_abstract_methods(cls)
62
+ setattr(cls, _ABSTRACT_METHODS_ATTR, abstracts)
36
63
  return cls
37
64
 
38
65
 
@@ -86,23 +113,26 @@ class Abstract:
86
113
  super().__init_subclass__(**kwargs)
87
114
 
88
115
  if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
89
- ams = {a: cls for a, o in cls.__dict__.items() if is_abstract_method(o)}
90
-
91
- seen = set(cls.__dict__)
92
- for b in cls.__bases__:
93
- ams.update({a: b for a in set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen}) # noqa
94
- seen.update(dir(b))
116
+ if ams := compute_abstract_methods(cls):
117
+ amd = {
118
+ a: mcls
119
+ for mcls in cls.__mro__[::-1]
120
+ for a in ams
121
+ if a in mcls.__dict__
122
+ }
95
123
 
96
- if ams:
97
124
  raise AbstractTypeError(
98
125
  f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
99
126
  ', '.join(sorted([
100
127
  '.'.join([
101
- *([m] if (m := getattr(c, '__module__')) else []),
102
- getattr(c, '__qualname__', getattr(c, '__name__')),
128
+ *([
129
+ *([m] if (m := getattr(c, '__module__')) else []),
130
+ getattr(c, '__qualname__', getattr(c, '__name__')),
131
+ ] if c is not None else '?'),
103
132
  a,
104
133
  ])
105
- for a, c in ams.items()
134
+ for a in ams
135
+ for c in [amd.get(a)]
106
136
  ])),
107
137
  )
108
138
 
omlish/lite/asyncs.py CHANGED
@@ -50,12 +50,12 @@ def sync_await(aw: ta.Awaitable[T]) -> T:
50
50
 
51
51
  ret = missing = object()
52
52
 
53
- async def gate():
53
+ async def thunk():
54
54
  nonlocal ret
55
55
 
56
56
  ret = await aw
57
57
 
58
- cr = gate()
58
+ cr = thunk()
59
59
  try:
60
60
  try:
61
61
  cr.send(None)
omlish/lite/attrops.py CHANGED
@@ -6,6 +6,8 @@ TODO:
6
6
  - per-attr repr transform / filter
7
7
  - __ne__ ? cases where it still matters
8
8
  - ordering ?
9
+ - repr_filter: ta.Union[ta.Callable[[ta.Any], ta.Optional[str]], ta.Literal['not_none', 'truthy']]] ?
10
+ - unify repr/repr_fn/repr_filter
9
11
  """
10
12
  import types # noqa
11
13
  import typing as ta
@@ -54,7 +54,7 @@ class ExitStacked:
54
54
  es.__enter__()
55
55
  try:
56
56
  self._enter_contexts()
57
- except Exception: # noqa
57
+ except BaseException: # noqa
58
58
  es.__exit__(*sys.exc_info())
59
59
  raise
60
60
  return self
@@ -65,7 +65,7 @@ class ExitStacked:
65
65
  return None
66
66
  try:
67
67
  self._exit_contexts()
68
- except Exception: # noqa
68
+ except BaseException: # noqa
69
69
  es.__exit__(*sys.exc_info())
70
70
  raise
71
71
  return es.__exit__(exc_type, exc_val, exc_tb)
@@ -113,7 +113,7 @@ class AsyncExitStacked:
113
113
  await es.__aenter__()
114
114
  try:
115
115
  await self._async_enter_contexts()
116
- except Exception: # noqa
116
+ except BaseException: # noqa
117
117
  await es.__aexit__(*sys.exc_info())
118
118
  raise
119
119
  return self
@@ -124,7 +124,7 @@ class AsyncExitStacked:
124
124
  return None
125
125
  try:
126
126
  await self._async_exit_contexts()
127
- except Exception: # noqa
127
+ except BaseException: # noqa
128
128
  await es.__aexit__(*sys.exc_info())
129
129
  raise
130
130
  return await es.__aexit__(exc_type, exc_val, exc_tb)
@@ -100,6 +100,36 @@ def dataclass_repr_omit_falsey(obj: ta.Any) -> str:
100
100
  ##
101
101
 
102
102
 
103
+ def dataclass_descriptor_method(*bind_attrs: str, bind_owner: bool = False) -> ta.Callable:
104
+ if not bind_attrs:
105
+ def __get__(self, instance, owner=None): # noqa
106
+ return self
107
+
108
+ elif bind_owner:
109
+ def __get__(self, instance, owner=None): # noqa
110
+ # Guaranteed to return a new instance even with no attrs
111
+ return dc.replace(self, **{
112
+ a: v.__get__(instance, owner) if (v := getattr(self, a)) is not None else None
113
+ for a in bind_attrs
114
+ })
115
+
116
+ else:
117
+ def __get__(self, instance, owner=None): # noqa
118
+ if instance is None:
119
+ return self
120
+
121
+ # Guaranteed to return a new instance even with no attrs
122
+ return dc.replace(self, **{
123
+ a: v.__get__(instance, owner) if (v := getattr(self, a)) is not None else None
124
+ for a in bind_attrs
125
+ })
126
+
127
+ return __get__
128
+
129
+
130
+ ##
131
+
132
+
103
133
  def dataclass_kw_only_init():
104
134
  def inner(cls):
105
135
  if not isinstance(cls, type) and dc.is_dataclass(cls):
@@ -161,3 +191,17 @@ def dataclass_kw_only_init():
161
191
  return cls
162
192
 
163
193
  return inner
194
+
195
+
196
+ ##
197
+
198
+
199
+ @dc.dataclass()
200
+ class DataclassFieldRequiredError(Exception):
201
+ name: str
202
+
203
+
204
+ def dataclass_field_required(name: str) -> ta.Callable[[], ta.Any]:
205
+ def inner() -> ta.NoReturn:
206
+ raise DataclassFieldRequiredError(name)
207
+ return inner
omlish/lite/maybes.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # ruff: noqa: UP007 UP045
2
2
  import abc
3
3
  import functools
4
+ import operator
4
5
  import typing as ta
5
6
 
6
7
  from .abstract import Abstract
@@ -208,3 +209,10 @@ class _EmptyMaybe(_Maybe[T]):
208
209
 
209
210
 
210
211
  Maybe._empty = _EmptyMaybe() # noqa
212
+
213
+
214
+ ##
215
+
216
+
217
+ setattr(Maybe, 'just', _JustMaybe) # noqa
218
+ setattr(Maybe, 'empty', functools.partial(operator.attrgetter('_empty'), Maybe))
omlish/lite/maysync.py CHANGED
@@ -25,6 +25,7 @@ Internally, it's not really correct to say that there is 'no event loop' in the
25
25
  ===
26
26
 
27
27
  TODO:
28
+ - ! impl iterators not just generators !
28
29
  - __del__
29
30
  - (test) maysync context managers
30
31
  - CancelledError
@@ -33,11 +34,6 @@ TODO:
33
34
  - works down to 3.8
34
35
  - make_maysync_from_sync can run with asyncio.run_in_thread
35
36
  - make_maysync_from_async can run with asyncio.run_soon
36
-
37
- TODO OVERHAUL:
38
- - no more sync/async context, just one Context, and it means sync
39
- - make FpMaywaitable *not reusable*
40
- - `cannot reuse already awaited coroutine`
41
37
  """
42
38
  import abc
43
39
  import inspect
omlish/lite/pycharm.py CHANGED
@@ -6,7 +6,7 @@ import typing as ta
6
6
  ##
7
7
 
8
8
 
9
- DEFAULT_PYCHARM_VERSION = '242.23726.102'
9
+ DEFAULT_PYCHARM_VERSION = '252.26199.168'
10
10
 
11
11
 
12
12
  @dc.dataclass(frozen=True)
omlish/lite/typing.py CHANGED
@@ -12,6 +12,12 @@ A2 = ta.TypeVar('A2')
12
12
 
13
13
  ##
14
14
  # A workaround for typing deficiencies (like `Argument 2 to NewType(...) must be subclassable`).
15
+ #
16
+ # Note that this problem doesn't happen at runtime - it happens in mypy:
17
+ #
18
+ # mypy <(echo "import typing as ta; MyCallback = ta.NewType('MyCallback', ta.Callable[[], None])")
19
+ # /dev/fd/11:1:22: error: Argument 2 to NewType(...) must be subclassable (got "Callable[[], None]") [valid-newtype]
20
+ #
15
21
 
16
22
 
17
23
  @dc.dataclass(frozen=True)
omlish/logs/all.py CHANGED
@@ -88,7 +88,7 @@ with _lang.auto_proxy_init(globals()):
88
88
  LogTimingContext,
89
89
  log_timing_context,
90
90
 
91
- error_logging,
91
+ exception_logging,
92
92
  )
93
93
 
94
94
  from .warnings import ( # noqa
omlish/logs/utils.py CHANGED
@@ -11,7 +11,7 @@ from .protocols import LoggerLike
11
11
  ##
12
12
 
13
13
 
14
- def error_logging(log): # noqa
14
+ def exception_logging(log): # noqa
15
15
  def outer(fn):
16
16
  @functools.wraps(fn)
17
17
  def inner(*args, **kwargs):
@@ -13,7 +13,7 @@ TODO:
13
13
  scan_root_dirs
14
14
  - currently the cli cant subprocess itself and keep manifests working
15
15
  - EnvVar cls is already lite
16
-
16
+ - can discover_packages deadlock with concurrent / multithreaded imports?
17
17
  """
18
18
  import dataclasses as dc
19
19
  import importlib.machinery
@@ -417,6 +417,7 @@ class ManifestLoader:
417
417
  def _read_package_file_text(cls, package_name: str, file_name: str) -> ta.Optional[str]:
418
418
  # importlib.resources.files actually imports the package - to avoid this, if possible, the file is read straight
419
419
  # off the filesystem.
420
+ # FIXME: find_spec *still* imports the parent package to get __path__ to feed to _find_spec...
420
421
  spec = importlib.util.find_spec(package_name)
421
422
  if (
422
423
  spec is not None and
@@ -49,11 +49,17 @@ with _lang.auto_proxy_init(globals()):
49
49
 
50
50
  from .base.configs import ( # noqa
51
51
  Config,
52
+ Configs,
53
+
52
54
  ConfigRegistry,
53
55
  )
54
56
 
55
57
  from .base.contexts import ( # noqa
56
58
  BaseContext,
59
+
60
+ MarshalFactoryContext,
61
+ UnmarshalFactoryContext,
62
+
57
63
  MarshalContext,
58
64
  UnmarshalContext,
59
65
  )
@@ -108,19 +114,35 @@ with _lang.auto_proxy_init(globals()):
108
114
  IterableUnmarshaler,
109
115
  )
110
116
 
117
+ from .composite.optionals import ( # noqa
118
+ OptionalMarshaler,
119
+ OptionalUnmarshaler,
120
+ )
121
+
122
+ from .composite.unions.literals import ( # noqa
123
+ LITERAL_UNION_TYPES,
124
+ LiteralUnionMarshaler,
125
+ LiteralUnionMarshalerFactory,
126
+ LiteralUnionUnmarshaler,
127
+ LiteralUnionUnmarshalerFactory,
128
+ )
129
+
130
+ from .composite.unions.primitives import ( # noqa
131
+ PRIMITIVE_UNION_TYPES,
132
+ PrimitiveUnionMarshaler,
133
+ PrimitiveUnionMarshalerFactory,
134
+ PrimitiveUnionUnmarshaler,
135
+ PrimitiveUnionUnmarshalerFactory,
136
+ )
137
+
111
138
  from .composite.wrapped import ( # noqa
112
139
  WrappedMarshaler,
113
140
  WrappedUnmarshaler,
114
141
  )
115
142
 
116
- from .factories.simple import ( # noqa
117
- SimpleMarshalerFactory,
118
- SimpleUnmarshalerFactory,
119
- )
120
-
121
- from .factories.match import ( # noqa
122
- MarshalerFactoryMatchClass,
123
- UnmarshalerFactoryMatchClass,
143
+ from .factories.method import ( # noqa
144
+ MarshalerFactoryMethodClass,
145
+ UnmarshalerFactoryMethodClass,
124
146
  )
125
147
 
126
148
  from .factories.moduleimport.configs import ( # noqa
@@ -192,6 +214,7 @@ with _lang.auto_proxy_init(globals()):
192
214
  )
193
215
 
194
216
  from .polymorphism.metadata import ( # noqa
217
+ AutoStripSuffix,
195
218
  FieldTypeTagging,
196
219
  Impl,
197
220
  Impls,
@@ -207,13 +230,8 @@ with _lang.auto_proxy_init(globals()):
207
230
  )
208
231
 
209
232
  from .polymorphism.unions import ( # noqa
210
- PRIMITIVE_UNION_TYPES,
211
233
  PolymorphismUnionMarshalerFactory,
212
234
  PolymorphismUnionUnmarshalerFactory,
213
- PrimitiveUnionMarshaler,
214
- PrimitiveUnionMarshalerFactory,
215
- PrimitiveUnionUnmarshaler,
216
- PrimitiveUnionUnmarshalerFactory,
217
235
  )
218
236
 
219
237
  from .polymorphism.unmarshal import ( # noqa
@@ -225,6 +243,7 @@ with _lang.auto_proxy_init(globals()):
225
243
  from .singular.base64 import ( # noqa
226
244
  BASE64_MARSHALER_FACTORY,
227
245
  BASE64_UNMARSHALER_FACTORY,
246
+ Base64MarshalerUnmarshaler,
228
247
  )
229
248
 
230
249
  from .singular.primitives import ( # noqa
@@ -233,6 +252,7 @@ with _lang.auto_proxy_init(globals()):
233
252
 
234
253
  from .trivial.forbidden import ( # noqa
235
254
  ForbiddenTypeMarshalerFactory,
255
+ ForbiddenTypeMarshalerFactoryUnmarshalerFactory,
236
256
  ForbiddenTypeUnmarshalerFactory,
237
257
  )
238
258