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
@@ -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)
@@ -57,6 +63,24 @@ class Func3(ta.Generic[A0, A1, A2, T]):
57
63
  ##
58
64
 
59
65
 
66
+ @dc.dataclass(frozen=True)
67
+ class CachedFunc0(ta.Generic[T]):
68
+ fn: ta.Callable[[], T]
69
+
70
+ def __call__(self) -> T:
71
+ try:
72
+ return object.__getattribute__(self, '_value')
73
+ except AttributeError:
74
+ pass
75
+
76
+ value = self.fn()
77
+ object.__setattr__(self, '_value', value)
78
+ return value
79
+
80
+
81
+ ##
82
+
83
+
60
84
  _TYPING_ANNOTATIONS_ATTR = '__annotate__' if sys.version_info >= (3, 14) else '__annotations__'
61
85
 
62
86
 
omlish/logs/_amalg.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # @omlish-lite
2
2
  # @omlish-amalg ../../omdev/scripts/lib/logs.py
3
3
  from .base import AnyLogger # noqa
4
- from .standard import configure_standard_logging # noqa
5
4
  from .std.loggers import StdLogger # noqa
5
+ from .std.standard import configure_standard_logging # noqa
6
6
 
7
7
 
8
8
  ##
omlish/logs/all.py CHANGED
@@ -38,6 +38,20 @@ with _lang.auto_proxy_init(globals()):
38
38
  LoggingContextLogRecord,
39
39
  )
40
40
 
41
+ from .std.standard import ( # noqa
42
+ STANDARD_LOG_FORMAT_PARTS,
43
+ StandardLoggingFormatter,
44
+
45
+ StandardConfiguredLoggingHandler,
46
+
47
+ configure_standard_logging,
48
+ )
49
+
50
+ from .asyncs import ( # noqa
51
+ AsyncLoggerToLogger,
52
+ LoggerToAsyncLogger,
53
+ )
54
+
41
55
  from .base import ( # noqa
42
56
  AnyLogger,
43
57
  Logger,
@@ -67,28 +81,28 @@ with _lang.auto_proxy_init(globals()):
67
81
  NamedLogLevel,
68
82
  )
69
83
 
84
+ from .lists import ( # noqa
85
+ AnyListLogger,
86
+ ListLogger,
87
+ AsyncLogger,
88
+ )
89
+
70
90
  from .modules import ( # noqa
71
91
  get_module_logger,
92
+ get_module_async_logger,
93
+ get_module_loggers,
72
94
  )
73
95
 
74
96
  from .protocols import ( # noqa
75
97
  LoggerLike,
76
98
  )
77
99
 
78
- from .standard import ( # noqa
79
- STANDARD_LOG_FORMAT_PARTS,
80
- StandardLoggingFormatter,
81
-
82
- StandardConfiguredLoggingHandler,
83
-
84
- configure_standard_logging,
85
- )
86
-
87
100
  from .utils import ( # noqa
101
+ exception_logging,
102
+ async_exception_logging,
103
+
88
104
  LogTimingContext,
89
105
  log_timing_context,
90
-
91
- error_logging,
92
106
  )
93
107
 
