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
@@ -0,0 +1,210 @@
1
+ """
2
+ TODO:
3
+ - lock?
4
+ """
5
+ import dataclasses as dc
6
+ import typing as ta
7
+ import weakref
8
+
9
+ from .. import check
10
+ from .. import lang
11
+ from .mappings import dict_factory
12
+
13
+
14
+ T = ta.TypeVar('T')
15
+ K = ta.TypeVar('K')
16
+ V = ta.TypeVar('V')
17
+
18
+
19
+ ##
20
+
21
+
22
+ class AttrRegistry(ta.Generic[K, V]):
23
+ """
24
+ MRO-honoring class member registry. There are many ways to do this, and this one is attr name based: a class is
25
+ considered to have objects registered to it based on whether or not they are accessible by a non-shadowed,
26
+ MRO-resolved named attribute on that class.
27
+
28
+ Care must be taken when overriding registered objects from superclasses in subclasses - shadowing the attribute name
29
+ of the superclass member will not automatically register the new member with the same name to the registry - it must
30
+ be explicitly registered itself. This is a feature, allowing for selective de-registration of objects in subclasses
31
+ via name shadowing.
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ *,
37
+ requires_override: bool = False,
38
+ is_override: ta.Callable[[ta.Any], bool] | None = None,
39
+ forbid_duplicates: bool = False,
40
+ identity: bool = False,
41
+ weak: bool = False,
42
+ ) -> None:
43
+ super().__init__()
44
+
45
+ self._requires_override = requires_override
46
+ self._is_override = is_override
47
+ self._forbid_duplicates = forbid_duplicates
48
+ self._identity = identity
49
+ self._weak = weak
50
+
51
+ self._objs: ta.MutableMapping[K, V] = dict_factory(identity=identity, weak=weak)()
52
+ self._invalidate_callbacks: list[ta.Callable[[], None]] = []
53
+
54
+ def add_invalidate_callback(self, callback: ta.Callable[[], None]) -> None:
55
+ self._invalidate_callbacks.append(callback)
56
+
57
+ @ta.overload
58
+ def register(self, obj: K, val: V) -> None:
59
+ ...
60
+
61
+ @ta.overload
62
+ def register(self, val: V) -> ta.Callable[[T], T]:
63
+ ...
64
+
65
+ def register(self, *args):
66
+ def inner(obj, val):
67
+ check.not_in(obj, self._objs)
68
+
69
+ self._objs[obj] = val
70
+
71
+ for iv in self._invalidate_callbacks:
72
+ iv()
73
+
74
+ return obj
75
+
76
+ if len(args) == 1:
77
+ return lambda obj: inner(obj, args[0])
78
+ elif len(args) == 2:
79
+ inner = inner(*args)
80
+ return None
81
+ else:
82
+ raise TypeError(args)
83
+
84
+ def _lookup(self, obj: ta.Any) -> lang.Maybe[V]:
85
+ if not self._identity:
86
+ try:
87
+ hash(obj)
88
+ except TypeError:
89
+ return lang.empty()
90
+
91
+ try:
92
+ val = self._objs[obj]
93
+ except KeyError:
94
+ return lang.empty()
95
+ else:
96
+ return lang.just(val)
97
+
98
+ @dc.dataclass()
99
+ class DuplicatesForbiddenError(Exception):
100
+ owner_cls: type
101
+ instance_cls: type
102
+ att: str
103
+ ex_att: str
104
+
105
+ def collect(self, instance_cls: type, owner_cls: type | None = None) -> dict[str, tuple[K, V]]:
106
+ if owner_cls is None:
107
+ owner_cls = instance_cls
108
+
109
+ mro = instance_cls.__mro__[-2::-1]
110
+ try:
111
+ mro_pos = mro.index(owner_cls)
112
+ except ValueError:
113
+ raise TypeError(f'Owner class {owner_cls} not in mro of instance class {instance_cls}') from None
114
+
115
+ mro_dct: dict[str, list[tuple[type, ta.Any]]] = {}
116
+ for cur_cls in mro[:mro_pos + 1]:
117
+ for att, obj in cur_cls.__dict__.items():
118
+ if att not in mro_dct:
119
+ if not self._lookup(obj).present:
120
+ continue
121
+
122
+ try:
123
+ lst = mro_dct[att]
124
+ except KeyError:
125
+ lst = mro_dct[att] = []
126
+ lst.append((cur_cls, obj))
127
+
128
+ #
129
+
130
+ seen: ta.MutableMapping[ta.Any, str] | None = None
131
+ if self._forbid_duplicates:
132
+ seen = dict_factory(identity=self._identity)()
133
+
134
+ out: dict[str, tuple[K, V]] = {}
135
+ for att, lst in mro_dct.items():
136
+ if not lst:
137
+ raise RuntimeError
138
+ _, obj = lst[-1]
139
+
140
+ if len(lst) > 1:
141
+ if self._requires_override and not (self._is_override or lang.is_override)(obj):
142
+ raise lang.RequiresOverrideError(
143
+ att,
144
+ instance_cls,
145
+ lst[-1][0],
146
+ lst[0][0],
147
+ )
148
+
149
+ if not (mv := self._lookup(obj)).present:
150
+ continue
151
+
152
+ if seen is not None:
153
+ try:
154
+ ex_att = seen[obj]
155
+ except KeyError:
156
+ pass
157
+ else:
158
+ raise AttrRegistry.DuplicatesForbiddenError(owner_cls, instance_cls, att, ex_att) # noqa
159
+ seen[obj] = att
160
+
161
+ out[att] = (obj, mv.must())
162
+
163
+ return out
164
+
165
+
166
+ ##
167
+
168
+
169
+ class AttrRegistryCache(ta.Generic[K, V, T]):
170
+ def __init__(
171
+ self,
172
+ registry: AttrRegistry[K, V],
173
+ prepare: ta.Callable[[type, dict[str, tuple[K, V]]], T],
174
+ ) -> None:
175
+ super().__init__()
176
+
177
+ self._registry = registry
178
+ self._prepare = prepare
179
+
180
+ self._cache: dict[ta.Any, T] = {}
181
+
182
+ def cache_remove(k, self_ref=weakref.ref(self)):
183
+ if (ref_self := self_ref()) is not None:
184
+ cache = ref_self._cache # noqa
185
+ try:
186
+ del cache[k]
187
+ except KeyError:
188
+ pass
189
+
190
+ self._cache_remove = cache_remove
191
+
192
+ registry.add_invalidate_callback(self._cache.clear)
193
+
194
+ def get(self, instance_cls: type) -> T:
195
+ cls_ref = weakref.ref(instance_cls)
196
+ try:
197
+ return self._cache[cls_ref]
198
+ except KeyError:
199
+ pass
200
+ del cls_ref
201
+
202
+ collected = self._registry.collect(instance_cls)
203
+ out = self._prepare(instance_cls, collected)
204
+ self._cache[weakref.ref(instance_cls, self._cache_remove)] = out
205
+ return out
206
+
207
+
208
+ class SimpleAttrRegistryCache(AttrRegistryCache[K, V, dict[str, tuple[K, V]]], ta.Generic[K, V]):
209
+ def __init__(self, registry: AttrRegistry[K, V]) -> None:
210
+ super().__init__(registry, lambda _, dct: dct)
@@ -41,6 +41,7 @@ def LRU(cache: 'Cache') -> None: # noqa
41
41
  cache._kill(cache._root.lru_next) # type: ignore # noqa
42
42
 
43
43
 
44
+ # aka FIFO
44
45
  def LRI(cache: 'Cache') -> None: # noqa
45
46
  cache._kill(cache._root.ins_next) # type: ignore # noqa
46
47
 
@@ -105,6 +105,7 @@ class IdentityWeakKeyDictionary(ta.MutableMapping[K, V]):
105
105
 
106
106
  TODO:
107
107
  - audit for freethreaded
108
+ - leans on stdlib impls so shouldn't matter?
108
109
  """
