omdev 0.0.0.dev440__py3-none-any.whl → 0.0.0.dev486__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 (132) hide show
  1. omdev/.omlish-manifests.json +17 -29
  2. omdev/__about__.py +9 -7
  3. omdev/amalg/gen/gen.py +49 -6
  4. omdev/amalg/gen/imports.py +1 -1
  5. omdev/amalg/gen/manifests.py +1 -1
  6. omdev/amalg/gen/resources.py +1 -1
  7. omdev/amalg/gen/srcfiles.py +13 -3
  8. omdev/amalg/gen/strip.py +1 -1
  9. omdev/amalg/gen/types.py +1 -1
  10. omdev/amalg/gen/typing.py +1 -1
  11. omdev/amalg/info.py +32 -0
  12. omdev/cache/data/actions.py +1 -1
  13. omdev/cache/data/specs.py +1 -1
  14. omdev/cexts/_boilerplate.cc +2 -3
  15. omdev/cexts/cmake.py +4 -1
  16. omdev/ci/cli.py +1 -2
  17. omdev/cmdlog/cli.py +1 -2
  18. omdev/dataclasses/_dumping.py +1960 -0
  19. omdev/dataclasses/_template.py +22 -0
  20. omdev/dataclasses/cli.py +6 -1
  21. omdev/dataclasses/codegen.py +340 -60
  22. omdev/dataclasses/dumping.py +200 -0
  23. omdev/interp/uv/provider.py +1 -0
  24. omdev/interp/venvs.py +1 -0
  25. omdev/irc/messages/base.py +50 -0
  26. omdev/irc/messages/formats.py +92 -0
  27. omdev/irc/messages/messages.py +775 -0
  28. omdev/irc/messages/parsing.py +99 -0
  29. omdev/irc/numerics/__init__.py +0 -0
  30. omdev/irc/numerics/formats.py +97 -0
  31. omdev/irc/numerics/numerics.py +865 -0
  32. omdev/irc/numerics/types.py +59 -0
  33. omdev/irc/protocol/LICENSE +11 -0
  34. omdev/irc/protocol/__init__.py +61 -0
  35. omdev/irc/protocol/consts.py +6 -0
  36. omdev/irc/protocol/errors.py +30 -0
  37. omdev/irc/protocol/message.py +21 -0
  38. omdev/irc/protocol/nuh.py +55 -0
  39. omdev/irc/protocol/parsing.py +158 -0
  40. omdev/irc/protocol/rendering.py +153 -0
  41. omdev/irc/protocol/tags.py +102 -0
  42. omdev/irc/protocol/utils.py +30 -0
  43. omdev/manifests/_dumping.py +125 -25
  44. omdev/markdown/__init__.py +0 -0
  45. omdev/markdown/incparse.py +116 -0
  46. omdev/markdown/tokens.py +51 -0
  47. omdev/packaging/marshal.py +8 -8
  48. omdev/packaging/requires.py +6 -6
  49. omdev/packaging/specifiers.py +2 -1
  50. omdev/packaging/versions.py +4 -4
  51. omdev/packaging/wheelfile.py +2 -0
  52. omdev/precheck/blanklines.py +66 -0
  53. omdev/precheck/caches.py +1 -1
  54. omdev/precheck/imports.py +14 -1
  55. omdev/precheck/main.py +4 -3
  56. omdev/precheck/unicode.py +39 -15
  57. omdev/py/asts/__init__.py +0 -0
  58. omdev/py/asts/parents.py +28 -0
  59. omdev/py/asts/toplevel.py +123 -0
  60. omdev/py/asts/visitors.py +18 -0
  61. omdev/py/attrdocs.py +1 -1
  62. omdev/py/bracepy.py +12 -4
  63. omdev/py/reprs.py +32 -0
  64. omdev/py/srcheaders.py +1 -1
  65. omdev/py/tokens/__init__.py +0 -0
  66. omdev/py/tools/mkrelimp.py +1 -1
  67. omdev/py/tools/pipdepup.py +629 -0
  68. omdev/pyproject/pkg.py +190 -45
  69. omdev/pyproject/reqs.py +31 -9
  70. omdev/pyproject/tools/__init__.py +0 -0
  71. omdev/pyproject/tools/aboutdeps.py +55 -0
  72. omdev/pyproject/venvs.py +8 -1
  73. omdev/rs/__init__.py +0 -0
  74. omdev/scripts/ci.py +398 -80
  75. omdev/scripts/interp.py +193 -35
  76. omdev/scripts/lib/inject.py +74 -27
  77. omdev/scripts/lib/logs.py +75 -27
  78. omdev/scripts/lib/marshal.py +67 -25
  79. omdev/scripts/pyproject.py +941 -90
  80. omdev/tools/git/cli.py +10 -0
  81. omdev/tools/json/processing.py +5 -2
  82. omdev/tools/jsonview/cli.py +31 -5
  83. omdev/tools/pawk/pawk.py +2 -2
  84. omdev/tools/pip.py +8 -0
  85. omdev/tui/__init__.py +0 -0
  86. omdev/tui/apps/__init__.py +0 -0
  87. omdev/tui/apps/edit/__init__.py +0 -0
  88. omdev/tui/apps/edit/main.py +163 -0
  89. omdev/tui/apps/irc/__init__.py +0 -0
  90. omdev/tui/apps/irc/__main__.py +4 -0
  91. omdev/tui/apps/irc/app.py +278 -0
  92. omdev/tui/apps/irc/client.py +187 -0
  93. omdev/tui/apps/irc/commands.py +175 -0
  94. omdev/tui/apps/irc/main.py +26 -0
  95. omdev/tui/apps/markdown/__init__.py +0 -0
  96. omdev/tui/apps/markdown/__main__.py +11 -0
  97. omdev/{ptk → tui/apps}/markdown/cli.py +5 -7
  98. omdev/tui/rich/__init__.py +34 -0
  99. omdev/tui/rich/console2.py +20 -0
  100. omdev/tui/rich/markdown2.py +186 -0
  101. omdev/tui/textual/__init__.py +226 -0
  102. omdev/tui/textual/app2.py +11 -0
  103. omdev/tui/textual/autocomplete/LICENSE +21 -0
  104. omdev/tui/textual/autocomplete/__init__.py +33 -0
  105. omdev/tui/textual/autocomplete/matching.py +226 -0
  106. omdev/tui/textual/autocomplete/paths.py +202 -0
  107. omdev/tui/textual/autocomplete/widget.py +612 -0
  108. omdev/tui/textual/drivers2.py +55 -0
  109. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev486.dist-info}/METADATA +11 -9
  110. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev486.dist-info}/RECORD +119 -73
  111. omdev/ptk/__init__.py +0 -103
  112. omdev/ptk/apps/ncdu.py +0 -167
  113. omdev/ptk/confirm.py +0 -60
  114. omdev/ptk/markdown/LICENSE +0 -22
  115. omdev/ptk/markdown/__init__.py +0 -10
  116. omdev/ptk/markdown/__main__.py +0 -11
  117. omdev/ptk/markdown/border.py +0 -94
  118. omdev/ptk/markdown/markdown.py +0 -390
  119. omdev/ptk/markdown/parser.py +0 -42
  120. omdev/ptk/markdown/styles.py +0 -29
  121. omdev/ptk/markdown/tags.py +0 -299
  122. omdev/ptk/markdown/utils.py +0 -366
  123. omdev/pyproject/cexts.py +0 -110
  124. /omdev/{ptk/apps → irc}/__init__.py +0 -0
  125. /omdev/{tokens → irc/messages}/__init__.py +0 -0
  126. /omdev/{tokens → py/tokens}/all.py +0 -0
  127. /omdev/{tokens → py/tokens}/tokenizert.py +0 -0
  128. /omdev/{tokens → py/tokens}/utils.py +0 -0
  129. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev486.dist-info}/WHEEL +0 -0
  130. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev486.dist-info}/entry_points.txt +0 -0
  131. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev486.dist-info}/licenses/LICENSE +0 -0
  132. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev486.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,122 @@ 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/cached.py', sha1='0c33cf961ac8f0727284303c7a30c5ea98f714f2'),