94
108
  from .warnings import ( # noqa
omlish/logs/asyncs.py ADDED
@@ -0,0 +1,73 @@
1
+ # ruff: noqa: UP006 UP007 UP045 UP046
2
+ # @omlish-lite
3
+ import typing as ta
4
+
5
+ from ..lite.asyncs import sync_await
6
+ from ..lite.check import check
7
+ from .base import AsyncLogger
8
+ from .base import CaptureLoggingContext
9
+ from .base import Logger
10
+ from .base import LoggingMsgFn
11
+ from .contexts import CaptureLoggingContextImpl
12
+ from .infos import LoggingContextInfos
13
+ from .levels import LogLevel
14
+
15
+
16
+ ##
17
+
18
+
19
+ class AsyncLoggerToLogger(Logger):
20
+ def __init__(self, u: AsyncLogger) -> None:
21
+ super().__init__()
22
+
23
+ self._u = u
24
+
25
+ def get_effective_level(self) -> LogLevel:
26
+ return self._u.get_effective_level()
27
+
28
+ def _log(
29
+ self,
30
+ ctx: CaptureLoggingContext,
31
+ msg: ta.Union[str, tuple, LoggingMsgFn],
32
+ *args: ta.Any,
33
+ **kwargs: ta.Any,
34
+ ) -> None:
35
+ # Nope out early to avoid sync_await if possible - don't bother in the LoggerToAsyncLogger.
36
+ if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
37
+ return
38
+
39
+ # Note: we hardcode the stack offset of sync_await (which is 2 - sync_await + sync_await.thunk). In non-lite
40
+ # code, lang.sync_await uses a cext if present to avoid being on the py stack, which would obviously complicate
41
+ # this, but this is lite code so we will always have the non-c version.
42
+ sync_await(
43
+ self._u._log( # noqa
44
+ check.isinstance(ctx, CaptureLoggingContextImpl).inc_stack_offset(3),
45
+ msg,
46
+ *args,
47
+ **kwargs,
48
+ ),
49
+ )
50
+
51
+
52
+ class LoggerToAsyncLogger(AsyncLogger):
53
+ def __init__(self, u: Logger) -> None:
54
+ super().__init__()
55
+
56
+ self._u = u
57
+
58
+ def get_effective_level(self) -> LogLevel:
59
+ return self._u.get_effective_level()
60
+
61
+ async def _log(
62
+ self,
63
+ ctx: CaptureLoggingContext,
64
+ msg: ta.Union[str, tuple, LoggingMsgFn],
65
+ *args: ta.Any,
66
+ **kwargs: ta.Any,
67
+ ) -> None:
68
+ return self._u._log( # noqa
69
+ check.isinstance(ctx, CaptureLoggingContextImpl).inc_stack_offset(),
70
+ msg,
71
+ *args,
72
+ **kwargs,
73
+ )
omlish/logs/base.py CHANGED
@@ -40,6 +40,11 @@ class AnyLogger(Abstract, ta.Generic[T]):
40
40
 
41
41
  ##
42
42
 
43
+ # This will be 1 for [Sync]Logger and 0 for AsyncLogger - in sync loggers these methods remain present on the stack,
44
+ # in async loggers they return a coroutine to be awaited and thus aren't actually present when said coroutine is
45
+ # awaited.
46
+ _level_proxy_method_stack_offset: ta.ClassVar[int]
47
+
43
48
  @ta.overload
44
49
  def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
45
50
  ...
@@ -54,7 +59,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
54
59
 
55
60
  @ta.final
56
61
  def log(self, level: LogLevel, *args, **kwargs):
57
- return self._log(CaptureLoggingContextImpl(level, stack_offset=1), *args, **kwargs)
62
+ return self._log(
63
+ CaptureLoggingContextImpl(
64
+ level,
65
+ stack_offset=self._level_proxy_method_stack_offset,
66
+ ),
67
+ *args,
68
+ **kwargs,
69
+ )
58
70
 
59
71
  #
60
72
 
@@ -72,7 +84,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
72
84
 
73
85
  @ta.final
74
86
  def debug(self, *args, **kwargs):
75
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.DEBUG, stack_offset=1), *args, **kwargs)
87
+ return self._log(
88
+ CaptureLoggingContextImpl(
89
+ NamedLogLevel.DEBUG,
90
+ stack_offset=self._level_proxy_method_stack_offset,
91
+ ),
92
+ *args,
93
+ **kwargs,
94
+ )
76
95
 
77
96
  #
78
97
 
@@ -90,7 +109,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
90
109
 
91
110
  @ta.final
92
111
  def info(self, *args, **kwargs):
93
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.INFO, stack_offset=1), *args, **kwargs)
112
+ return self._log(
113
+ CaptureLoggingContextImpl(
114
+ NamedLogLevel.INFO,
115
+ stack_offset=self._level_proxy_method_stack_offset,
116
+ ),
117
+ *args,
118
+ **kwargs,
119
+ )
94
120
 
95
121
  #
96
122
 
@@ -108,7 +134,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
108
134
 
109
135
  @ta.final
110
136
  def warning(self, *args, **kwargs):
111
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.WARNING, stack_offset=1), *args, **kwargs)
137
+ return self._log(
138
+ CaptureLoggingContextImpl(
139
+ NamedLogLevel.WARNING,
140
+ stack_offset=self._level_proxy_method_stack_offset,
141
+ ),
142
+ *args,
143
+ **kwargs,
144
+ )
112
145
 
113
146
  #
