omdev 0.0.0.dev440__py3-none-any.whl → 0.0.0.dev495__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 omdev might be problematic. Click here for more details.

Files changed (148) hide show
  1. omdev/.omlish-manifests.json +18 -30
  2. omdev/README.md +51 -0
  3. omdev/__about__.py +11 -7
  4. omdev/amalg/gen/gen.py +49 -6
  5. omdev/amalg/gen/imports.py +1 -1
  6. omdev/amalg/gen/manifests.py +1 -1
  7. omdev/amalg/gen/resources.py +1 -1
  8. omdev/amalg/gen/srcfiles.py +13 -3
  9. omdev/amalg/gen/strip.py +1 -1
  10. omdev/amalg/gen/types.py +1 -1
  11. omdev/amalg/gen/typing.py +1 -1
  12. omdev/amalg/info.py +32 -0
  13. omdev/cache/data/actions.py +1 -1
  14. omdev/cache/data/specs.py +1 -1
  15. omdev/cexts/_boilerplate.cc +2 -3
  16. omdev/cexts/cmake.py +4 -1
  17. omdev/ci/cli.py +2 -3
  18. omdev/cli/clicli.py +37 -7
  19. omdev/cmdlog/cli.py +1 -2
  20. omdev/dataclasses/_dumping.py +1960 -0
  21. omdev/dataclasses/_template.py +22 -0
  22. omdev/dataclasses/cli.py +7 -2
  23. omdev/dataclasses/codegen.py +340 -60
  24. omdev/dataclasses/dumping.py +200 -0
  25. omdev/interp/cli.py +1 -1
  26. omdev/interp/types.py +3 -2
  27. omdev/interp/uv/provider.py +37 -0
  28. omdev/interp/venvs.py +1 -0
  29. omdev/irc/messages/base.py +50 -0
  30. omdev/irc/messages/formats.py +92 -0
  31. omdev/irc/messages/messages.py +775 -0
  32. omdev/irc/messages/parsing.py +99 -0
  33. omdev/irc/numerics/__init__.py +0 -0
  34. omdev/irc/numerics/formats.py +97 -0
  35. omdev/irc/numerics/numerics.py +865 -0
  36. omdev/irc/numerics/types.py +59 -0
  37. omdev/irc/protocol/LICENSE +11 -0
  38. omdev/irc/protocol/__init__.py +61 -0
  39. omdev/irc/protocol/consts.py +6 -0
  40. omdev/irc/protocol/errors.py +30 -0
  41. omdev/irc/protocol/message.py +21 -0
  42. omdev/irc/protocol/nuh.py +55 -0
  43. omdev/irc/protocol/parsing.py +158 -0
  44. omdev/irc/protocol/rendering.py +153 -0
  45. omdev/irc/protocol/tags.py +102 -0
  46. omdev/irc/protocol/utils.py +30 -0
  47. omdev/manifests/_dumping.py +125 -25
  48. omdev/manifests/main.py +1 -1
  49. omdev/markdown/__init__.py +0 -0
  50. omdev/markdown/incparse.py +116 -0
  51. omdev/markdown/tokens.py +51 -0
  52. omdev/packaging/marshal.py +8 -8
  53. omdev/packaging/requires.py +6 -6
  54. omdev/packaging/revisions.py +1 -1
  55. omdev/packaging/specifiers.py +2 -1
  56. omdev/packaging/versions.py +4 -4
  57. omdev/packaging/wheelfile.py +2 -0
  58. omdev/precheck/blanklines.py +66 -0
  59. omdev/precheck/caches.py +1 -1
  60. omdev/precheck/imports.py +14 -1
  61. omdev/precheck/main.py +4 -3
  62. omdev/precheck/unicode.py +39 -15
  63. omdev/py/asts/__init__.py +0 -0
  64. omdev/py/asts/parents.py +28 -0
  65. omdev/py/asts/toplevel.py +123 -0
  66. omdev/py/asts/visitors.py +18 -0
  67. omdev/py/attrdocs.py +1 -1
  68. omdev/py/bracepy.py +12 -4
  69. omdev/py/reprs.py +32 -0
  70. omdev/py/srcheaders.py +1 -1
  71. omdev/py/tokens/__init__.py +0 -0
  72. omdev/py/tools/mkrelimp.py +1 -1
  73. omdev/py/tools/pipdepup.py +686 -0
  74. omdev/pyproject/cli.py +1 -1
  75. omdev/pyproject/pkg.py +190 -45
  76. omdev/pyproject/reqs.py +31 -9
  77. omdev/pyproject/tools/__init__.py +0 -0
  78. omdev/pyproject/tools/aboutdeps.py +60 -0
  79. omdev/pyproject/venvs.py +8 -1
  80. omdev/rs/__init__.py +0 -0
  81. omdev/scripts/ci.py +752 -98
  82. omdev/scripts/interp.py +232 -39
  83. omdev/scripts/lib/inject.py +74 -27
  84. omdev/scripts/lib/logs.py +187 -43
  85. omdev/scripts/lib/marshal.py +67 -25
  86. omdev/scripts/pyproject.py +1369 -143
  87. omdev/tools/git/cli.py +10 -0
  88. omdev/tools/json/formats.py +2 -0
  89. omdev/tools/json/processing.py +5 -2
  90. omdev/tools/jsonview/cli.py +49 -65
  91. omdev/tools/jsonview/resources/jsonview.html.j2 +43 -0
  92. omdev/tools/pawk/README.md +195 -0
  93. omdev/tools/pawk/pawk.py +2 -2
  94. omdev/tools/pip.py +8 -0
  95. omdev/tui/__init__.py +0 -0
  96. omdev/tui/apps/__init__.py +0 -0
  97. omdev/tui/apps/edit/__init__.py +0 -0
  98. omdev/tui/apps/edit/main.py +167 -0
  99. omdev/tui/apps/irc/__init__.py +0 -0
  100. omdev/tui/apps/irc/__main__.py +4 -0
  101. omdev/tui/apps/irc/app.py +286 -0
  102. omdev/tui/apps/irc/client.py +187 -0
  103. omdev/tui/apps/irc/commands.py +175 -0
  104. omdev/tui/apps/irc/main.py +26 -0
  105. omdev/tui/apps/markdown/__init__.py +0 -0
  106. omdev/tui/apps/markdown/__main__.py +11 -0
  107. omdev/{ptk → tui/apps}/markdown/cli.py +5 -7
  108. omdev/tui/rich/__init__.py +46 -0
  109. omdev/tui/rich/console2.py +20 -0
  110. omdev/tui/rich/markdown2.py +186 -0
  111. omdev/tui/textual/__init__.py +265 -0
  112. omdev/tui/textual/app2.py +16 -0
  113. omdev/tui/textual/autocomplete/LICENSE +21 -0
  114. omdev/tui/textual/autocomplete/__init__.py +33 -0
  115. omdev/tui/textual/autocomplete/matching.py +226 -0
  116. omdev/tui/textual/autocomplete/paths.py +202 -0
  117. omdev/tui/textual/autocomplete/widget.py +612 -0
  118. omdev/tui/textual/debug/__init__.py +10 -0
  119. omdev/tui/textual/debug/dominfo.py +151 -0
  120. omdev/tui/textual/debug/screen.py +24 -0
  121. omdev/tui/textual/devtools.py +187 -0
  122. omdev/tui/textual/drivers2.py +55 -0
  123. omdev/tui/textual/logging2.py +20 -0
  124. omdev/tui/textual/types.py +45 -0
  125. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/METADATA +15 -9
  126. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/RECORD +135 -80
  127. omdev/ptk/__init__.py +0 -103
  128. omdev/ptk/apps/ncdu.py +0 -167
  129. omdev/ptk/confirm.py +0 -60
  130. omdev/ptk/markdown/LICENSE +0 -22
  131. omdev/ptk/markdown/__init__.py +0 -10
  132. omdev/ptk/markdown/__main__.py +0 -11
  133. omdev/ptk/markdown/border.py +0 -94
  134. omdev/ptk/markdown/markdown.py +0 -390
  135. omdev/ptk/markdown/parser.py +0 -42
  136. omdev/ptk/markdown/styles.py +0 -29
  137. omdev/ptk/markdown/tags.py +0 -299
  138. omdev/ptk/markdown/utils.py +0 -366
  139. omdev/pyproject/cexts.py +0 -110
  140. /omdev/{ptk/apps → irc}/__init__.py +0 -0
  141. /omdev/{tokens → irc/messages}/__init__.py +0 -0
  142. /omdev/{tokens → py/tokens}/all.py +0 -0
  143. /omdev/{tokens → py/tokens}/tokenizert.py +0 -0
  144. /omdev/{tokens → py/tokens}/utils.py +0 -0
  145. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/WHEEL +0 -0
  146. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/entry_points.txt +0 -0
  147. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/licenses/LICENSE +0 -0
  148. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/top_level.txt +0 -0
omdev/scripts/ci.py CHANGED
@@ -49,6 +49,7 @@ import io
49
49
  import itertools
50
50
  import json
51
51
  import logging
52
+ import operator
52
53
  import os
53
54
  import os.path
54
55
  import re
@@ -83,6 +84,124 @@ if sys.version_info < (3, 8):
83
84
  raise OSError(f'Requires python (3, 8), got {sys.version_info} from {sys.executable}') # noqa
84
85
 
85
86
 
87
+ def __omlish_amalg__(): # noqa
88
+ return dict(
89
+ src_files=[
90
+ dict(path='consts.py', sha1='ef00d55ab4cdd799b22f2e8b736eacd52ee8a80e'),
91
+ dict(path='github/env.py', sha1='c7a2389048f008f46f59f6bcc11e0d15655f2b1c'),
92
+ dict(path='shell.py', sha1='a59e59b812394d0502837f4c198e1cf604f90227'),
93
+ dict(path='utils.py', sha1='f0f9ef0778db316b1ba83e6eeac79c8fd132d86a'),
94
+ dict(path='../oci/compression.py', sha1='7d165bc51a77db13ff45927daecc42839cfd75ea'),
95
+ dict(path='../../omlish/asyncs/asyncio/utils.py', sha1='34691d4d0e5bab68f14e193a6200df040cfd0136'),
96
+ dict(path='../../omlish/docker/ports.py', sha1='a3202c69b85bc4f1034479df3400fddc86130e5c'),
97
+ dict(path='../../omlish/http/urllib.py', sha1='25431c5bdc7dd5cbecfcb8c0bdffaabf8c1691b9'),
98
+ dict(path='../../omlish/http/versions.py', sha1='197685ffbb62a457a0e8d4047a9df26aebd7dae4'),
99
+ dict(path='../../omlish/lite/abstract.py', sha1='a2fc3f3697fa8de5247761e9d554e70176f37aac'),
100
+ dict(path='../../omlish/lite/asyncs.py', sha1='b3f2251c56617ce548abf9c333ac996b63edb23e'),
101
+ dict(path='../../omlish/lite/cached.py', sha1='0c33cf961ac8f0727284303c7a30c5ea98f714f2'),
102
+ dict(path='../../omlish/lite/check.py', sha1='bb6b6b63333699b84462951a854d99ae83195b94'),
103
+ dict(path='../../omlish/lite/contextmanagers.py', sha1='993f5ed96d3410f739a20363f55670d5e5267fa3'),
104
+ dict(path='../../omlish/lite/dataclasses.py', sha1='73b7f5e5493c7ed12ff0ce36e37b596e5984cb08'),
105
+ dict(path='../../omlish/lite/json.py', sha1='57eeddc4d23a17931e00284ffa5cb6e3ce089486'),
106
+ dict(path='../../omlish/lite/objects.py', sha1='9566bbf3530fd71fcc56321485216b592fae21e9'),
107
+ dict(path='../../omlish/lite/reflect.py', sha1='c4fec44bf144e9d93293c996af06f6c65fc5e63d'),
108
+ dict(path='../../omlish/lite/strings.py', sha1='89831ecbc34ad80e118a865eceb390ed399dc4d6'),
109
+ dict(path='../../omlish/logs/levels.py', sha1='91405563d082a5eba874da82aac89d83ce7b6152'),
110
+ dict(path='../../omlish/logs/std/filters.py', sha1='f36aab646d84d31e295b33aaaaa6f8b67ff38b3d'),
111
+ dict(path='../../omlish/logs/std/proxy.py', sha1='3e7301a2aa351127f9c85f61b2f85dcc3f15aafb'),
112
+ dict(path='../../omlish/logs/warnings.py', sha1='c4eb694b24773351107fcc058f3620f1dbfb6799'),
113
+ dict(path='../../omlish/os/files.py', sha1='32f4fe2e7d32a6b368619081bd300e6f150e312b'),
114
+ dict(path='../../omlish/os/paths.py', sha1='56c40b7c2aa84d1778d60ee4cda498f8c380cc8d'),
115
+ dict(path='../../omlish/secrets/ssl.py', sha1='68af8abad22d64afa1736a8363036aa2121ada78'),
116
+ dict(path='../../omlish/sockets/addresses.py', sha1='26533e88a8073f89646c0f77f1fbe0869282ab0e'),
117
+ dict(path='../../omlish/sockets/io.py', sha1='b6b8a73ac0f29893c7128f9d4f62240efbd917bb'),
118
+ dict(path='docker/utils.py', sha1='082e2b962ca1bc8e6b3f9bbe6ecfcadff310d54f'),
119
+ dict(path='github/api/v1/api.py', sha1='1985c16447f5d245b659936571d361285374c5d9'),
120
+ dict(path='github/api/v2/api.py', sha1='08322895cd895db1cbffceb1e5bfc7b10cac50cc'),
121
+ dict(path='github/bootstrap.py', sha1='9bf24b05603cd1a82db8be8b645bbad3e0d3f22f'),
122
+ dict(path='../oci/datarefs.py', sha1='793ce5f2774e052b28d04b226a5f1eff6eec0a72'),
123
+ dict(path='../oci/pack/unpacking.py', sha1='f43dee9a2eee79cbbb90f0721ed234a2bc35daa7'),
124
+ dict(path='../../omlish/argparse/cli.py', sha1='f4dc3cd353d14386b5da0306768700e396afd2b3'),
125
+ dict(path='../../omlish/http/coro/io.py', sha1='2cdf6529c37a37cc0c1db2e02032157cf906d5d6'),
126
+ dict(path='../../omlish/http/parsing.py', sha1='3fea28dc6341908ba7c8fad42bf7bbe711f21b82'),
127
+ dict(path='../../omlish/lite/marshal.py', sha1='96348f5f2a26dc27d842d33cc3927e9da163436b'),
128
+ dict(path='../../omlish/lite/maybes.py', sha1='bdf5136654ccd14b6a072588cad228925bdfbabd'),
129
+ dict(path='../../omlish/lite/runtime.py', sha1='2e752a27ae2bf89b1bb79b4a2da522a3ec360c70'),
130
+ dict(path='../../omlish/lite/timeouts.py', sha1='a0f673033a6943f242e35848d78a41892b9c62a1'),
131
+ dict(path='../../omlish/logs/infos.py', sha1='4dd104bd468a8c438601dd0bbda619b47d2f1620'),
132
+ dict(path='../../omlish/logs/protocols.py', sha1='05ca4d1d7feb50c4e3b9f22ee371aa7bf4b3dbd1'),
133
+ dict(path='../../omlish/logs/std/json.py', sha1='2a75553131e4d5331bb0cedde42aa183f403fc3b'),
134
+ dict(path='../../omlish/os/temp.py', sha1='fba3470ac095a6c3f893156cc4437bda1b8796cb'),
135
+ dict(path='../../omlish/sockets/bind.py', sha1='d0040d74960fb00c30ae9ece5252c270b59ae3f4'),
136
+ dict(path='../../omlish/sockets/handlers.py', sha1='733a4855d563001ad09df511531d710aa2632770'),
137
+ dict(path='../../omlish/text/mangle.py', sha1='5631de37ca659e064eb1afcf2213d317b626f8bd'),
138
+ dict(path='../dataserver/targets.py', sha1='661fc3e60ad436646c8acff3c547d5b86ceb0bd6'),
139
+ dict(path='../oci/data.py', sha1='9cfc3bb8e23fb4cd746c6e8f0e02624e8f63f234'),
140
+ dict(path='../oci/repositories.py', sha1='bd0ac0fb906f679a660c87124da268370733fcc6'),
141
+ dict(path='../oci/tars.py', sha1='3ed00e97a494bd92c6a6149d22d51469bc0af384'),
142
+ dict(path='../../omlish/asyncs/asyncio/sockets.py', sha1='8d24dae988a30bb73f167a9ab62d4fc9eef4ad06'),
143
+ dict(path='../../omlish/asyncs/asyncio/timeouts.py', sha1='4d31b02b3c39b8f2fa7e94db36552fde6942e36a'),
144
+ dict(path='../../omlish/http/handlers.py', sha1='40629060bac22ea5e94b720b57001861a4ec9031'),
145
+ dict(path='../../omlish/lite/inject.py', sha1='6f097e3170019a34ff6834d36fcc9cbeed3a7ab4'),
146
+ dict(path='../../omlish/logs/contexts.py', sha1='1000a6d5ddfb642865ca532e34b1d50759781cf0'),
147
+ dict(path='../../omlish/logs/std/standard.py', sha1='5c97c1b9f7ead58d6127d047b873398f708f288d'),
148
+ dict(path='../../omlish/logs/utils.py', sha1='39599f5e9d9911a06eac6e0d7e5116fdc69b1263'),
149
+ dict(path='../../omlish/sockets/server/handlers.py', sha1='6f9adca9fa04774a28a488a4e2a11bb4492c71d0'),
150
+ dict(path='../../omlish/subprocesses/run.py', sha1='8200e48f0c49d164df3503cd0143038d0c4d30aa'),
151
+ dict(path='../../omlish/subprocesses/wrap.py', sha1='8a9b7d2255481fae15c05f5624b0cdc0766f4b3f'),
152
+ dict(path='../dataserver/handlers.py', sha1='f624715f2500087226ec3374315cc8a1ea47a29b'),
153
+ dict(path='../dataserver/routes.py', sha1='0186bb2e84ff4d5c05af2a57c61f6fd605eba790'),
154
+ dict(path='../oci/media.py', sha1='a20324c5b0661c9a9a7679406d019ab3ba4acd98'),
155
+ dict(path='../oci/pack/packing.py', sha1='7585c3dea6b8a62b6ca63fe78968497db915ea57'),
156
+ dict(path='../../omlish/http/coro/server/server.py', sha1='c0a980afa8346dbc20570acddb2b3b579bfc1ce0'),
157
+ dict(path='../../omlish/logs/base.py', sha1='8d06faee05fead6b1dd98c9035a5b042af4aebb1'),
158
+ dict(path='../../omlish/logs/std/records.py', sha1='8bbf6ef9eccb3a012c6ca416ddf3969450fd8fc9'),
159
+ dict(path='../../omlish/secrets/tempssl.py', sha1='360d4cd98483357bcf013e156dafd92fd37ed220'),
160
+ dict(path='../../omlish/sockets/server/server.py', sha1='a93a74f6beb38d69e0fb9047c932f2a95aa37eca'),
161
+ dict(path='../../omlish/sockets/server/ssl.py', sha1='790dfd208f4d267c826d491d4eb5deeda5ebdddc'),
162
+ dict(path='../../omlish/sockets/server/threading.py', sha1='0ba3c7a3d15781326610b12feef94e53903d0ce9'),
163
+ dict(path='../../omlish/subprocesses/base.py', sha1='cb9f668be5422fecb27222caabb67daac6c1bab9'),
164
+ dict(path='docker/cacheserved/manifests.py', sha1='80a65d08319d152f0bc6f893351e23368b3de93b'),
165
+ dict(path='../dataserver/server.py', sha1='e1ba8ca6f85458a64ede4ca07836aa103246132a'),
166
+ dict(path='../oci/building.py', sha1='b4fea06c03ba02d3ecfc6d10d955dc76f263846a'),
167
+ dict(path='../oci/loading.py', sha1='64d806ffad8d24087ccc29f759f672e6d795bee2'),
168
+ dict(path='../../omlish/http/coro/server/sockets.py', sha1='40ef4aa43f94f1a1a2a431a012cb961f25905ff4'),
169
+ dict(path='../../omlish/logs/asyncs.py', sha1='ab11b70033d9f2e9a4e70254185aa1c6130c6077'),
170
+ dict(path='../../omlish/logs/std/loggers.py', sha1='a569179445d6a8a942b5dcfad1d1f77702868803'),
171
+ dict(path='../../omlish/subprocesses/asyncs.py', sha1='bba44d524c24c6ac73168aee6343488414e5bf48'),
172
+ dict(path='../../omlish/subprocesses/sync.py', sha1='8434919eba4da67825773d56918fdc0cb2f1883b'),
173
+ dict(path='requirements.py', sha1='c370a65958a00412e00608a0e1f12795e276aee1'),
174
+ dict(path='../dataserver/http.py', sha1='f85ca91015bac434281326ef7885babd9d6d834c'),
175
+ dict(path='../oci/dataserver.py', sha1='dd147b56282b054cef264556a0ff3b3d1719bcee'),
176
+ dict(path='../../omlish/asyncs/asyncio/subprocesses.py', sha1='b6b5f9ae3fd0b9c83593bad2e04a08f726e5904d'),
177
+ dict(path='../../omlish/http/coro/server/simple.py', sha1='2332079fe29993123c68d7dbc266b47cd44cd6a6'),
178
+ dict(path='../../omlish/logs/modules.py', sha1='dd7d5f8e63fe8829dfb49460f3929ab64b68ee14'),
179
+ dict(path='cache.py', sha1='9353e5c3b73bed47258680fd15ac49417113f0ca'),
180
+ dict(path='compose.py', sha1='d2bec1385701979c7ff9913456b72d8c7b31f70b'),
181
+ dict(path='docker/cmds.py', sha1='5528c384f68f9003732bfaf6be302e84747909dd'),
182
+ dict(path='docker/dataserver.py', sha1='949e561ab756846425a39c05964c0fb256ae61db'),
183
+ dict(path='../../omlish/lite/timing.py', sha1='af5022f5a508939f1b433ed0514ede340fd0d672'),
184
+ dict(path='docker/cache.py', sha1='07a9e3d00bdd60f1a0a9b55aca12430fa1b4e3d9'),
185
+ dict(path='docker/repositories.py', sha1='e4bfc0e91c3bf20259895ce7c95a1eb3f6507518'),
186
+ dict(path='github/api/clients.py', sha1='8ddc0f5ccf718f1b530f4a965f0cc56b68a6a2a8'),
187
+ dict(path='github/api/v2/azure.py', sha1='19052e8e2babb8b2953ec10e6b9dcee97c21419a'),
188
+ dict(path='../oci/pack/repositories.py', sha1='e9bf6fa8bdaae2031dd0967d07a56c93a2e099b6'),
189
+ dict(path='docker/buildcaching.py', sha1='7b2633d5b8dac6aab01dd459e273cb370c5b11c8'),
190
+ dict(path='docker/cacheserved/cache.py', sha1='69732c658dba7ccf260e784132ff0c60192e3c69'),
191
+ dict(path='docker/imagepulling.py', sha1='d6b1ca1ecb9aa5c593a25e6deb78e942c75ebcb4'),
192
+ dict(path='github/api/v1/client.py', sha1='6ddd600cd8a7ff72a6a3408ded14240bafab6944'),
193
+ dict(path='github/api/v2/client.py', sha1='e28f27c07011487d5a3f4ae32fdfa1a857d02459'),
194
+ dict(path='ci.py', sha1='87b82e2bd86aa886764f1e0067251b056e359650'),
195
+ dict(path='docker/inject.py', sha1='69acac65fae413cb58c1f9aa739d2cc1c3ffa09d'),
196
+ dict(path='github/cache.py', sha1='d91f9c87d167574e94c99817e6c3a0f75925dfb9'),
197
+ dict(path='github/cli.py', sha1='6d14b0eb4ca5f606ad2821b63b9707ce57f50406'),
198
+ dict(path='github/inject.py', sha1='99c0dd7c55767e7c49f70b7edac25da67f718b2e'),
199
+ dict(path='inject.py', sha1='e86b16d79a113a4f387e68ed0db1d067bcada93a'),
200
+ dict(path='cli.py', sha1='92948cf3ec76fd9ceb25762aadbf497354adce6d'),
201
+ ],
202
+ )
203
+
204
+
86
205
  ########################################
87
206
 
88
207
 
@@ -345,6 +464,8 @@ async def asyncio_wait_concurrent(
345
464
  if isinstance(concurrency, asyncio.Semaphore):
346
465
  semaphore = concurrency
347
466
  elif isinstance(concurrency, int):
467
+ if concurrency < 1:
468
+ raise ValueError(f'Concurrency must be >= 1, got {concurrency}')
348
469
  semaphore = asyncio.Semaphore(concurrency)
349
470
  else:
350
471
  raise TypeError(concurrency)
@@ -508,25 +629,49 @@ def is_abstract_method(obj: ta.Any) -> bool:
508
629
  return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
509
630
 
510
631
 
511
- def update_abstracts(cls, *, force=False):
632
+ def compute_abstract_methods(cls: type) -> ta.FrozenSet[str]:
633
+ # ~> https://github.com/python/cpython/blob/f3476c6507381ca860eec0989f53647b13517423/Modules/_abc.c#L358
634
+
635
+ # Stage 1: direct abstract methods
636
+
637
+ abstracts = {
638
+ a
639
+ # Get items as a list to avoid mutation issues during iteration
640
+ for a, v in list(cls.__dict__.items())
641
+ if is_abstract_method(v)
642
+ }
643
+
644
+ # Stage 2: inherited abstract methods
645
+
646
+ for base in cls.__bases__:
647
+ # Get __abstractmethods__ from base if it exists
648
+ if (base_abstracts := getattr(base, _ABSTRACT_METHODS_ATTR, None)) is None:
649
+ continue
650
+
651
+ # Iterate over abstract methods in base
652
+ for key in base_abstracts:
653
+ # Check if this class has an attribute with this name
654
+ try:
655
+ value = getattr(cls, key)
656
+ except AttributeError:
657
+ # Attribute not found in this class, skip
658
+ continue
659
+
660
+ # Check if it's still abstract
661
+ if is_abstract_method(value):
662
+ abstracts.add(key)
663
+
664
+ return frozenset(abstracts)
665
+
666
+
667
+ def update_abstracts(cls: ta.Type[T], *, force: bool = False) -> ta.Type[T]:
512
668
  if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
513
669
  # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
514
670
  # implementation (especially during testing), and we want to handle both cases.
515
671
  return cls
516
672
 
517
- abstracts: ta.Set[str] = set()
518
-
519
- for scls in cls.__bases__:
520
- for name in getattr(scls, _ABSTRACT_METHODS_ATTR, ()):
521
- value = getattr(cls, name, None)
522
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
523
- abstracts.add(name)
524
-
525
- for name, value in cls.__dict__.items():
526
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
527
- abstracts.add(name)
528
-
529
- setattr(cls, _ABSTRACT_METHODS_ATTR, frozenset(abstracts))
673
+ abstracts = compute_abstract_methods(cls)
674
+ setattr(cls, _ABSTRACT_METHODS_ATTR, abstracts)
530
675
  return cls
531
676
 
532
677
 
@@ -580,23 +725,26 @@ class Abstract:
580
725
  super().__init_subclass__(**kwargs)
581
726
 
582
727
  if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
583
- ams = {a: cls for a, o in cls.__dict__.items() if is_abstract_method(o)}
584
-
585
- seen = set(cls.__dict__)
586
- for b in cls.__bases__:
587
- ams.update({a: b for a in set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen}) # noqa
588
- seen.update(dir(b))
728
+ if ams := compute_abstract_methods(cls):
729
+ amd = {
730
+ a: mcls
731
+ for mcls in cls.__mro__[::-1]
732
+ for a in ams
733
+ if a in mcls.__dict__
734
+ }
589
735
 
590
- if ams:
591
736
  raise AbstractTypeError(
592
737
  f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
593
738
  ', '.join(sorted([
594
739
  '.'.join([
595
- *([m] if (m := getattr(c, '__module__')) else []),
596
- getattr(c, '__qualname__', getattr(c, '__name__')),
740
+ *([
741
+ *([m] if (m := getattr(c, '__module__')) else []),
742
+ getattr(c, '__qualname__', getattr(c, '__name__')),
743
+ ] if c is not None else '?'),
597
744
  a,
598
745
  ])
599
- for a, c in ams.items()
746
+ for a in ams
747
+ for c in [amd.get(a)]
600
748
  ])),
601
749
  )
602
750
 
@@ -613,6 +761,150 @@ class Abstract:
613
761
  update_abstracts(cls, force=True)
614
762
 
615
763
 
764
+ ########################################
765
+ # ../../../omlish/lite/asyncs.py
766
+
767
+
768
+ ##
769
+
770
+
771
+ async def opt_await(aw: ta.Optional[ta.Awaitable[T]]) -> ta.Optional[T]:
772
+ return (await aw if aw is not None else None)
773
+
774
+
775
+ async def async_list(ai: ta.AsyncIterable[T]) -> ta.List[T]:
776
+ return [v async for v in ai]
777
+
778
+
779
+ async def async_enumerate(ai: ta.AsyncIterable[T]) -> ta.AsyncIterable[ta.Tuple[int, T]]:
780
+ i = 0
781
+ async for e in ai:
782
+ yield (i, e)
783
+ i += 1
784
+
785
+
786
+ ##
787
+
788
+
789
+ def as_async(fn: ta.Callable[..., T], *, wrap: bool = False) -> ta.Callable[..., ta.Awaitable[T]]:
790
+ async def inner(*args, **kwargs):
791
+ return fn(*args, **kwargs)
792
+
793
+ return functools.wraps(fn)(inner) if wrap else inner
794
+
795
+
796
+ ##
797
+
798
+
799
+ class SyncAwaitCoroutineNotTerminatedError(Exception):
800
+ pass
801
+
802
+
803
+ def sync_await(aw: ta.Awaitable[T]) -> T:
804
+ """
805
+ Allows for the synchronous execution of async functions which will never actually *externally* await anything. These
806
+ functions are allowed to await any number of other functions - including contextmanagers and generators - so long as
807
+ nothing ever actually 'leaks' out of the function, presumably to an event loop.
808
+ """
809
+
810
+ ret = missing = object()
811
+
812
+ async def thunk():
813
+ nonlocal ret
814
+
815
+ ret = await aw
816
+
817
+ cr = thunk()
818
+ try:
819
+ try:
820
+ cr.send(None)
821
+ except StopIteration:
822
+ pass
823
+
824
+ if ret is missing or cr.cr_await is not None or cr.cr_running:
825
+ raise SyncAwaitCoroutineNotTerminatedError('Not terminated')
826
+
827
+ finally:
828
+ cr.close()
829
+
830
+ return ta.cast(T, ret)
831
+
832
+
833
+ #
834
+
835
+
836
+ def sync_aiter(ai: ta.AsyncIterator[T]) -> ta.Iterator[T]:
837
+ while True:
838
+ try:
839
+ o = sync_await(ai.__anext__())
840
+ except StopAsyncIteration:
841
+ break
842
+ yield o
843
+
844
+
845
+ def sync_async_list(ai: ta.AsyncIterable[T]) -> ta.List[T]:
846
+ """
847
+ Uses `sync_await` to synchronously read the full contents of a function call returning an async iterator, given that
848
+ the function never externally awaits anything.
849
+ """
850
+
851
+ lst: ta.Optional[ta.List[T]] = None
852
+
853
+ async def inner():
854
+ nonlocal lst
855
+
856
+ lst = [v async for v in ai]
857
+
858
+ sync_await(inner())
859
+
860
+ if not isinstance(lst, list):
861
+ raise TypeError(lst)
862
+
863
+ return lst
864
+
865
+
866
+ #
867
+
868
+
869
+ @ta.final
870
+ class SyncAwaitContextManager(ta.Generic[T]):
871
+ def __init__(self, acm: ta.AsyncContextManager[T]) -> None:
872
+ self._acm = acm
873
+
874
+ def __repr__(self) -> str:
875
+ return f'{self.__class__.__name__}({self._acm!r})'
876
+
877
+ def __enter__(self) -> T:
878
+ return sync_await(self._acm.__aenter__())
879
+
880
+ def __exit__(self, exc_type, exc_val, exc_tb):
881
+ return sync_await(self._acm.__aexit__(exc_type, exc_val, exc_tb))
882
+
883
+
884
+ sync_async_with = SyncAwaitContextManager
885
+
886
+
887
+ ##
888
+
889
+
890
+ @ta.final
891
+ class SyncToAsyncContextManager(ta.Generic[T]):
892
+ def __init__(self, cm: ta.ContextManager[T]) -> None:
893
+ self._cm = cm
894
+
895
+ def __repr__(self) -> str:
896
+ return f'{self.__class__.__name__}({self._cm!r})'
897
+
898
+ async def __aenter__(self) -> T:
899
+ return self._cm.__enter__()
900
+
901
+ async def __aexit__(self, exc_type, exc_value, traceback, /):
902
+ return self._cm.__exit__(exc_type, exc_value, traceback)
903
+
904
+
905
+ as_async_context_manager = SyncToAsyncContextManager
906
+
907
+
616
908
  ########################################
617
909
  # ../../../omlish/lite/cached.py
618
910
 
@@ -670,6 +962,62 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
670
962
  return _AsyncCachedNullary(fn)
671
963
 
672
964
 
965
+ ##
966
+
967
+
968
+ cached_property = functools.cached_property
969
+
970
+
971
+ class _cached_property: # noqa
972
+ """Backported to pick up https://github.com/python/cpython/commit/056dfc71dce15f81887f0bd6da09d6099d71f979 ."""
973
+
974
+ def __init__(self, func):
975
+ self.func = func
976
+ self.attrname = None # noqa
977
+ self.__doc__ = func.__doc__
978
+ self.__module__ = func.__module__
979
+
980
+ _NOT_FOUND = object()
981
+
982
+ def __set_name__(self, owner, name):
983
+ if self.attrname is None:
984
+ self.attrname = name # noqa
985
+ elif name != self.attrname:
986
+ raise TypeError(
987
+ f'Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r}).',
988
+ )
989
+
990
+ def __get__(self, instance, owner=None):
991
+ if instance is None:
992
+ return self
993
+ if self.attrname is None:
994
+ raise TypeError('Cannot use cached_property instance without calling __set_name__ on it.')
995
+
996
+ try:
997
+ cache = instance.__dict__
998
+ except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
999
+ raise TypeError(
1000
+ f"No '__dict__' attribute on {type(instance).__name__!r} instance to cache {self.attrname!r} property.",
1001
+ ) from None
1002
+
1003
+ val = cache.get(self.attrname, self._NOT_FOUND)
1004
+
1005
+ if val is self._NOT_FOUND:
1006
+ val = self.func(instance)
1007
+ try:
1008
+ cache[self.attrname] = val
1009
+ except TypeError:
1010
+ raise TypeError(
1011
+ f"The '__dict__' attribute on {type(instance).__name__!r} instance does not support item "
1012
+ f"assignment for caching {self.attrname!r} property.",
1013
+ ) from None
1014
+
1015
+ return val
1016
+
1017
+
1018
+ globals()['cached_property'] = _cached_property
1019
+
1020
+
673
1021
  ########################################
674
1022
  # ../../../omlish/lite/check.py
675
1023
  """
@@ -1209,7 +1557,7 @@ class ExitStacked:
1209
1557
  es.__enter__()
1210
1558
  try:
1211
1559
  self._enter_contexts()
1212
- except Exception: # noqa
1560
+ except BaseException: # noqa
1213
1561
  es.__exit__(*sys.exc_info())
1214
1562
  raise
1215
1563
  return self
@@ -1220,7 +1568,7 @@ class ExitStacked:
1220
1568
  return None
1221
1569
  try:
1222
1570
  self._exit_contexts()
1223
- except Exception: # noqa
1571
+ except BaseException: # noqa
1224
1572
  es.__exit__(*sys.exc_info())
1225
1573
  raise
1226
1574
  return es.__exit__(exc_type, exc_val, exc_tb)
@@ -1268,7 +1616,7 @@ class AsyncExitStacked:
1268
1616
  await es.__aenter__()
1269
1617
  try:
1270
1618
  await self._async_enter_contexts()
1271
- except Exception: # noqa
1619
+ except BaseException: # noqa
1272
1620
  await es.__aexit__(*sys.exc_info())
1273
1621
  raise
1274
1622
  return self
@@ -1279,7 +1627,7 @@ class AsyncExitStacked:
1279
1627
  return None
1280
1628
  try:
1281
1629
  await self._async_exit_contexts()
1282
- except Exception: # noqa
1630
+ except BaseException: # noqa
1283
1631
  await es.__aexit__(*sys.exc_info())
1284
1632
  raise
1285
1633
  return await es.__aexit__(exc_type, exc_val, exc_tb)
@@ -1366,6 +1714,17 @@ aclosing = AsyncClosingManager
1366
1714
  ##
1367
1715
 
1368
1716
 
1717
+ def dataclass_shallow_astuple(o: ta.Any) -> ta.Tuple[ta.Any, ...]:
1718
+ return tuple(getattr(o, f.name) for f in dc.fields(o))
1719
+
1720
+
1721
+ def dataclass_shallow_asdict(o: ta.Any) -> ta.Dict[str, ta.Any]:
1722
+ return {f.name: getattr(o, f.name) for f in dc.fields(o)}
1723
+
1724
+
1725
+ ##
1726
+
1727
+
1369
1728
  def is_immediate_dataclass(cls: type) -> bool:
1370
1729
  if not isinstance(cls, type):
1371
1730
  raise TypeError(cls)
@@ -1448,6 +1807,36 @@ def dataclass_repr_omit_falsey(obj: ta.Any) -> str:
1448
1807
  ##
1449
1808
 
1450
1809
 
1810
+ def dataclass_descriptor_method(*bind_attrs: str, bind_owner: bool = False) -> ta.Callable:
1811
+ if not bind_attrs:
1812
+ def __get__(self, instance, owner=None): # noqa
1813
+ return self
1814
+
1815
+ elif bind_owner:
1816
+ def __get__(self, instance, owner=None): # noqa
1817
+ # Guaranteed to return a new instance even with no attrs
1818
+ return dc.replace(self, **{
1819
+ a: v.__get__(instance, owner) if (v := getattr(self, a)) is not None else None
1820
+ for a in bind_attrs
1821
+ })
1822
+
1823
+ else:
1824
+ def __get__(self, instance, owner=None): # noqa
1825
+ if instance is None:
1826
+ return self
1827
+
1828
+ # Guaranteed to return a new instance even with no attrs
1829
+ return dc.replace(self, **{
1830
+ a: v.__get__(instance, owner) if (v := getattr(self, a)) is not None else None
1831
+ for a in bind_attrs
1832
+ })
1833
+
1834
+ return __get__
1835
+
1836
+
1837
+ ##
1838
+
1839
+
1451
1840
  def dataclass_kw_only_init():
1452
1841
  def inner(cls):
1453
1842
  if not isinstance(cls, type) and dc.is_dataclass(cls):
@@ -1511,6 +1900,20 @@ def dataclass_kw_only_init():
1511
1900
  return inner
1512
1901
 
1513
1902
 
1903
+ ##
1904
+
1905
+
1906
+ @dc.dataclass()
1907
+ class DataclassFieldRequiredError(Exception):
1908
+ name: str
1909
+
1910
+
1911
+ def dataclass_field_required(name: str) -> ta.Callable[[], ta.Any]:
1912
+ def inner() -> ta.NoReturn:
1913
+ raise DataclassFieldRequiredError(name)
1914
+ return inner
1915
+
1916
+
1514
1917
  ########################################
1515
1918
  # ../../../omlish/lite/json.py
1516
1919
 
@@ -2968,6 +3371,7 @@ TODO:
2968
3371
  - pre-run, post-run hooks
2969
3372
  - exitstack?
2970
3373
  - suggestion - difflib.get_close_matches
3374
+ - add_argument_group - group kw on ArgparseKwarg?
2971
3375
  """
2972
3376
 
2973
3377
 
@@ -2978,6 +3382,7 @@ TODO:
2978
3382
  class ArgparseArg:
2979
3383
  args: ta.Sequence[ta.Any]
2980
3384
  kwargs: ta.Mapping[str, ta.Any]
3385
+ group: ta.Optional[str] = None
2981
3386
  dest: ta.Optional[str] = None
2982
3387
 
2983
3388
  def __get__(self, instance, owner=None):
@@ -2987,7 +3392,11 @@ class ArgparseArg:
2987
3392
 
2988
3393
 
2989
3394
  def argparse_arg(*args, **kwargs) -> ArgparseArg:
2990
- return ArgparseArg(args, kwargs)
3395
+ return ArgparseArg(
3396
+ args=args,
3397
+ group=kwargs.pop('group', None),
3398
+ kwargs=kwargs,
3399
+ )
2991
3400
 
2992
3401
 
2993
3402
  def argparse_arg_(*args, **kwargs) -> ta.Any:
@@ -3157,6 +3566,10 @@ class ArgparseCli:
3157
3566
  subparser.set_defaults(_cmd=obj)
3158
3567
 
3159
3568
  elif isinstance(obj, ArgparseArg):
3569
+ if obj.group is not None:
3570
+ # FIXME: add_argument_group
3571
+ raise NotImplementedError
3572
+
3160
3573
  if att in anns:
3161
3574
  ann_kwargs = _get_argparse_arg_ann_kwargs(anns[att])
3162
3575
  obj.kwargs = {**ann_kwargs, **obj.kwargs}
@@ -3202,7 +3615,7 @@ class ArgparseCli:
3202
3615
 
3203
3616
  if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
3204
3617
  msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
3205
- if (parser := self.get_parser()).exit_on_error:
3618
+ if (parser := self.get_parser()).exit_on_error: # noqa
3206
3619
  parser.error(msg)
3207
3620
  else:
3208
3621
  raise argparse.ArgumentError(None, msg)
@@ -3222,7 +3635,10 @@ class ArgparseCli:
3222
3635
  return fn()
3223
3636
 
3224
3637
  def cli_run_and_exit(self) -> ta.NoReturn:
3225
- sys.exit(rc if isinstance(rc := self.cli_run(), int) else 0)
3638
+ rc = self.cli_run()
3639
+ if not isinstance(rc, int):
3640
+ rc = 0
3641
+ raise SystemExit(rc)
3226
3642
 
3227
3643
  def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
3228
3644
  if exit:
@@ -3254,6 +3670,72 @@ class ArgparseCli:
3254
3670
  return fn()
3255
3671
 
3256
3672
 
3673
+ ########################################
3674
+ # ../../../omlish/http/coro/io.py
3675
+
3676
+
3677
+ ##
3678
+
3679
+
3680
+ class CoroHttpIo:
3681
+ def __new__(cls, *args, **kwargs): # noqa
3682
+ raise TypeError
3683
+
3684
+ def __init_subclass__(cls, **kwargs): # noqa
3685
+ raise TypeError
3686
+
3687
+ #
3688
+
3689
+ MAX_LINE: ta.ClassVar[int] = 65536
3690
+
3691
+ #
3692
+
3693
+ class Io(Abstract):
3694
+ pass
3695
+
3696
+ #
3697
+
3698
+ class AnyLogIo(Io, Abstract):
3699
+ pass
3700
+
3701
+ #
3702
+
3703
+ @dc.dataclass(frozen=True)
3704
+ class ConnectIo(Io):
3705
+ args: ta.Tuple[ta.Any, ...]
3706
+ kwargs: ta.Optional[ta.Dict[str, ta.Any]] = None
3707
+
3708
+ server_hostname: ta.Optional[str] = None
3709
+
3710
+ #
3711
+
3712
+ class CloseIo(Io):
3713
+ pass
3714
+
3715
+ #
3716
+
3717
+ class AnyReadIo(Io): # noqa
3718
+ pass
3719
+
3720
+ @dc.dataclass(frozen=True)
3721
+ class ReadIo(AnyReadIo):
3722
+ sz: ta.Optional[int]
3723
+
3724
+ @dc.dataclass(frozen=True)
3725
+ class ReadLineIo(AnyReadIo):
3726
+ sz: int
3727
+
3728
+ @dc.dataclass(frozen=True)
3729
+ class PeekIo(AnyReadIo):
3730
+ sz: int
3731
+
3732
+ #
3733
+
3734
+ @dc.dataclass(frozen=True)
3735
+ class WriteIo(Io):
3736
+ data: bytes
3737
+
3738
+
3257
3739
  ########################################
3258
3740
  # ../../../omlish/http/parsing.py
3259
3741
  # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
@@ -4587,8 +5069,6 @@ class _JustMaybe(_Maybe[T]):
4587
5069
  __slots__ = ('_v', '_hash')
4588
5070
 
4589
5071
  def __init__(self, v: T) -> None:
4590
- super().__init__()
4591
-
4592
5072
  self._v = v
4593
5073
 
4594
5074
  @property
@@ -4646,6 +5126,13 @@ class _EmptyMaybe(_Maybe[T]):
4646
5126
  Maybe._empty = _EmptyMaybe() # noqa
4647
5127
 
4648
5128
 
5129
+ ##
5130
+
5131
+
5132
+ setattr(Maybe, 'just', _JustMaybe) # noqa
5133
+ setattr(Maybe, 'empty', functools.partial(operator.attrgetter('_empty'), Maybe))
5134
+
5135
+
4649
5136
  ########################################
4650
5137
  # ../../../omlish/lite/runtime.py
4651
5138
 
@@ -7740,6 +8227,9 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
7740
8227
  self._infos[type(info)] = info
7741
8228
  return self
7742
8229
 
8230
+ def get_infos(self) -> ta.Mapping[ta.Type[LoggingContextInfo], LoggingContextInfo]:
8231
+ return self._infos
8232
+
7743
8233
  def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
7744
8234
  return self._infos.get(ty)
7745
8235
 
@@ -7762,7 +8252,7 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
7762
8252
  _stack_offset: int
7763
8253
  _stack_info: bool
7764
8254
 
7765
- def inc_stack_offset(self, ofs: int = 1) -> 'CaptureLoggingContext':
8255
+ def inc_stack_offset(self, ofs: int = 1) -> 'CaptureLoggingContextImpl':
7766
8256
  if hasattr(self, '_stack_offset'):
7767
8257
  self._stack_offset += ofs
7768
8258
  return self
@@ -7794,10 +8284,9 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
7794
8284
 
7795
8285
 
7796
8286
  ########################################
7797
- # ../../../omlish/logs/standard.py
8287
+ # ../../../omlish/logs/std/standard.py
7798
8288
  """
7799
8289
  TODO:
7800
- - !! move to std !!
7801
8290
  - structured
7802
8291
  - prefixed
7803
8292
  - debug
@@ -7927,13 +8416,13 @@ def configure_standard_logging(
7927
8416
  ##
7928
8417
 
7929
8418
 
7930
- def error_logging(log): # noqa
8419
+ def exception_logging(log): # noqa
7931
8420
  def outer(fn):
7932
8421
  @functools.wraps(fn)
7933
8422
  def inner(*args, **kwargs):
7934
8423
  try:
7935
8424
  return fn(*args, **kwargs)
7936
- except Exception:
8425
+ except Exception as e: # noqa
7937
8426
  log.exception('Error in %r', fn)
7938
8427
  raise
7939
8428
 
@@ -7942,6 +8431,21 @@ def error_logging(log): # noqa
7942
8431
  return outer
7943
8432
 
7944
8433
 
8434
+ def async_exception_logging(alog): # noqa
8435
+ def outer(fn):
8436
+ @functools.wraps(fn)
8437
+ async def inner(*args, **kwargs):
8438
+ try:
8439
+ return await fn(*args, **kwargs)
8440
+ except Exception as e: # noqa
8441
+ await alog.exception('Error in %r', fn)
8442
+ raise
8443
+
8444
+ return inner
8445
+
8446
+ return outer
8447
+
8448
+
7945
8449
  ##
7946
8450
 
7947
8451
 
@@ -9247,48 +9751,21 @@ class CoroHttpServer:
9247
9751
 
9248
9752
  #
9249
9753
 
9250
- class Io(Abstract):
9251
- pass
9252
-
9253
- #
9254
-
9255
- class AnyLogIo(Io):
9256
- pass
9257
-
9258
9754
  @dc.dataclass(frozen=True)
9259
- class ParsedRequestLogIo(AnyLogIo):
9755
+ class ParsedRequestLogIo(CoroHttpIo.AnyLogIo):
9260
9756
  request: ParsedHttpRequest
9261
9757
 
9262
9758
  @dc.dataclass(frozen=True)
9263
- class ErrorLogIo(AnyLogIo):
9759
+ class ErrorLogIo(CoroHttpIo.AnyLogIo):
9264
9760
  error: 'CoroHttpServer.Error'
9265
9761
 
9266
9762
  #
9267
9763
 
9268
- class AnyReadIo(Io): # noqa
9269
- pass
9270
-
9271
- @dc.dataclass(frozen=True)
9272
- class ReadIo(AnyReadIo):
9273
- sz: int
9274
-
9275
- @dc.dataclass(frozen=True)
9276
- class ReadLineIo(AnyReadIo):
9277
- sz: int
9278
-
9279
- #
9280
-
9281
- @dc.dataclass(frozen=True)
9282
- class WriteIo(Io):
9283
- data: bytes
9284
-
9285
- #
9286
-
9287
9764
  @dc.dataclass(frozen=True)
9288
9765
  class CoroHandleResult:
9289
9766
  close_reason: ta.Literal['response', 'internal', None] = None
9290
9767
 
9291
- def coro_handle(self) -> ta.Generator[Io, ta.Optional[bytes], CoroHandleResult]:
9768
+ def coro_handle(self) -> ta.Generator[CoroHttpIo.Io, ta.Optional[bytes], CoroHandleResult]:
9292
9769
  return self._coro_run_handler(self._coro_handle_one())
9293
9770
 
9294
9771
  class Close(Exception): # noqa
@@ -9297,20 +9774,20 @@ class CoroHttpServer:
9297
9774
  def _coro_run_handler(
9298
9775
  self,
9299
9776
  gen: ta.Generator[
9300
- ta.Union[AnyLogIo, AnyReadIo, _Response],
9777
+ ta.Union[CoroHttpIo.AnyLogIo, CoroHttpIo.AnyReadIo, _Response],
9301
9778
  ta.Optional[bytes],
9302
9779
  None,
9303
9780
  ],
9304
- ) -> ta.Generator[Io, ta.Optional[bytes], CoroHandleResult]:
9781
+ ) -> ta.Generator[CoroHttpIo.Io, ta.Optional[bytes], CoroHandleResult]:
9305
9782
  i: ta.Optional[bytes]
9306
9783
  o: ta.Any = next(gen)
9307
9784
  while True:
9308
9785
  try:
9309
- if isinstance(o, self.AnyLogIo):
9786
+ if isinstance(o, CoroHttpIo.AnyLogIo):
9310
9787
  i = None
9311
9788
  yield o
9312
9789
 
9313
- elif isinstance(o, self.AnyReadIo):
9790
+ elif isinstance(o, CoroHttpIo.AnyReadIo):
9314
9791
  i = check.isinstance((yield o), bytes)
9315
9792
 
9316
9793
  elif isinstance(o, self._Response):
@@ -9318,10 +9795,10 @@ class CoroHttpServer:
9318
9795
 
9319
9796
  r = self._preprocess_response(o)
9320
9797
  hb = self._build_response_head_bytes(r)
9321
- check.none((yield self.WriteIo(hb)))
9798
+ check.none((yield CoroHttpIo.WriteIo(hb)))
9322
9799
 
9323
9800
  for b in self._yield_response_data(r):
9324
- yield self.WriteIo(b)
9801
+ yield CoroHttpIo.WriteIo(b)
9325
9802
 
9326
9803
  o.close()
9327
9804
  if o.close_connection:
@@ -9349,7 +9826,7 @@ class CoroHttpServer:
9349
9826
  raise
9350
9827
 
9351
9828
  def _coro_handle_one(self) -> ta.Generator[
9352
- ta.Union[AnyLogIo, AnyReadIo, _Response],
9829
+ ta.Union[CoroHttpIo.AnyLogIo, CoroHttpIo.AnyReadIo, _Response],
9353
9830
  ta.Optional[bytes],
9354
9831
  None,
9355
9832
  ]:
@@ -9359,7 +9836,7 @@ class CoroHttpServer:
9359
9836
  sz = next(gen)
9360
9837
  while True:
9361
9838
  try:
9362
- line = check.isinstance((yield self.ReadLineIo(sz)), bytes)
9839
+ line = check.isinstance((yield CoroHttpIo.ReadLineIo(sz)), bytes)
9363
9840
  sz = gen.send(line)
9364
9841
  except StopIteration as e:
9365
9842
  parsed = e.value
@@ -9398,7 +9875,7 @@ class CoroHttpServer:
9398
9875
 
9399
9876
  request_data: ta.Optional[bytes]
9400
9877
  if (cl := parsed.headers.get('Content-Length')) is not None:
9401
- request_data = check.isinstance((yield self.ReadIo(int(cl))), bytes)
9878
+ request_data = check.isinstance((yield CoroHttpIo.ReadIo(int(cl))), bytes)
9402
9879
  else:
9403
9880
  request_data = None
9404
9881
 
@@ -9485,6 +9962,11 @@ class AnyLogger(Abstract, ta.Generic[T]):
9485
9962
 
9486
9963
  ##
9487
9964
 
9965
+ # This will be 1 for [Sync]Logger and 0 for AsyncLogger - in sync loggers these methods remain present on the stack,
9966
+ # in async loggers they return a coroutine to be awaited and thus aren't actually present when said coroutine is
9967
+ # awaited.
9968
+ _level_proxy_method_stack_offset: int
9969
+
9488
9970
  @ta.overload
9489
9971
  def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
9490
9972
  ...
@@ -9499,7 +9981,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
9499
9981
 
9500
9982
  @ta.final
9501
9983
  def log(self, level: LogLevel, *args, **kwargs):
9502
- return self._log(CaptureLoggingContextImpl(level, stack_offset=1), *args, **kwargs)
9984
+ return self._log(
9985
+ CaptureLoggingContextImpl(
9986
+ level,
9987
+ stack_offset=self._level_proxy_method_stack_offset,
9988
+ ),
9989
+ *args,
9990
+ **kwargs,
9991
+ )
9503
9992
 
9504
9993
  #
9505
9994
 
@@ -9517,7 +10006,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
9517
10006
 
9518
10007
  @ta.final
9519
10008
  def debug(self, *args, **kwargs):
9520
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.DEBUG, stack_offset=1), *args, **kwargs)
10009
+ return self._log(
10010
+ CaptureLoggingContextImpl(
10011
+ NamedLogLevel.DEBUG,
10012
+ stack_offset=self._level_proxy_method_stack_offset,
10013
+ ),
10014
+ *args,
10015
+ **kwargs,
10016
+ )
9521
10017
 
9522
10018
  #
9523
10019
 
@@ -9535,7 +10031,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
9535
10031
 
9536
10032
  @ta.final
9537
10033
  def info(self, *args, **kwargs):
9538
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.INFO, stack_offset=1), *args, **kwargs)
10034
+ return self._log(
10035
+ CaptureLoggingContextImpl(
10036
+ NamedLogLevel.INFO,
10037
+ stack_offset=self._level_proxy_method_stack_offset,
10038
+ ),
10039
+ *args,
10040
+ **kwargs,
10041
+ )
9539
10042
 
9540
10043
  #
9541
10044
 
@@ -9553,7 +10056,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
9553
10056
 
9554
10057
  @ta.final
9555
10058
  def warning(self, *args, **kwargs):
9556
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.WARNING, stack_offset=1), *args, **kwargs)
10059
+ return self._log(
10060
+ CaptureLoggingContextImpl(
10061
+ NamedLogLevel.WARNING,
10062
+ stack_offset=self._level_proxy_method_stack_offset,
10063
+ ),
10064
+ *args,
10065
+ **kwargs,
10066
+ )
9557
10067
 
9558
10068
  #
9559
10069
 
@@ -9571,7 +10081,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
9571
10081
 
9572
10082
  @ta.final
9573
10083
  def error(self, *args, **kwargs):
9574
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.ERROR, stack_offset=1), *args, **kwargs)
10084
+ return self._log(
10085
+ CaptureLoggingContextImpl(
10086
+ NamedLogLevel.ERROR,
10087
+ stack_offset=self._level_proxy_method_stack_offset,
10088
+ ),
10089
+ *args,
10090
+ **kwargs,
10091
+ )
9575
10092
 
9576
10093
  #
9577
10094
 
@@ -9589,7 +10106,15 @@ class AnyLogger(Abstract, ta.Generic[T]):
9589
10106
 
9590
10107
  @ta.final
9591
10108
  def exception(self, *args, exc_info: LoggingExcInfoArg = True, **kwargs):
9592
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.ERROR, exc_info=exc_info, stack_offset=1), *args, **kwargs) # noqa
10109
+ return self._log(
10110
+ CaptureLoggingContextImpl(
10111
+ NamedLogLevel.ERROR,
10112
+ exc_info=exc_info,
10113
+ stack_offset=self._level_proxy_method_stack_offset,
10114
+ ),
10115
+ *args,
10116
+ **kwargs,
10117
+ )
9593
10118
 
9594
10119
  #
9595
10120
 
@@ -9607,24 +10132,53 @@ class AnyLogger(Abstract, ta.Generic[T]):
9607
10132
 
9608
10133
  @ta.final
9609
10134
  def critical(self, *args, **kwargs):
9610
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.CRITICAL, stack_offset=1), *args, **kwargs)
10135
+ return self._log(
10136
+ CaptureLoggingContextImpl(
10137
+ NamedLogLevel.CRITICAL,
10138
+ stack_offset=self._level_proxy_method_stack_offset,
10139
+ ),
10140
+ *args,
10141
+ **kwargs,
10142
+ )
9611
10143
 
9612
10144
  ##
9613
10145
 
9614
10146
  @abc.abstractmethod
9615
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
10147
+ def _log(
10148
+ self,
10149
+ ctx: CaptureLoggingContext,
10150
+ msg: ta.Union[str, tuple, LoggingMsgFn],
10151
+ *args: ta.Any,
10152
+ **kwargs: ta.Any,
10153
+ ) -> T:
9616
10154
  raise NotImplementedError
9617
10155
 
9618
10156
 
9619
10157
  class Logger(AnyLogger[None], Abstract):
10158
+ _level_proxy_method_stack_offset: int = 1
10159
+
9620
10160
  @abc.abstractmethod
9621
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
10161
+ def _log(
10162
+ self,
10163
+ ctx: CaptureLoggingContext,
10164
+ msg: ta.Union[str, tuple, LoggingMsgFn],
10165
+ *args: ta.Any,
10166
+ **kwargs: ta.Any,
10167
+ ) -> None:
9622
10168
  raise NotImplementedError
9623
10169
 
9624
10170
 
9625
10171
  class AsyncLogger(AnyLogger[ta.Awaitable[None]], Abstract):
10172
+ _level_proxy_method_stack_offset: int = 0
10173
+
9626
10174
  @abc.abstractmethod
9627
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> ta.Awaitable[None]: # noqa
10175
+ def _log(
10176
+ self,
10177
+ ctx: CaptureLoggingContext,
10178
+ msg: ta.Union[str, tuple, LoggingMsgFn],
10179
+ *args: ta.Any,
10180
+ **kwargs: ta.Any,
10181
+ ) -> ta.Awaitable[None]:
9628
10182
  raise NotImplementedError
9629
10183
 
9630
10184
 
@@ -9639,13 +10193,25 @@ class AnyNopLogger(AnyLogger[T], Abstract):
9639
10193
 
9640
10194
  @ta.final
9641
10195
  class NopLogger(AnyNopLogger[None], Logger):
9642
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
10196
+ def _log(
10197
+ self,
10198
+ ctx: CaptureLoggingContext,
10199
+ msg: ta.Union[str, tuple, LoggingMsgFn],
10200
+ *args: ta.Any,
10201
+ **kwargs: ta.Any,
10202
+ ) -> None:
9643
10203
  pass
9644
10204
 
9645
10205
 
9646
10206
  @ta.final
9647
10207
  class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
9648
- async def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
10208
+ async def _log(
10209
+ self,
10210
+ ctx: CaptureLoggingContext,
10211
+ msg: ta.Union[str, tuple, LoggingMsgFn],
10212
+ *args: ta.Any,
10213
+ **kwargs: ta.Any,
10214
+ ) -> None:
9649
10215
  pass
9650
10216
 
9651
10217
 
@@ -10753,6 +11319,10 @@ class VerboseCalledProcessError(subprocess.CalledProcessError):
10753
11319
  class BaseSubprocesses(Abstract):
10754
11320
  DEFAULT_LOGGER: ta.ClassVar[ta.Optional[LoggerLike]] = None
10755
11321
 
11322
+ PIPE: ta.ClassVar[int] = subprocess.PIPE
11323
+ STDOUT: ta.ClassVar[int] = subprocess.STDOUT
11324
+ DEVNULL: ta.ClassVar[int] = subprocess.DEVNULL
11325
+
10756
11326
  def __init__(
10757
11327
  self,
10758
11328
  *,
@@ -11443,7 +12013,7 @@ class CoroHttpServerSocketHandler(SocketHandler_):
11443
12013
  server_factory: CoroHttpServerFactory,
11444
12014
  *,
11445
12015
  keep_alive: bool = False,
11446
- log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
12016
+ log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpIo.AnyLogIo], None]] = None,
11447
12017
  ) -> None:
11448
12018
  super().__init__()
11449
12019
 
@@ -11472,18 +12042,18 @@ class CoroHttpServerSocketHandler(SocketHandler_):
11472
12042
 
11473
12043
  o = next(gen)
11474
12044
  while True:
11475
- if isinstance(o, CoroHttpServer.AnyLogIo):
12045
+ if isinstance(o, CoroHttpIo.AnyLogIo):
11476
12046
  i = None
11477
12047
  if self._log_handler is not None:
11478
12048
  self._log_handler(server, o)
11479
12049
 
11480
- elif isinstance(o, CoroHttpServer.ReadIo):
11481
- i = fp.r.read(o.sz)
12050
+ elif isinstance(o, CoroHttpIo.ReadIo):
12051
+ i = fp.r.read(check.not_none(o.sz))
11482
12052
 
11483
- elif isinstance(o, CoroHttpServer.ReadLineIo):
12053
+ elif isinstance(o, CoroHttpIo.ReadLineIo):
11484
12054
  i = fp.r.readline(o.sz)
11485
12055
 
11486
- elif isinstance(o, CoroHttpServer.WriteIo):
12056
+ elif isinstance(o, CoroHttpIo.WriteIo):
11487
12057
  i = None
11488
12058
  fp.w.write(o.data)
11489
12059
  fp.w.flush()
@@ -11500,6 +12070,70 @@ class CoroHttpServerSocketHandler(SocketHandler_):
11500
12070
  return check.isinstance(e.value, CoroHttpServer.CoroHandleResult)
11501
12071
 
11502
12072
 
12073
+ ########################################
12074
+ # ../../../omlish/logs/asyncs.py
12075
+
12076
+
12077
+ ##
12078
+
12079
+
12080
+ class AsyncLoggerToLogger(Logger):
12081
+ def __init__(self, u: AsyncLogger) -> None:
12082
+ super().__init__()
12083
+
12084
+ self._u = u
12085
+
12086
+ def get_effective_level(self) -> LogLevel:
12087
+ return self._u.get_effective_level()
12088
+
12089
+ def _log(
12090
+ self,
12091
+ ctx: CaptureLoggingContext,
12092
+ msg: ta.Union[str, tuple, LoggingMsgFn],
12093
+ *args: ta.Any,
12094
+ **kwargs: ta.Any,
12095
+ ) -> None:
12096
+ # Nope out early to avoid sync_await if possible - don't bother in the LoggerToAsyncLogger.
12097
+ if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
12098
+ return
12099
+
12100
+ # Note: we hardcode the stack offset of sync_await (which is 2 - sync_await + sync_await.thunk). In non-lite
12101
+ # code, lang.sync_await uses a cext if present to avoid being on the py stack, which would obviously complicate
12102
+ # this, but this is lite code so we will always have the non-c version.
12103
+ sync_await(
12104
+ self._u._log( # noqa
12105
+ check.isinstance(ctx, CaptureLoggingContextImpl).inc_stack_offset(3),
12106
+ msg,
12107
+ *args,
12108
+ **kwargs,
12109
+ ),
12110
+ )
12111
+
12112
+
12113
+ class LoggerToAsyncLogger(AsyncLogger):
12114
+ def __init__(self, u: Logger) -> None:
12115
+ super().__init__()
12116
+
12117
+ self._u = u
12118
+
12119
+ def get_effective_level(self) -> LogLevel:
12120
+ return self._u.get_effective_level()
12121
+
12122
+ async def _log(
12123
+ self,
12124
+ ctx: CaptureLoggingContext,
12125
+ msg: ta.Union[str, tuple, LoggingMsgFn],
12126
+ *args: ta.Any,
12127
+ **kwargs: ta.Any,
12128
+ ) -> None:
12129
+ return self._u._log( # noqa
12130
+ check.isinstance(ctx, CaptureLoggingContextImpl).inc_stack_offset(),
12131
+ msg,
12132
+ *args,
12133
+ **kwargs,
12134
+ )
12135
+
12136
+
11503
12137
  ########################################
11504
12138
  # ../../../omlish/logs/std/loggers.py
11505
12139
 
@@ -11523,7 +12157,12 @@ class StdLogger(Logger):
11523
12157
  def get_effective_level(self) -> LogLevel:
11524
12158
  return self._std.getEffectiveLevel()
11525
12159
 
11526
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
12160
+ def _log(
12161
+ self,
12162
+ ctx: CaptureLoggingContext,
12163
+ msg: ta.Union[str, tuple, LoggingMsgFn],
12164
+ *args: ta.Any,
12165
+ ) -> None:
11527
12166
  if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
11528
12167
  return
11529
12168
 
@@ -12274,8 +12913,23 @@ def make_simple_http_server(
12274
12913
  ##
12275
12914
 
12276
12915
 
12916
+ def _get_module_std_logger(mod_globals: ta.Mapping[str, ta.Any]) -> logging.Logger:
12917
+ return logging.getLogger(mod_globals.get('__name__'))
12918
+
12919
+
12277
12920
  def get_module_logger(mod_globals: ta.Mapping[str, ta.Any]) -> Logger:
12278
- return StdLogger(logging.getLogger(mod_globals.get('__name__'))) # noqa
12921
+ return StdLogger(_get_module_std_logger(mod_globals))
12922
+
12923
+
12924
+ def get_module_async_logger(mod_globals: ta.Mapping[str, ta.Any]) -> AsyncLogger:
12925
+ return LoggerToAsyncLogger(get_module_logger(mod_globals))
12926
+
12927
+
12928
+ def get_module_loggers(mod_globals: ta.Mapping[str, ta.Any]) -> ta.Tuple[Logger, AsyncLogger]:
12929
+ return (
12930
+ log := get_module_logger(mod_globals),
12931
+ LoggerToAsyncLogger(log),
12932
+ )
12279
12933
 
12280
12934
 
12281
12935
  ########################################
@@ -15376,7 +16030,7 @@ async def _async_main() -> ta.Optional[int]:
15376
16030
  def _main() -> None:
15377
16031
  configure_standard_logging('DEBUG')
15378
16032
 
15379
- sys.exit(rc if isinstance(rc := asyncio.run(_async_main()), int) else 0)
16033
+ raise SystemExit(rc if isinstance(rc := asyncio.run(_async_main()), int) else 0)
15380
16034
 
15381
16035
 
15382
16036
  if __name__ == '__main__':