101
+ dict(path='../../omlish/lite/check.py', sha1='bb6b6b63333699b84462951a854d99ae83195b94'),
102
+ dict(path='../../omlish/lite/contextmanagers.py', sha1='993f5ed96d3410f739a20363f55670d5e5267fa3'),
103
+ dict(path='../../omlish/lite/dataclasses.py', sha1='73b7f5e5493c7ed12ff0ce36e37b596e5984cb08'),
104
+ dict(path='../../omlish/lite/json.py', sha1='57eeddc4d23a17931e00284ffa5cb6e3ce089486'),
105
+ dict(path='../../omlish/lite/objects.py', sha1='9566bbf3530fd71fcc56321485216b592fae21e9'),
106
+ dict(path='../../omlish/lite/reflect.py', sha1='c4fec44bf144e9d93293c996af06f6c65fc5e63d'),
107
+ dict(path='../../omlish/lite/strings.py', sha1='89831ecbc34ad80e118a865eceb390ed399dc4d6'),
108
+ dict(path='../../omlish/logs/levels.py', sha1='91405563d082a5eba874da82aac89d83ce7b6152'),
109
+ dict(path='../../omlish/logs/std/filters.py', sha1='f36aab646d84d31e295b33aaaaa6f8b67ff38b3d'),
110
+ dict(path='../../omlish/logs/std/proxy.py', sha1='3e7301a2aa351127f9c85f61b2f85dcc3f15aafb'),
111
+ dict(path='../../omlish/logs/warnings.py', sha1='c4eb694b24773351107fcc058f3620f1dbfb6799'),
112
+ dict(path='../../omlish/os/files.py', sha1='32f4fe2e7d32a6b368619081bd300e6f150e312b'),
113
+ dict(path='../../omlish/os/paths.py', sha1='56c40b7c2aa84d1778d60ee4cda498f8c380cc8d'),
114
+ dict(path='../../omlish/secrets/ssl.py', sha1='68af8abad22d64afa1736a8363036aa2121ada78'),
115
+ dict(path='../../omlish/sockets/addresses.py', sha1='26533e88a8073f89646c0f77f1fbe0869282ab0e'),
116
+ dict(path='../../omlish/sockets/io.py', sha1='b6b8a73ac0f29893c7128f9d4f62240efbd917bb'),
117
+ dict(path='docker/utils.py', sha1='082e2b962ca1bc8e6b3f9bbe6ecfcadff310d54f'),
118
+ dict(path='github/api/v1/api.py', sha1='1985c16447f5d245b659936571d361285374c5d9'),
119
+ dict(path='github/api/v2/api.py', sha1='08322895cd895db1cbffceb1e5bfc7b10cac50cc'),
120
+ dict(path='github/bootstrap.py', sha1='9bf24b05603cd1a82db8be8b645bbad3e0d3f22f'),
121
+ dict(path='../oci/datarefs.py', sha1='793ce5f2774e052b28d04b226a5f1eff6eec0a72'),
122
+ dict(path='../oci/pack/unpacking.py', sha1='f43dee9a2eee79cbbb90f0721ed234a2bc35daa7'),
123
+ dict(path='../../omlish/argparse/cli.py', sha1='f4dc3cd353d14386b5da0306768700e396afd2b3'),
124
+ dict(path='../../omlish/http/coro/io.py', sha1='2cdf6529c37a37cc0c1db2e02032157cf906d5d6'),
125
+ dict(path='../../omlish/http/parsing.py', sha1='3fea28dc6341908ba7c8fad42bf7bbe711f21b82'),
126
+ dict(path='../../omlish/lite/marshal.py', sha1='96348f5f2a26dc27d842d33cc3927e9da163436b'),
127
+ dict(path='../../omlish/lite/maybes.py', sha1='bdf5136654ccd14b6a072588cad228925bdfbabd'),
128
+ dict(path='../../omlish/lite/runtime.py', sha1='2e752a27ae2bf89b1bb79b4a2da522a3ec360c70'),
129
+ dict(path='../../omlish/lite/timeouts.py', sha1='a0f673033a6943f242e35848d78a41892b9c62a1'),
130
+ dict(path='../../omlish/logs/infos.py', sha1='4dd104bd468a8c438601dd0bbda619b47d2f1620'),
131
+ dict(path='../../omlish/logs/protocols.py', sha1='05ca4d1d7feb50c4e3b9f22ee371aa7bf4b3dbd1'),
132
+ dict(path='../../omlish/logs/std/json.py', sha1='2a75553131e4d5331bb0cedde42aa183f403fc3b'),
133
+ dict(path='../../omlish/os/temp.py', sha1='fba3470ac095a6c3f893156cc4437bda1b8796cb'),
134
+ dict(path='../../omlish/sockets/bind.py', sha1='d0040d74960fb00c30ae9ece5252c270b59ae3f4'),
135
+ dict(path='../../omlish/sockets/handlers.py', sha1='733a4855d563001ad09df511531d710aa2632770'),
136
+ dict(path='../../omlish/text/mangle.py', sha1='5631de37ca659e064eb1afcf2213d317b626f8bd'),
137
+ dict(path='../dataserver/targets.py', sha1='661fc3e60ad436646c8acff3c547d5b86ceb0bd6'),
138
+ dict(path='../oci/data.py', sha1='9cfc3bb8e23fb4cd746c6e8f0e02624e8f63f234'),
139
+ dict(path='../oci/repositories.py', sha1='bd0ac0fb906f679a660c87124da268370733fcc6'),
140
+ dict(path='../oci/tars.py', sha1='3ed00e97a494bd92c6a6149d22d51469bc0af384'),
141
+ dict(path='../../omlish/asyncs/asyncio/sockets.py', sha1='8d24dae988a30bb73f167a9ab62d4fc9eef4ad06'),
142
+ dict(path='../../omlish/asyncs/asyncio/timeouts.py', sha1='4d31b02b3c39b8f2fa7e94db36552fde6942e36a'),
143
+ dict(path='../../omlish/http/handlers.py', sha1='40629060bac22ea5e94b720b57001861a4ec9031'),
144
+ dict(path='../../omlish/lite/inject.py', sha1='6f097e3170019a34ff6834d36fcc9cbeed3a7ab4'),
145
+ dict(path='../../omlish/logs/contexts.py', sha1='7456964ade9ac66460e9ade4e242dbdc24b39501'),
146
+ dict(path='../../omlish/logs/standard.py', sha1='818b674f7d15012f25b79f52f6e8e7368b633038'),
147
+ dict(path='../../omlish/logs/utils.py', sha1='8430cddbb7de34afb2793ab8a0cc6fbee47fef2c'),
148
+ dict(path='../../omlish/sockets/server/handlers.py', sha1='6f9adca9fa04774a28a488a4e2a11bb4492c71d0'),
149
+ dict(path='../../omlish/subprocesses/run.py', sha1='8200e48f0c49d164df3503cd0143038d0c4d30aa'),
150
+ dict(path='../../omlish/subprocesses/wrap.py', sha1='8a9b7d2255481fae15c05f5624b0cdc0766f4b3f'),
151
+ dict(path='../dataserver/handlers.py', sha1='f624715f2500087226ec3374315cc8a1ea47a29b'),
152
+ dict(path='../dataserver/routes.py', sha1='0186bb2e84ff4d5c05af2a57c61f6fd605eba790'),
153
+ dict(path='../oci/media.py', sha1='a20324c5b0661c9a9a7679406d019ab3ba4acd98'),
154
+ dict(path='../oci/pack/packing.py', sha1='7585c3dea6b8a62b6ca63fe78968497db915ea57'),
155
+ dict(path='../../omlish/http/coro/server/server.py', sha1='c0a980afa8346dbc20570acddb2b3b579bfc1ce0'),
156
+ dict(path='../../omlish/logs/base.py', sha1='a376460b11b9dc0555fd4ead5437af62c2109a4b'),
157
+ dict(path='../../omlish/logs/std/records.py', sha1='8bbf6ef9eccb3a012c6ca416ddf3969450fd8fc9'),
158
+ dict(path='../../omlish/secrets/tempssl.py', sha1='360d4cd98483357bcf013e156dafd92fd37ed220'),
159
+ dict(path='../../omlish/sockets/server/server.py', sha1='a93a74f6beb38d69e0fb9047c932f2a95aa37eca'),
160
+ dict(path='../../omlish/sockets/server/ssl.py', sha1='790dfd208f4d267c826d491d4eb5deeda5ebdddc'),
161
+ dict(path='../../omlish/sockets/server/threading.py', sha1='0ba3c7a3d15781326610b12feef94e53903d0ce9'),
162
+ dict(path='../../omlish/subprocesses/base.py', sha1='cb9f668be5422fecb27222caabb67daac6c1bab9'),
163
+ dict(path='docker/cacheserved/manifests.py', sha1='80a65d08319d152f0bc6f893351e23368b3de93b'),
164
+ dict(path='../dataserver/server.py', sha1='e1ba8ca6f85458a64ede4ca07836aa103246132a'),
165
+ dict(path='../oci/building.py', sha1='b4fea06c03ba02d3ecfc6d10d955dc76f263846a'),
166
+ dict(path='../oci/loading.py', sha1='64d806ffad8d24087ccc29f759f672e6d795bee2'),
167
+ dict(path='../../omlish/http/coro/server/sockets.py', sha1='40ef4aa43f94f1a1a2a431a012cb961f25905ff4'),
168
+ dict(path='../../omlish/logs/std/loggers.py', sha1='daa35bdc4adea5006e442688017f0de3392579b7'),
169
+ dict(path='../../omlish/subprocesses/asyncs.py', sha1='bba44d524c24c6ac73168aee6343488414e5bf48'),
170
+ dict(path='../../omlish/subprocesses/sync.py', sha1='8434919eba4da67825773d56918fdc0cb2f1883b'),
171
+ dict(path='requirements.py', sha1='c370a65958a00412e00608a0e1f12795e276aee1'),
172
+ dict(path='../dataserver/http.py', sha1='f85ca91015bac434281326ef7885babd9d6d834c'),
173
+ dict(path='../oci/dataserver.py', sha1='dd147b56282b054cef264556a0ff3b3d1719bcee'),
174
+ dict(path='../../omlish/asyncs/asyncio/subprocesses.py', sha1='b6b5f9ae3fd0b9c83593bad2e04a08f726e5904d'),
175
+ dict(path='../../omlish/http/coro/server/simple.py', sha1='2332079fe29993123c68d7dbc266b47cd44cd6a6'),
176
+ dict(path='../../omlish/logs/modules.py', sha1='99e73cde6872fd5eda6af3dbf0fc9322bdeb641a'),
177
+ dict(path='cache.py', sha1='9353e5c3b73bed47258680fd15ac49417113f0ca'),
178
+ dict(path='compose.py', sha1='d2bec1385701979c7ff9913456b72d8c7b31f70b'),
179
+ dict(path='docker/cmds.py', sha1='5528c384f68f9003732bfaf6be302e84747909dd'),
180
+ dict(path='docker/dataserver.py', sha1='949e561ab756846425a39c05964c0fb256ae61db'),
181
+ dict(path='../../omlish/lite/timing.py', sha1='af5022f5a508939f1b433ed0514ede340fd0d672'),
182
+ dict(path='docker/cache.py', sha1='07a9e3d00bdd60f1a0a9b55aca12430fa1b4e3d9'),
183
+ dict(path='docker/repositories.py', sha1='e4bfc0e91c3bf20259895ce7c95a1eb3f6507518'),
184
+ dict(path='github/api/clients.py', sha1='8ddc0f5ccf718f1b530f4a965f0cc56b68a6a2a8'),
185
+ dict(path='github/api/v2/azure.py', sha1='19052e8e2babb8b2953ec10e6b9dcee97c21419a'),
186
+ dict(path='../oci/pack/repositories.py', sha1='e9bf6fa8bdaae2031dd0967d07a56c93a2e099b6'),
187
+ dict(path='docker/buildcaching.py', sha1='7b2633d5b8dac6aab01dd459e273cb370c5b11c8'),
188
+ dict(path='docker/cacheserved/cache.py', sha1='69732c658dba7ccf260e784132ff0c60192e3c69'),
189
+ dict(path='docker/imagepulling.py', sha1='d6b1ca1ecb9aa5c593a25e6deb78e942c75ebcb4'),
190
+ dict(path='github/api/v1/client.py', sha1='6ddd600cd8a7ff72a6a3408ded14240bafab6944'),
191
+ dict(path='github/api/v2/client.py', sha1='e28f27c07011487d5a3f4ae32fdfa1a857d02459'),
192
+ dict(path='ci.py', sha1='87b82e2bd86aa886764f1e0067251b056e359650'),
193
+ dict(path='docker/inject.py', sha1='69acac65fae413cb58c1f9aa739d2cc1c3ffa09d'),
194
+ dict(path='github/cache.py', sha1='d91f9c87d167574e94c99817e6c3a0f75925dfb9'),
195
+ dict(path='github/cli.py', sha1='6d14b0eb4ca5f606ad2821b63b9707ce57f50406'),
196
+ dict(path='github/inject.py', sha1='99c0dd7c55767e7c49f70b7edac25da67f718b2e'),
197
+ dict(path='inject.py', sha1='e86b16d79a113a4f387e68ed0db1d067bcada93a'),
198
+ dict(path='cli.py', sha1='49bcd482bd814ba436237099b4bdb62f77d6c054'),
199
+ ],
200
+ )
201
+
202
+
86
203
  ########################################