114
147
 
@@ -126,7 +159,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
126
159
 
127
160
  @ta.final
128
161
  def error(self, *args, **kwargs):
129
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.ERROR, stack_offset=1), *args, **kwargs)
162
+ return self._log(
163
+ CaptureLoggingContextImpl(
164
+ NamedLogLevel.ERROR,
165
+ stack_offset=self._level_proxy_method_stack_offset,
166
+ ),
167
+ *args,
168
+ **kwargs,
169
+ )
130
170
 
131
171
  #
132
172
 
@@ -144,7 +184,15 @@ class AnyLogger(Abstract, ta.Generic[T]):
144
184
 
145
185
  @ta.final
146
186
  def exception(self, *args, exc_info: LoggingExcInfoArg = True, **kwargs):
147
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.ERROR, exc_info=exc_info, stack_offset=1), *args, **kwargs) # noqa
187
+ return self._log(
188
+ CaptureLoggingContextImpl(
189
+ NamedLogLevel.ERROR,
190
+ exc_info=exc_info,
191
+ stack_offset=self._level_proxy_method_stack_offset,
192
+ ),
193
+ *args,
194
+ **kwargs,
195
+ )
148
196
 
149
197
  #
150
198
 
@@ -162,24 +210,53 @@ class AnyLogger(Abstract, ta.Generic[T]):
162
210
 
163
211
  @ta.final
164
212
  def critical(self, *args, **kwargs):
165
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.CRITICAL, stack_offset=1), *args, **kwargs)
213
+ return self._log(
214
+ CaptureLoggingContextImpl(
215
+ NamedLogLevel.CRITICAL,
216
+ stack_offset=self._level_proxy_method_stack_offset,
217
+ ),
218
+ *args,
219
+ **kwargs,
220
+ )
166
221
 
167
222
  ##
168
223
 
169
224
  @abc.abstractmethod
170
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
225
+ def _log(
226
+ self,
227
+ ctx: CaptureLoggingContext,
228
+ msg: ta.Union[str, tuple, LoggingMsgFn],
229
+ *args: ta.Any,
230
+ **kwargs: ta.Any,
231
+ ) -> T:
171
232
  raise NotImplementedError
172
233
 
173
234
 
174
235
  class Logger(AnyLogger[None], Abstract):
236
+ _level_proxy_method_stack_offset: ta.ClassVar[int] = 1
237
+
175
238
  @abc.abstractmethod
176
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
239
+ def _log(
240
+ self,
241
+ ctx: CaptureLoggingContext,
242
+ msg: ta.Union[str, tuple, LoggingMsgFn],
243
+ *args: ta.Any,
244
+ **kwargs: ta.Any,
245
+ ) -> None:
177
246
  raise NotImplementedError
178
247
 
179
248
 
180
249
  class AsyncLogger(AnyLogger[ta.Awaitable[None]], Abstract):
250
+ _level_proxy_method_stack_offset: ta.ClassVar[int] = 0
251
+
181
252
  @abc.abstractmethod
182
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> ta.Awaitable[None]: # noqa
253
+ def _log(
254
+ self,
255
+ ctx: CaptureLoggingContext,
256
+ msg: ta.Union[str, tuple, LoggingMsgFn],
257
+ *args: ta.Any,
258
+ **kwargs: ta.Any,
259
+ ) -> ta.Awaitable[None]:
183
260
  raise NotImplementedError
184
261
 
185
262
 
@@ -194,11 +271,23 @@ class AnyNopLogger(AnyLogger[T], Abstract):
194
271
 
195
272
  @ta.final
196
273
  class NopLogger(AnyNopLogger[None], Logger):
197
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
274
+ def _log(
275
+ self,
276
+ ctx: CaptureLoggingContext,
277
+ msg: ta.Union[str, tuple, LoggingMsgFn],
278
+ *args: ta.Any,
279
+ **kwargs: ta.Any,
280
+ ) -> None:
198
281
  pass
199
282
 
200
283
 
201
284
  @ta.final
202
285
  class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
203
- async def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
286
+ async def _log(
287
+ self,
288
+ ctx: CaptureLoggingContext,
289
+ msg: ta.Union[str, tuple, LoggingMsgFn],
290
+ *args: ta.Any,
291
+ **kwargs: ta.Any,
292
+ ) -> None:
204
293
  pass
