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
omlish/diag/pydevd.py CHANGED
@@ -6,7 +6,7 @@ an already-debugging PyCharm instance to debug PySpark jobs.
6
6
  TODO:
7
7
  - https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html#
8
8
 
9
- ==
9
+ ====
10
10
 
11
11
  https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html#remote-debug-config ->
12
12
 
@@ -25,13 +25,34 @@ buf = textwrap.dedent(f'''
25
25
  stderrToServer=True,
26
26
  )
27
27
  ''') + '\n' * 2 + buf
28
+
29
+ ====
30
+
31
+ TODO: monkeypatch:
32
+
33
+ /Applications/PyCharm.app/Contents/plugins/python-ce/helpers/pydev/_pydev_bundle/pydev_monkey.py ::
34
+
35
+ def starts_with_python_shebang(path):
36
+ try:
37
+ with open(path) as f:
38
+ for line in f:
39
+ line = line.strip()
40
+ if line:
41
+ for name in PYTHON_NAMES:
42
+ if line.startswith('#!/usr/bin/env %s' % name):
43
+ return True
44
+ return False
45
+ except (UnicodeDecodeError, IsADirectoryError): # <-- Add catch for `IsADirectoryError`
46
+ return False
47
+ except:
48
+ traceback.print_exc()
49
+ return False
28
50
  """
29
51
  import json
30
52
  import os
31
53
  import sys
32
54
  import tempfile
33
55
  import textwrap
34
- import threading
35
56
  import types
36
57
  import typing as ta
37
58
 
@@ -119,7 +140,19 @@ def _pydevd() -> types.ModuleType | None:
119
140
 
120
141
 
121
142
  def is_present() -> bool:
122
- return _pydevd() is not None
143
+ # FIXME: try to use `lang.can_import('pydevd'), but raises with:
144
+ # INTERNALERROR> File "/Users/spinlock/src/wrmsr/omlish/omlish/lang/imports/resolving.py", line 16, in can_import
145
+ # INTERNALERROR> spec = importlib.util.find_spec(name, package)
146
+ # INTERNALERROR> File "<frozen importlib.util>", line 111, in find_spec
147
+ # INTERNALERROR> ValueError: pydevd.__spec__ is None
148
+ # Really want to avoid actually importing pydevd due to side-effects, slowness, and even a pkg_resources deprecation
149
+ # warning...
150
+ try:
151
+ __import__('pydevd')
152
+ except ImportError:
153
+ return False
154
+ else:
155
+ return True
123
156
 
124
157
 
125
158
  def get_setup() -> dict | None:
@@ -243,23 +276,23 @@ def maybe_reexec(
243
276
  bootstrap_path = os.path.join(tmpdir, 'bootstrap.py')
244
277
  with open(bootstrap_path, 'w') as f:
245
278
  f.write(textwrap.dedent(f"""
246
- import sys
247
- old_paths = set(sys.path)
248
- for new_path in {sys.path!r}:
249
- if new_path not in old_paths:
250
- sys.path.insert(0, new_path)
251
-
252
- _stderr_write = sys.stderr.write
253
- def stderr_write(*args, **kwargs):
254
- code = sys._getframe(1).f_code
255
- if code is not None and code.co_filename and code.co_filename.endswith('/pydev_log.py'):
256
- return
257
- _stderr_write(*args, **kwargs)
258
- sys.stderr.write = stderr_write
259
-
260
- sys.argv = {args[1:]!r}
261
- import runpy
262
- runpy.run_path({args[1]!r}, run_name='__main__')
279
+ import sys
280
+ old_paths = set(sys.path)
281
+ for new_path in {sys.path!r}:
282
+ if new_path not in old_paths:
283
+ sys.path.insert(0, new_path)
284
+
285
+ _stderr_write = sys.stderr.write
286
+ def stderr_write(*args, **kwargs):
287
+ code = sys._getframe(1).f_code
288
+ if code is not None and code.co_filename and code.co_filename.endswith('/pydev_log.py'):
289
+ return
290
+ _stderr_write(*args, **kwargs)
291
+ sys.stderr.write = stderr_write
292
+
293
+ sys.argv = {args[1:]!r}
294
+ import runpy
295
+ runpy.run_path({args[1]!r}, run_name='__main__')
263
296
  """))
264
297
  args = [args[0], bootstrap_path]
265
298
 
@@ -272,29 +305,14 @@ def debug_unhandled_exception(exc_info: ta.Any = None) -> None:
272
305
 
273
306
  try:
274
307
  import pydevd
275
- from pydevd import pydevd_tracing
276
308
 
277
309
  except ImportError:
278
310
  return
279
311
 
280
- exctype, value, traceback = exc_info
281
- frames = []
282
- while traceback:
283
- frames.append(traceback.tb_frame)
284
- traceback = traceback.tb_next
285
-
286
- thread = threading.current_thread()
287
- frames_by_id = {id(frame): frame for frame in frames}
288
- frame = frames[-1]
289
- exception = (exctype, value, traceback)
290
-
291
- if hasattr(thread, 'additional_info'):
292
- thread.additional_info.pydev_message = 'server exception'
293
- try:
294
- debugger = pydevd.debugger # noqa
295
- except AttributeError:
296
- debugger = pydevd.get_global_debugger() # noqa
312
+ et, e, tb = exc_info
297
313
 
298
- pydevd_tracing.SetTrace(None)
314
+ while tb.tb_next is not None:
315
+ tb = tb.tb_next
316
+ original_frame = tb.tb_frame
299
317
 
300
- debugger.stop_on_unhandled_exception(thread, frame, frames_by_id, exception)
318
+ pydevd.settrace(stop_at_frame=original_frame, suspend=True)
@@ -173,7 +173,7 @@ class InteractiveSocketConsole:
173
173
 
174
174
  # Case 3 (complete)
175
175
  try:
176
- node = ast.parse(source) # type: ignore
176
+ node = ast.parse(source)
177
177
  except (OverflowError, SyntaxError, ValueError):
178
178
  return True
179
179
 
@@ -1,12 +1,18 @@
1
- from .dispatch import ( # noqa
2
- Dispatcher,
3
- )
4
-
5
- from .functions import ( # noqa
6
- function,
7
- )
8
-
9
- from .methods import ( # noqa
10
- install_method,
11
- method,
12
- )
1
+ from .. import lang as _lang
2
+
3
+
4
+ with _lang.auto_proxy_init(globals()):
5
+ ##
6
+
7
+ from .dispatch import ( # noqa
8
+ Dispatcher,
9
+ )
10
+
11
+ from .functions import ( # noqa
12
+ function,
13
+ )
14
+
15
+ from .methods import ( # noqa
16
+ install_method,
17
+ method,
18
+ )
@@ -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
  )