87
204
 
88
205
 
@@ -345,6 +462,8 @@ async def asyncio_wait_concurrent(
345
462
  if isinstance(concurrency, asyncio.Semaphore):
346
463
  semaphore = concurrency
347
464
  elif isinstance(concurrency, int):
465
+ if concurrency < 1:
466
+ raise ValueError(f'Concurrency must be >= 1, got {concurrency}')
348
467
  semaphore = asyncio.Semaphore(concurrency)
349
468
  else:
350
469
  raise TypeError(concurrency)
@@ -508,25 +627,49 @@ def is_abstract_method(obj: ta.Any) -> bool:
508
627
  return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
509
628
 
510
629
 
511
- def update_abstracts(cls, *, force=False):
630
+ def compute_abstract_methods(cls: type) -> ta.FrozenSet[str]:
631
+ # ~> https://github.com/python/cpython/blob/f3476c6507381ca860eec0989f53647b13517423/Modules/_abc.c#L358
632
+
633
+ # Stage 1: direct abstract methods
634
+
635
+ abstracts = {
636
+ a
637
+ # Get items as a list to avoid mutation issues during iteration
638
+ for a, v in list(cls.__dict__.items())
639
+ if is_abstract_method(v)
640
+ }
641
+
642
+ # Stage 2: inherited abstract methods
643
+
644
+ for base in cls.__bases__:
645
+ # Get __abstractmethods__ from base if it exists
646
+ if (base_abstracts := getattr(base, _ABSTRACT_METHODS_ATTR, None)) is None:
647
+ continue
648
+
649
+ # Iterate over abstract methods in base
650
+ for key in base_abstracts:
651
+ # Check if this class has an attribute with this name
652
+ try:
653
+ value = getattr(cls, key)
654
+ except AttributeError:
655
+ # Attribute not found in this class, skip
656
+ continue
657
+
658
+ # Check if it's still abstract
659
+ if is_abstract_method(value):
660
+ abstracts.add(key)
661
+
662
+ return frozenset(abstracts)
663
+
664
+
665
+ def update_abstracts(cls: ta.Type[T], *, force: bool = False) -> ta.Type[T]:
512
666
  if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
513
667
  # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
514
668
  # implementation (especially during testing), and we want to handle both cases.
515
669
  return cls
516
670
 
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))
671
+ abstracts = compute_abstract_methods(cls)
672
+ setattr(cls, _ABSTRACT_METHODS_ATTR, abstracts)
530
673
  return cls
531
674
 
532
675
 
@@ -580,23 +723,26 @@ class Abstract:
580
723
  super().__init_subclass__(**kwargs)
581
724
 
582
725
  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)}
726
+ if ams := compute_abstract_methods(cls):
727
+ amd = {
728
+ a: mcls
729
+ for mcls in cls.__mro__[::-1]
730
+ for a in ams
731
+ if a in mcls.__dict__
732
+ }
584
733
 
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))
589
-
590
- if ams:
591
734
  raise AbstractTypeError(
592
735
  f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
593
736
  ', '.join(sorted([
594
737
  '.'.join([
595
- *([m] if (m := getattr(c, '__module__')) else []),
596
- getattr(c, '__qualname__', getattr(c, '__name__')),
738
+ *([
739
+ *([m] if (m := getattr(c, '__module__')) else []),
740
+ getattr(c, '__qualname__', getattr(c, '__name__')),
741
+ ] if c is not None else '?'),
597
742
  a,
598
743
  ])
599
- for a, c in ams.items()
744
+ for a in ams
745
+ for c in [amd.get(a)]
600
746
  ])),
601
747
  )
602
748
 
@@ -670,6 +816,62 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
670
816
  return _AsyncCachedNullary(fn)
671
817
 
672
818
 
819
+ ##
820
+
821
+
822
+ cached_property = functools.cached_property
823
+
824
+
825
+ class _cached_property: # noqa
826
+ """Backported to pick up https://github.com/python/cpython/commit/056dfc71dce15f81887f0bd6da09d6099d71f979 ."""
827
+
828
+ def __init__(self, func):
829
+ self.func = func
830
+ self.attrname = None # noqa
831
+ self.__doc__ = func.__doc__
832
+ self.__module__ = func.__module__
833
+
834
+ _NOT_FOUND = object()
835
+
836
+ def __set_name__(self, owner, name):
837
+ if self.attrname is None:
838
+ self.attrname = name # noqa
839
+ elif name != self.attrname:
840
+ raise TypeError(
841
+ f'Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r}).',
842
+ )
843
+
844
+ def __get__(self, instance, owner=None):
845
+ if instance is None:
846
+ return self
847
+ if self.attrname is None:
848
+ raise TypeError('Cannot use cached_property instance without calling __set_name__ on it.')
849
+
850
+ try:
851
+ cache = instance.__dict__
852
+ except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
853
+ raise TypeError(
854
+ f"No '__dict__' attribute on {type(instance).__name__!r} instance to cache {self.attrname!r} property.",
855
+ ) from None
856
+
857
+ val = cache.get(self.attrname, self._NOT_FOUND)
858
+
859
+ if val is self._NOT_FOUND:
860
+ val = self.func(instance)
861
+ try:
862
+ cache[self.attrname] = val
863
+ except TypeError:
864
+ raise TypeError(
865
+ f"The '__dict__' attribute on {type(instance).__name__!r} instance does not support item "
866
+ f"assignment for caching {self.attrname!r} property.",
867
+ ) from None
868
+
869
+ return val
870
+
871
+
872
+ globals()['cached_property'] = _cached_property
873
+
874
+
673
875
  ########################################