109
110
 
110
111
  def __init__(self, *args: ta.Any, **kwargs: ta.Any) -> None:
@@ -1,6 +1,12 @@
1
1
  import typing as ta
2
2
  import weakref
3
3
 
4
+ from .. import lang
5
+
6
+
7
+ with lang.auto_proxy_import(globals()):
8
+ from . import identity as _identity
9
+
4
10
 
5
11
  T = ta.TypeVar('T')
6
12
  K = ta.TypeVar('K')
@@ -63,6 +69,9 @@ class TypeMap(ta.Generic[T]):
63
69
  def __iter__(self) -> ta.Iterator[T]:
64
70
  return iter(self._items)
65
71
 
72
+ def __contains__(self, ty: type[T]) -> bool:
73
+ return ty in self._items
74
+
66
75
  def get(self, ty: type[T]) -> T | None:
67
76
  return self._dct.get(ty)
68
77
 
@@ -138,3 +147,22 @@ class MissingDict(dict[K, V]):
138
147
  def __missing__(self, key):
139
148
  v = self[key] = self._missing_fn(key)
140
149
  return v
150
+
151
+
152
+ ##
153
+
154
+
155
+ def dict_factory[K, V](
156
+ *,
157
+ identity: bool = False,
158
+ weak: bool = False,
159
+ ) -> ta.Callable[..., ta.MutableMapping[K, V]]:
160
+ if identity:
161
+ if weak:
162
+ return _identity.IdentityWeakKeyDictionary
163
+ else:
164
+ return _identity.IdentityKeyDict
165
+ elif weak:
166
+ return weakref.WeakKeyDictionary
167
+ else:
168
+ return dict
@@ -37,12 +37,16 @@ class Trie(ta.MutableMapping[ta.Sequence[K], V], ta.Generic[K, V]):
37
37
  def children(self) -> ta.Mapping[K2, 'Trie.Node[K2, V2]']:
38
38
  return self._children
39
39
 
40
- def __init__(self) -> None:
40
+ def __init__(self, items: ta.Iterable[tuple[ta.Iterable[K], V]] | None = None) -> None:
41
41
  super().__init__()
42
42
 
43
43
  self._len = 0
44
44
  self._root: Trie.Node[K, V] = Trie.Node()
45
45
 
46
+ if items is not None:
47
+ for k, v in items:
48
+ self[k] = v
49
+
46
50
  @property
47
51
  def root(self) -> Node[K, V]:
48
52
  return self._root
@@ -7,7 +7,9 @@ from .identity import IdentitySet
7
7
 
8
8
  T = ta.TypeVar('T')
9
9
  K = ta.TypeVar('K')
10
+ K2 = ta.TypeVar('K2')
10
11
  V = ta.TypeVar('V')
12
+ V2 = ta.TypeVar('V2')
11
13
 
12
14
 
13
15
  ##
@@ -135,6 +137,81 @@ def make_map_by(
135
137
  ##
136
138
 
137
139
 
140
+ @ta.overload
141
+ def map_keys(
142
+ fn: ta.Callable[[K], K2],
143
+ dct: ta.Mapping[K, V] | ta.Iterable[tuple[K, V]],
144
+ *,
145
+ identity: ta.Literal[False] = False,
146
+ strict: bool = False,
147
+ ) -> dict[K2, V]:
148
+ return {}
149
+
150
+
151
+ @ta.overload
152
+ def map_keys(
153
+ fn: ta.Callable[[K], K2],
154
+ dct: ta.Mapping[K, V] | ta.Iterable[tuple[K, V]],
155
+ *,
156
+ identity: bool = False,
157
+ strict: bool = False,
158
+ ) -> ta.MutableMapping[K2, V]:
159
+ return {}
160
+
161
+
162
+ def map_keys(
163
+ fn,
164
+ dct,
165
+ *,
166
+ identity=False,
167
+ strict=False,
168
+ ):
169
+ return make_map(
170
+ ((fn(k), v) for k, v in (dct.items() if isinstance(dct, ta.Mapping) else dct)),
171
+ identity=identity,
172
+ strict=strict,
173
+ )
174
+
175
+
176
+ @ta.overload
177
+ def map_values(
178
+ fn: ta.Callable[[V], V2],
179
+ dct: ta.Mapping[K, V] | ta.Iterable[tuple[K, V]],
180
+ *,
181
+ identity: ta.Literal[False] = False,
182
+ strict: bool = False,
183
+ ) -> dict[K, V2]:
184
+ return {}
185
+
186
+
187
+ @ta.overload
188
+ def map_values(
189
+ fn: ta.Callable[[V], V2],
190
+ dct: ta.Mapping[K, V] | ta.Iterable[tuple[K, V]],
191
+ *,
192
+ identity: bool = False,
193
+ strict: bool = False,
194
+ ) -> ta.MutableMapping[K2, V]:
195
+ return {}
196
+
197
+
198
+ def map_values(
199
+ fn,
200
+ dct,
201
+ *,
202
+ identity=False,
203
+ strict=False,
204
+ ):
205
+ return make_map(
206
+ ((k, fn(v)) for k, v in (dct.items() if isinstance(dct, ta.Mapping) else dct)),
207
+ identity=identity,
208
+ strict=strict,
209
+ )
210
+
211
+
212
+ ##
213
+
214
+
138
215
  @ta.overload
139
216
  def multi_map(
140
217
  kvs: ta.Iterable[tuple[K, V]],
omlish/concurrent/all.py CHANGED
@@ -12,6 +12,7 @@ with _lang.auto_proxy_init(globals()):
12
12
  from .futures import ( # noqa
13
13
  FutureError,
14
14
  FutureTimeoutError,
15
- wait_futures,
15
+ wait_all_futures_or_raise,
16
16
  wait_dependent_futures,
17
+ wait_futures,
17
18
  )
@@ -48,6 +48,13 @@ def wait_futures(
48
48
  raise_exceptions: bool = False,
49
49
  cancel_on_exception: bool = False,
50
50
  ) -> bool:
51
+ # TODO:
52
+ # - raise_exceptions 'at_end', ExceptionGroup
53
+ # - more responsive than tick_interval_s - semaphore, add callbacks to each fut to decrement, sleep with a timed
54
+ # wait on sem
55
+ # - cancel_on_timeout
56
+ # - obviate wait_all_futures_or_raise
57
+
51
58
  start = time.time()
52
59
 
53
60
  not_done = set(futures)
@@ -72,6 +79,24 @@ def wait_futures(
72
79
  return False
73
80
 
74
81
 
82
+ def wait_all_futures_or_raise(futures: ta.Sequence[cf.Future]) -> None:
83
+ done, not_done = cf.wait(futures, return_when=cf.ALL_COMPLETED)
84
+ if not_done:
85
+ raise RuntimeError(f'Not all futures finished: {not_done}')
86
+
87
+ excs: list[Exception] = []
88
+ for f in done:
89
+ try:
90
+ f.result()
91
+ except Exception as e: # noqa
92
+ excs.append(e)
93
+
94
+ if len(excs) == 1:
95
+ raise excs[0]
96
+ elif excs:
97
+ raise ExceptionGroup('One or more futures failed', excs)
98
+
99
+
75
100
  def wait_dependent_futures(
76
101
  executor: cf.Executor,
77
102
  dependency_sets_by_fn: ta.Mapping[ta.Callable, ta.AbstractSet[ta.Callable]],
@@ -75,7 +75,7 @@ class GreenletThreadlet(Threadlet):
75
75
 
76
76
  @property
77
77
  def parent(self) -> ta.Optional['GreenletThreadlet']:
78
- return GreenletThreadlet(self.g.parent)
78
+ return GreenletThreadlet(p) if (p := self.g.parent) is not None else None
79
79
 
80
80
  @property
81
81
  def dead(self) -> bool:
@@ -10,13 +10,12 @@ def reparent_process(
10
10
  no_close_stdio: bool = False,
11
11
  ) -> None:
12
12
  if (pid := os.fork()): # noqa
13
- sys.exit(0)
14
- raise RuntimeError('Unreachable') # noqa
13
+ raise SystemExit(0)
15
14
 
16
15
  os.setsid()
17
16
 
18
17
  if (pid := os.fork()): # noqa
19
- sys.exit(0)
18
+ raise SystemExit(0)
20
19
 
21
20
  if not no_close_stdio:
22
21
  rn_fd = os.open('/dev/null', os.O_RDONLY)
@@ -2,7 +2,6 @@ import abc
2
2
  import enum
3
3
  import functools
4
4
  import os
5
- import sys
6
5
  import threading
7
6
  import typing as ta
8
7
 
@@ -166,9 +165,9 @@ class ForkSpawner(Spawner, dc.Frozen):
166
165
  try:
167
166
  spawn.fn()
168
167
  except BaseException: # noqa
169
- sys.exit(1)
168
+ raise SystemExit(1) from None
170
169
  else:
171
- sys.exit(0)
170
+ raise SystemExit(0)
172
171
 
173
172
  raise RuntimeError('Unreachable') # noqa
174
173
 
@@ -159,6 +159,8 @@ with _lang.auto_proxy_init(globals()) as _api_cap:
159
159
  is_immediate_dataclass,
160
160
 
161
161
  dataclass_maybe_post_init as maybe_post_init,
162
+
163
+ dataclass_descriptor_method as descriptor_method,
162
164
  )
163
165
 
164
166
 
@@ -10,6 +10,7 @@ from ..... import lang
10
10
  from ...._internals import STD_FIELDS_ATTR
11
11
  from ...._internals import STD_PARAMS_ATTR
12
12
  from ....specs import ClassSpec
13
+ from ....specs import ReprFn
13
14
  from ...processing.driving import drive_cls_processing
14
15
  from ...utils import class_decorator
15
16
  from ..fields.building import build_cls_std_fields
@@ -53,6 +54,7 @@ def dataclass(
53
54
 
54
55
  repr_id: bool | None = None,
55
56
  terse_repr: bool | None = None,
57
+ default_repr_fn: ReprFn | None = None,
56
58
 
57
59
  allow_redundant_decorator: bool | None = None,
58
60
 
@@ -158,6 +160,7 @@ def dataclass(
158
160
 
159
161
  repr_id=repr_id,
160
162
  terse_repr=terse_repr,
163
+ default_repr_fn=default_repr_fn,
161
164
 
162
165
  allow_redundant_decorator=allow_redundant_decorator,
163
166
  ),
@@ -4,6 +4,7 @@ import sys
4
4
  import types
5
5
  import typing as ta
6
6
 
7
+ from ....specs import ReprFn
7
8
  from .decorator import dataclass
8
9
 
9
10
 
@@ -53,6 +54,7 @@ def make_dataclass( # noqa
53
54
 
54
55
  repr_id: bool | None = None,
55
56
  terse_repr: bool | None = None,
57
+ default_repr_fn: ReprFn | None = None,
56
58
 
57
59
  allow_redundant_decorator: bool | None = None,
58
60
 
@@ -174,6 +176,7 @@ def make_dataclass( # noqa
174
176
 
175
177
  repr_id=repr_id,
176
178
  terse_repr=terse_repr,
179
+ default_repr_fn=default_repr_fn,
177
180
 
178
181
  allow_redundant_decorator=allow_redundant_decorator,
179
182
  )
@@ -32,6 +32,7 @@ class ReprPlan(Plan):
32
32
 
33
33
  id: bool = False
34
34
  terse: bool = False
35
+ default_fn: OpRef[ReprFn] | None = None
35
36
 
36
37
 
37
38
  @register_generator_type(ReprPlan)
@@ -66,11 +67,17 @@ class ReprGenerator(Generator[ReprPlan]):
66
67
  fn=fnr,
67
68
  ))
68
69
 
70
+ drf: OpRef | None = None
71
+ if ctx.cs.default_repr_fn is not None:
72
+ drf = OpRef(f'repr.default_fn')
73
+ orm[drf] = ctx.cs.default_repr_fn
74
+
69
75
  return PlanResult(
70
76
  ReprPlan(
71
77
  fields=tuple(rfs),
72
78
  id=ctx.cs.repr_id,
73
79
  terse=ctx.cs.terse_repr,
80
+ default_fn=drf,
74
81
  ),
75
82
  orm,
76
83
  )
@@ -85,10 +92,16 @@ class ReprGenerator(Generator[ReprPlan]):
85
92
  if not (pl.terse and not f.kw_only):
86
93
  pfx = f'{f.name}='
87
94
 
95
+ fn: OpRef[ReprFn] | None = None
88
96
  if f.fn is not None:
89
- ors.add(f.fn)
97
+ fn = f.fn
98
+ elif pl.default_fn is not None:
99
+ fn = pl.default_fn
100
+
101
+ if fn is not None:
102
+ ors.add(fn)
90
103
  part_lines.extend([
91
- f' if (s := {f.fn.ident()}(self.{f.name})) is not None:',
104
+ f' if (s := {fn.ident()}(self.{f.name})) is not None:',
92
105
  f' parts.append(f"{pfx}{{s}}")',
93
106
  ])
94
107
  else: