omdev 0.0.0.dev439__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 (134) hide show
  1. omdev/.omlish-manifests.json +18 -30
  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/ci/github/api/v2/api.py +2 -0
  18. omdev/cmdlog/cli.py +1 -2
  19. omdev/dataclasses/_dumping.py +1960 -0
  20. omdev/dataclasses/_template.py +22 -0
  21. omdev/dataclasses/cli.py +6 -1
  22. omdev/dataclasses/codegen.py +340 -60
  23. omdev/dataclasses/dumping.py +200 -0
  24. omdev/interp/uv/provider.py +1 -0
  25. omdev/interp/venvs.py +1 -0
  26. omdev/irc/messages/base.py +50 -0
  27. omdev/irc/messages/formats.py +92 -0
  28. omdev/irc/messages/messages.py +775 -0
  29. omdev/irc/messages/parsing.py +99 -0
  30. omdev/irc/numerics/__init__.py +0 -0
  31. omdev/irc/numerics/formats.py +97 -0
  32. omdev/irc/numerics/numerics.py +865 -0
  33. omdev/irc/numerics/types.py +59 -0
  34. omdev/irc/protocol/LICENSE +11 -0
  35. omdev/irc/protocol/__init__.py +61 -0
  36. omdev/irc/protocol/consts.py +6 -0
  37. omdev/irc/protocol/errors.py +30 -0
  38. omdev/irc/protocol/message.py +21 -0
  39. omdev/irc/protocol/nuh.py +55 -0
  40. omdev/irc/protocol/parsing.py +158 -0
  41. omdev/irc/protocol/rendering.py +153 -0
  42. omdev/irc/protocol/tags.py +102 -0
  43. omdev/irc/protocol/utils.py +30 -0
  44. omdev/manifests/_dumping.py +125 -25
  45. omdev/markdown/__init__.py +0 -0
  46. omdev/markdown/incparse.py +116 -0
  47. omdev/markdown/tokens.py +51 -0
  48. omdev/packaging/marshal.py +8 -8
  49. omdev/packaging/requires.py +6 -6
  50. omdev/packaging/specifiers.py +2 -1
  51. omdev/packaging/versions.py +4 -4
  52. omdev/packaging/wheelfile.py +2 -0
  53. omdev/precheck/blanklines.py +66 -0
  54. omdev/precheck/caches.py +1 -1
  55. omdev/precheck/imports.py +14 -1
  56. omdev/precheck/main.py +4 -3
  57. omdev/precheck/unicode.py +39 -15
  58. omdev/py/asts/__init__.py +0 -0
  59. omdev/py/asts/parents.py +28 -0
  60. omdev/py/asts/toplevel.py +123 -0
  61. omdev/py/asts/visitors.py +18 -0
  62. omdev/py/attrdocs.py +6 -7
  63. omdev/py/bracepy.py +12 -4
  64. omdev/py/reprs.py +32 -0
  65. omdev/py/srcheaders.py +1 -1
  66. omdev/py/tokens/__init__.py +0 -0
  67. omdev/py/tools/mkrelimp.py +1 -1
  68. omdev/py/tools/pipdepup.py +629 -0
  69. omdev/pyproject/pkg.py +190 -45
  70. omdev/pyproject/reqs.py +31 -9
  71. omdev/pyproject/tools/__init__.py +0 -0
  72. omdev/pyproject/tools/aboutdeps.py +55 -0
  73. omdev/pyproject/venvs.py +8 -1
  74. omdev/rs/__init__.py +0 -0
  75. omdev/scripts/ci.py +400 -80
  76. omdev/scripts/interp.py +193 -35
  77. omdev/scripts/lib/__init__.py +0 -0
  78. omdev/scripts/{inject.py → lib/inject.py} +75 -28
  79. omdev/scripts/lib/logs.py +2079 -0
  80. omdev/scripts/{marshal.py → lib/marshal.py} +68 -26
  81. omdev/scripts/pyproject.py +941 -90
  82. omdev/tools/git/cli.py +12 -1
  83. omdev/tools/json/processing.py +5 -2
  84. omdev/tools/jsonview/cli.py +31 -5
  85. omdev/tools/pawk/pawk.py +2 -2
  86. omdev/tools/pip.py +8 -0
  87. omdev/tui/__init__.py +0 -0
  88. omdev/tui/apps/__init__.py +0 -0
  89. omdev/tui/apps/edit/__init__.py +0 -0
  90. omdev/tui/apps/edit/main.py +163 -0
  91. omdev/tui/apps/irc/__init__.py +0 -0
  92. omdev/tui/apps/irc/__main__.py +4 -0
  93. omdev/tui/apps/irc/app.py +278 -0
  94. omdev/tui/apps/irc/client.py +187 -0
  95. omdev/tui/apps/irc/commands.py +175 -0
  96. omdev/tui/apps/irc/main.py +26 -0
  97. omdev/tui/apps/markdown/__init__.py +0 -0
  98. omdev/tui/apps/markdown/__main__.py +11 -0
  99. omdev/{ptk → tui/apps}/markdown/cli.py +5 -7
  100. omdev/tui/rich/__init__.py +34 -0
  101. omdev/tui/rich/console2.py +20 -0
  102. omdev/tui/rich/markdown2.py +186 -0
  103. omdev/tui/textual/__init__.py +226 -0
  104. omdev/tui/textual/app2.py +11 -0
  105. omdev/tui/textual/autocomplete/LICENSE +21 -0
  106. omdev/tui/textual/autocomplete/__init__.py +33 -0
  107. omdev/tui/textual/autocomplete/matching.py +226 -0
  108. omdev/tui/textual/autocomplete/paths.py +202 -0
  109. omdev/tui/textual/autocomplete/widget.py +612 -0
  110. omdev/tui/textual/drivers2.py +55 -0
  111. {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/METADATA +11 -9
  112. {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/RECORD +121 -73
  113. omdev/ptk/__init__.py +0 -103
  114. omdev/ptk/apps/ncdu.py +0 -167
  115. omdev/ptk/confirm.py +0 -60
  116. omdev/ptk/markdown/LICENSE +0 -22
  117. omdev/ptk/markdown/__init__.py +0 -10
  118. omdev/ptk/markdown/__main__.py +0 -11
  119. omdev/ptk/markdown/border.py +0 -94
  120. omdev/ptk/markdown/markdown.py +0 -390
  121. omdev/ptk/markdown/parser.py +0 -42
  122. omdev/ptk/markdown/styles.py +0 -29
  123. omdev/ptk/markdown/tags.py +0 -299
  124. omdev/ptk/markdown/utils.py +0 -366
  125. omdev/pyproject/cexts.py +0 -110
  126. /omdev/{ptk/apps → irc}/__init__.py +0 -0
  127. /omdev/{tokens → irc/messages}/__init__.py +0 -0
  128. /omdev/{tokens → py/tokens}/all.py +0 -0
  129. /omdev/{tokens → py/tokens}/tokenizert.py +0 -0
  130. /omdev/{tokens → py/tokens}/utils.py +0 -0
  131. {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/WHEEL +0 -0
  132. {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/entry_points.txt +0 -0
  133. {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/licenses/LICENSE +0 -0
  134. {omdev-0.0.0.dev439.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)}
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))
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
+ }
589
733
 
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
 
@@ -2547,6 +2804,7 @@ class GithubCacheServiceV2:
2547
2804
  class CreateCacheEntryResponse:
2548
2805
  ok: bool
2549
2806
  signed_upload_url: str
2807
+ message: ta.Optional[str] = None
2550
2808
 
2551
2809
  CREATE_CACHE_ENTRY_METHOD: Method[
2552
2810
  CreateCacheEntryRequest,
@@ -2570,6 +2828,7 @@ class GithubCacheServiceV2:
2570
2828
  class FinalizeCacheEntryUploadResponse:
2571
2829
  ok: bool
2572
2830
  entry_id: str
2831
+ message: ta.Optional[str] = None
2573
2832
 
2574
2833
  FINALIZE_CACHE_ENTRY_METHOD: Method[
2575
2834
  FinalizeCacheEntryUploadRequest,
@@ -2966,6 +3225,7 @@ TODO:
2966
3225
  - pre-run, post-run hooks
2967
3226
  - exitstack?
2968
3227
  - suggestion - difflib.get_close_matches
3228
+ - add_argument_group - group kw on ArgparseKwarg?
2969
3229
  """
2970
3230
 
2971
3231
 
@@ -2976,6 +3236,7 @@ TODO:
2976
3236
  class ArgparseArg:
2977
3237
  args: ta.Sequence[ta.Any]
2978
3238
  kwargs: ta.Mapping[str, ta.Any]
3239
+ group: ta.Optional[str] = None
2979
3240
  dest: ta.Optional[str] = None
2980
3241
 
2981
3242
  def __get__(self, instance, owner=None):
@@ -2985,7 +3246,11 @@ class ArgparseArg:
2985
3246
 
2986
3247
 
2987
3248
  def argparse_arg(*args, **kwargs) -> ArgparseArg:
2988
- return ArgparseArg(args, kwargs)
3249
+ return ArgparseArg(
3250
+ args=args,
3251
+ group=kwargs.pop('group', None),
3252
+ kwargs=kwargs,
3253
+ )
2989
3254
 
2990
3255
 
2991
3256
  def argparse_arg_(*args, **kwargs) -> ta.Any:
@@ -3155,6 +3420,10 @@ class ArgparseCli:
3155
3420
  subparser.set_defaults(_cmd=obj)
3156
3421
 
3157
3422
  elif isinstance(obj, ArgparseArg):
3423
+ if obj.group is not None:
3424
+ # FIXME: add_argument_group
3425
+ raise NotImplementedError
3426
+
3158
3427
  if att in anns:
3159
3428
  ann_kwargs = _get_argparse_arg_ann_kwargs(anns[att])
3160
3429
  obj.kwargs = {**ann_kwargs, **obj.kwargs}
@@ -3200,7 +3469,7 @@ class ArgparseCli:
3200
3469
 
3201
3470
  if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
3202
3471
  msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
3203
- if (parser := self.get_parser()).exit_on_error:
3472
+ if (parser := self.get_parser()).exit_on_error: # noqa
3204
3473
  parser.error(msg)
3205
3474
  else:
3206
3475
  raise argparse.ArgumentError(None, msg)
@@ -3220,7 +3489,10 @@ class ArgparseCli:
3220
3489
  return fn()
3221
3490
 
3222
3491
  def cli_run_and_exit(self) -> ta.NoReturn:
3223
- 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)
3224
3496
 
3225
3497
  def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
3226
3498
  if exit:
@@ -3252,6 +3524,72 @@ class ArgparseCli:
3252
3524
  return fn()
3253
3525
 
3254
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
+
3255
3593
  ########################################
3256
3594
  # ../../../omlish/http/parsing.py
3257
3595
  # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
@@ -4585,8 +4923,6 @@ class _JustMaybe(_Maybe[T]):
4585
4923
  __slots__ = ('_v', '_hash')
4586
4924
 
4587
4925
  def __init__(self, v: T) -> None:
4588
- super().__init__()
4589
-
4590
4926
  self._v = v
4591
4927
 
4592
4928
  @property
@@ -4644,6 +4980,13 @@ class _EmptyMaybe(_Maybe[T]):
4644
4980
  Maybe._empty = _EmptyMaybe() # noqa
4645
4981
 
4646
4982
 
4983
+ ##
4984
+
4985
+
4986
+ setattr(Maybe, 'just', _JustMaybe) # noqa
4987
+ setattr(Maybe, 'empty', functools.partial(operator.attrgetter('_empty'), Maybe))
4988
+
4989
+
4647
4990
  ########################################
4648
4991
  # ../../../omlish/lite/runtime.py
4649
4992
 
@@ -7925,7 +8268,7 @@ def configure_standard_logging(
7925
8268
  ##
7926
8269
 
7927
8270
 
7928
- def error_logging(log): # noqa
8271
+ def exception_logging(log): # noqa
7929
8272
  def outer(fn):
7930
8273
  @functools.wraps(fn)
7931
8274
  def inner(*args, **kwargs):
@@ -9245,48 +9588,21 @@ class CoroHttpServer:
9245
9588
 
9246
9589
  #
9247
9590
 
9248
- class Io(Abstract):
9249
- pass
9250
-
9251
- #
9252
-
9253
- class AnyLogIo(Io):
9254
- pass
9255
-
9256
9591
  @dc.dataclass(frozen=True)
9257
- class ParsedRequestLogIo(AnyLogIo):
9592
+ class ParsedRequestLogIo(CoroHttpIo.AnyLogIo):
9258
9593
  request: ParsedHttpRequest
9259
9594
 
9260
9595
  @dc.dataclass(frozen=True)
9261
- class ErrorLogIo(AnyLogIo):
9596
+ class ErrorLogIo(CoroHttpIo.AnyLogIo):
9262
9597
  error: 'CoroHttpServer.Error'
9263
9598
 
9264
9599
  #
9265
9600
 
9266
- class AnyReadIo(Io): # noqa
9267
- pass
9268
-
9269
- @dc.dataclass(frozen=True)
9270
- class ReadIo(AnyReadIo):
9271
- sz: int
9272
-
9273
- @dc.dataclass(frozen=True)
9274
- class ReadLineIo(AnyReadIo):
9275
- sz: int
9276
-
9277
- #
9278
-
9279
- @dc.dataclass(frozen=True)
9280
- class WriteIo(Io):
9281
- data: bytes
9282
-
9283
- #
9284
-
9285
9601
  @dc.dataclass(frozen=True)
9286
9602
  class CoroHandleResult:
9287
9603
  close_reason: ta.Literal['response', 'internal', None] = None
9288
9604
 
9289
- def coro_handle(self) -> ta.Generator[Io, ta.Optional[bytes], CoroHandleResult]:
9605
+ def coro_handle(self) -> ta.Generator[CoroHttpIo.Io, ta.Optional[bytes], CoroHandleResult]:
9290
9606
  return self._coro_run_handler(self._coro_handle_one())
9291
9607
 
9292
9608
  class Close(Exception): # noqa
@@ -9295,20 +9611,20 @@ class CoroHttpServer:
9295
9611
  def _coro_run_handler(
9296
9612
  self,
9297
9613
  gen: ta.Generator[
9298
- ta.Union[AnyLogIo, AnyReadIo, _Response],
9614
+ ta.Union[CoroHttpIo.AnyLogIo, CoroHttpIo.AnyReadIo, _Response],
9299
9615
  ta.Optional[bytes],
9300
9616
  None,
9301
9617
  ],
9302
- ) -> ta.Generator[Io, ta.Optional[bytes], CoroHandleResult]:
9618
+ ) -> ta.Generator[CoroHttpIo.Io, ta.Optional[bytes], CoroHandleResult]:
9303
9619
  i: ta.Optional[bytes]
9304
9620
  o: ta.Any = next(gen)
9305
9621
  while True:
9306
9622
  try:
9307
- if isinstance(o, self.AnyLogIo):
9623
+ if isinstance(o, CoroHttpIo.AnyLogIo):
9308
9624
  i = None
9309
9625
  yield o
9310
9626
 
9311
- elif isinstance(o, self.AnyReadIo):
9627
+ elif isinstance(o, CoroHttpIo.AnyReadIo):
9312
9628
  i = check.isinstance((yield o), bytes)
9313
9629
 
9314
9630
  elif isinstance(o, self._Response):
@@ -9316,10 +9632,10 @@ class CoroHttpServer:
9316
9632
 
9317
9633
  r = self._preprocess_response(o)
9318
9634
  hb = self._build_response_head_bytes(r)
9319
- check.none((yield self.WriteIo(hb)))
9635
+ check.none((yield CoroHttpIo.WriteIo(hb)))
9320
9636
 
9321
9637
  for b in self._yield_response_data(r):
9322
- yield self.WriteIo(b)
9638
+ yield CoroHttpIo.WriteIo(b)
9323
9639
 
9324
9640
  o.close()
9325
9641
  if o.close_connection:
@@ -9347,7 +9663,7 @@ class CoroHttpServer:
9347
9663
  raise
9348
9664
 
9349
9665
  def _coro_handle_one(self) -> ta.Generator[
9350
- ta.Union[AnyLogIo, AnyReadIo, _Response],
9666
+ ta.Union[CoroHttpIo.AnyLogIo, CoroHttpIo.AnyReadIo, _Response],
9351
9667
  ta.Optional[bytes],
9352
9668
  None,
9353
9669
  ]:
@@ -9357,7 +9673,7 @@ class CoroHttpServer:
9357
9673
  sz = next(gen)
9358
9674
  while True:
9359
9675
  try:
9360
- line = check.isinstance((yield self.ReadLineIo(sz)), bytes)
9676
+ line = check.isinstance((yield CoroHttpIo.ReadLineIo(sz)), bytes)
9361
9677
  sz = gen.send(line)
9362
9678
  except StopIteration as e:
9363
9679
  parsed = e.value
@@ -9396,7 +9712,7 @@ class CoroHttpServer:
9396
9712
 
9397
9713
  request_data: ta.Optional[bytes]
9398
9714
  if (cl := parsed.headers.get('Content-Length')) is not None:
9399
- request_data = check.isinstance((yield self.ReadIo(int(cl))), bytes)
9715
+ request_data = check.isinstance((yield CoroHttpIo.ReadIo(int(cl))), bytes)
9400
9716
  else:
9401
9717
  request_data = None
9402
9718
 
@@ -10751,6 +11067,10 @@ class VerboseCalledProcessError(subprocess.CalledProcessError):
10751
11067
  class BaseSubprocesses(Abstract):
10752
11068
  DEFAULT_LOGGER: ta.ClassVar[ta.Optional[LoggerLike]] = None
10753
11069
 
11070
+ PIPE: ta.ClassVar[int] = subprocess.PIPE
11071
+ STDOUT: ta.ClassVar[int] = subprocess.STDOUT
11072
+ DEVNULL: ta.ClassVar[int] = subprocess.DEVNULL
11073
+
10754
11074
  def __init__(
10755
11075
  self,
10756
11076
  *,
@@ -11441,7 +11761,7 @@ class CoroHttpServerSocketHandler(SocketHandler_):
11441
11761
  server_factory: CoroHttpServerFactory,
11442
11762
  *,
11443
11763
  keep_alive: bool = False,
11444
- log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
11764
+ log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpIo.AnyLogIo], None]] = None,
11445
11765
  ) -> None:
11446
11766
  super().__init__()
11447
11767
 
@@ -11470,18 +11790,18 @@ class CoroHttpServerSocketHandler(SocketHandler_):
11470
11790
 
11471
11791
  o = next(gen)
11472
11792
  while True:
11473
- if isinstance(o, CoroHttpServer.AnyLogIo):
11793
+ if isinstance(o, CoroHttpIo.AnyLogIo):
11474
11794
  i = None
11475
11795
  if self._log_handler is not None:
11476
11796
  self._log_handler(server, o)
11477
11797
 
11478
- elif isinstance(o, CoroHttpServer.ReadIo):
11479
- i = fp.r.read(o.sz)
11798
+ elif isinstance(o, CoroHttpIo.ReadIo):
11799
+ i = fp.r.read(check.not_none(o.sz))
11480
11800
 
11481
- elif isinstance(o, CoroHttpServer.ReadLineIo):
11801
+ elif isinstance(o, CoroHttpIo.ReadLineIo):
11482
11802
  i = fp.r.readline(o.sz)
11483
11803
 
11484
- elif isinstance(o, CoroHttpServer.WriteIo):
11804
+ elif isinstance(o, CoroHttpIo.WriteIo):
11485
11805
  i = None
11486
11806
  fp.w.write(o.data)
11487
11807
  fp.w.flush()
@@ -15374,7 +15694,7 @@ async def _async_main() -> ta.Optional[int]:
15374
15694
  def _main() -> None:
15375
15695
  configure_standard_logging('DEBUG')
15376
15696
 
15377
- 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)
15378
15698
 
15379
15699
 
15380
15700
  if __name__ == '__main__':