674
876
  # ../../../omlish/lite/check.py
675
877
  """
@@ -1209,7 +1411,7 @@ class ExitStacked:
1209
1411
  es.__enter__()
1210
1412
  try:
1211
1413
  self._enter_contexts()
1212
- except Exception: # noqa
1414
+ except BaseException: # noqa
1213
1415
  es.__exit__(*sys.exc_info())
1214
1416
  raise
1215
1417
  return self
@@ -1220,7 +1422,7 @@ class ExitStacked:
1220
1422
  return None
1221
1423
  try:
1222
1424
  self._exit_contexts()
1223
- except Exception: # noqa
1425
+ except BaseException: # noqa
1224
1426
  es.__exit__(*sys.exc_info())
1225
1427
  raise
1226
1428
  return es.__exit__(exc_type, exc_val, exc_tb)
@@ -1268,7 +1470,7 @@ class AsyncExitStacked:
1268
1470
  await es.__aenter__()
1269
1471
  try:
1270
1472
  await self._async_enter_contexts()
1271
- except Exception: # noqa
1473
+ except BaseException: # noqa
1272
1474
  await es.__aexit__(*sys.exc_info())
1273
1475
  raise
1274
1476
  return self
@@ -1279,7 +1481,7 @@ class AsyncExitStacked:
1279
1481
  return None
1280
1482
  try:
1281
1483
  await self._async_exit_contexts()
1282
- except Exception: # noqa
1484
+ except BaseException: # noqa
1283
1485
  await es.__aexit__(*sys.exc_info())
1284
1486
  raise
1285
1487
  return await es.__aexit__(exc_type, exc_val, exc_tb)
@@ -1366,6 +1568,17 @@ aclosing = AsyncClosingManager
1366
1568
  ##
1367
1569
 
1368
1570
 
1571
+ def dataclass_shallow_astuple(o: ta.Any) -> ta.Tuple[ta.Any, ...]:
1572
+ return tuple(getattr(o, f.name) for f in dc.fields(o))
1573
+
1574
+
1575
+ def dataclass_shallow_asdict(o: ta.Any) -> ta.Dict[str, ta.Any]:
1576
+ return {f.name: getattr(o, f.name) for f in dc.fields(o)}
1577
+
1578
+
1579
+ ##
1580
+
1581
+
1369
1582
  def is_immediate_dataclass(cls: type) -> bool:
1370
1583
  if not isinstance(cls, type):
1371
1584
  raise TypeError(cls)
@@ -1448,6 +1661,36 @@ def dataclass_repr_omit_falsey(obj: ta.Any) -> str:
1448
1661
  ##
1449
1662
 
1450
1663
 
1664
+ def dataclass_descriptor_method(*bind_attrs: str, bind_owner: bool = False) -> ta.Callable:
1665
+ if not bind_attrs:
1666
+ def __get__(self, instance, owner=None): # noqa
1667
+ return self
1668
+
1669
+ elif bind_owner:
1670
+ def __get__(self, instance, owner=None): # noqa
1671
+ # Guaranteed to return a new instance even with no attrs
1672
+ return dc.replace(self, **{
1673
+ a: v.__get__(instance, owner) if (v := getattr(self, a)) is not None else None
1674
+ for a in bind_attrs
1675
+ })
1676
+
1677
+ else:
1678
+ def __get__(self, instance, owner=None): # noqa
1679
+ if instance is None:
1680
+ return self
1681
+
1682
+ # Guaranteed to return a new instance even with no attrs
1683
+ return dc.replace(self, **{
1684
+ a: v.__get__(instance, owner) if (v := getattr(self, a)) is not None else None
1685
+ for a in bind_attrs
1686
+ })
1687
+
1688
+ return __get__
1689
+
1690
+
1691
+ ##
1692
+
1693
+
1451
1694
  def dataclass_kw_only_init():
1452
1695
  def inner(cls):
1453
1696
  if not isinstance(cls, type) and dc.is_dataclass(cls):
@@ -1511,6 +1754,20 @@ def dataclass_kw_only_init():
1511
1754
  return inner
1512
1755
 
1513
1756
 
1757
+ ##
1758
+
1759
+
1760
+ @dc.dataclass()
1761
+ class DataclassFieldRequiredError(Exception):
1762
+ name: str
1763
+
1764
+
1765
+ def dataclass_field_required(name: str) -> ta.Callable[[], ta.Any]:
1766
+ def inner() -> ta.NoReturn:
1767
+ raise DataclassFieldRequiredError(name)
1768
+ return inner
1769
+
1770
+
1514
1771
  ########################################
1515
1772
  # ../../../omlish/lite/json.py
1516
1773
 
@@ -2968,6 +3225,7 @@ TODO:
2968
3225
  - pre-run, post-run hooks
2969
3226
  - exitstack?
2970
3227
  - suggestion - difflib.get_close_matches
3228
+ - add_argument_group - group kw on ArgparseKwarg?
2971
3229
  """
2972
3230
 
2973
3231
 
@@ -2978,6 +3236,7 @@ TODO:
2978
3236
  class ArgparseArg:
2979
3237
  args: ta.Sequence[ta.Any]
2980
3238
  kwargs: ta.Mapping[str, ta.Any]
3239
+ group: ta.Optional[str] = None
2981
3240
  dest: ta.Optional[str] = None
2982
3241
 
2983
3242
  def __get__(self, instance, owner=None):
@@ -2987,7 +3246,11 @@ class ArgparseArg:
2987
3246
 
2988
3247
 
2989
3248
  def argparse_arg(*args, **kwargs) -> ArgparseArg:
2990
- return ArgparseArg(args, kwargs)
3249
+ return ArgparseArg(
3250
+ args=args,
3251
+ group=kwargs.pop('group', None),
3252
+ kwargs=kwargs,
3253
+ )
2991
3254
 
2992
3255
 
2993
3256
  def argparse_arg_(*args, **kwargs) -> ta.Any:
@@ -3157,6 +3420,10 @@ class ArgparseCli:
3157
3420
  subparser.set_defaults(_cmd=obj)
3158
3421
 
3159
3422
  elif isinstance(obj, ArgparseArg):
3423
+ if obj.group is not None:
3424
+ # FIXME: add_argument_group
3425
+ raise NotImplementedError
3426
+
3160
3427
  if att in anns:
3161
3428
  ann_kwargs = _get_argparse_arg_ann_kwargs(anns[att])
3162
3429
  obj.kwargs = {**ann_kwargs, **obj.kwargs}
@@ -3202,7 +3469,7 @@ class ArgparseCli:
3202
3469
 
3203
3470
  if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
3204
3471
  msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
3205
- if (parser := self.get_parser()).exit_on_error:
3472
+ if (parser := self.get_parser()).exit_on_error: # noqa
3206
3473
  parser.error(msg)
3207
3474
  else:
3208
3475
  raise argparse.ArgumentError(None, msg)
@@ -3222,7 +3489,10 @@ class ArgparseCli:
3222
3489
  return fn()
3223
3490
 
3224
3491
  def cli_run_and_exit(self) -> ta.NoReturn:
3225
- sys.exit(rc if isinstance(rc := self.cli_run(), int) else 0)
3492
+ rc = self.cli_run()
3493
+ if not isinstance(rc, int):
3494
+ rc = 0
3495
+ raise SystemExit(rc)
3226
3496
 
3227
3497
  def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
3228
3498
  if exit:
@@ -3254,6 +3524,72 @@ class ArgparseCli:
3254
3524
  return fn()
3255
3525
 
3256
3526
 
3527
+ ########################################
3528
+ # ../../../omlish/http/coro/io.py
3529
+
3530
+
3531
+ ##
3532
+
3533
+
3534
+ class CoroHttpIo:
3535
+ def __new__(cls, *args, **kwargs): # noqa
3536
+ raise TypeError
3537
+
3538
+ def __init_subclass__(cls, **kwargs): # noqa
3539
+ raise TypeError
3540
+
3541
+ #
3542
+
3543
+ MAX_LINE: ta.ClassVar[int] = 65536
3544
+
3545
+ #
3546
+
3547
+ class Io(Abstract):
3548
+ pass
3549
+
3550
+ #
3551
+
3552
+ class AnyLogIo(Io, Abstract):
3553
+ pass
3554
+
3555
+ #
3556
+
3557
+ @dc.dataclass(frozen=True)
3558
+ class ConnectIo(Io):
3559
+ args: ta.Tuple[ta.Any, ...]
3560
+ kwargs: ta.Optional[ta.Dict[str, ta.Any]] = None
3561
+
3562
+ server_hostname: ta.Optional[str] = None
3563
+
3564
+ #
3565
+
3566
+ class CloseIo(Io):
3567
+ pass
3568
+
3569
+ #
3570
+
3571
+ class AnyReadIo(Io): # noqa
3572
+ pass
3573
+
3574
+ @dc.dataclass(frozen=True)
3575
+ class ReadIo(AnyReadIo):
3576
+ sz: ta.Optional[int]
3577
+
3578
+ @dc.dataclass(frozen=True)
3579
+ class ReadLineIo(AnyReadIo):
3580
+ sz: int
3581
+
3582
+ @dc.dataclass(frozen=True)
3583
+ class PeekIo(AnyReadIo):
3584
+ sz: int
3585
+
3586
+ #
3587
+
3588
+ @dc.dataclass(frozen=True)
3589
+ class WriteIo(Io):
3590
+ data: bytes
3591
+
3592
+
3257
3593
  ########################################
3258
3594
  # ../../../omlish/http/parsing.py
3259
3595
  # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
@@ -4587,8 +4923,6 @@ class _JustMaybe(_Maybe[T]):
4587
4923
  __slots__ = ('_v', '_hash')
4588
4924
 
4589
4925
  def __init__(self, v: T) -> None:
4590
- super().__init__()
4591
-
4592
4926
  self._v = v
4593
4927
 
4594
4928
  @property
@@ -4646,6 +4980,13 @@ class _EmptyMaybe(_Maybe[T]):
4646
4980
  Maybe._empty = _EmptyMaybe() # noqa
4647
4981
 
4648
4982
 
4983
+ ##
4984
+
4985
+
4986
+ setattr(Maybe, 'just', _JustMaybe) # noqa
4987
+ setattr(Maybe, 'empty', functools.partial(operator.attrgetter('_empty'), Maybe))
4988
+
4989
+
4649
4990
  ########################################
4650
4991
  # ../../../omlish/lite/runtime.py
4651
4992
 
@@ -7927,7 +8268,7 @@ def configure_standard_logging(
7927
8268
  ##
7928
8269
 
7929
8270
 
7930
- def error_logging(log): # noqa
8271
+ def exception_logging(log): # noqa
7931
8272
  def outer(fn):
7932
8273
  @functools.wraps(fn)
7933
8274
  def inner(*args, **kwargs):
@@ -9247,48 +9588,21 @@ class CoroHttpServer:
9247
9588
 
9248
9589
  #
9249
9590
 
9250
- class Io(Abstract):
9251
- pass
9252
-
9253
- #
9254
-
9255
- class AnyLogIo(Io):
9256
- pass
9257
-
9258
9591
  @dc.dataclass(frozen=True)
9259
- class ParsedRequestLogIo(AnyLogIo):
9592
+ class ParsedRequestLogIo(CoroHttpIo.AnyLogIo):
9260
9593
  request: ParsedHttpRequest
9261
9594
 
9262
9595
  @dc.dataclass(frozen=True)
9263
- class ErrorLogIo(AnyLogIo):
9596
+ class ErrorLogIo(CoroHttpIo.AnyLogIo):
9264
9597
  error: 'CoroHttpServer.Error'
9265
9598
 
9266
9599
  #
9267
9600
 
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
9601
  @dc.dataclass(frozen=True)
9288
9602
  class CoroHandleResult:
9289
9603
  close_reason: ta.Literal['response', 'internal', None] = None
9290
9604
 
9291
- def coro_handle(self) -> ta.Generator[Io, ta.Optional[bytes], CoroHandleResult]:
9605
+ def coro_handle(self) -> ta.Generator[CoroHttpIo.Io, ta.Optional[bytes], CoroHandleResult]:
9292
9606
  return self._coro_run_handler(self._coro_handle_one())
9293
9607
 
9294
9608
  class Close(Exception): # noqa
@@ -9297,20 +9611,20 @@ class CoroHttpServer:
9297
9611
  def _coro_run_handler(
9298
9612
  self,
9299
9613
  gen: ta.Generator[
9300
- ta.Union[AnyLogIo, AnyReadIo, _Response],
9614
+ ta.Union[CoroHttpIo.AnyLogIo, CoroHttpIo.AnyReadIo, _Response],
9301
9615
  ta.Optional[bytes],
9302
9616
  None,
9303
9617
  ],
9304
- ) -> ta.Generator[Io, ta.Optional[bytes], CoroHandleResult]:
9618
+ ) -> ta.Generator[CoroHttpIo.Io, ta.Optional[bytes], CoroHandleResult]:
9305
9619
  i: ta.Optional[bytes]
