omlish 0.0.0.dev447__py3-none-any.whl → 0.0.0.dev493__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 (265) hide show
  1. omlish/.omlish-manifests.json +12 -0
  2. omlish/README.md +199 -0
  3. omlish/__about__.py +21 -16
  4. omlish/argparse/all.py +17 -9
  5. omlish/argparse/cli.py +16 -3
  6. omlish/argparse/utils.py +21 -0
  7. omlish/asyncs/asyncio/rlock.py +110 -0
  8. omlish/asyncs/asyncio/sync.py +43 -0
  9. omlish/asyncs/asyncio/utils.py +2 -0
  10. omlish/asyncs/sync.py +25 -0
  11. omlish/bootstrap/_marshal.py +1 -1
  12. omlish/bootstrap/diag.py +12 -21
  13. omlish/bootstrap/main.py +2 -5
  14. omlish/bootstrap/sys.py +27 -28
  15. omlish/cexts/__init__.py +0 -0
  16. omlish/cexts/include/omlish/omlish.hh +1 -0
  17. omlish/collections/__init__.py +13 -1
  18. omlish/collections/attrregistry.py +210 -0
  19. omlish/collections/cache/impl.py +1 -0
  20. omlish/collections/identity.py +1 -0
  21. omlish/collections/mappings.py +28 -0
  22. omlish/collections/trie.py +5 -1
  23. omlish/collections/utils.py +77 -0
  24. omlish/concurrent/all.py +2 -1
  25. omlish/concurrent/futures.py +25 -0
  26. omlish/concurrent/threadlets.py +1 -1
  27. omlish/daemons/reparent.py +2 -3
  28. omlish/daemons/spawning.py +2 -3
  29. omlish/dataclasses/__init__.py +2 -0
  30. omlish/dataclasses/impl/api/classes/decorator.py +3 -0
  31. omlish/dataclasses/impl/api/classes/make.py +3 -0
  32. omlish/dataclasses/impl/concerns/repr.py +15 -2
  33. omlish/dataclasses/impl/configs.py +97 -37
  34. omlish/dataclasses/impl/generation/compilation.py +21 -19
  35. omlish/dataclasses/impl/generation/globals.py +1 -0
  36. omlish/dataclasses/impl/generation/ops.py +1 -0
  37. omlish/dataclasses/impl/generation/plans.py +2 -17
  38. omlish/dataclasses/impl/generation/processor.py +106 -25
  39. omlish/dataclasses/impl/processing/base.py +8 -0
  40. omlish/dataclasses/impl/processing/driving.py +19 -7
  41. omlish/dataclasses/specs.py +1 -0
  42. omlish/dataclasses/tools/modifiers.py +5 -0
  43. omlish/diag/_pycharm/runhack.py +1 -1
  44. omlish/diag/cmds/__init__.py +0 -0
  45. omlish/diag/{lslocks.py → cmds/lslocks.py} +6 -6
  46. omlish/diag/{lsof.py → cmds/lsof.py} +6 -6
  47. omlish/diag/{ps.py → cmds/ps.py} +6 -6
  48. omlish/diag/pycharm.py +16 -2
  49. omlish/diag/pydevd.py +58 -40
  50. omlish/diag/replserver/console.py +1 -1
  51. omlish/dispatch/__init__.py +18 -12
  52. omlish/dispatch/methods.py +50 -140
  53. omlish/dom/rendering.py +1 -1
  54. omlish/formats/dotenv.py +1 -1
  55. omlish/formats/json/stream/__init__.py +13 -0
  56. omlish/funcs/guard.py +225 -0
  57. omlish/graphs/dot/rendering.py +1 -1
  58. omlish/http/all.py +44 -4
  59. omlish/http/clients/asyncs.py +33 -27
  60. omlish/http/clients/base.py +17 -1
  61. omlish/http/clients/coro/__init__.py +0 -0
  62. omlish/http/clients/coro/sync.py +171 -0
  63. omlish/http/clients/default.py +208 -29
  64. omlish/http/clients/executor.py +56 -0
  65. omlish/http/clients/httpx.py +72 -11
  66. omlish/http/clients/middleware.py +181 -0
  67. omlish/http/clients/sync.py +33 -27
  68. omlish/http/clients/syncasync.py +49 -0
  69. omlish/http/clients/urllib.py +6 -3
  70. omlish/http/coro/client/connection.py +15 -6
  71. omlish/http/coro/io.py +2 -0
  72. omlish/http/flasky/__init__.py +40 -0
  73. omlish/http/flasky/_compat.py +2 -0
  74. omlish/http/flasky/api.py +82 -0
  75. omlish/http/flasky/app.py +203 -0
  76. omlish/http/flasky/cvs.py +59 -0
  77. omlish/http/flasky/requests.py +20 -0
  78. omlish/http/flasky/responses.py +23 -0
  79. omlish/http/flasky/routes.py +23 -0
  80. omlish/http/flasky/types.py +15 -0
  81. omlish/http/urls.py +67 -0
  82. omlish/inject/__init__.py +57 -29
  83. omlish/inject/_dataclasses.py +5148 -0
  84. omlish/inject/binder.py +11 -52
  85. omlish/inject/eagers.py +2 -0
  86. omlish/inject/elements.py +27 -0
  87. omlish/inject/helpers/__init__.py +0 -0
  88. omlish/inject/{utils.py → helpers/constfn.py} +3 -3
  89. omlish/inject/{tags.py → helpers/id.py} +2 -2
  90. omlish/inject/helpers/late.py +76 -0
  91. omlish/inject/{managed.py → helpers/managed.py} +10 -10
  92. omlish/inject/helpers/multis.py +143 -0
  93. omlish/inject/helpers/wrappers.py +54 -0
  94. omlish/inject/impl/elements.py +54 -21
  95. omlish/inject/impl/injector.py +29 -25
  96. omlish/inject/impl/inspect.py +10 -1
  97. omlish/inject/impl/maysync.py +3 -4
  98. omlish/inject/impl/multis.py +3 -0
  99. omlish/inject/impl/sync.py +3 -4
  100. omlish/inject/injector.py +31 -2
  101. omlish/inject/inspect.py +35 -0
  102. omlish/inject/maysync.py +2 -4
  103. omlish/inject/multis.py +8 -0
  104. omlish/inject/overrides.py +3 -3
  105. omlish/inject/privates.py +6 -0
  106. omlish/inject/providers.py +3 -2
  107. omlish/inject/sync.py +5 -4
  108. omlish/io/buffers.py +118 -0
  109. omlish/io/readers.py +29 -0
  110. omlish/iterators/transforms.py +2 -2
  111. omlish/lang/__init__.py +180 -97
  112. omlish/lang/_asyncs.cc +186 -0
  113. omlish/lang/asyncs.py +17 -0
  114. omlish/lang/casing.py +11 -0
  115. omlish/lang/contextmanagers.py +28 -4
  116. omlish/lang/functions.py +31 -22
  117. omlish/lang/imports/_capture.cc +11 -9
  118. omlish/lang/imports/capture.py +363 -170
  119. omlish/lang/imports/proxy.py +455 -152
  120. omlish/lang/lazyglobals.py +22 -9
  121. omlish/lang/params.py +17 -0
  122. omlish/lang/recursion.py +0 -1
  123. omlish/lang/sequences.py +124 -0
  124. omlish/lifecycles/README.md +30 -0
  125. omlish/lifecycles/__init__.py +87 -13
  126. omlish/lifecycles/_dataclasses.py +1388 -0
  127. omlish/lifecycles/base.py +178 -64
  128. omlish/lifecycles/contextmanagers.py +113 -4
  129. omlish/lifecycles/controller.py +150 -87
  130. omlish/lifecycles/injection.py +143 -0
  131. omlish/lifecycles/listeners.py +56 -0
  132. omlish/lifecycles/managed.py +142 -0
  133. omlish/lifecycles/manager.py +218 -93
  134. omlish/lifecycles/states.py +2 -0
  135. omlish/lifecycles/transitions.py +3 -0
  136. omlish/lifecycles/unwrap.py +57 -0
  137. omlish/lite/abstract.py +54 -24
  138. omlish/lite/asyncs.py +2 -2
  139. omlish/lite/attrops.py +2 -0
  140. omlish/lite/contextmanagers.py +4 -4
  141. omlish/lite/dataclasses.py +44 -0
  142. omlish/lite/maybes.py +8 -0
  143. omlish/lite/maysync.py +1 -5
  144. omlish/lite/pycharm.py +1 -1
  145. omlish/lite/typing.py +24 -0
  146. omlish/logs/_amalg.py +1 -1
  147. omlish/logs/all.py +25 -11
  148. omlish/logs/asyncs.py +73 -0
  149. omlish/logs/base.py +101 -12
  150. omlish/logs/contexts.py +4 -1
  151. omlish/logs/lists.py +125 -0
  152. omlish/logs/modules.py +19 -1
  153. omlish/logs/std/loggers.py +6 -1
  154. omlish/logs/std/noisy.py +11 -9
  155. omlish/logs/{standard.py → std/standard.py} +3 -4
  156. omlish/logs/utils.py +17 -2
  157. omlish/manifests/loading.py +2 -1
  158. omlish/marshal/__init__.py +33 -13
  159. omlish/marshal/_dataclasses.py +2774 -0
  160. omlish/marshal/base/configs.py +12 -0
  161. omlish/marshal/base/contexts.py +36 -21
  162. omlish/marshal/base/funcs.py +8 -11
  163. omlish/marshal/base/options.py +8 -0
  164. omlish/marshal/base/registries.py +146 -44
  165. omlish/marshal/base/types.py +40 -16
  166. omlish/marshal/composite/iterables.py +33 -20
  167. omlish/marshal/composite/literals.py +20 -18
  168. omlish/marshal/composite/mappings.py +36 -23
  169. omlish/marshal/composite/maybes.py +29 -19
  170. omlish/marshal/composite/newtypes.py +16 -16
  171. omlish/marshal/composite/optionals.py +14 -14
  172. omlish/marshal/composite/special.py +15 -15
  173. omlish/marshal/composite/unions/__init__.py +0 -0
  174. omlish/marshal/composite/unions/literals.py +93 -0
  175. omlish/marshal/composite/unions/primitives.py +103 -0
  176. omlish/marshal/factories/invalidate.py +18 -68
  177. omlish/marshal/factories/method.py +26 -0
  178. omlish/marshal/factories/moduleimport/factories.py +22 -65
  179. omlish/marshal/factories/multi.py +13 -25
  180. omlish/marshal/factories/recursive.py +42 -56
  181. omlish/marshal/factories/typecache.py +29 -74
  182. omlish/marshal/factories/typemap.py +42 -43
  183. omlish/marshal/objects/dataclasses.py +129 -106
  184. omlish/marshal/objects/marshal.py +18 -14
  185. omlish/marshal/objects/namedtuples.py +48 -42
  186. omlish/marshal/objects/unmarshal.py +19 -15
  187. omlish/marshal/polymorphism/marshal.py +9 -11
  188. omlish/marshal/polymorphism/metadata.py +16 -5
  189. omlish/marshal/polymorphism/standard.py +13 -1
  190. omlish/marshal/polymorphism/unions.py +15 -105
  191. omlish/marshal/polymorphism/unmarshal.py +9 -10
  192. omlish/marshal/singular/enums.py +14 -18
  193. omlish/marshal/standard.py +10 -6
  194. omlish/marshal/trivial/any.py +1 -1
  195. omlish/marshal/trivial/forbidden.py +21 -26
  196. omlish/metadata.py +23 -1
  197. omlish/os/forkhooks.py +4 -0
  198. omlish/os/pidfiles/pinning.py +2 -2
  199. omlish/reflect/__init__.py +43 -26
  200. omlish/reflect/ops.py +10 -1
  201. omlish/reflect/types.py +1 -0
  202. omlish/secrets/marshal.py +1 -1
  203. omlish/specs/jmespath/__init__.py +12 -3
  204. omlish/specs/jmespath/_dataclasses.py +2893 -0
  205. omlish/specs/jmespath/ast.py +1 -1
  206. omlish/specs/jsonrpc/__init__.py +13 -0
  207. omlish/specs/jsonrpc/_marshal.py +32 -23
  208. omlish/specs/jsonrpc/conns.py +10 -7
  209. omlish/specs/jsonschema/_marshal.py +1 -1
  210. omlish/specs/jsonschema/keywords/__init__.py +7 -0
  211. omlish/specs/jsonschema/keywords/_dataclasses.py +1644 -0
  212. omlish/specs/openapi/_marshal.py +31 -22
  213. omlish/sql/__init__.py +24 -5
  214. omlish/sql/{tabledefs/alchemy.py → alchemy/tabledefs.py} +2 -2
  215. omlish/sql/api/dbapi.py +1 -1
  216. omlish/sql/dbapi/__init__.py +15 -0
  217. omlish/sql/{dbapi.py → dbapi/drivers.py} +2 -2
  218. omlish/sql/queries/__init__.py +3 -0
  219. omlish/sql/queries/_marshal.py +2 -2
  220. omlish/sql/queries/rendering.py +1 -1
  221. omlish/sql/tabledefs/_marshal.py +1 -1
  222. omlish/subprocesses/base.py +4 -0
  223. omlish/subprocesses/editor.py +1 -1
  224. omlish/sync.py +155 -21
  225. omlish/term/alt.py +60 -0
  226. omlish/term/confirm.py +8 -8
  227. omlish/term/pager.py +235 -0
  228. omlish/term/terminfo.py +935 -0
  229. omlish/term/termstate.py +67 -0
  230. omlish/term/vt100/terminal.py +0 -3
  231. omlish/testing/pytest/plugins/asyncs/fixtures.py +4 -1
  232. omlish/testing/pytest/plugins/asyncs/plugin.py +2 -0
  233. omlish/testing/pytest/plugins/skips.py +2 -1
  234. omlish/testing/unittest/main.py +3 -3
  235. omlish/text/docwrap/__init__.py +3 -0
  236. omlish/text/docwrap/__main__.py +11 -0
  237. omlish/text/docwrap/api.py +83 -0
  238. omlish/text/docwrap/cli.py +91 -0
  239. omlish/text/docwrap/groups.py +84 -0
  240. omlish/text/docwrap/lists.py +167 -0
  241. omlish/text/docwrap/parts.py +146 -0
  242. omlish/text/docwrap/reflowing.py +106 -0
  243. omlish/text/docwrap/rendering.py +151 -0
  244. omlish/text/docwrap/utils.py +11 -0
  245. omlish/text/docwrap/wrapping.py +59 -0
  246. omlish/text/filecache.py +2 -2
  247. omlish/text/lorem.py +6 -0
  248. omlish/text/parts.py +2 -2
  249. omlish/text/textwrap.py +51 -0
  250. omlish/typedvalues/marshal.py +85 -59
  251. omlish/typedvalues/values.py +2 -1
  252. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/METADATA +36 -32
  253. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/RECORD +260 -199
  254. omlish/dataclasses/impl/generation/mangling.py +0 -18
  255. omlish/funcs/match.py +0 -227
  256. omlish/lifecycles/abstract.py +0 -86
  257. omlish/marshal/factories/match.py +0 -34
  258. omlish/marshal/factories/simple.py +0 -28
  259. /omlish/inject/{impl → helpers}/proxy.py +0 -0
  260. /omlish/inject/impl/{providers2.py → providersmap.py} +0 -0
  261. /omlish/sql/{abc.py → dbapi/abc.py} +0 -0
  262. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/WHEEL +0 -0
  263. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/entry_points.txt +0 -0
  264. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/licenses/LICENSE +0 -0
  265. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/top_level.txt +0 -0
@@ -4,13 +4,11 @@ TODO:
4
4
  - ALT: A.f(super(), ... ? :/
5
5
  - classmethod/staticmethod
6
6
  """
7
- import contextlib
8
7
  import functools
9
8
  import typing as ta
10
- import weakref
11
9
 
12
10
  from .. import check
13
- from .. import lang
11
+ from .. import collections as col
14
12
  from .dispatch import Dispatcher
15
13
  from .impls import get_impl_func_cls_set
16
14
 
@@ -34,9 +32,8 @@ class Method(ta.Generic[P, R]):
34
32
  it must be explicitly `@register`'ed itself. This is a feature, allowing for selective de-registration of
35
33
  implementations in subclasses via name shadowing.
36
34
 
37
- Methods have ability to choose to allow external installation of implementations outside of direct subclasses. This
38
- is to be used *extremely* rarely - basically only in the rare case of externally extensible type hierarchies with
39
- visitors.
35
+ Methods can choose to allow external installation of implementations outside of direct subclasses. This is to be
36
+ used *extremely* rarely - basically only in the rare case of externally extensible type hierarchies with visitors.
40
37
  """
41
38
 
42
39
  def __init__(
@@ -45,6 +42,7 @@ class Method(ta.Generic[P, R]):
45
42
  *,
46
43
  installable: bool = False,
47
44
  requires_override: bool = False,
45
+ instance_cache: bool = False,
48
46
  ) -> None:
49
47
  super().__init__()
50
48
 
@@ -53,9 +51,16 @@ class Method(ta.Generic[P, R]):
53
51
 
54
52
  self._func = func
55
53
  self._installable = installable
56
- self._requires_override = requires_override
54
+ self._instance_cache = instance_cache
57
55
 
58
- self._impls: ta.MutableMapping[ta.Callable, frozenset[type] | None] = weakref.WeakKeyDictionary()
56
+ self._registry: col.AttrRegistry[ta.Callable, Method._Entry] = col.AttrRegistry(
57
+ requires_override=requires_override,
58
+ )
59
+
60
+ self._cache: col.AttrRegistryCache[ta.Callable, Method._Entry, ta.Callable] = col.AttrRegistryCache(
61
+ self._registry,
62
+ self._prepare,
63
+ )
59
64
 
60
65
  # bpo-45678: special-casing for classmethod/staticmethod in Python <=3.9, as functools.update_wrapper doesn't
61
66
  # work properly in singledispatchmethod.__get__ if it is applied to an unbound classmethod/staticmethod
@@ -68,19 +73,12 @@ class Method(ta.Generic[P, R]):
68
73
  self._is_abstractmethod = getattr(func, '__isabstractmethod__', False) # noqa
69
74
  self.update_wrapper(self)
70
75
 
71
- self._dispatch_func_cache: dict[ta.Any, ta.Callable] = {}
72
-
73
- def dispatch_func_cache_remove(k, self_ref=weakref.ref(self)):
74
- if (ref_self := self_ref()) is not None:
75
- cache = ref_self._dispatch_func_cache # noqa
76
- with contextlib.suppress(KeyError):
77
- del cache[k]
78
-
79
- self._dispatch_func_cache_remove = dispatch_func_cache_remove
80
-
81
76
  self._owner: type | None = None
82
77
  self._name: str | None = None
83
78
 
79
+ class _Entry:
80
+ cls_set: frozenset[type]
81
+
84
82
  def __set_name__(self, owner, name):
85
83
  if self._owner is None:
86
84
  self._owner = owner
@@ -102,83 +100,29 @@ class Method(ta.Generic[P, R]):
102
100
 
103
101
  def register(self, impl: T, cls_set: frozenset[type] | None = None) -> T:
104
102
  check.callable(impl)
105
- if impl not in self._impls:
106
- self._impls[impl] = cls_set # type: ignore
107
- self._dispatch_func_cache.clear()
108
-
109
- return impl
110
-
111
- def _is_impl(self, obj: ta.Any) -> bool:
112
- try:
113
- hash(obj)
114
- except TypeError:
115
- return False
116
-
117
- return obj in self._impls
118
103
 
119
- def build_attr_dispatcher(self, instance_cls: type, owner_cls: type | None = None) -> Dispatcher[str]:
120
- if owner_cls is None:
121
- owner_cls = instance_cls
104
+ entry = Method._Entry()
105
+ if cls_set is not None:
106
+ entry.cls_set = cls_set
122
107
 
123
- mro = instance_cls.__mro__[-2::-1]
124
- try:
125
- mro_pos = mro.index(owner_cls)
126
- except ValueError:
127
- raise TypeError(f'Owner class {owner_cls} not in mro of instance class {instance_cls}') from None
108
+ self._registry.register(ta.cast(ta.Callable, impl), entry)
128
109
 
129
- mro_dct: dict[str, list[tuple[type, ta.Any]]] = {}
130
- for cur_cls in mro[:mro_pos + 1]:
131
- for att, obj in cur_cls.__dict__.items():
132
- if att not in mro_dct:
133
- if not self._is_impl(obj):
134
- continue
135
-
136
- try:
137
- lst = mro_dct[att]
138
- except KeyError:
139
- lst = mro_dct[att] = []
140
- lst.append((cur_cls, obj))
141
-
142
- #
110
+ return impl
143
111
 
112
+ def _build_dispatcher(self, collected: ta.Mapping[str, tuple[ta.Callable, _Entry]]) -> Dispatcher[str]:
144
113
  disp: Dispatcher[str] = Dispatcher()
145
114
 
146
- seen: dict[ta.Any, str] = {}
147
- for att, lst in mro_dct.items():
148
- if not lst:
149
- raise RuntimeError
150
- _, obj = lst[-1]
151
-
152
- if len(lst) > 1:
153
- if self._requires_override and not lang.is_override(obj):
154
- raise lang.RequiresOverrideError(
155
- att,
156
- instance_cls,
157
- lst[-1][0],
158
- lst[0][0],
159
- )
160
-
161
- if not self._is_impl(obj):
162
- continue
163
-
164
- cls_set = self._impls[obj]
165
- if cls_set is None:
166
- cls_set = get_impl_func_cls_set(obj, arg_offset=1)
167
- self._impls[obj] = cls_set
168
-
115
+ for a, (f, e) in collected.items():
169
116
  try:
170
- ex_att = seen[obj]
171
- except KeyError:
172
- pass
173
- else:
174
- raise TypeError(f'Duplicate impl: {owner_cls} {instance_cls} {att} {ex_att}')
175
- seen[obj] = att
117
+ cls_set = e.cls_set
118
+ except AttributeError:
119
+ cls_set = e.cls_set = get_impl_func_cls_set(f, arg_offset=1)
176
120
 
177
- disp.register(att, cls_set)
121
+ disp.register(a, cls_set)
178
122
 
179
123
  return disp
180
124
 
181
- def build_dispatch_func(self, disp: Dispatcher[str]) -> ta.Callable:
125
+ def _build_dispatch_func(self, disp: Dispatcher[str]) -> ta.Callable:
182
126
  dispatch = disp.dispatch
183
127
  type_ = type
184
128
  getattr_ = getattr
@@ -190,25 +134,16 @@ class Method(ta.Generic[P, R]):
190
134
  raise TypeError(f'{func_name} requires at least 1 positional argument')
191
135
 
192
136
  if (impl_att := dispatch(type_(args[0]))) is not None:
193
- fn = getattr_(self, impl_att)
194
- return fn(*args, **kwargs)
137
+ return getattr_(self, impl_att)(*args, **kwargs)
195
138
 
196
139
  return base_func.__get__(self)(*args, **kwargs) # noqa
197
140
 
198
141
  self.update_wrapper(__call__)
199
142
  return __call__
200
143
 
201
- def get_dispatch_func(self, instance_cls: type) -> ta.Callable:
202
- cls_ref = weakref.ref(instance_cls)
203
- try:
204
- return self._dispatch_func_cache[cls_ref]
205
- except KeyError:
206
- pass
207
- del cls_ref
208
-
209
- att_disp = self.build_attr_dispatcher(instance_cls)
210
- func = self.build_dispatch_func(att_disp)
211
- self._dispatch_func_cache[weakref.ref(instance_cls, self._dispatch_func_cache_remove)] = func
144
+ def _prepare(self, instance_cls: type, collected: ta.Mapping[str, tuple[ta.Callable, _Entry]]) -> ta.Callable:
145
+ disp = self._build_dispatcher(collected)
146
+ func = self._build_dispatch_func(disp)
212
147
  return func
213
148
 
214
149
  def __get__(self, instance, owner=None):
@@ -216,16 +151,21 @@ class Method(ta.Generic[P, R]):
216
151
  # FIXME: classmethod/staticmethod
217
152
  return self
218
153
 
219
- instance_cls = type(instance)
220
- try:
221
- func = self._dispatch_func_cache[weakref.ref(instance_cls)]
222
- except KeyError:
223
- func = self.get_dispatch_func(instance_cls)
224
- return func.__get__(instance, owner) # noqa
154
+ if self._instance_cache:
155
+ try:
156
+ return instance.__dict__[self._name]
157
+ except KeyError:
158
+ pass
159
+
160
+ bound = self._cache.get(type(instance)).__get__(instance, owner) # noqa
161
+
162
+ if self._instance_cache:
163
+ instance.__dict__[self._name] = bound
164
+
165
+ return bound
225
166
 
226
167
  def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
227
168
  instance, *rest = args
228
- instance_cls = type(instance)
229
169
 
230
170
  # if instance_cls is super:
231
171
  # owner = instance.__self_class__.__mro__[instance.__self_class__.__mro__.index(instance.__thisclass__) + 1]
@@ -233,55 +173,25 @@ class Method(ta.Generic[P, R]):
233
173
  # func = self.build_dispatch_func(att_disp)
234
174
  # return func.__get__(instance, instance.__thisclass__)(*rest, **kwargs)
235
175
 
236
- try:
237
- func = self._dispatch_func_cache[weakref.ref(instance_cls)]
238
- except KeyError:
239
- func = self.get_dispatch_func(instance_cls)
240
- return func.__get__(instance)(*rest, **kwargs) # noqa
176
+ return self.__get__(instance)(*rest, **kwargs)
241
177
 
242
178
 
243
179
  ##
244
180
 
245
181
 
246
- @ta.overload
247
- def method(
248
- func: ta.Callable[P, R],
249
- /,
250
- *,
251
- installable: bool = False,
252
- requires_override: bool = False,
253
- ) -> Method[P, R]: # noqa
254
- ...
255
-
256
-
257
- @ta.overload
258
182
  def method(
259
- func: None = None,
260
- /,
261
183
  *,
262
184
  installable: bool = False,
263
185
  requires_override: bool = False,
186
+ instance_cache: bool = False,
264
187
  ) -> ta.Callable[[ta.Callable[P, R]], Method[P, R]]: # noqa
265
- ...
266
-
267
-
268
- def method(
269
- func=None,
270
- /,
271
- *,
272
- installable=False,
273
- requires_override=False,
274
- ):
275
- kw = dict(
188
+ return functools.partial(
189
+ Method, # type: ignore[arg-type]
276
190
  installable=installable,
277
191
  requires_override=requires_override,
192
+ instance_cache=instance_cache,
278
193
  )
279
194
 
280
- if func is None:
281
- return functools.partial(Method, **kw)
282
-
283
- return Method(func, **kw)
284
-
285
195
 
286
196
  #
287
197
 
@@ -305,7 +215,7 @@ def install_method(
305
215
  if on is None:
306
216
  cls = owner
307
217
  else:
308
- cls = check.issubclass(on, owner)
218
+ cls = check.issubclass(on, owner) # noqa
309
219
 
310
220
  check.arg(not hasattr(cls, a))
311
221
  setattr(cls, a, fn)
omlish/dom/rendering.py CHANGED
@@ -100,7 +100,7 @@ class Renderer:
100
100
 
101
101
  #
102
102
 
103
- @dispatch.method
103
+ @dispatch.method(instance_cache=True)
104
104
  def render(self, o: ta.Any) -> None:
105
105
  raise TypeError(o)
106
106
 
omlish/formats/dotenv.py CHANGED
@@ -584,7 +584,7 @@ def dotenv_values(
584
584
  For example, `foo=bar` results in `{"foo": "bar"}` whereas `foo` alone results in
585
585
  `{"foo": None}`
586
586
 
587
- Parameters:
587
+ Args:
588
588
  path: Absolute or relative path to the .env file.
589
589
  stream: `StringIO` object with .env content, used if `path` is `None`.
590
590
  verbose: Whether to output a warning if the .env file is missing.
@@ -1,3 +1,16 @@
1
+ """
2
+ A generator powered, configurable, mostly fully streaming JSON parser.
3
+
4
+ Regarding the 'streamyness' of the subsystems:
5
+ - Lexing only buffers for string and number literals.
6
+ - Parsing maintains only a stack that scales by nesting depth.
7
+ - Building values will obviously hold everything under the topmost object it's building until it's finished.
8
+
9
+ It's reasonably optimized, but performance is not a primary or even secondary goal: its goal is flexibility. If speed
10
+ matters use a native library.
11
+ """
12
+
13
+
1
14
  from .building import ( # noqa
2
15
  JsonValueBuilder,
3
16
  )
omlish/funcs/guard.py ADDED
@@ -0,0 +1,225 @@
1
+ import abc
2
+ import functools
3
+ import operator
4
+ import typing as ta
5
+
6
+ from .. import check
7
+ from .. import collections as col
8
+ from .. import lang
9
+
10
+
11
+ T = ta.TypeVar('T')
12
+ T_co = ta.TypeVar('T_co', covariant=True)
13
+ U = ta.TypeVar('U')
14
+ P = ta.ParamSpec('P')
15
+
16
+
17
+ ##
18
+
19
+
20
+ class GuardFn(ta.Protocol[P, T_co]):
21
+ def __get__(self, instance, owner=None): ...
22
+
23
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> ta.Callable[[], T_co] | None: ...
24
+
25
+
26
+ ##
27
+
28
+
29
+ @ta.final
30
+ class DumbGuardFn(ta.Generic[P, T]):
31
+ def __init__(self, fn: ta.Callable[P, T]) -> None:
32
+ self._fn = fn
33
+
34
+ def __get__(self, instance, owner=None):
35
+ return DumbGuardFn(self._fn.__get__(instance, owner)) # noqa
36
+
37
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> ta.Callable[[], T]:
38
+ return functools.partial(self._fn, *args, **kwargs)
39
+
40
+
41
+ dumb = DumbGuardFn
42
+
43
+
44
+ ##
45
+
46
+
47
+ class AmbiguousGuardFnError(Exception):
48
+ pass
49
+
50
+
51
+ @ta.final
52
+ class MultiGuardFn(ta.Generic[P, T]):
53
+ def __init__(
54
+ self,
55
+ *children: GuardFn[P, T],
56
+ default: GuardFn[P, T] | None = None,
57
+ strict: bool = False,
58
+ ) -> None:
59
+ self._children, self._default, self._strict = children, default, strict
60
+
61
+ lang.attr_ops(lambda self: (
62
+ self._children,
63
+ self._default,
64
+ self._strict,
65
+ )).install(locals())
66
+
67
+ def __get__(self, instance, owner=None):
68
+ return MultiGuardFn(*map(operator.methodcaller('__get__', instance, owner), self._children), default=self._default.__get__(instance, owner) if self._default is not None else None, strict=self._strict) # noqa
69
+
70
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> ta.Callable[[], T] | None:
71
+ matches = []
72
+ for c in self._children:
73
+ if (m := c(*args, **kwargs)) is not None:
74
+ if not self._strict:
75
+ return m
76
+ matches.append(m)
77
+ if not matches:
78
+ if (dfl := self._default) is not None:
79
+ return dfl(*args, **kwargs)
80
+ else:
81
+ return None
82
+ elif len(matches) > 1:
83
+ raise AmbiguousGuardFnError
84
+ else:
85
+ return matches[0]
86
+
87
+
88
+ multi = MultiGuardFn
89
+
90
+
91
+ ##
92
+
93
+
94
+ class _BaseGuardFnMethod(lang.Abstract, ta.Generic[P, T]):
95
+ def __init__(
96
+ self,
97
+ *,
98
+ strict: bool = False,
99
+ requires_override: bool = False,
100
+ instance_cache: bool = False,
101
+ default: GuardFn[P, T] | None = None,
102
+ ) -> None:
103
+ super().__init__()
104
+
105
+ self._strict = strict
106
+ self._instance_cache = instance_cache
107
+ self._default = default
108
+
109
+ self._registry: col.AttrRegistry[ta.Callable, None] = col.AttrRegistry(
110
+ requires_override=requires_override,
111
+ )
112
+
113
+ self._cache: col.AttrRegistryCache[ta.Callable, None, MultiGuardFn] = col.AttrRegistryCache(
114
+ self._registry,
115
+ self._prepare,
116
+ )
117
+
118
+ _owner: type | None = None
119
+ _name: str | None = None
120
+
121
+ def __set_name__(self, owner, name):
122
+ if self._owner is None:
123
+ self._owner = owner
124
+ if self._name is None:
125
+ self._name = name
126
+
127
+ def register(self, fn: U) -> U:
128
+ check.callable(fn)
129
+ self._registry.register(ta.cast(ta.Callable, fn), None)
130
+ return fn
131
+
132
+ def _prepare(self, instance_cls: type, collected: ta.Mapping[str, tuple[ta.Callable, None]]) -> MultiGuardFn:
133
+ return MultiGuardFn(
134
+ *[getattr(instance_cls, a) for a in collected],
135
+ default=self._default,
136
+ strict=self._strict,
137
+ )
138
+
139
+ @abc.abstractmethod
140
+ def _bind(self, instance, owner):
141
+ raise NotImplementedError
142
+
143
+ def __get__(self, instance, owner=None):
144
+ if instance is None:
145
+ return self
146
+
147
+ if self._instance_cache:
148
+ try:
149
+ return instance.__dict__[self._name]
150
+ except KeyError:
151
+ pass
152
+
153
+ bound = self._bind(instance, owner)
154
+
155
+ if self._instance_cache:
156
+ instance.__dict__[self._name] = bound
157
+
158
+ return bound
159
+
160
+ def _call(self, *args, **kwargs):
161
+ instance, *rest = args
162
+ return self.__get__(instance)(*rest, **kwargs)
163
+
164
+ #
165
+
166
+
167
+ @ta.final
168
+ class GuardFnMethod(_BaseGuardFnMethod[P, T]):
169
+ def _bind(self, instance, owner):
170
+ return self._cache.get(type(instance)).__get__(instance, owner) # noqa
171
+
172
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> ta.Callable[[], T] | None:
173
+ return self._call(*args, **kwargs)
174
+
175
+
176
+ def method(
177
+ *,
178
+ strict: bool = False,
179
+ requires_override: bool = False,
180
+ instance_cache: bool = False,
181
+ default: bool = False,
182
+ ) -> ta.Callable[[ta.Callable[P, T]], GuardFnMethod[P, T]]: # noqa
183
+ def inner(fn):
184
+ return GuardFnMethod(
185
+ strict=strict,
186
+ requires_override=requires_override,
187
+ instance_cache=instance_cache,
188
+ default=fn if default else None,
189
+ )
190
+
191
+ return inner
192
+
193
+
194
+ #
195
+
196
+
197
+ @ta.final
198
+ class ImmediateGuardFnMethod(_BaseGuardFnMethod[P, T]):
199
+ def _bind(self, instance, owner):
200
+ gf = self._cache.get(type(instance)).__get__(instance, owner) # noqa
201
+
202
+ def inner(*args, **kwargs):
203
+ return gf(*args, **kwargs)() # Note: cannot be None due to non-optional default
204
+
205
+ return inner
206
+
207
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
208
+ return self._call(*args, **kwargs)
209
+
210
+
211
+ def immediate_method(
212
+ *,
213
+ strict: bool = False,
214
+ requires_override: bool = False,
215
+ instance_cache: bool = False,
216
+ ) -> ta.Callable[[ta.Callable[P, T]], ImmediateGuardFnMethod[P, T]]: # noqa
217
+ def inner(fn):
218
+ return ImmediateGuardFnMethod(
219
+ strict=strict,
220
+ requires_override=requires_override,
221
+ instance_cache=instance_cache,
222
+ default=(lambda *args, **kwargs: lambda: fn(*args, **kwargs)),
223
+ )
224
+
225
+ return inner
@@ -29,7 +29,7 @@ class Renderer:
29
29
 
30
30
  self._out = out
31
31
 
32
- @dispatch.method
32
+ @dispatch.method(instance_cache=True)
33
33
  def render(self, item: Item) -> None:
34
34
  raise TypeError(item)
35
35
 
omlish/http/all.py CHANGED
@@ -4,9 +4,18 @@ from .. import lang as _lang
4
4
  with _lang.auto_proxy_init(globals()):
5
5
  ##
6
6
 
7
+ from .clients.asyncs import ( # noqa
8
+ AsyncStreamHttpResponse,
9
+
10
+ async_close_http_client_response,
11
+ async_closing_http_client_response,
12
+ async_read_http_client_response,
13
+
14
+ AsyncHttpClient,
15
+ )
16
+
7
17
  from .clients.base import ( # noqa
8
18
  DEFAULT_ENCODING,
9
-
10
19
  is_success_status,
11
20
 
12
21
  HttpRequest,
@@ -14,34 +23,63 @@ with _lang.auto_proxy_init(globals()):
14
23
  BaseHttpResponse,
15
24
  HttpResponse,
16
25
 
26
+ HttpClientContext,
27
+
17
28
  HttpClientError,
18
29
  HttpStatusError,
30
+
31
+ BaseHttpClient,
19
32
  )
20
33
 
21
34
  from .clients.default import ( # noqa
22
35
  client,
36
+ manage_client,
23
37
 
24
38
  request,
39
+
40
+ async_client,
41
+ manage_async_client,
42
+
43
+ async_request,
25
44
  )
26
45
 
27
46
  from .clients.httpx import ( # noqa
28
47
  HttpxHttpClient,
48
+
49
+ HttpxAsyncHttpClient,
50
+ )
51
+
52
+ from .clients.middleware import ( # noqa
53
+ HttpClientMiddleware,
54
+ AbstractMiddlewareHttpClient,
55
+
56
+ MiddlewareHttpClient,
57
+ MiddlewareAsyncHttpClient,
58
+
59
+ TooManyRedirectsHttpClientError,
60
+ RedirectHandlingHttpClientMiddleware,
29
61
  )
30
62
 
31
63
  from .clients.sync import ( # noqa
32
64
  StreamHttpResponse,
33
65
 
34
- close_response,
35
- closing_response,
36
- read_response,
66
+ close_http_client_response,
67
+ closing_http_client_response,
68
+ read_http_client_response,
37
69
 
38
70
  HttpClient,
39
71
  )
40
72
 
73
+ from .clients.syncasync import ( # noqa
74
+ SyncAsyncHttpClient,
75
+ )
76
+
41
77
  from .clients.urllib import ( # noqa
42
78
  UrllibHttpClient,
43
79
  )
44
80
 
81
+ from . import asgi # noqa
82
+
45
83
  from . import consts # noqa
46
84
 
47
85
  from .cookies import ( # noqa
@@ -80,3 +118,5 @@ with _lang.auto_proxy_init(globals()):
80
118
  MultipartEncoder,
81
119
  MultipartField,
82
120
  )
121
+
122
+ from . import wsgi # noqa