omlish/logs/contexts.py CHANGED
@@ -118,6 +118,9 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
118
118
  self._infos[type(info)] = info
119
119
  return self
120
120
 
121
+ def get_infos(self) -> ta.Mapping[ta.Type[LoggingContextInfo], LoggingContextInfo]:
122
+ return self._infos
123
+
121
124
  def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
122
125
  return self._infos.get(ty)
123
126
 
@@ -140,7 +143,7 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
140
143
  _stack_offset: int
141
144
  _stack_info: bool
142
145
 
143
- def inc_stack_offset(self, ofs: int = 1) -> 'CaptureLoggingContext':
146
+ def inc_stack_offset(self, ofs: int = 1) -> 'CaptureLoggingContextImpl':
144
147
  if hasattr(self, '_stack_offset'):
145
148
  self._stack_offset += ofs
146
149
  return self
omlish/logs/lists.py ADDED
@@ -0,0 +1,125 @@
1
+ # ruff: noqa: N802 UP006 UP007 UP045
2
+ # @omlish-lite
3
+ import dataclasses as dc
4
+ import typing as ta
5
+
6
+ from ..lite.abstract import Abstract
7
+ from ..lite.check import check
8
+ from .base import AnyLogger
9
+ from .base import AsyncLogger
10
+ from .base import CaptureLoggingContext
11
+ from .base import Logger
12
+ from .base import LoggingMsgFn
13
+ from .contexts import CaptureLoggingContextImpl
14
+ from .infos import LoggingContextInfo
15
+ from .infos import LoggingContextInfos
16
+ from .levels import LogLevel
17
+ from .levels import NamedLogLevel
18
+
19
+
20
+ T = ta.TypeVar('T')
21
+
22
+ LoggingContextInfoT = ta.TypeVar('LoggingContextInfoT', bound=LoggingContextInfo)
23
+
24
+
25
+ ##
26
+
27
+
28
+ @dc.dataclass(frozen=True)
29
+ class ListLoggerEntry:
30
+ infos: ta.Mapping[ta.Type[LoggingContextInfo], LoggingContextInfo]
31
+
32
+ def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
33
+ return self.infos.get(ty)
34
+
35
+ @ta.final
36
+ def __getitem__(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
37
+ return self.get_info(ty)
38
+
39
+ def must_get_info(self, ty: ta.Type[LoggingContextInfoT]) -> LoggingContextInfoT:
40
+ return check.not_none(self.get_info(ty))
41
+
42
+
43
+ class AnyListLogger(AnyLogger[T], Abstract):
44
+ def __init__(
45
+ self,
46
+ *,
47
+ name: ta.Optional[str] = None,
48
+ level: LogLevel = NamedLogLevel.NOTSET,
49
+ ) -> None:
50
+ super().__init__()
51
+
52
+ if name is None:
53
+ name = f'{type(self).__name__}@{id(self):x}'
54
+ self._name = name
55
+ self._level = level
56
+
57
+ self._entries: ta.List[ListLoggerEntry] = []
58
+
59
+ @property
60
+ def name(self) -> str:
61
+ return self._name
62
+
63
+ @property
64
+ def entries(self) -> ta.List[ListLoggerEntry]:
65
+ """Intentionally mutable."""
66
+
67
+ return self._entries
68
+
69
+ def set_level(self, level: LogLevel) -> None:
70
+ self._level = level
71
+
72
+ def get_effective_level(self) -> LogLevel:
73
+ return self._level
74
+
75
+ def _add_entry(
76
+ self,
77
+ ctx: CaptureLoggingContextImpl,
78
+ msg: ta.Union[str, tuple, LoggingMsgFn],
79
+ *args: ta.Any,
80
+ ) -> None:
81
+ if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
82
+ return
83
+
84
+ ctx.set_basic(
85
+ name=self._name,
86
+
87
+ msg=msg,
88
+ args=args,
89
+ )
90
+
91
+ ctx.capture()
92
+
93
+ self._entries.append(ListLoggerEntry(ctx.get_infos()))
94
+
95
+
96
+ class ListLogger(AnyListLogger[None], Logger):
97
+ def _log(
98
+ self,
99
+ ctx: CaptureLoggingContext,
100
+ msg: ta.Union[str, tuple, LoggingMsgFn],
101
+ *args: ta.Any,
102
+ **kwargs: ta.Any,
103
+ ) -> None:
104
+ self._add_entry(
105
+ check.isinstance(ctx, CaptureLoggingContextImpl).inc_stack_offset(),
106
+ msg,
107
+ *args,
108
+ **kwargs,
109
+ )
110
+
111
+
112
+ class ListAsyncLogger(AnyListLogger[ta.Awaitable[None]], AsyncLogger):
113
+ async def _log(
114
+ self,
115
+ ctx: CaptureLoggingContext,
116
+ msg: ta.Union[str, tuple, LoggingMsgFn],
117
+ *args: ta.Any,
118
+ **kwargs: ta.Any,
119
+ ) -> None:
120
+ self._add_entry(
121
+ check.isinstance(ctx, CaptureLoggingContextImpl).inc_stack_offset(),
122
+ msg,
123
+ *args,
124
+ **kwargs,
125
+ )
omlish/logs/modules.py CHANGED
@@ -1,7 +1,10 @@
1
+ # ruff: noqa: UP006
1
2
  # @omlish-lite
2
3
  import logging
3
4
  import typing as ta
4
5
 
6
+ from .asyncs import LoggerToAsyncLogger
7
+ from .base import AsyncLogger
5
8
  from .base import Logger
6
9
  from .std.loggers import StdLogger
7
10
 
@@ -9,5 +12,20 @@ from .std.loggers import StdLogger
9
12
  ##
10
13
 
11
14
 
15
+ def _get_module_std_logger(mod_globals: ta.Mapping[str, ta.Any]) -> logging.Logger:
16
+ return logging.getLogger(mod_globals.get('__name__'))
17
+
18
+
12
19
  def get_module_logger(mod_globals: ta.Mapping[str, ta.Any]) -> Logger:
13
- return StdLogger(logging.getLogger(mod_globals.get('__name__'))) # noqa
20
+ return StdLogger(_get_module_std_logger(mod_globals))
21
+
22
+
23
+ def get_module_async_logger(mod_globals: ta.Mapping[str, ta.Any]) -> AsyncLogger:
24
+ return LoggerToAsyncLogger(get_module_logger(mod_globals))
25
+
26
+
27
+ def get_module_loggers(mod_globals: ta.Mapping[str, ta.Any]) -> ta.Tuple[Logger, AsyncLogger]:
28
+ return (
29
+ log := get_module_logger(mod_globals),
30
+ LoggerToAsyncLogger(log),
31
+ )
@@ -30,7 +30,12 @@ class StdLogger(Logger):
30
30
  def get_effective_level(self) -> LogLevel:
31
31
  return self._std.getEffectiveLevel()
32
32
 
33
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
33
+ def _log(
34
+ self,
35
+ ctx: CaptureLoggingContext,
36
+ msg: ta.Union[str, tuple, LoggingMsgFn],
37
+ *args: ta.Any,
38
+ ) -> None:
34
39
  if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
35
40
  return
36
41
 
omlish/logs/std/noisy.py CHANGED
@@ -4,16 +4,18 @@ import logging
4
4
  ##
5
5
 
6
6
 
7
- NOISY_LOGGERS: set[str] = {
8
- 'boto3.resources.action',
9
- 'datadog.dogstatsd',
10
- 'elasticsearch',
11
- 'httpx',
12
- 'kazoo.client',
13
- 'requests.packages.urllib3.connectionpool',
7
+ NOISY_LOGGER_LEVELS: dict[str, int] = {
8
+ 'boto3.resources.action': logging.WARNING,
9
+ 'datadog.dogstatsd': logging.WARNING,
10
+ 'elasticsearch': logging.WARNING,
11
+ 'httpcore': logging.INFO,
12
+ 'httpx': logging.WARNING,
13
+ 'kazoo.client': logging.WARNING,
14
+ 'markdown_it': logging.INFO,
15
+ 'requests.packages.urllib3.connectionpool': logging.WARNING,
14
16
  }
15
17
 
16
18
 
17
19
  def silence_noisy_loggers() -> None:
18
- for noisy_logger in NOISY_LOGGERS:
19
- logging.getLogger(noisy_logger).setLevel(logging.WARNING)
20
+ for name, level in NOISY_LOGGER_LEVELS.items():
21
+ logging.getLogger(name).setLevel(level)