9306
9620
  o: ta.Any = next(gen)
9307
9621
  while True:
9308
9622
  try:
9309
- if isinstance(o, self.AnyLogIo):
9623
+ if isinstance(o, CoroHttpIo.AnyLogIo):
9310
9624
  i = None
9311
9625
  yield o
9312
9626
 
9313
- elif isinstance(o, self.AnyReadIo):
9627
+ elif isinstance(o, CoroHttpIo.AnyReadIo):
9314
9628
  i = check.isinstance((yield o), bytes)
9315
9629
 
9316
9630
  elif isinstance(o, self._Response):
@@ -9318,10 +9632,10 @@ class CoroHttpServer:
9318
9632
 
9319
9633
  r = self._preprocess_response(o)
9320
9634
  hb = self._build_response_head_bytes(r)
9321
- check.none((yield self.WriteIo(hb)))
9635
+ check.none((yield CoroHttpIo.WriteIo(hb)))
9322
9636
 
9323
9637
  for b in self._yield_response_data(r):
9324
- yield self.WriteIo(b)
9638
+ yield CoroHttpIo.WriteIo(b)
9325
9639
 
9326
9640
  o.close()
9327
9641
  if o.close_connection:
@@ -9349,7 +9663,7 @@ class CoroHttpServer:
9349
9663
  raise
9350
9664
 
9351
9665
  def _coro_handle_one(self) -> ta.Generator[
9352
- ta.Union[AnyLogIo, AnyReadIo, _Response],
9666
+ ta.Union[CoroHttpIo.AnyLogIo, CoroHttpIo.AnyReadIo, _Response],
9353
9667
  ta.Optional[bytes],
9354
9668
  None,
9355
9669
  ]:
@@ -9359,7 +9673,7 @@ class CoroHttpServer:
9359
9673
  sz = next(gen)
9360
9674
  while True:
9361
9675
  try:
9362
- line = check.isinstance((yield self.ReadLineIo(sz)), bytes)
9676
+ line = check.isinstance((yield CoroHttpIo.ReadLineIo(sz)), bytes)
9363
9677
  sz = gen.send(line)
9364
9678
  except StopIteration as e:
9365
9679
  parsed = e.value
@@ -9398,7 +9712,7 @@ class CoroHttpServer:
9398
9712
 
9399
9713
  request_data: ta.Optional[bytes]
9400
9714
  if (cl := parsed.headers.get('Content-Length')) is not None:
9401
- request_data = check.isinstance((yield self.ReadIo(int(cl))), bytes)
9715
+ request_data = check.isinstance((yield CoroHttpIo.ReadIo(int(cl))), bytes)
9402
9716
  else:
9403
9717
  request_data = None
9404
9718
 
@@ -10753,6 +11067,10 @@ class VerboseCalledProcessError(subprocess.CalledProcessError):
10753
11067
  class BaseSubprocesses(Abstract):
10754
11068
  DEFAULT_LOGGER: ta.ClassVar[ta.Optional[LoggerLike]] = None
10755
11069
 
11070
+ PIPE: ta.ClassVar[int] = subprocess.PIPE
11071
+ STDOUT: ta.ClassVar[int] = subprocess.STDOUT
11072
+ DEVNULL: ta.ClassVar[int] = subprocess.DEVNULL
11073
+
10756
11074
  def __init__(
10757
11075
  self,
10758
11076
  *,
@@ -11443,7 +11761,7 @@ class CoroHttpServerSocketHandler(SocketHandler_):
11443
11761
  server_factory: CoroHttpServerFactory,
11444
11762
  *,
11445
11763
  keep_alive: bool = False,
11446
- log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
11764
+ log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpIo.AnyLogIo], None]] = None,
11447
11765
  ) -> None:
11448
11766
  super().__init__()
11449
11767
 
@@ -11472,18 +11790,18 @@ class CoroHttpServerSocketHandler(SocketHandler_):
11472
11790
 
11473
11791
  o = next(gen)
11474
11792
  while True:
11475
- if isinstance(o, CoroHttpServer.AnyLogIo):
11793
+ if isinstance(o, CoroHttpIo.AnyLogIo):
11476
11794
  i = None
11477
11795
  if self._log_handler is not None:
11478
11796
  self._log_handler(server, o)
11479
11797
 
11480
- elif isinstance(o, CoroHttpServer.ReadIo):
11481
- i = fp.r.read(o.sz)
11798
+ elif isinstance(o, CoroHttpIo.ReadIo):
11799
+ i = fp.r.read(check.not_none(o.sz))
11482
11800
 
11483
- elif isinstance(o, CoroHttpServer.ReadLineIo):
11801
+ elif isinstance(o, CoroHttpIo.ReadLineIo):
11484
11802
  i = fp.r.readline(o.sz)
11485
11803
 
11486
- elif isinstance(o, CoroHttpServer.WriteIo):
11804
+ elif isinstance(o, CoroHttpIo.WriteIo):
11487
11805
  i = None
11488
11806
  fp.w.write(o.data)
11489
11807
  fp.w.flush()
@@ -15376,7 +15694,7 @@ async def _async_main() -> ta.Optional[int]:
15376
15694
  def _main() -> None:
15377
15695
  configure_standard_logging('DEBUG')
15378
15696
 
15379
- sys.exit(rc if isinstance(rc := asyncio.run(_async_main()), int) else 0)
15697
+ raise SystemExit(rc if isinstance(rc := asyncio.run(_async_main()), int) else 0)
15380
15698
 
15381
15699
 
15382
15700
  if __name__ == '__main__':