omdev 0.0.0.dev416__py3-none-any.whl → 0.0.0.dev500__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 (211) hide show
  1. omdev/{.manifests.json → .omlish-manifests.json} +23 -47
  2. omdev/README.md +51 -0
  3. omdev/__about__.py +12 -8
  4. omdev/amalg/cli/main.py +1 -2
  5. omdev/amalg/gen/gen.py +49 -6
  6. omdev/amalg/gen/imports.py +1 -1
  7. omdev/amalg/gen/manifests.py +1 -1
  8. omdev/amalg/gen/resources.py +1 -1
  9. omdev/amalg/gen/srcfiles.py +26 -3
  10. omdev/amalg/gen/strip.py +1 -1
  11. omdev/amalg/gen/types.py +1 -1
  12. omdev/amalg/gen/typing.py +1 -1
  13. omdev/amalg/info.py +32 -0
  14. omdev/cache/compute/storage.py +3 -1
  15. omdev/cache/data/actions.py +1 -1
  16. omdev/cache/data/cache.py +2 -2
  17. omdev/cache/data/specs.py +1 -1
  18. omdev/cexts/_boilerplate.cc +2 -3
  19. omdev/cexts/_distutils/build_ext.py +5 -2
  20. omdev/cexts/_distutils/compilers/ccompiler.py +5 -2
  21. omdev/cexts/_distutils/compilers/options.py +3 -0
  22. omdev/cexts/_distutils/compilers/unixccompiler.py +6 -2
  23. omdev/cexts/_distutils/dir_util.py +6 -2
  24. omdev/cexts/_distutils/errors.py +3 -0
  25. omdev/cexts/_distutils/extension.py +3 -0
  26. omdev/cexts/_distutils/file_util.py +6 -2
  27. omdev/cexts/_distutils/modified.py +3 -0
  28. omdev/cexts/_distutils/spawn.py +6 -2
  29. omdev/cexts/_distutils/sysconfig.py +3 -0
  30. omdev/cexts/_distutils/util.py +6 -2
  31. omdev/cexts/_distutils/version.py +3 -0
  32. omdev/cexts/cmake.py +5 -3
  33. omdev/cexts/scan.py +1 -2
  34. omdev/ci/cache.py +7 -3
  35. omdev/ci/cli.py +6 -4
  36. omdev/ci/docker/buildcaching.py +3 -1
  37. omdev/ci/docker/cache.py +2 -1
  38. omdev/ci/docker/cacheserved/cache.py +4 -1
  39. omdev/ci/docker/cacheserved/manifests.py +2 -2
  40. omdev/ci/docker/dataserver.py +2 -2
  41. omdev/ci/docker/imagepulling.py +2 -1
  42. omdev/ci/docker/packing.py +1 -1
  43. omdev/ci/docker/repositories.py +2 -1
  44. omdev/ci/github/api/clients.py +8 -4
  45. omdev/ci/github/api/v1/client.py +4 -1
  46. omdev/ci/github/api/v2/api.py +2 -0
  47. omdev/ci/github/api/v2/azure.py +4 -1
  48. omdev/ci/github/api/v2/client.py +4 -1
  49. omdev/cli/clicli.py +37 -7
  50. omdev/clipboard/clipboard.py +1 -1
  51. omdev/cmake.py +2 -1
  52. omdev/cmdlog/cli.py +1 -2
  53. omdev/dataclasses/_dumping.py +1960 -0
  54. omdev/dataclasses/_template.py +22 -0
  55. omdev/dataclasses/cli.py +7 -2
  56. omdev/dataclasses/codegen.py +342 -62
  57. omdev/dataclasses/dumping.py +200 -0
  58. omdev/dataserver/handlers.py +3 -2
  59. omdev/dataserver/targets.py +2 -2
  60. omdev/imgur.py +2 -2
  61. omdev/interp/cli.py +1 -1
  62. omdev/interp/inspect.py +2 -1
  63. omdev/interp/providers/base.py +3 -2
  64. omdev/interp/providers/standalone.py +4 -1
  65. omdev/interp/providers/system.py +2 -2
  66. omdev/interp/pyenv/install.py +2 -1
  67. omdev/interp/pyenv/provider.py +2 -2
  68. omdev/interp/types.py +3 -2
  69. omdev/interp/uv/provider.py +40 -2
  70. omdev/interp/uv/uv.py +2 -2
  71. omdev/interp/venvs.py +3 -2
  72. omdev/irc/messages/base.py +50 -0
  73. omdev/irc/messages/formats.py +92 -0
  74. omdev/irc/messages/messages.py +775 -0
  75. omdev/irc/messages/parsing.py +99 -0
  76. omdev/irc/numerics/formats.py +97 -0
  77. omdev/irc/numerics/numerics.py +865 -0
  78. omdev/irc/numerics/types.py +59 -0
  79. omdev/irc/protocol/LICENSE +11 -0
  80. omdev/irc/protocol/__init__.py +61 -0
  81. omdev/irc/protocol/consts.py +6 -0
  82. omdev/irc/protocol/errors.py +30 -0
  83. omdev/irc/protocol/message.py +21 -0
  84. omdev/irc/protocol/nuh.py +55 -0
  85. omdev/irc/protocol/parsing.py +158 -0
  86. omdev/irc/protocol/rendering.py +153 -0
  87. omdev/irc/protocol/tags.py +102 -0
  88. omdev/irc/protocol/utils.py +30 -0
  89. omdev/manifests/_dumping.py +529 -136
  90. omdev/manifests/building.py +6 -3
  91. omdev/manifests/main.py +1 -1
  92. omdev/markdown/__init__.py +0 -0
  93. omdev/markdown/incparse.py +116 -0
  94. omdev/markdown/tokens.py +51 -0
  95. omdev/oci/data.py +2 -2
  96. omdev/oci/datarefs.py +2 -2
  97. omdev/oci/media.py +2 -2
  98. omdev/oci/repositories.py +3 -2
  99. omdev/packaging/marshal.py +9 -9
  100. omdev/packaging/requires.py +6 -6
  101. omdev/packaging/revisions.py +5 -2
  102. omdev/packaging/specifiers.py +41 -42
  103. omdev/packaging/versions.py +10 -10
  104. omdev/packaging/wheelfile.py +4 -2
  105. omdev/precheck/blanklines.py +66 -0
  106. omdev/precheck/caches.py +1 -1
  107. omdev/precheck/imports.py +14 -1
  108. omdev/precheck/lite.py +2 -2
  109. omdev/precheck/main.py +5 -5
  110. omdev/precheck/unicode.py +39 -15
  111. omdev/py/asts/__init__.py +0 -0
  112. omdev/py/asts/parents.py +28 -0
  113. omdev/py/asts/toplevel.py +123 -0
  114. omdev/py/asts/visitors.py +18 -0
  115. omdev/py/attrdocs.py +6 -7
  116. omdev/py/bracepy.py +12 -4
  117. omdev/py/docstrings/numpydoc.py +4 -4
  118. omdev/py/reprs.py +32 -0
  119. omdev/py/scripts/execstat.py +31 -26
  120. omdev/py/srcheaders.py +1 -1
  121. omdev/py/tokens/__init__.py +0 -0
  122. omdev/{tokens → py/tokens}/utils.py +2 -1
  123. omdev/py/tools/importscan.py +2 -2
  124. omdev/py/tools/mkrelimp.py +3 -4
  125. omdev/py/tools/pipdepup.py +686 -0
  126. omdev/pyproject/cli.py +1 -1
  127. omdev/pyproject/pkg.py +197 -48
  128. omdev/pyproject/reqs.py +36 -10
  129. omdev/pyproject/tools/__init__.py +0 -0
  130. omdev/pyproject/tools/aboutdeps.py +60 -0
  131. omdev/pyproject/venvs.py +12 -2
  132. omdev/rs/__init__.py +0 -0
  133. omdev/scripts/ci.py +9551 -6982
  134. omdev/scripts/interp.py +1323 -892
  135. omdev/scripts/lib/__init__.py +0 -0
  136. omdev/scripts/lib/inject.py +2086 -0
  137. omdev/scripts/lib/logs.py +2175 -0
  138. omdev/scripts/lib/marshal.py +1731 -0
  139. omdev/scripts/pyproject.py +4979 -1874
  140. omdev/tools/docker.py +19 -7
  141. omdev/tools/git/cli.py +56 -16
  142. omdev/tools/git/messages.py +2 -2
  143. omdev/tools/json/cli.py +6 -6
  144. omdev/tools/json/formats.py +2 -0
  145. omdev/tools/json/parsing.py +5 -5
  146. omdev/tools/json/processing.py +6 -3
  147. omdev/tools/json/rendering.py +2 -2
  148. omdev/tools/jsonview/cli.py +49 -65
  149. omdev/tools/jsonview/resources/jsonview.html.j2 +43 -0
  150. omdev/tools/pawk/README.md +195 -0
  151. omdev/tools/pawk/pawk.py +2 -2
  152. omdev/tools/pip.py +8 -0
  153. omdev/tui/__init__.py +0 -0
  154. omdev/tui/apps/__init__.py +0 -0
  155. omdev/tui/apps/edit/__init__.py +0 -0
  156. omdev/tui/apps/edit/main.py +167 -0
  157. omdev/tui/apps/irc/__init__.py +0 -0
  158. omdev/tui/apps/irc/__main__.py +4 -0
  159. omdev/tui/apps/irc/app.py +286 -0
  160. omdev/tui/apps/irc/client.py +187 -0
  161. omdev/tui/apps/irc/commands.py +175 -0
  162. omdev/tui/apps/irc/main.py +26 -0
  163. omdev/tui/apps/markdown/__init__.py +0 -0
  164. omdev/tui/apps/markdown/__main__.py +11 -0
  165. omdev/{ptk → tui/apps}/markdown/cli.py +5 -7
  166. omdev/tui/rich/__init__.py +46 -0
  167. omdev/tui/rich/console2.py +20 -0
  168. omdev/tui/rich/markdown2.py +186 -0
  169. omdev/tui/textual/__init__.py +265 -0
  170. omdev/tui/textual/app2.py +16 -0
  171. omdev/tui/textual/autocomplete/LICENSE +21 -0
  172. omdev/tui/textual/autocomplete/__init__.py +33 -0
  173. omdev/tui/textual/autocomplete/matching.py +226 -0
  174. omdev/tui/textual/autocomplete/paths.py +202 -0
  175. omdev/tui/textual/autocomplete/widget.py +612 -0
  176. omdev/tui/textual/debug/__init__.py +10 -0
  177. omdev/tui/textual/debug/dominfo.py +151 -0
  178. omdev/tui/textual/debug/screen.py +24 -0
  179. omdev/tui/textual/devtools.py +187 -0
  180. omdev/tui/textual/drivers2.py +55 -0
  181. omdev/tui/textual/logging2.py +20 -0
  182. omdev/tui/textual/types.py +45 -0
  183. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/METADATA +18 -12
  184. omdev-0.0.0.dev500.dist-info/RECORD +386 -0
  185. omdev/ptk/__init__.py +0 -103
  186. omdev/ptk/apps/ncdu.py +0 -167
  187. omdev/ptk/confirm.py +0 -60
  188. omdev/ptk/markdown/LICENSE +0 -22
  189. omdev/ptk/markdown/__init__.py +0 -10
  190. omdev/ptk/markdown/__main__.py +0 -11
  191. omdev/ptk/markdown/border.py +0 -94
  192. omdev/ptk/markdown/markdown.py +0 -390
  193. omdev/ptk/markdown/parser.py +0 -42
  194. omdev/ptk/markdown/styles.py +0 -29
  195. omdev/ptk/markdown/tags.py +0 -299
  196. omdev/ptk/markdown/utils.py +0 -366
  197. omdev/pyproject/cexts.py +0 -110
  198. omdev/tools/antlr/__main__.py +0 -11
  199. omdev/tools/antlr/cli.py +0 -62
  200. omdev/tools/antlr/consts.py +0 -7
  201. omdev/tools/antlr/gen.py +0 -188
  202. omdev-0.0.0.dev416.dist-info/RECORD +0 -332
  203. /omdev/{ptk/apps → irc}/__init__.py +0 -0
  204. /omdev/{tokens → irc/messages}/__init__.py +0 -0
  205. /omdev/{tools/antlr → irc/numerics}/__init__.py +0 -0
  206. /omdev/{tokens → py/tokens}/all.py +0 -0
  207. /omdev/{tokens → py/tokens}/tokenizert.py +0 -0
  208. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/WHEEL +0 -0
  209. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/entry_points.txt +0 -0
  210. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/licenses/LICENSE +0 -0
  211. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/top_level.txt +0 -0
omdev/scripts/interp.py CHANGED
@@ -27,6 +27,7 @@ import inspect
27
27
  import itertools
28
28
  import json
29
29
  import logging
30
+ import operator
30
31
  import os
31
32
  import os.path
32
33
  import re
@@ -49,19 +50,69 @@ if sys.version_info < (3, 8):
49
50
  raise OSError(f'Requires python (3, 8), got {sys.version_info} from {sys.executable}') # noqa
50
51
 
51
52
 
53
+ def __omlish_amalg__(): # noqa
54
+ return dict(
55
+ src_files=[
56
+ dict(path='../packaging/versions.py', sha1='71627ad600b3529b829b0e227b0952f2c63c7271'),
57
+ dict(path='../../omlish/lite/abstract.py', sha1='a2fc3f3697fa8de5247761e9d554e70176f37aac'),
58
+ dict(path='../../omlish/lite/cached.py', sha1='0c33cf961ac8f0727284303c7a30c5ea98f714f2'),
59
+ dict(path='../../omlish/lite/check.py', sha1='bb6b6b63333699b84462951a854d99ae83195b94'),
60
+ dict(path='../../omlish/lite/json.py', sha1='57eeddc4d23a17931e00284ffa5cb6e3ce089486'),
61
+ dict(path='../../omlish/lite/reflect.py', sha1='c4fec44bf144e9d93293c996af06f6c65fc5e63d'),
62
+ dict(path='../../omlish/lite/strings.py', sha1='89831ecbc34ad80e118a865eceb390ed399dc4d6'),
63
+ dict(path='../../omlish/logs/levels.py', sha1='91405563d082a5eba874da82aac89d83ce7b6152'),
64
+ dict(path='../../omlish/logs/std/filters.py', sha1='f36aab646d84d31e295b33aaaaa6f8b67ff38b3d'),
65
+ dict(path='../../omlish/logs/std/proxy.py', sha1='3e7301a2aa351127f9c85f61b2f85dcc3f15aafb'),
66
+ dict(path='../packaging/specifiers.py', sha1='a56ab4e8c9b174adb523921f6280ac41e0fce749'),
67
+ dict(path='../../omlish/argparse/cli.py', sha1='f4dc3cd353d14386b5da0306768700e396afd2b3'),
68
+ dict(path='../../omlish/lite/maybes.py', sha1='bdf5136654ccd14b6a072588cad228925bdfbabd'),
69
+ dict(path='../../omlish/lite/runtime.py', sha1='2e752a27ae2bf89b1bb79b4a2da522a3ec360c70'),
70
+ dict(path='../../omlish/lite/timeouts.py', sha1='a0f673033a6943f242e35848d78a41892b9c62a1'),
71
+ dict(path='../../omlish/logs/protocols.py', sha1='05ca4d1d7feb50c4e3b9f22ee371aa7bf4b3dbd1'),
72
+ dict(path='../../omlish/logs/std/json.py', sha1='2a75553131e4d5331bb0cedde42aa183f403fc3b'),
73
+ dict(path='types.py', sha1='caf068a6e81fb6e221d777b341ac5777d92b8091'),
74
+ dict(path='../../omlish/asyncs/asyncio/timeouts.py', sha1='4d31b02b3c39b8f2fa7e94db36552fde6942e36a'),
75
+ dict(path='../../omlish/lite/inject.py', sha1='6f097e3170019a34ff6834d36fcc9cbeed3a7ab4'),
76
+ dict(path='../../omlish/logs/std/standard.py', sha1='5c97c1b9f7ead58d6127d047b873398f708f288d'),
77
+ dict(path='../../omlish/subprocesses/run.py', sha1='8200e48f0c49d164df3503cd0143038d0c4d30aa'),
78
+ dict(path='../../omlish/subprocesses/wrap.py', sha1='8a9b7d2255481fae15c05f5624b0cdc0766f4b3f'),
79
+ dict(path='providers/base.py', sha1='f5d068c21f230d742e9015b033cd6320f4c68898'),
80
+ dict(path='../../omlish/subprocesses/base.py', sha1='cb9f668be5422fecb27222caabb67daac6c1bab9'),
81
+ dict(path='resolvers.py', sha1='817b8e76401cd7a19eb43ca54d65272e4c8a4b0e'),
82
+ dict(path='../../omlish/subprocesses/asyncs.py', sha1='bba44d524c24c6ac73168aee6343488414e5bf48'),
83
+ dict(path='../../omlish/asyncs/asyncio/subprocesses.py', sha1='b6b5f9ae3fd0b9c83593bad2e04a08f726e5904d'),
84
+ dict(path='inspect.py', sha1='736287b4ec8d14a8c30afa0ba23996fdc0662caa'),
85
+ dict(path='pyenv/pyenv.py', sha1='d1f6e657c671c1b1a5b0e627284df656fe2d10d3'),
86
+ dict(path='uv/uv.py', sha1='8c6515cd6755efab3972da92a285e94ccb255515'),
87
+ dict(path='providers/running.py', sha1='85c9cc69ff6fbd6c8cf78ed6262619a30856c2f1'),
88
+ dict(path='providers/system.py', sha1='9638a154475ca98775159d27739563ac7fb2eb16'),
89
+ dict(path='pyenv/install.py', sha1='4a10a19717364b4ba9f3b8bf1d12621cf21ba8b8'),
90
+ dict(path='uv/provider.py', sha1='3c3980878ad2b9fd2cd02172f9424954759c7f06'),
91
+ dict(path='providers/inject.py', sha1='7cc9ebf58cf2ec09545321456bd9da9f9a3a79fb'),
92
+ dict(path='pyenv/provider.py', sha1='377542ce01a35849e2a5b4a4dbafedc26882f983'),
93
+ dict(path='uv/inject.py', sha1='e95d058c2340baa5a3155ec3440f311d1daa10a8'),
94
+ dict(path='pyenv/inject.py', sha1='b8fb68f5a7cae86c70fe1bad6c29a8b2dfc985c3'),
95
+ dict(path='inject.py', sha1='b039abbadf0b096d2724182af2e0ebda2a230852'),
96
+ dict(path='cli.py', sha1='6b747ba4f91e0ab6290b791c2c274f268d11c33e'),
97
+ ],
98
+ )
99
+
100
+
52
101
  ########################################
53
102
 
54
103
 
55
104
  # ../packaging/versions.py
56
- VersionLocalType = ta.Tuple[ta.Union[int, str], ...]
57
- VersionCmpPrePostDevType = ta.Union['InfinityVersionType', 'NegativeInfinityVersionType', ta.Tuple[str, int]]
58
- _VersionCmpLocalType0 = ta.Tuple[ta.Union[ta.Tuple[int, str], ta.Tuple['NegativeInfinityVersionType', ta.Union[int, str]]], ...] # noqa
59
- VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalType0]
60
- VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
61
- VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
105
+ VersionLocalType = ta.Tuple[ta.Union[int, str], ...] # ta.TypeAlias
106
+ VersionCmpPrePostDevType = ta.Union['InfinityVersionType', 'NegativeInfinityVersionType', ta.Tuple[str, int]] # ta.TypeAlias # noqa
107
+ _VersionCmpLocalType0 = ta.Tuple[ta.Union[ta.Tuple[int, str], ta.Tuple['NegativeInfinityVersionType', ta.Union[int, str]]], ...] # ta.TypeAlias # noqa
108
+ VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalType0] # ta.TypeAlias
109
+ VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # ta.TypeAlias # noqa
110
+ VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool] # ta.TypeAlias
111
+
112
+ # ../../omlish/lite/abstract.py
113
+ T = ta.TypeVar('T')
62
114
 
63
115
  # ../../omlish/lite/cached.py
64
- T = ta.TypeVar('T')
65
116
  CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
66
117
 
67
118
  # ../../omlish/lite/check.py
@@ -72,28 +123,31 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
72
123
  CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
73
124
  CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
74
125
 
75
- # ../../omlish/lite/maybes.py
76
- U = ta.TypeVar('U')
77
-
78
- # ../../omlish/lite/timeouts.py
79
- TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.DEFAULT'], ta.Iterable['TimeoutLike'], float, None] # ta.TypeAlias
126
+ # ../../omlish/logs/levels.py
127
+ LogLevel = int # ta.TypeAlias
80
128
 
81
129
  # ../packaging/specifiers.py
82
- UnparsedVersion = ta.Union['Version', str]
130
+ UnparsedVersion = ta.Union['Version', str] # ta.TypeAlias
83
131
  UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
84
- CallableVersionOperator = ta.Callable[['Version', str], bool]
132
+ CallableVersionOperator = ta.Callable[['Version', str], bool] # ta.TypeAlias
85
133
 
86
134
  # ../../omlish/argparse/cli.py
87
135
  ArgparseCmdFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
88
136
 
137
+ # ../../omlish/lite/maybes.py
138
+ U = ta.TypeVar('U')
139
+
140
+ # ../../omlish/lite/timeouts.py
141
+ TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.DEFAULT'], ta.Iterable['TimeoutLike'], float, None] # ta.TypeAlias
142
+
89
143
  # ../../omlish/asyncs/asyncio/timeouts.py
90
144
  AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
91
145
 
92
146
  # ../../omlish/lite/inject.py
93
- InjectorKeyCls = ta.Union[type, ta.NewType]
94
- InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
95
- InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
96
- InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
147
+ InjectorKeyCls = ta.Union[type, ta.NewType] # ta.TypeAlias
148
+ InjectorProviderFn = ta.Callable[['Injector'], ta.Any] # ta.TypeAlias
149
+ InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn'] # ta.TypeAlias
150
+ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings'] # ta.TypeAlias
97
151
 
98
152
  # ../../omlish/subprocesses/base.py
99
153
  SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
@@ -210,12 +264,12 @@ class _BaseVersion:
210
264
 
211
265
  def __lt__(self, other: '_BaseVersion') -> bool:
212
266
  if not isinstance(other, _BaseVersion):
213
- return NotImplemented # type: ignore
267
+ return NotImplemented
214
268
  return self._key < other._key
215
269
 
216
270
  def __le__(self, other: '_BaseVersion') -> bool:
217
271
  if not isinstance(other, _BaseVersion):
218
- return NotImplemented # type: ignore
272
+ return NotImplemented
219
273
  return self._key <= other._key
220
274
 
221
275
  def __eq__(self, other: object) -> bool:
@@ -225,12 +279,12 @@ class _BaseVersion:
225
279
 
226
280
  def __ge__(self, other: '_BaseVersion') -> bool:
227
281
  if not isinstance(other, _BaseVersion):
228
- return NotImplemented # type: ignore
282
+ return NotImplemented
229
283
  return self._key >= other._key
230
284
 
231
285
  def __gt__(self, other: '_BaseVersion') -> bool:
232
286
  if not isinstance(other, _BaseVersion):
233
- return NotImplemented # type: ignore
287
+ return NotImplemented
234
288
  return self._key > other._key
235
289
 
236
290
  def __ne__(self, other: object) -> bool:
@@ -506,6 +560,153 @@ def canonicalize_version(
506
560
  return ''.join(parts)
507
561
 
508
562
 
563
+ ########################################
564
+ # ../../../omlish/lite/abstract.py
565
+
566
+
567
+ ##
568
+
569
+
570
+ _ABSTRACT_METHODS_ATTR = '__abstractmethods__'
571
+ _IS_ABSTRACT_METHOD_ATTR = '__isabstractmethod__'
572
+
573
+
574
+ def is_abstract_method(obj: ta.Any) -> bool:
575
+ return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
576
+
577
+
578
+ def compute_abstract_methods(cls: type) -> ta.FrozenSet[str]:
579
+ # ~> https://github.com/python/cpython/blob/f3476c6507381ca860eec0989f53647b13517423/Modules/_abc.c#L358
580
+
581
+ # Stage 1: direct abstract methods
582
+
583
+ abstracts = {
584
+ a
585
+ # Get items as a list to avoid mutation issues during iteration
586
+ for a, v in list(cls.__dict__.items())
587
+ if is_abstract_method(v)
588
+ }
589
+
590
+ # Stage 2: inherited abstract methods
591
+
592
+ for base in cls.__bases__:
593
+ # Get __abstractmethods__ from base if it exists
594
+ if (base_abstracts := getattr(base, _ABSTRACT_METHODS_ATTR, None)) is None:
595
+ continue
596
+
597
+ # Iterate over abstract methods in base
598
+ for key in base_abstracts:
599
+ # Check if this class has an attribute with this name
600
+ try:
601
+ value = getattr(cls, key)
602
+ except AttributeError:
603
+ # Attribute not found in this class, skip
604
+ continue
605
+
606
+ # Check if it's still abstract
607
+ if is_abstract_method(value):
608
+ abstracts.add(key)
609
+
610
+ return frozenset(abstracts)
611
+
612
+
613
+ def update_abstracts(cls: ta.Type[T], *, force: bool = False) -> ta.Type[T]:
614
+ if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
615
+ # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
616
+ # implementation (especially during testing), and we want to handle both cases.
617
+ return cls
618
+
619
+ abstracts = compute_abstract_methods(cls)
620
+ setattr(cls, _ABSTRACT_METHODS_ATTR, abstracts)
621
+ return cls
622
+
623
+
624
+ #
625
+
626
+
627
+ class AbstractTypeError(TypeError):
628
+ pass
629
+
630
+
631
+ _FORCE_ABSTRACT_ATTR = '__forceabstract__'
632
+
633
+
634
+ class Abstract:
635
+ """
636
+ Different from, but interoperable with, abc.ABC / abc.ABCMeta:
637
+
638
+ - This raises AbstractTypeError during class creation, not instance instantiation - unless Abstract or abc.ABC are
639
+ explicitly present in the class's direct bases.
640
+ - This will forbid instantiation of classes with Abstract in their direct bases even if there are no
641
+ abstractmethods left on the class.
642
+ - This is a mixin, not a metaclass.
643
+ - As it is not an ABCMeta, this does not support virtual base classes. As a result, operations like `isinstance`
644
+ and `issubclass` are ~7x faster.
645
+ - It additionally enforces a base class order of (Abstract, abc.ABC) to preemptively prevent common mro conflicts.
646
+
647
+ If not mixed-in with an ABCMeta, it will update __abstractmethods__ itself.
648
+ """
649
+
650
+ __slots__ = ()
651
+
652
+ __abstractmethods__: ta.ClassVar[ta.FrozenSet[str]] = frozenset()
653
+
654
+ #
655
+
656
+ def __forceabstract__(self):
657
+ raise TypeError
658
+
659
+ # This is done manually, rather than through @abc.abstractmethod, to mask it from static analysis.
660
+ setattr(__forceabstract__, _IS_ABSTRACT_METHOD_ATTR, True)
661
+
662
+ #
663
+
664
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
665
+ setattr(
666
+ cls,
667
+ _FORCE_ABSTRACT_ATTR,
668
+ getattr(Abstract, _FORCE_ABSTRACT_ATTR) if Abstract in cls.__bases__ else False,
669
+ )
670
+
671
+ super().__init_subclass__(**kwargs)
672
+
673
+ if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
674
+ if ams := compute_abstract_methods(cls):
675
+ amd = {
676
+ a: mcls
677
+ for mcls in cls.__mro__[::-1]
678
+ for a in ams
679
+ if a in mcls.__dict__
680
+ }
681
+
682
+ raise AbstractTypeError(
683
+ f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
684
+ ', '.join(sorted([
685
+ '.'.join([
686
+ *([
687
+ *([m] if (m := getattr(c, '__module__')) else []),
688
+ getattr(c, '__qualname__', getattr(c, '__name__')),
689
+ ] if c is not None else '?'),
690
+ a,
691
+ ])
692
+ for a in ams
693
+ for c in [amd.get(a)]
694
+ ])),
695
+ )
696
+
697
+ xbi = (Abstract, abc.ABC) # , ta.Generic ?
698
+ bis = [(cls.__bases__.index(b), b) for b in xbi if b in cls.__bases__]
699
+ if bis != sorted(bis):
700
+ raise TypeError(
701
+ f'Abstract subclass {cls.__name__} must have proper base class order of '
702
+ f'({", ".join(getattr(b, "__name__") for b in xbi)}), got: '
703
+ f'({", ".join(getattr(b, "__name__") for _, b in sorted(bis))})',
704
+ )
705
+
706
+ if not isinstance(cls, abc.ABCMeta):
707
+ update_abstracts(cls, force=True)
708
+
709
+
509
710
  ########################################
510
711
  # ../../../omlish/lite/cached.py
511
712
 
@@ -524,7 +725,7 @@ class _AbstractCachedNullary:
524
725
  def __call__(self, *args, **kwargs): # noqa
525
726
  raise TypeError
526
727
 
527
- def __get__(self, instance, owner): # noqa
728
+ def __get__(self, instance, owner=None): # noqa
528
729
  bound = instance.__dict__[self._fn.__name__] = self.__class__(self._fn.__get__(instance, owner))
529
730
  return bound
530
731
 
@@ -563,6 +764,62 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
563
764
  return _AsyncCachedNullary(fn)
564
765
 
565
766
 
767
+ ##
768
+
769
+
770
+ cached_property = functools.cached_property
771
+
772
+
773
+ class _cached_property: # noqa
774
+ """Backported to pick up https://github.com/python/cpython/commit/056dfc71dce15f81887f0bd6da09d6099d71f979 ."""
775
+
776
+ def __init__(self, func):
777
+ self.func = func
778
+ self.attrname = None # noqa
779
+ self.__doc__ = func.__doc__
780
+ self.__module__ = func.__module__
781
+
782
+ _NOT_FOUND = object()
783
+
784
+ def __set_name__(self, owner, name):
785
+ if self.attrname is None:
786
+ self.attrname = name # noqa
787
+ elif name != self.attrname:
788
+ raise TypeError(
789
+ f'Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r}).',
790
+ )
791
+
792
+ def __get__(self, instance, owner=None):
793
+ if instance is None:
794
+ return self
795
+ if self.attrname is None:
796
+ raise TypeError('Cannot use cached_property instance without calling __set_name__ on it.')
797
+
798
+ try:
799
+ cache = instance.__dict__
800
+ except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
801
+ raise TypeError(
802
+ f"No '__dict__' attribute on {type(instance).__name__!r} instance to cache {self.attrname!r} property.",
803
+ ) from None
804
+
805
+ val = cache.get(self.attrname, self._NOT_FOUND)
806
+
807
+ if val is self._NOT_FOUND:
808
+ val = self.func(instance)
809
+ try:
810
+ cache[self.attrname] = val
811
+ except TypeError:
812
+ raise TypeError(
813
+ f"The '__dict__' attribute on {type(instance).__name__!r} instance does not support item "
814
+ f"assignment for caching {self.attrname!r} property.",
815
+ ) from None
816
+
817
+ return val
818
+
819
+
820
+ globals()['cached_property'] = _cached_property
821
+
822
+
566
823
  ########################################
567
824
  # ../../../omlish/lite/check.py
568
825
  """
@@ -1086,311 +1343,92 @@ json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON
1086
1343
 
1087
1344
 
1088
1345
  ########################################
1089
- # ../../../omlish/lite/maybes.py
1346
+ # ../../../omlish/lite/reflect.py
1090
1347
 
1091
1348
 
1092
1349
  ##
1093
1350
 
1094
1351
 
1095
- @functools.total_ordering
1096
- class Maybe(ta.Generic[T]):
1097
- class ValueNotPresentError(BaseException):
1098
- pass
1352
+ _GENERIC_ALIAS_TYPES = (
1353
+ ta._GenericAlias, # type: ignore # noqa
1354
+ *([ta._SpecialGenericAlias] if hasattr(ta, '_SpecialGenericAlias') else []), # noqa
1355
+ )
1099
1356
 
1100
- #
1101
1357
 
1102
- @property
1103
- @abc.abstractmethod
1104
- def present(self) -> bool:
1105
- raise NotImplementedError
1358
+ def is_generic_alias(obj: ta.Any, *, origin: ta.Any = None) -> bool:
1359
+ return (
1360
+ isinstance(obj, _GENERIC_ALIAS_TYPES) and
1361
+ (origin is None or ta.get_origin(obj) is origin)
1362
+ )
1106
1363
 
1107
- @abc.abstractmethod
1108
- def must(self) -> T:
1109
- raise NotImplementedError
1110
1364
 
1111
- #
1365
+ is_callable_alias = functools.partial(is_generic_alias, origin=ta.Callable)
1112
1366
 
1113
- @abc.abstractmethod
1114
- def __repr__(self) -> str:
1115
- raise NotImplementedError
1116
1367
 
1117
- @abc.abstractmethod
1118
- def __hash__(self) -> int:
1119
- raise NotImplementedError
1368
+ ##
1120
1369
 
1121
- @abc.abstractmethod
1122
- def __eq__(self, other) -> bool:
1123
- raise NotImplementedError
1124
1370
 
1125
- @abc.abstractmethod
1126
- def __lt__(self, other) -> bool:
1127
- raise NotImplementedError
1371
+ _UNION_ALIAS_ORIGINS = frozenset([
1372
+ ta.get_origin(ta.Optional[int]),
1373
+ *(
1374
+ [
1375
+ ta.get_origin(int | None),
1376
+ ta.get_origin(getattr(ta, 'TypeVar')('_T') | None),
1377
+ ] if sys.version_info >= (3, 10) else ()
1378
+ ),
1379
+ ])
1128
1380
 
1129
- #
1130
1381
 
1131
- @ta.final
1132
- def __ne__(self, other):
1133
- return not (self == other)
1382
+ def is_union_alias(obj: ta.Any) -> bool:
1383
+ return ta.get_origin(obj) in _UNION_ALIAS_ORIGINS
1134
1384
 
1135
- @ta.final
1136
- def __iter__(self) -> ta.Iterator[T]:
1137
- if self.present:
1138
- yield self.must()
1139
1385
 
1140
- @ta.final
1141
- def __bool__(self) -> ta.NoReturn:
1142
- raise TypeError
1386
+ #
1143
1387
 
1144
- #
1145
1388
 
1146
- @ta.final
1147
- def if_present(self, consumer: ta.Callable[[T], None]) -> None:
1148
- if self.present:
1149
- consumer(self.must())
1389
+ def is_optional_alias(spec: ta.Any) -> bool:
1390
+ return (
1391
+ is_union_alias(spec) and
1392
+ len(ta.get_args(spec)) == 2 and
1393
+ any(a in (None, type(None)) for a in ta.get_args(spec))
1394
+ )
1150
1395
 
1151
- @ta.final
1152
- def filter(self, predicate: ta.Callable[[T], bool]) -> 'Maybe[T]':
1153
- if self.present and predicate(self.must()):
1154
- return self
1155
- else:
1156
- return Maybe.empty()
1157
1396
 
1158
- @ta.final
1159
- def map(self, mapper: ta.Callable[[T], U]) -> 'Maybe[U]':
1160
- if self.present:
1161
- return Maybe.just(mapper(self.must()))
1162
- else:
1163
- return Maybe.empty()
1397
+ def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
1398
+ [it] = [it for it in ta.get_args(spec) if it not in (None, type(None))]
1399
+ return it
1164
1400
 
1165
- @ta.final
1166
- def flat_map(self, mapper: ta.Callable[[T], 'Maybe[U]']) -> 'Maybe[U]':
1167
- if self.present:
1168
- if not isinstance(v := mapper(self.must()), Maybe):
1169
- raise TypeError(v)
1170
- return v
1171
- else:
1172
- return Maybe.empty()
1173
1401
 
1174
- @ta.final
1175
- def or_else(self, other: ta.Union[T, U]) -> ta.Union[T, U]:
1176
- if self.present:
1177
- return self.must()
1178
- else:
1179
- return other
1402
+ ##
1180
1403
 
1181
- @ta.final
1182
- def or_else_get(self, supplier: ta.Callable[[], ta.Union[T, U]]) -> ta.Union[T, U]:
1183
- if self.present:
1184
- return self.must()
1185
- else:
1186
- return supplier()
1187
1404
 
1188
- @ta.final
1189
- def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> T:
1190
- if self.present:
1191
- return self.must()
1192
- else:
1193
- raise exception_supplier()
1405
+ def is_new_type(spec: ta.Any) -> bool:
1406
+ if isinstance(ta.NewType, type):
1407
+ return isinstance(spec, ta.NewType)
1408
+ else:
1409
+ # Before https://github.com/python/cpython/commit/c2f33dfc83ab270412bf243fb21f724037effa1a
1410
+ return isinstance(spec, types.FunctionType) and spec.__code__ is ta.NewType.__code__.co_consts[1] # type: ignore # noqa
1194
1411
 
1195
- #
1196
1412
 
1197
- @classmethod
1198
- def of_optional(cls, v: ta.Optional[T]) -> 'Maybe[T]':
1199
- if v is not None:
1200
- return cls.just(v)
1201
- else:
1202
- return cls.empty()
1413
+ def get_new_type_supertype(spec: ta.Any) -> ta.Any:
1414
+ return spec.__supertype__
1203
1415
 
1204
- @classmethod
1205
- def just(cls, v: T) -> 'Maybe[T]':
1206
- return _JustMaybe(v)
1207
1416
 
1208
- _empty: ta.ClassVar['Maybe']
1417
+ ##
1209
1418
 
1210
- @classmethod
1211
- def empty(cls) -> 'Maybe[T]':
1212
- return Maybe._empty
1213
1419
 
1420
+ def is_literal_type(spec: ta.Any) -> bool:
1421
+ if hasattr(ta, '_LiteralGenericAlias'):
1422
+ return isinstance(spec, ta._LiteralGenericAlias) # noqa
1423
+ else:
1424
+ return (
1425
+ isinstance(spec, ta._GenericAlias) and # type: ignore # noqa
1426
+ spec.__origin__ is ta.Literal
1427
+ )
1214
1428
 
1215
- ##
1216
1429
 
1217
-
1218
- class _Maybe(Maybe[T], abc.ABC):
1219
- def __lt__(self, other):
1220
- if not isinstance(other, _Maybe):
1221
- return NotImplemented
1222
- sp = self.present
1223
- op = other.present
1224
- if self.present and other.present:
1225
- return self.must() < other.must()
1226
- else:
1227
- return op and not sp
1228
-
1229
-
1230
- class _JustMaybe(_Maybe[T]):
1231
- __slots__ = ('_v', '_hash')
1232
-
1233
- def __init__(self, v: T) -> None:
1234
- super().__init__()
1235
-
1236
- self._v = v
1237
-
1238
- @property
1239
- def present(self) -> bool:
1240
- return True
1241
-
1242
- def must(self) -> T:
1243
- return self._v
1244
-
1245
- #
1246
-
1247
- def __repr__(self) -> str:
1248
- return f'just({self._v!r})'
1249
-
1250
- _hash: int
1251
-
1252
- def __hash__(self) -> int:
1253
- try:
1254
- return self._hash
1255
- except AttributeError:
1256
- pass
1257
- h = self._hash = hash((_JustMaybe, self._v))
1258
- return h
1259
-
1260
- def __eq__(self, other):
1261
- return (
1262
- self.__class__ is other.__class__ and
1263
- self._v == other._v # noqa
1264
- )
1265
-
1266
-
1267
- class _EmptyMaybe(_Maybe[T]):
1268
- __slots__ = ()
1269
-
1270
- @property
1271
- def present(self) -> bool:
1272
- return False
1273
-
1274
- def must(self) -> T:
1275
- raise Maybe.ValueNotPresentError
1276
-
1277
- #
1278
-
1279
- def __repr__(self) -> str:
1280
- return 'empty()'
1281
-
1282
- def __hash__(self) -> int:
1283
- return hash(_EmptyMaybe)
1284
-
1285
- def __eq__(self, other):
1286
- return self.__class__ is other.__class__
1287
-
1288
-
1289
- Maybe._empty = _EmptyMaybe() # noqa
1290
-
1291
-
1292
- ########################################
1293
- # ../../../omlish/lite/reflect.py
1294
-
1295
-
1296
- ##
1297
-
1298
-
1299
- _GENERIC_ALIAS_TYPES = (
1300
- ta._GenericAlias, # type: ignore # noqa
1301
- *([ta._SpecialGenericAlias] if hasattr(ta, '_SpecialGenericAlias') else []), # noqa
1302
- )
1303
-
1304
-
1305
- def is_generic_alias(obj: ta.Any, *, origin: ta.Any = None) -> bool:
1306
- return (
1307
- isinstance(obj, _GENERIC_ALIAS_TYPES) and
1308
- (origin is None or ta.get_origin(obj) is origin)
1309
- )
1310
-
1311
-
1312
- is_callable_alias = functools.partial(is_generic_alias, origin=ta.Callable)
1313
-
1314
-
1315
- ##
1316
-
1317
-
1318
- _UNION_ALIAS_ORIGINS = frozenset([
1319
- ta.get_origin(ta.Optional[int]),
1320
- *(
1321
- [
1322
- ta.get_origin(int | None),
1323
- ta.get_origin(getattr(ta, 'TypeVar')('_T') | None),
1324
- ] if sys.version_info >= (3, 10) else ()
1325
- ),
1326
- ])
1327
-
1328
-
1329
- def is_union_alias(obj: ta.Any) -> bool:
1330
- return ta.get_origin(obj) in _UNION_ALIAS_ORIGINS
1331
-
1332
-
1333
- #
1334
-
1335
-
1336
- def is_optional_alias(spec: ta.Any) -> bool:
1337
- return (
1338
- is_union_alias(spec) and
1339
- len(ta.get_args(spec)) == 2 and
1340
- any(a in (None, type(None)) for a in ta.get_args(spec))
1341
- )
1342
-
1343
-
1344
- def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
1345
- [it] = [it for it in ta.get_args(spec) if it not in (None, type(None))]
1346
- return it
1347
-
1348
-
1349
- ##
1350
-
1351
-
1352
- def is_new_type(spec: ta.Any) -> bool:
1353
- if isinstance(ta.NewType, type):
1354
- return isinstance(spec, ta.NewType)
1355
- else:
1356
- # Before https://github.com/python/cpython/commit/c2f33dfc83ab270412bf243fb21f724037effa1a
1357
- return isinstance(spec, types.FunctionType) and spec.__code__ is ta.NewType.__code__.co_consts[1] # type: ignore # noqa
1358
-
1359
-
1360
- def get_new_type_supertype(spec: ta.Any) -> ta.Any:
1361
- return spec.__supertype__
1362
-
1363
-
1364
- ##
1365
-
1366
-
1367
- def is_literal_type(spec: ta.Any) -> bool:
1368
- if hasattr(ta, '_LiteralGenericAlias'):
1369
- return isinstance(spec, ta._LiteralGenericAlias) # noqa
1370
- else:
1371
- return (
1372
- isinstance(spec, ta._GenericAlias) and # type: ignore # noqa
1373
- spec.__origin__ is ta.Literal
1374
- )
1375
-
1376
-
1377
- def get_literal_type_args(spec: ta.Any) -> ta.Iterable[ta.Any]:
1378
- return spec.__args__
1379
-
1380
-
1381
- ##
1382
-
1383
-
1384
- def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
1385
- seen = set()
1386
- todo = list(reversed(cls.__subclasses__()))
1387
- while todo:
1388
- cur = todo.pop()
1389
- if cur in seen:
1390
- continue
1391
- seen.add(cur)
1392
- yield cur
1393
- todo.extend(reversed(cur.__subclasses__()))
1430
+ def get_literal_type_args(spec: ta.Any) -> ta.Iterable[ta.Any]:
1431
+ return spec.__args__
1394
1432
 
1395
1433
 
1396
1434
  ########################################
@@ -1469,13 +1507,6 @@ def split_keep_delimiter(s, d):
1469
1507
  ##
1470
1508
 
1471
1509
 
1472
- def attr_repr(obj: ta.Any, *attrs: str) -> str:
1473
- return f'{type(obj).__name__}({", ".join(f"{attr}={getattr(obj, attr)!r}" for attr in attrs)})'
1474
-
1475
-
1476
- ##
1477
-
1478
-
1479
1510
  FORMAT_NUM_BYTES_SUFFIXES: ta.Sequence[str] = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB']
1480
1511
 
1481
1512
 
@@ -1492,212 +1523,114 @@ def format_num_bytes(num_bytes: int) -> str:
1492
1523
 
1493
1524
 
1494
1525
  ########################################
1495
- # ../../../omlish/lite/timeouts.py
1496
- """
1497
- TODO:
1498
- - Event (/ Predicate)
1499
- """
1526
+ # ../../../omlish/logs/levels.py
1500
1527
 
1501
1528
 
1502
1529
  ##
1503
1530
 
1504
1531
 
1505
- class Timeout(abc.ABC):
1506
- @property
1507
- @abc.abstractmethod
1508
- def can_expire(self) -> bool:
1509
- """Indicates whether or not this timeout will ever expire."""
1532
+ @ta.final
1533
+ class NamedLogLevel(int):
1534
+ # logging.getLevelNamesMapping (or, as that is unavailable <3.11, logging._nameToLevel) includes the deprecated
1535
+ # aliases.
1536
+ _NAMES_BY_INT: ta.ClassVar[ta.Mapping[LogLevel, str]] = dict(sorted(logging._levelToName.items(), key=lambda t: -t[0])) # noqa
1510
1537
 
1511
- raise NotImplementedError
1538
+ _INTS_BY_NAME: ta.ClassVar[ta.Mapping[str, LogLevel]] = {v: k for k, v in _NAMES_BY_INT.items()}
1512
1539
 
1513
- @abc.abstractmethod
1514
- def expired(self) -> bool:
1515
- """Return whether or not this timeout has expired."""
1540
+ _NAME_INT_PAIRS: ta.ClassVar[ta.Sequence[ta.Tuple[str, LogLevel]]] = list(_INTS_BY_NAME.items())
1516
1541
 
1517
- raise NotImplementedError
1542
+ #
1518
1543
 
1519
- @abc.abstractmethod
1520
- def remaining(self) -> float:
1521
- """Returns the time (in seconds) remaining until the timeout expires. May be negative and/or infinite."""
1544
+ _CACHE: ta.ClassVar[ta.MutableMapping[int, 'NamedLogLevel']] = {}
1522
1545
 
1523
- raise NotImplementedError
1546
+ @ta.overload
1547
+ def __new__(cls, name: str, offset: int = 0, /) -> 'NamedLogLevel':
1548
+ ...
1524
1549
 
1525
- @abc.abstractmethod
1526
- def __call__(self) -> float:
1527
- """Returns the time (in seconds) remaining until the timeout expires, or raises if the timeout has expired."""
1550
+ @ta.overload
1551
+ def __new__(cls, i: int, /) -> 'NamedLogLevel':
1552
+ ...
1528
1553
 
1529
- raise NotImplementedError
1554
+ def __new__(cls, x, offset=0, /):
1555
+ if isinstance(x, str):
1556
+ return cls(cls._INTS_BY_NAME[x.upper()] + offset)
1557
+ elif not offset and (c := cls._CACHE.get(x)) is not None:
1558
+ return c
1559
+ else:
1560
+ return super().__new__(cls, x + offset)
1530
1561
 
1531
- @abc.abstractmethod
1532
- def or_(self, o: ta.Any) -> ta.Any:
1533
- """Evaluates time remaining via remaining() if this timeout can expire, otherwise returns `o`."""
1562
+ #
1534
1563
 
1535
- raise NotImplementedError
1564
+ _name_and_offset: ta.Tuple[str, int]
1536
1565
 
1537
- #
1566
+ @property
1567
+ def name_and_offset(self) -> ta.Tuple[str, int]:
1568
+ try:
1569
+ return self._name_and_offset
1570
+ except AttributeError:
1571
+ pass
1538
1572
 
1539
- @classmethod
1540
- def _now(cls) -> float:
1541
- return time.monotonic()
1573
+ if (n := self._NAMES_BY_INT.get(self)) is not None:
1574
+ t = (n, 0)
1575
+ else:
1576
+ for n, i in self._NAME_INT_PAIRS: # noqa
1577
+ if self >= i:
1578
+ t = (n, (self - i))
1579
+ break
1580
+ else:
1581
+ t = ('NOTSET', int(self))
1582
+
1583
+ self._name_and_offset = t
1584
+ return t
1585
+
1586
+ @property
1587
+ def exact_name(self) -> ta.Optional[str]:
1588
+ n, o = self.name_and_offset
1589
+ return n if not o else None
1590
+
1591
+ @property
1592
+ def effective_name(self) -> str:
1593
+ n, _ = self.name_and_offset
1594
+ return n
1542
1595
 
1543
1596
  #
1544
1597
 
1545
- class DEFAULT: # Noqa
1546
- def __new__(cls, *args, **kwargs): # noqa
1547
- raise TypeError
1598
+ def __str__(self) -> str:
1599
+ return self.exact_name or f'{self.effective_name}{int(self):+}'
1548
1600
 
1549
- class _NOT_SPECIFIED: # noqa
1550
- def __new__(cls, *args, **kwargs): # noqa
1551
- raise TypeError
1601
+ def __repr__(self) -> str:
1602
+ n, o = self.name_and_offset
1603
+ return f'{self.__class__.__name__}({n!r}{f", {int(o)}" if o else ""})'
1552
1604
 
1553
- @classmethod
1554
- def of(
1555
- cls,
1556
- obj: TimeoutLike,
1557
- default: ta.Union[TimeoutLike, ta.Type[_NOT_SPECIFIED]] = _NOT_SPECIFIED,
1558
- ) -> 'Timeout':
1559
- if obj is None:
1560
- return InfiniteTimeout()
1605
+ #
1561
1606
 
1562
- elif isinstance(obj, Timeout):
1563
- return obj
1607
+ CRITICAL: ta.ClassVar['NamedLogLevel']
1608
+ ERROR: ta.ClassVar['NamedLogLevel']
1609
+ WARNING: ta.ClassVar['NamedLogLevel']
1610
+ INFO: ta.ClassVar['NamedLogLevel']
1611
+ DEBUG: ta.ClassVar['NamedLogLevel']
1612
+ NOTSET: ta.ClassVar['NamedLogLevel']
1564
1613
 
1565
- elif isinstance(obj, (float, int)):
1566
- return DeadlineTimeout(cls._now() + obj)
1567
1614
 
1568
- elif isinstance(obj, ta.Iterable):
1569
- return CompositeTimeout(*[Timeout.of(c) for c in obj])
1615
+ NamedLogLevel.CRITICAL = NamedLogLevel(logging.CRITICAL)
1616
+ NamedLogLevel.ERROR = NamedLogLevel(logging.ERROR)
1617
+ NamedLogLevel.WARNING = NamedLogLevel(logging.WARNING)
1618
+ NamedLogLevel.INFO = NamedLogLevel(logging.INFO)
1619
+ NamedLogLevel.DEBUG = NamedLogLevel(logging.DEBUG)
1620
+ NamedLogLevel.NOTSET = NamedLogLevel(logging.NOTSET)
1570
1621
 
1571
- elif obj is Timeout.DEFAULT:
1572
- if default is Timeout._NOT_SPECIFIED or default is Timeout.DEFAULT:
1573
- raise RuntimeError('Must specify a default timeout')
1574
1622
 
1575
- else:
1576
- return Timeout.of(default) # type: ignore[arg-type]
1623
+ NamedLogLevel._CACHE.update({i: NamedLogLevel(i) for i in NamedLogLevel._NAMES_BY_INT}) # noqa
1577
1624
 
1578
- else:
1579
- raise TypeError(obj)
1580
1625
 
1581
- @classmethod
1582
- def of_deadline(cls, deadline: float) -> 'DeadlineTimeout':
1583
- return DeadlineTimeout(deadline)
1626
+ ########################################
1627
+ # ../../../omlish/logs/std/filters.py
1584
1628
 
1585
- @classmethod
1586
- def of_predicate(cls, expired_fn: ta.Callable[[], bool]) -> 'PredicateTimeout':
1587
- return PredicateTimeout(expired_fn)
1588
1629
 
1630
+ ##
1589
1631
 
1590
- class DeadlineTimeout(Timeout):
1591
- def __init__(
1592
- self,
1593
- deadline: float,
1594
- exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
1595
- ) -> None:
1596
- super().__init__()
1597
-
1598
- self.deadline = deadline
1599
- self.exc = exc
1600
-
1601
- @property
1602
- def can_expire(self) -> bool:
1603
- return True
1604
-
1605
- def expired(self) -> bool:
1606
- return not (self.remaining() > 0)
1607
-
1608
- def remaining(self) -> float:
1609
- return self.deadline - self._now()
1610
-
1611
- def __call__(self) -> float:
1612
- if (rem := self.remaining()) > 0:
1613
- return rem
1614
- raise self.exc
1615
-
1616
- def or_(self, o: ta.Any) -> ta.Any:
1617
- return self()
1618
-
1619
-
1620
- class InfiniteTimeout(Timeout):
1621
- @property
1622
- def can_expire(self) -> bool:
1623
- return False
1624
-
1625
- def expired(self) -> bool:
1626
- return False
1627
-
1628
- def remaining(self) -> float:
1629
- return float('inf')
1630
-
1631
- def __call__(self) -> float:
1632
- return float('inf')
1633
-
1634
- def or_(self, o: ta.Any) -> ta.Any:
1635
- return o
1636
-
1637
-
1638
- class CompositeTimeout(Timeout):
1639
- def __init__(self, *children: Timeout) -> None:
1640
- super().__init__()
1641
-
1642
- self.children = children
1643
-
1644
- @property
1645
- def can_expire(self) -> bool:
1646
- return any(c.can_expire for c in self.children)
1647
-
1648
- def expired(self) -> bool:
1649
- return any(c.expired() for c in self.children)
1650
-
1651
- def remaining(self) -> float:
1652
- return min(c.remaining() for c in self.children)
1653
-
1654
- def __call__(self) -> float:
1655
- return min(c() for c in self.children)
1656
-
1657
- def or_(self, o: ta.Any) -> ta.Any:
1658
- if self.can_expire:
1659
- return self()
1660
- return o
1661
-
1662
-
1663
- class PredicateTimeout(Timeout):
1664
- def __init__(
1665
- self,
1666
- expired_fn: ta.Callable[[], bool],
1667
- exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
1668
- ) -> None:
1669
- super().__init__()
1670
-
1671
- self.expired_fn = expired_fn
1672
- self.exc = exc
1673
-
1674
- @property
1675
- def can_expire(self) -> bool:
1676
- return True
1677
-
1678
- def expired(self) -> bool:
1679
- return self.expired_fn()
1680
-
1681
- def remaining(self) -> float:
1682
- return float('inf')
1683
-
1684
- def __call__(self) -> float:
1685
- if not self.expired_fn():
1686
- return float('inf')
1687
- raise self.exc
1688
-
1689
- def or_(self, o: ta.Any) -> ta.Any:
1690
- return self()
1691
-
1692
-
1693
- ########################################
1694
- # ../../../omlish/logs/filters.py
1695
-
1696
-
1697
- ##
1698
1632
 
1699
-
1700
- class TidLogFilter(logging.Filter):
1633
+ class TidLoggingFilter(logging.Filter):
1701
1634
  def filter(self, record):
1702
1635
  # FIXME: handle better - missing from wasm and cosmos
1703
1636
  if hasattr(threading, 'get_native_id'):
@@ -1708,13 +1641,13 @@ class TidLogFilter(logging.Filter):
1708
1641
 
1709
1642
 
1710
1643
  ########################################
1711
- # ../../../omlish/logs/proxy.py
1644
+ # ../../../omlish/logs/std/proxy.py
1712
1645
 
1713
1646
 
1714
1647
  ##
1715
1648
 
1716
1649
 
1717
- class ProxyLogFilterer(logging.Filterer):
1650
+ class ProxyLoggingFilterer(logging.Filterer):
1718
1651
  def __init__(self, underlying: logging.Filterer) -> None: # noqa
1719
1652
  self._underlying = underlying
1720
1653
 
@@ -1740,9 +1673,9 @@ class ProxyLogFilterer(logging.Filterer):
1740
1673
  return self._underlying.filter(record)
1741
1674
 
1742
1675
 
1743
- class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
1676
+ class ProxyLoggingHandler(ProxyLoggingFilterer, logging.Handler):
1744
1677
  def __init__(self, underlying: logging.Handler) -> None: # noqa
1745
- ProxyLogFilterer.__init__(self, underlying)
1678
+ ProxyLoggingFilterer.__init__(self, underlying)
1746
1679
 
1747
1680
  _underlying: logging.Handler
1748
1681
 
@@ -1833,7 +1766,7 @@ class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
1833
1766
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This file is dual licensed under the terms of the
1834
1767
  # Apache License, Version 2.0, and the BSD License. See the LICENSE file in the root of this repository for complete
1835
1768
  # details.
1836
- # https://github.com/pypa/packaging/blob/2c885fe91a54559e2382902dce28428ad2887be5/src/packaging/specifiers.py
1769
+ # https://github.com/pypa/packaging/blob/48125006684bb2d7d28c50af48a03176da45942d/src/packaging/specifiers.py
1837
1770
 
1838
1771
 
1839
1772
  ##
@@ -1981,7 +1914,7 @@ class Specifier(BaseSpecifier):
1981
1914
  ) -> None:
1982
1915
  match = self._regex.search(spec)
1983
1916
  if not match:
1984
- raise InvalidSpecifier(f"Invalid specifier: '{spec}'")
1917
+ raise InvalidSpecifier(f'Invalid specifier: {spec!r}')
1985
1918
 
1986
1919
  self._spec: ta.Tuple[str, str] = (
1987
1920
  match.group('operator').strip(),
@@ -1996,7 +1929,7 @@ class Specifier(BaseSpecifier):
1996
1929
  return self._prereleases
1997
1930
 
1998
1931
  operator, version = self._spec
1999
- if operator in ['==', '>=', '<=', '~=', '===']:
1932
+ if operator in ['==', '>=', '<=', '~=', '===', '>', '<']:
2000
1933
  if operator == '==' and version.endswith('.*'):
2001
1934
  version = version[:-2]
2002
1935
 
@@ -2122,40 +2055,38 @@ class Specifier(BaseSpecifier):
2122
2055
  return self.contains(item)
2123
2056
 
2124
2057
  def contains(self, item: UnparsedVersion, prereleases: ta.Optional[bool] = None) -> bool:
2125
- if prereleases is None:
2126
- prereleases = self.prereleases
2127
-
2128
- normalized_item = _coerce_version(item)
2129
-
2130
- if normalized_item.is_prerelease and not prereleases:
2131
- return False
2132
-
2133
- operator_callable: CallableVersionOperator = self._get_operator(self.operator)
2134
- return operator_callable(normalized_item, self.version)
2058
+ return bool(list(self.filter([item], prereleases=prereleases)))
2135
2059
 
2136
2060
  def filter(
2137
2061
  self,
2138
2062
  iterable: ta.Iterable[UnparsedVersionVar],
2139
2063
  prereleases: ta.Optional[bool] = None,
2140
2064
  ) -> ta.Iterator[UnparsedVersionVar]:
2141
- yielded = False
2142
- found_prereleases = []
2065
+ prereleases_versions = []
2066
+ found_non_prereleases = False
2067
+
2068
+ include_prereleases = (
2069
+ prereleases if prereleases is not None else self.prereleases
2070
+ )
2143
2071
 
2144
- kw = {'prereleases': prereleases if prereleases is not None else True}
2072
+ operator_callable = self._get_operator(self.operator)
2145
2073
 
2146
2074
  for version in iterable:
2147
2075
  parsed_version = _coerce_version(version)
2148
2076
 
2149
- if self.contains(parsed_version, **kw):
2150
- if parsed_version.is_prerelease and not (prereleases or self.prereleases):
2151
- found_prereleases.append(version)
2152
- else:
2153
- yielded = True
2077
+ if operator_callable(parsed_version, self.version):
2078
+ if not parsed_version.is_prerelease or include_prereleases:
2079
+ found_non_prereleases = True
2154
2080
  yield version
2081
+ elif prereleases is None and self._prereleases is not False:
2082
+ prereleases_versions.append(version)
2155
2083
 
2156
- if not yielded and found_prereleases:
2157
- for version in found_prereleases:
2158
- yield version
2084
+ if (
2085
+ not found_non_prereleases and
2086
+ prereleases is None and
2087
+ self._prereleases is not False
2088
+ ):
2089
+ yield from prereleases_versions
2159
2090
 
2160
2091
 
2161
2092
  _version_prefix_regex = re.compile(r'^([0-9]+)((?:a|b|c|rc)[0-9]+)$')
@@ -2206,12 +2137,15 @@ def _pad_version(left: ta.List[str], right: ta.List[str]) -> ta.Tuple[ta.List[st
2206
2137
  class SpecifierSet(BaseSpecifier):
2207
2138
  def __init__(
2208
2139
  self,
2209
- specifiers: str = '',
2140
+ specifiers: ta.Union[str, ta.Iterable['Specifier']] = '',
2210
2141
  prereleases: ta.Optional[bool] = None,
2211
2142
  ) -> None:
2212
- split_specifiers = [s.strip() for s in specifiers.split(',') if s.strip()]
2143
+ if isinstance(specifiers, str):
2144
+ split_specifiers = [s.strip() for s in specifiers.split(',') if s.strip()]
2145
+ self._specs = frozenset(map(Specifier, split_specifiers))
2146
+ else:
2147
+ self._specs = frozenset(specifiers)
2213
2148
 
2214
- self._specs = frozenset(map(Specifier, split_specifiers))
2215
2149
  self._prereleases = prereleases
2216
2150
 
2217
2151
  @property
@@ -2222,7 +2156,10 @@ class SpecifierSet(BaseSpecifier):
2222
2156
  if not self._specs:
2223
2157
  return None
2224
2158
 
2225
- return any(s.prereleases for s in self._specs)
2159
+ if any(s.prereleases for s in self._specs):
2160
+ return True
2161
+
2162
+ return None
2226
2163
 
2227
2164
  @prereleases.setter
2228
2165
  def prereleases(self, value: bool) -> None:
@@ -2247,7 +2184,7 @@ class SpecifierSet(BaseSpecifier):
2247
2184
  if isinstance(other, str):
2248
2185
  other = SpecifierSet(other)
2249
2186
  elif not isinstance(other, SpecifierSet):
2250
- return NotImplemented # type: ignore
2187
+ return NotImplemented
2251
2188
 
2252
2189
  specifier = SpecifierSet()
2253
2190
  specifier._specs = frozenset(self._specs | other._specs)
@@ -2267,6 +2204,7 @@ class SpecifierSet(BaseSpecifier):
2267
2204
  if isinstance(other, (str, Specifier)):
2268
2205
  other = SpecifierSet(str(other))
2269
2206
  elif not isinstance(other, SpecifierSet):
2207
+
2270
2208
  return NotImplemented
2271
2209
 
2272
2210
  return self._specs == other._specs
@@ -2289,28 +2227,22 @@ class SpecifierSet(BaseSpecifier):
2289
2227
  if not isinstance(item, Version):
2290
2228
  item = Version(item)
2291
2229
 
2292
- if prereleases is None:
2293
- prereleases = self.prereleases
2294
-
2295
- if not prereleases and item.is_prerelease:
2296
- return False
2297
-
2298
2230
  if installed and item.is_prerelease:
2299
- item = Version(item.base_version)
2231
+ prereleases = True
2300
2232
 
2301
- return all(s.contains(item, prereleases=prereleases) for s in self._specs)
2233
+ return bool(list(self.filter([item], prereleases=prereleases)))
2302
2234
 
2303
2235
  def filter(
2304
2236
  self,
2305
2237
  iterable: ta.Iterable[UnparsedVersionVar],
2306
2238
  prereleases: ta.Optional[bool] = None,
2307
2239
  ) -> ta.Iterator[UnparsedVersionVar]:
2308
- if prereleases is None:
2240
+ if prereleases is None and self.prereleases is not None:
2309
2241
  prereleases = self.prereleases
2310
2242
 
2311
2243
  if self._specs:
2312
2244
  for spec in self._specs:
2313
- iterable = spec.filter(iterable, prereleases=bool(prereleases))
2245
+ iterable = spec.filter(iterable, prereleases=prereleases)
2314
2246
  return iter(iterable)
2315
2247
 
2316
2248
  else:
@@ -2344,6 +2276,7 @@ TODO:
2344
2276
  - pre-run, post-run hooks
2345
2277
  - exitstack?
2346
2278
  - suggestion - difflib.get_close_matches
2279
+ - add_argument_group - group kw on ArgparseKwarg?
2347
2280
  """
2348
2281
 
2349
2282
 
@@ -2354,6 +2287,7 @@ TODO:
2354
2287
  class ArgparseArg:
2355
2288
  args: ta.Sequence[ta.Any]
2356
2289
  kwargs: ta.Mapping[str, ta.Any]
2290
+ group: ta.Optional[str] = None
2357
2291
  dest: ta.Optional[str] = None
2358
2292
 
2359
2293
  def __get__(self, instance, owner=None):
@@ -2363,7 +2297,11 @@ class ArgparseArg:
2363
2297
 
2364
2298
 
2365
2299
  def argparse_arg(*args, **kwargs) -> ArgparseArg:
2366
- return ArgparseArg(args, kwargs)
2300
+ return ArgparseArg(
2301
+ args=args,
2302
+ group=kwargs.pop('group', None),
2303
+ kwargs=kwargs,
2304
+ )
2367
2305
 
2368
2306
 
2369
2307
  def argparse_arg_(*args, **kwargs) -> ta.Any:
@@ -2533,101 +2471,733 @@ class ArgparseCli:
2533
2471
  subparser.set_defaults(_cmd=obj)
2534
2472
 
2535
2473
  elif isinstance(obj, ArgparseArg):
2474
+ if obj.group is not None:
2475
+ # FIXME: add_argument_group
2476
+ raise NotImplementedError
2477
+
2536
2478
  if att in anns:
2537
2479
  ann_kwargs = _get_argparse_arg_ann_kwargs(anns[att])
2538
2480
  obj.kwargs = {**ann_kwargs, **obj.kwargs}
2539
2481
 
2540
- if not obj.dest:
2541
- if 'dest' in obj.kwargs:
2542
- obj.dest = obj.kwargs['dest']
2543
- else:
2544
- obj.dest = obj.kwargs['dest'] = att # type: ignore
2482
+ if not obj.dest:
2483
+ if 'dest' in obj.kwargs:
2484
+ obj.dest = obj.kwargs['dest']
2485
+ else:
2486
+ obj.dest = obj.kwargs['dest'] = att # type: ignore
2487
+
2488
+ parser.add_argument(*obj.args, **obj.kwargs)
2489
+
2490
+ else:
2491
+ raise TypeError(obj)
2492
+
2493
+ #
2494
+
2495
+ _parser: ta.ClassVar[argparse.ArgumentParser]
2496
+
2497
+ @classmethod
2498
+ def get_parser(cls) -> argparse.ArgumentParser:
2499
+ return cls._parser
2500
+
2501
+ @property
2502
+ def argv(self) -> ta.Sequence[str]:
2503
+ return self._argv
2504
+
2505
+ @property
2506
+ def args(self) -> argparse.Namespace:
2507
+ return self._args
2508
+
2509
+ @property
2510
+ def unknown_args(self) -> ta.Sequence[str]:
2511
+ return self._unknown_args
2512
+
2513
+ #
2514
+
2515
+ def _bind_cli_cmd(self, cmd: ArgparseCmd) -> ta.Callable:
2516
+ return cmd.__get__(self, type(self))
2517
+
2518
+ def prepare_cli_run(self) -> ta.Optional[ta.Callable]:
2519
+ cmd = getattr(self.args, '_cmd', None)
2520
+
2521
+ if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
2522
+ msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
2523
+ if (parser := self.get_parser()).exit_on_error: # noqa
2524
+ parser.error(msg)
2525
+ else:
2526
+ raise argparse.ArgumentError(None, msg)
2527
+
2528
+ if cmd is None:
2529
+ self.get_parser().print_help()
2530
+ return None
2531
+
2532
+ return self._bind_cli_cmd(cmd)
2533
+
2534
+ #
2535
+
2536
+ def cli_run(self) -> ta.Optional[int]:
2537
+ if (fn := self.prepare_cli_run()) is None:
2538
+ return 0
2539
+
2540
+ return fn()
2541
+
2542
+ def cli_run_and_exit(self) -> ta.NoReturn:
2543
+ rc = self.cli_run()
2544
+ if not isinstance(rc, int):
2545
+ rc = 0
2546
+ raise SystemExit(rc)
2547
+
2548
+ def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
2549
+ if exit:
2550
+ return self.cli_run_and_exit()
2551
+ else:
2552
+ return self.cli_run()
2553
+
2554
+ #
2555
+
2556
+ async def async_cli_run(
2557
+ self,
2558
+ *,
2559
+ force_async: bool = False,
2560
+ ) -> ta.Optional[int]:
2561
+ if (fn := self.prepare_cli_run()) is None:
2562
+ return 0
2563
+
2564
+ if force_async:
2565
+ is_async = True
2566
+ else:
2567
+ tfn = fn
2568
+ if isinstance(tfn, ArgparseCmd):
2569
+ tfn = tfn.fn
2570
+ is_async = inspect.iscoroutinefunction(tfn)
2571
+
2572
+ if is_async:
2573
+ return await fn()
2574
+ else:
2575
+ return fn()
2576
+
2577
+
2578
+ ########################################
2579
+ # ../../../omlish/lite/maybes.py
2580
+
2581
+
2582
+ ##
2583
+
2584
+
2585
+ @functools.total_ordering
2586
+ class Maybe(ta.Generic[T]):
2587
+ class ValueNotPresentError(BaseException):
2588
+ pass
2589
+
2590
+ #
2591
+
2592
+ @property
2593
+ @abc.abstractmethod
2594
+ def present(self) -> bool:
2595
+ raise NotImplementedError
2596
+
2597
+ @abc.abstractmethod
2598
+ def must(self) -> T:
2599
+ raise NotImplementedError
2600
+
2601
+ #
2602
+
2603
+ @abc.abstractmethod
2604
+ def __repr__(self) -> str:
2605
+ raise NotImplementedError
2606
+
2607
+ @abc.abstractmethod
2608
+ def __hash__(self) -> int:
2609
+ raise NotImplementedError
2610
+
2611
+ @abc.abstractmethod
2612
+ def __eq__(self, other) -> bool:
2613
+ raise NotImplementedError
2614
+
2615
+ @abc.abstractmethod
2616
+ def __lt__(self, other) -> bool:
2617
+ raise NotImplementedError
2618
+
2619
+ #
2620
+
2621
+ @ta.final
2622
+ def __ne__(self, other):
2623
+ return not (self == other)
2624
+
2625
+ @ta.final
2626
+ def __iter__(self) -> ta.Iterator[T]:
2627
+ if self.present:
2628
+ yield self.must()
2629
+
2630
+ @ta.final
2631
+ def __bool__(self) -> ta.NoReturn:
2632
+ raise TypeError
2633
+
2634
+ #
2635
+
2636
+ @ta.final
2637
+ def if_present(self, consumer: ta.Callable[[T], None]) -> None:
2638
+ if self.present:
2639
+ consumer(self.must())
2640
+
2641
+ @ta.final
2642
+ def filter(self, predicate: ta.Callable[[T], bool]) -> 'Maybe[T]':
2643
+ if self.present and predicate(self.must()):
2644
+ return self
2645
+ else:
2646
+ return Maybe.empty()
2647
+
2648
+ @ta.final
2649
+ def map(self, mapper: ta.Callable[[T], U]) -> 'Maybe[U]':
2650
+ if self.present:
2651
+ return Maybe.just(mapper(self.must()))
2652
+ else:
2653
+ return Maybe.empty()
2654
+
2655
+ @ta.final
2656
+ def flat_map(self, mapper: ta.Callable[[T], 'Maybe[U]']) -> 'Maybe[U]':
2657
+ if self.present:
2658
+ if not isinstance(v := mapper(self.must()), Maybe):
2659
+ raise TypeError(v)
2660
+ return v
2661
+ else:
2662
+ return Maybe.empty()
2663
+
2664
+ @ta.final
2665
+ def or_else(self, other: ta.Union[T, U]) -> ta.Union[T, U]:
2666
+ if self.present:
2667
+ return self.must()
2668
+ else:
2669
+ return other
2670
+
2671
+ @ta.final
2672
+ def or_else_get(self, supplier: ta.Callable[[], ta.Union[T, U]]) -> ta.Union[T, U]:
2673
+ if self.present:
2674
+ return self.must()
2675
+ else:
2676
+ return supplier()
2677
+
2678
+ @ta.final
2679
+ def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> T:
2680
+ if self.present:
2681
+ return self.must()
2682
+ else:
2683
+ raise exception_supplier()
2684
+
2685
+ #
2686
+
2687
+ @classmethod
2688
+ def of_optional(cls, v: ta.Optional[T]) -> 'Maybe[T]':
2689
+ if v is not None:
2690
+ return cls.just(v)
2691
+ else:
2692
+ return cls.empty()
2693
+
2694
+ @classmethod
2695
+ def just(cls, v: T) -> 'Maybe[T]':
2696
+ return _JustMaybe(v)
2697
+
2698
+ _empty: ta.ClassVar['Maybe']
2699
+
2700
+ @classmethod
2701
+ def empty(cls) -> 'Maybe[T]':
2702
+ return Maybe._empty
2703
+
2704
+
2705
+ ##
2706
+
2707
+
2708
+ class _Maybe(Maybe[T], Abstract):
2709
+ def __lt__(self, other):
2710
+ if not isinstance(other, _Maybe):
2711
+ return NotImplemented
2712
+ sp = self.present
2713
+ op = other.present
2714
+ if self.present and other.present:
2715
+ return self.must() < other.must()
2716
+ else:
2717
+ return op and not sp
2718
+
2719
+
2720
+ @ta.final
2721
+ class _JustMaybe(_Maybe[T]):
2722
+ __slots__ = ('_v', '_hash')
2723
+
2724
+ def __init__(self, v: T) -> None:
2725
+ self._v = v
2726
+
2727
+ @property
2728
+ def present(self) -> bool:
2729
+ return True
2730
+
2731
+ def must(self) -> T:
2732
+ return self._v
2733
+
2734
+ #
2735
+
2736
+ def __repr__(self) -> str:
2737
+ return f'just({self._v!r})'
2738
+
2739
+ _hash: int
2740
+
2741
+ def __hash__(self) -> int:
2742
+ try:
2743
+ return self._hash
2744
+ except AttributeError:
2745
+ pass
2746
+ h = self._hash = hash((_JustMaybe, self._v))
2747
+ return h
2748
+
2749
+ def __eq__(self, other):
2750
+ return (
2751
+ self.__class__ is other.__class__ and
2752
+ self._v == other._v # noqa
2753
+ )
2754
+
2755
+
2756
+ @ta.final
2757
+ class _EmptyMaybe(_Maybe[T]):
2758
+ __slots__ = ()
2759
+
2760
+ @property
2761
+ def present(self) -> bool:
2762
+ return False
2763
+
2764
+ def must(self) -> T:
2765
+ raise Maybe.ValueNotPresentError
2766
+
2767
+ #
2768
+
2769
+ def __repr__(self) -> str:
2770
+ return 'empty()'
2771
+
2772
+ def __hash__(self) -> int:
2773
+ return hash(_EmptyMaybe)
2774
+
2775
+ def __eq__(self, other):
2776
+ return self.__class__ is other.__class__
2777
+
2778
+
2779
+ Maybe._empty = _EmptyMaybe() # noqa
2780
+
2781
+
2782
+ ##
2783
+
2784
+
2785
+ setattr(Maybe, 'just', _JustMaybe) # noqa
2786
+ setattr(Maybe, 'empty', functools.partial(operator.attrgetter('_empty'), Maybe))
2787
+
2788
+
2789
+ ########################################
2790
+ # ../../../omlish/lite/runtime.py
2791
+
2792
+
2793
+ ##
2794
+
2795
+
2796
+ @cached_nullary
2797
+ def is_debugger_attached() -> bool:
2798
+ return any(frame[1].endswith('pydevd.py') for frame in inspect.stack())
2799
+
2800
+
2801
+ LITE_REQUIRED_PYTHON_VERSION = (3, 8)
2802
+
2803
+
2804
+ def check_lite_runtime_version() -> None:
2805
+ if sys.version_info < LITE_REQUIRED_PYTHON_VERSION:
2806
+ raise OSError(f'Requires python {LITE_REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
2807
+
2808
+
2809
+ ########################################
2810
+ # ../../../omlish/lite/timeouts.py
2811
+ """
2812
+ TODO:
2813
+ - Event (/ Predicate)
2814
+ """
2815
+
2816
+
2817
+ ##
2818
+
2819
+
2820
+ class Timeout(Abstract):
2821
+ @property
2822
+ @abc.abstractmethod
2823
+ def can_expire(self) -> bool:
2824
+ """Indicates whether or not this timeout will ever expire."""
2825
+
2826
+ raise NotImplementedError
2827
+
2828
+ @abc.abstractmethod
2829
+ def expired(self) -> bool:
2830
+ """Return whether or not this timeout has expired."""
2831
+
2832
+ raise NotImplementedError
2833
+
2834
+ @abc.abstractmethod
2835
+ def remaining(self) -> float:
2836
+ """Returns the time (in seconds) remaining until the timeout expires. May be negative and/or infinite."""
2837
+
2838
+ raise NotImplementedError
2839
+
2840
+ @abc.abstractmethod
2841
+ def __call__(self) -> float:
2842
+ """Returns the time (in seconds) remaining until the timeout expires, or raises if the timeout has expired."""
2843
+
2844
+ raise NotImplementedError
2845
+
2846
+ @abc.abstractmethod
2847
+ def or_(self, o: ta.Any) -> ta.Any:
2848
+ """Evaluates time remaining via remaining() if this timeout can expire, otherwise returns `o`."""
2849
+
2850
+ raise NotImplementedError
2851
+
2852
+ #
2853
+
2854
+ @classmethod
2855
+ def _now(cls) -> float:
2856
+ return time.monotonic()
2857
+
2858
+ #
2859
+
2860
+ class DEFAULT: # Noqa
2861
+ def __new__(cls, *args, **kwargs): # noqa
2862
+ raise TypeError
2863
+
2864
+ class _NOT_SPECIFIED: # noqa
2865
+ def __new__(cls, *args, **kwargs): # noqa
2866
+ raise TypeError
2867
+
2868
+ @classmethod
2869
+ def of(
2870
+ cls,
2871
+ obj: TimeoutLike,
2872
+ default: ta.Union[TimeoutLike, ta.Type[_NOT_SPECIFIED]] = _NOT_SPECIFIED,
2873
+ ) -> 'Timeout':
2874
+ if obj is None:
2875
+ return InfiniteTimeout()
2876
+
2877
+ elif isinstance(obj, Timeout):
2878
+ return obj
2879
+
2880
+ elif isinstance(obj, (float, int)):
2881
+ return DeadlineTimeout(cls._now() + obj)
2882
+
2883
+ elif isinstance(obj, ta.Iterable):
2884
+ return CompositeTimeout(*[Timeout.of(c) for c in obj])
2885
+
2886
+ elif obj is Timeout.DEFAULT:
2887
+ if default is Timeout._NOT_SPECIFIED or default is Timeout.DEFAULT:
2888
+ raise RuntimeError('Must specify a default timeout')
2889
+
2890
+ else:
2891
+ return Timeout.of(default) # type: ignore[arg-type]
2892
+
2893
+ else:
2894
+ raise TypeError(obj)
2895
+
2896
+ @classmethod
2897
+ def of_deadline(cls, deadline: float) -> 'DeadlineTimeout':
2898
+ return DeadlineTimeout(deadline)
2899
+
2900
+ @classmethod
2901
+ def of_predicate(cls, expired_fn: ta.Callable[[], bool]) -> 'PredicateTimeout':
2902
+ return PredicateTimeout(expired_fn)
2903
+
2904
+
2905
+ class DeadlineTimeout(Timeout):
2906
+ def __init__(
2907
+ self,
2908
+ deadline: float,
2909
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
2910
+ ) -> None:
2911
+ super().__init__()
2912
+
2913
+ self.deadline = deadline
2914
+ self.exc = exc
2915
+
2916
+ @property
2917
+ def can_expire(self) -> bool:
2918
+ return True
2919
+
2920
+ def expired(self) -> bool:
2921
+ return not (self.remaining() > 0)
2922
+
2923
+ def remaining(self) -> float:
2924
+ return self.deadline - self._now()
2925
+
2926
+ def __call__(self) -> float:
2927
+ if (rem := self.remaining()) > 0:
2928
+ return rem
2929
+ raise self.exc
2930
+
2931
+ def or_(self, o: ta.Any) -> ta.Any:
2932
+ return self()
2933
+
2934
+
2935
+ class InfiniteTimeout(Timeout):
2936
+ @property
2937
+ def can_expire(self) -> bool:
2938
+ return False
2939
+
2940
+ def expired(self) -> bool:
2941
+ return False
2942
+
2943
+ def remaining(self) -> float:
2944
+ return float('inf')
2945
+
2946
+ def __call__(self) -> float:
2947
+ return float('inf')
2948
+
2949
+ def or_(self, o: ta.Any) -> ta.Any:
2950
+ return o
2951
+
2952
+
2953
+ class CompositeTimeout(Timeout):
2954
+ def __init__(self, *children: Timeout) -> None:
2955
+ super().__init__()
2956
+
2957
+ self.children = children
2958
+
2959
+ @property
2960
+ def can_expire(self) -> bool:
2961
+ return any(c.can_expire for c in self.children)
2962
+
2963
+ def expired(self) -> bool:
2964
+ return any(c.expired() for c in self.children)
2965
+
2966
+ def remaining(self) -> float:
2967
+ return min(c.remaining() for c in self.children)
2968
+
2969
+ def __call__(self) -> float:
2970
+ return min(c() for c in self.children)
2971
+
2972
+ def or_(self, o: ta.Any) -> ta.Any:
2973
+ if self.can_expire:
2974
+ return self()
2975
+ return o
2976
+
2977
+
2978
+ class PredicateTimeout(Timeout):
2979
+ def __init__(
2980
+ self,
2981
+ expired_fn: ta.Callable[[], bool],
2982
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
2983
+ ) -> None:
2984
+ super().__init__()
2985
+
2986
+ self.expired_fn = expired_fn
2987
+ self.exc = exc
2988
+
2989
+ @property
2990
+ def can_expire(self) -> bool:
2991
+ return True
2992
+
2993
+ def expired(self) -> bool:
2994
+ return self.expired_fn()
2995
+
2996
+ def remaining(self) -> float:
2997
+ return float('inf')
2998
+
2999
+ def __call__(self) -> float:
3000
+ if not self.expired_fn():
3001
+ return float('inf')
3002
+ raise self.exc
3003
+
3004
+ def or_(self, o: ta.Any) -> ta.Any:
3005
+ return self()
3006
+
3007
+
3008
+ ########################################
3009
+ # ../../../omlish/logs/protocols.py
3010
+
3011
+
3012
+ ##
3013
+
3014
+
3015
+ @ta.runtime_checkable
3016
+ class LoggerLike(ta.Protocol):
3017
+ """Satisfied by both our Logger and stdlib logging.Logger."""
3018
+
3019
+ def isEnabledFor(self, level: LogLevel) -> bool: ... # noqa
3020
+
3021
+ def getEffectiveLevel(self) -> LogLevel: ... # noqa
3022
+
3023
+ #
3024
+
3025
+ def log(self, level: LogLevel, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
3026
+
3027
+ def debug(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
3028
+
3029
+ def info(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
3030
+
3031
+ def warning(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
3032
+
3033
+ def error(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
3034
+
3035
+ def exception(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
3036
+
3037
+ def critical(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
3038
+
3039
+
3040
+ ########################################
3041
+ # ../../../omlish/logs/std/json.py
3042
+ """
3043
+ TODO:
3044
+ - translate json keys
3045
+ """
3046
+
3047
+
3048
+ ##
3049
+
3050
+
3051
+ class JsonLoggingFormatter(logging.Formatter):
3052
+ KEYS: ta.Mapping[str, bool] = {
3053
+ 'name': False,
3054
+ 'msg': False,
3055
+ 'args': False,
3056
+ 'levelname': False,
3057
+ 'levelno': False,
3058
+ 'pathname': False,
3059
+ 'filename': False,
3060
+ 'module': False,
3061
+ 'exc_info': True,
3062
+ 'exc_text': True,
3063
+ 'stack_info': True,
3064
+ 'lineno': False,
3065
+ 'funcName': False,
3066
+ 'created': False,
3067
+ 'msecs': False,
3068
+ 'relativeCreated': False,
3069
+ 'thread': False,
3070
+ 'threadName': False,
3071
+ 'processName': False,
3072
+ 'process': False,
3073
+ }
3074
+
3075
+ def __init__(
3076
+ self,
3077
+ *args: ta.Any,
3078
+ json_dumps: ta.Optional[ta.Callable[[ta.Any], str]] = None,
3079
+ **kwargs: ta.Any,
3080
+ ) -> None:
3081
+ super().__init__(*args, **kwargs)
3082
+
3083
+ if json_dumps is None:
3084
+ json_dumps = json_dumps_compact
3085
+ self._json_dumps = json_dumps
3086
+
3087
+ def format(self, record: logging.LogRecord) -> str:
3088
+ dct = {
3089
+ k: v
3090
+ for k, o in self.KEYS.items()
3091
+ for v in [getattr(record, k)]
3092
+ if not (o and v is None)
3093
+ }
3094
+ return self._json_dumps(dct)
3095
+
3096
+
3097
+ ########################################
3098
+ # ../types.py
3099
+
3100
+
3101
+ ##
2545
3102
 
2546
- parser.add_argument(*obj.args, **obj.kwargs)
2547
3103
 
2548
- else:
2549
- raise TypeError(obj)
3104
+ # See https://peps.python.org/pep-3149/
3105
+ INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
3106
+ ('debug', 'd'),
3107
+ ('threaded', 't'),
3108
+ ])
2550
3109
 
2551
- #
3110
+ INTERP_OPT_ATTRS_BY_GLYPH: ta.Mapping[str, str] = collections.OrderedDict(
3111
+ (g, a) for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items()
3112
+ )
2552
3113
 
2553
- _parser: ta.ClassVar[argparse.ArgumentParser]
3114
+
3115
+ @dc.dataclass(frozen=True)
3116
+ class InterpOpts:
3117
+ threaded: bool = False
3118
+ debug: bool = False
3119
+
3120
+ def __str__(self) -> str:
3121
+ return ''.join(g for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items() if getattr(self, a))
2554
3122
 
2555
3123
  @classmethod
2556
- def get_parser(cls) -> argparse.ArgumentParser:
2557
- return cls._parser
3124
+ def parse(cls, s: str) -> 'InterpOpts':
3125
+ return cls(**{INTERP_OPT_ATTRS_BY_GLYPH[g]: True for g in s})
2558
3126
 
2559
- @property
2560
- def argv(self) -> ta.Sequence[str]:
2561
- return self._argv
3127
+ @classmethod
3128
+ def parse_suffix(cls, s: str) -> ta.Tuple[str, 'InterpOpts']:
3129
+ kw = {}
3130
+ while s and (a := INTERP_OPT_ATTRS_BY_GLYPH.get(s[-1])):
3131
+ s, kw[a] = s[:-1], True
3132
+ return s, cls(**kw)
2562
3133
 
2563
- @property
2564
- def args(self) -> argparse.Namespace:
2565
- return self._args
2566
3134
 
2567
- @property
2568
- def unknown_args(self) -> ta.Sequence[str]:
2569
- return self._unknown_args
3135
+ ##
2570
3136
 
2571
- #
2572
3137
 
2573
- def _bind_cli_cmd(self, cmd: ArgparseCmd) -> ta.Callable:
2574
- return cmd.__get__(self, type(self))
3138
+ @dc.dataclass(frozen=True)
3139
+ class InterpVersion:
3140
+ version: Version
3141
+ opts: InterpOpts
2575
3142
 
2576
- def prepare_cli_run(self) -> ta.Optional[ta.Callable]:
2577
- cmd = getattr(self.args, '_cmd', None)
3143
+ def __str__(self) -> str:
3144
+ return str(self.version) + str(self.opts)
2578
3145
 
2579
- if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
2580
- msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
2581
- if (parser := self.get_parser()).exit_on_error:
2582
- parser.error(msg)
2583
- else:
2584
- raise argparse.ArgumentError(None, msg)
3146
+ @classmethod
3147
+ def parse(cls, s: str) -> 'InterpVersion':
3148
+ s, o = InterpOpts.parse_suffix(s)
3149
+ v = Version(s)
3150
+ return cls(
3151
+ version=v,
3152
+ opts=o,
3153
+ )
2585
3154
 
2586
- if cmd is None:
2587
- self.get_parser().print_help()
3155
+ @classmethod
3156
+ def try_parse(cls, s: str) -> ta.Optional['InterpVersion']:
3157
+ try:
3158
+ return cls.parse(s)
3159
+ except (KeyError, InvalidVersion):
2588
3160
  return None
2589
3161
 
2590
- return self._bind_cli_cmd(cmd)
2591
3162
 
2592
- #
3163
+ ##
2593
3164
 
2594
- def cli_run(self) -> ta.Optional[int]:
2595
- if (fn := self.prepare_cli_run()) is None:
2596
- return 0
2597
3165
 
2598
- return fn()
3166
+ @dc.dataclass(frozen=True)
3167
+ class InterpSpecifier:
3168
+ specifier: Specifier
3169
+ opts: InterpOpts
2599
3170
 
2600
- def cli_run_and_exit(self) -> ta.NoReturn:
2601
- sys.exit(rc if isinstance(rc := self.cli_run(), int) else 0)
3171
+ def __str__(self) -> str:
3172
+ return str(self.specifier) + str(self.opts)
2602
3173
 
2603
- def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
2604
- if exit:
2605
- return self.cli_run_and_exit()
2606
- else:
2607
- return self.cli_run()
3174
+ @classmethod
3175
+ def parse(cls, s: str) -> 'InterpSpecifier':
3176
+ s, o = InterpOpts.parse_suffix(s)
3177
+ if not any(s.startswith(o) for o in Specifier.OPERATORS):
3178
+ if s.count('.') < 2:
3179
+ s = '~=' + s + '.0'
3180
+ else:
3181
+ s = '==' + s
3182
+ return cls(
3183
+ specifier=Specifier(s),
3184
+ opts=o,
3185
+ )
2608
3186
 
2609
- #
3187
+ def contains(self, iv: InterpVersion) -> bool:
3188
+ return self.specifier.contains(iv.version) and self.opts == iv.opts
2610
3189
 
2611
- async def async_cli_run(
2612
- self,
2613
- *,
2614
- force_async: bool = False,
2615
- ) -> ta.Optional[int]:
2616
- if (fn := self.prepare_cli_run()) is None:
2617
- return 0
3190
+ def __contains__(self, iv: InterpVersion) -> bool:
3191
+ return self.contains(iv)
2618
3192
 
2619
- if force_async:
2620
- is_async = True
2621
- else:
2622
- tfn = fn
2623
- if isinstance(tfn, ArgparseCmd):
2624
- tfn = tfn.fn
2625
- is_async = inspect.iscoroutinefunction(tfn)
2626
3193
 
2627
- if is_async:
2628
- return await fn()
2629
- else:
2630
- return fn()
3194
+ ##
3195
+
3196
+
3197
+ @dc.dataclass(frozen=True)
3198
+ class Interp:
3199
+ exe: str
3200
+ version: InterpVersion
2631
3201
 
2632
3202
 
2633
3203
  ########################################
@@ -2680,7 +3250,7 @@ def check_valid_injector_key_cls(cls: T) -> T:
2680
3250
  ##
2681
3251
 
2682
3252
 
2683
- class InjectorProvider(abc.ABC):
3253
+ class InjectorProvider(Abstract):
2684
3254
  @abc.abstractmethod
2685
3255
  def provider_fn(self) -> InjectorProviderFn:
2686
3256
  raise NotImplementedError
@@ -2699,7 +3269,7 @@ class InjectorBinding:
2699
3269
  check.isinstance(self.provider, InjectorProvider)
2700
3270
 
2701
3271
 
2702
- class InjectorBindings(abc.ABC):
3272
+ class InjectorBindings(Abstract):
2703
3273
  @abc.abstractmethod
2704
3274
  def bindings(self) -> ta.Iterator[InjectorBinding]:
2705
3275
  raise NotImplementedError
@@ -2707,7 +3277,7 @@ class InjectorBindings(abc.ABC):
2707
3277
  ##
2708
3278
 
2709
3279
 
2710
- class Injector(abc.ABC):
3280
+ class Injector(Abstract):
2711
3281
  @abc.abstractmethod
2712
3282
  def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
2713
3283
  raise NotImplementedError
@@ -2966,14 +3536,12 @@ def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) ->
2966
3536
  # scopes
2967
3537
 
2968
3538
 
2969
- class InjectorScope(abc.ABC): # noqa
3539
+ class InjectorScope(Abstract):
2970
3540
  def __init__(
2971
3541
  self,
2972
3542
  *,
2973
3543
  _i: Injector,
2974
3544
  ) -> None:
2975
- check.not_in(abc.ABC, type(self).__bases__)
2976
-
2977
3545
  super().__init__()
2978
3546
 
2979
3547
  self._i = _i
@@ -3004,7 +3572,7 @@ class InjectorScope(abc.ABC): # noqa
3004
3572
  raise NotImplementedError
3005
3573
 
3006
3574
 
3007
- class ExclusiveInjectorScope(InjectorScope, abc.ABC):
3575
+ class ExclusiveInjectorScope(InjectorScope, Abstract):
3008
3576
  _st: ta.Optional[InjectorScope.State] = None
3009
3577
 
3010
3578
  def state(self) -> InjectorScope.State:
@@ -3020,12 +3588,13 @@ class ExclusiveInjectorScope(InjectorScope, abc.ABC):
3020
3588
  self._st = None
3021
3589
 
3022
3590
 
3023
- class ContextvarInjectorScope(InjectorScope, abc.ABC):
3591
+ class ContextvarInjectorScope(InjectorScope, Abstract):
3024
3592
  _cv: contextvars.ContextVar
3025
3593
 
3026
3594
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
3027
3595
  super().__init_subclass__(**kwargs)
3028
3596
 
3597
+ check.not_in(Abstract, cls.__bases__)
3029
3598
  check.not_in(abc.ABC, cls.__bases__)
3030
3599
  check.state(not hasattr(cls, '_cv'))
3031
3600
  cls._cv = contextvars.ContextVar(f'{cls.__name__}_cv')
@@ -3531,7 +4100,7 @@ class InjectorBinder:
3531
4100
  pws: ta.List[ta.Any] = []
3532
4101
  if in_ is not None:
3533
4102
  check.issubclass(in_, InjectorScope)
3534
- check.not_in(abc.ABC, in_.__bases__)
4103
+ check.not_in(Abstract, in_.__bases__)
3535
4104
  pws.append(functools.partial(ScopedInjectorProvider, k=key, sc=in_))
3536
4105
  if singleton:
3537
4106
  pws.append(SingletonInjectorProvider)
@@ -3732,80 +4301,129 @@ inj = InjectionApi()
3732
4301
 
3733
4302
 
3734
4303
  ########################################
3735
- # ../../../omlish/lite/runtime.py
4304
+ # ../../../omlish/logs/std/standard.py
4305
+ """
4306
+ TODO:
4307
+ - structured
4308
+ - prefixed
4309
+ - debug
4310
+ - optional noisy? noisy will never be lite - some kinda configure_standard callback mechanism?
4311
+ """
3736
4312
 
3737
4313
 
3738
4314
  ##
3739
4315
 
3740
4316
 
3741
- @cached_nullary
3742
- def is_debugger_attached() -> bool:
3743
- return any(frame[1].endswith('pydevd.py') for frame in inspect.stack())
4317
+ STANDARD_LOG_FORMAT_PARTS = [
4318
+ ('asctime', '%(asctime)-15s'),
4319
+ ('process', 'pid=%(process)s'),
4320
+ ('thread', 'tid=%(thread)x'),
4321
+ ('levelname', '%(levelname)s'),
4322
+ ('name', '%(name)s'),
4323
+ ('separator', '::'),
4324
+ ('message', '%(message)s'),
4325
+ ]
3744
4326
 
3745
4327
 
3746
- LITE_REQUIRED_PYTHON_VERSION = (3, 8)
4328
+ class StandardLoggingFormatter(logging.Formatter):
4329
+ @staticmethod
4330
+ def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
4331
+ return ' '.join(v for k, v in parts)
3747
4332
 
4333
+ converter = datetime.datetime.fromtimestamp # type: ignore
3748
4334
 
3749
- def check_lite_runtime_version() -> None:
3750
- if sys.version_info < LITE_REQUIRED_PYTHON_VERSION:
3751
- raise OSError(f'Requires python {LITE_REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
4335
+ def formatTime(self, record, datefmt=None):
4336
+ ct = self.converter(record.created)
4337
+ if datefmt:
4338
+ return ct.strftime(datefmt) # noqa
4339
+ else:
4340
+ t = ct.strftime('%Y-%m-%d %H:%M:%S')
4341
+ return '%s.%03d' % (t, record.msecs) # noqa
3752
4342
 
3753
4343
 
3754
- ########################################
3755
- # ../../../omlish/logs/json.py
3756
- """
3757
- TODO:
3758
- - translate json keys
3759
- """
4344
+ ##
4345
+
4346
+
4347
+ class StandardConfiguredLoggingHandler(ProxyLoggingHandler):
4348
+ def __init_subclass__(cls, **kwargs):
4349
+ raise TypeError('This class serves only as a marker and should not be subclassed.')
3760
4350
 
3761
4351
 
3762
4352
  ##
3763
4353
 
3764
4354
 
3765
- class JsonLogFormatter(logging.Formatter):
3766
- KEYS: ta.Mapping[str, bool] = {
3767
- 'name': False,
3768
- 'msg': False,
3769
- 'args': False,
3770
- 'levelname': False,
3771
- 'levelno': False,
3772
- 'pathname': False,
3773
- 'filename': False,
3774
- 'module': False,
3775
- 'exc_info': True,
3776
- 'exc_text': True,
3777
- 'stack_info': True,
3778
- 'lineno': False,
3779
- 'funcName': False,
3780
- 'created': False,
3781
- 'msecs': False,
3782
- 'relativeCreated': False,
3783
- 'thread': False,
3784
- 'threadName': False,
3785
- 'processName': False,
3786
- 'process': False,
3787
- }
4355
+ @contextlib.contextmanager
4356
+ def _locking_logging_module_lock() -> ta.Iterator[None]:
4357
+ if hasattr(logging, '_acquireLock'):
4358
+ logging._acquireLock() # noqa
4359
+ try:
4360
+ yield
4361
+ finally:
4362
+ logging._releaseLock() # type: ignore # noqa
4363
+
4364
+ elif hasattr(logging, '_lock'):
4365
+ # https://github.com/python/cpython/commit/74723e11109a320e628898817ab449b3dad9ee96
4366
+ with logging._lock: # noqa
4367
+ yield
4368
+
4369
+ else:
4370
+ raise Exception("Can't find lock in logging module")
4371
+
4372
+
4373
+ def configure_standard_logging(
4374
+ level: ta.Union[int, str] = logging.INFO,
4375
+ *,
4376
+ target: ta.Optional[logging.Logger] = None,
4377
+
4378
+ force: bool = False,
4379
+
4380
+ handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
4381
+
4382
+ formatter: ta.Optional[logging.Formatter] = None, # noqa
4383
+ json: bool = False,
4384
+ ) -> ta.Optional[StandardConfiguredLoggingHandler]:
4385
+ with _locking_logging_module_lock():
4386
+ if target is None:
4387
+ target = logging.root
4388
+
4389
+ #
4390
+
4391
+ if not force:
4392
+ if any(isinstance(h, StandardConfiguredLoggingHandler) for h in list(target.handlers)):
4393
+ return None
4394
+
4395
+ #
4396
+
4397
+ if handler_factory is not None:
4398
+ handler = handler_factory()
4399
+ else:
4400
+ handler = logging.StreamHandler()
4401
+
4402
+ #
4403
+
4404
+ if formatter is None:
4405
+ if json:
4406
+ formatter = JsonLoggingFormatter()
4407
+ else:
4408
+ formatter = StandardLoggingFormatter(StandardLoggingFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS)) # noqa
4409
+ handler.setFormatter(formatter)
4410
+
4411
+ #
4412
+
4413
+ handler.addFilter(TidLoggingFilter())
3788
4414
 
3789
- def __init__(
3790
- self,
3791
- *args: ta.Any,
3792
- json_dumps: ta.Optional[ta.Callable[[ta.Any], str]] = None,
3793
- **kwargs: ta.Any,
3794
- ) -> None:
3795
- super().__init__(*args, **kwargs)
4415
+ #
3796
4416
 
3797
- if json_dumps is None:
3798
- json_dumps = json_dumps_compact
3799
- self._json_dumps = json_dumps
4417
+ target.addHandler(handler)
3800
4418
 
3801
- def format(self, record: logging.LogRecord) -> str:
3802
- dct = {
3803
- k: v
3804
- for k, o in self.KEYS.items()
3805
- for v in [getattr(record, k)]
3806
- if not (o and v is None)
3807
- }
3808
- return self._json_dumps(dct)
4419
+ #
4420
+
4421
+ if level is not None:
4422
+ target.setLevel(level)
4423
+
4424
+ #
4425
+
4426
+ return StandardConfiguredLoggingHandler(handler)
3809
4427
 
3810
4428
 
3811
4429
  ########################################
@@ -3924,7 +4542,7 @@ SubprocessRun._FIELD_NAMES = frozenset(fld.name for fld in dc.fields(SubprocessR
3924
4542
  ##
3925
4543
 
3926
4544
 
3927
- class SubprocessRunnable(abc.ABC, ta.Generic[T]):
4545
+ class SubprocessRunnable(Abstract, ta.Generic[T]):
3928
4546
  @abc.abstractmethod
3929
4547
  def make_run(self) -> SubprocessRun:
3930
4548
  raise NotImplementedError
@@ -3957,233 +4575,6 @@ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
3957
4575
  return self.handle_run_output(await self.make_run().maysync_run(maysync_subprocesses, **kwargs))
3958
4576
 
3959
4577
 
3960
- ########################################
3961
- # ../types.py
3962
-
3963
-
3964
- ##
3965
-
3966
-
3967
- # See https://peps.python.org/pep-3149/
3968
- INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
3969
- ('debug', 'd'),
3970
- ('threaded', 't'),
3971
- ])
3972
-
3973
- INTERP_OPT_ATTRS_BY_GLYPH: ta.Mapping[str, str] = collections.OrderedDict(
3974
- (g, a) for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items()
3975
- )
3976
-
3977
-
3978
- @dc.dataclass(frozen=True)
3979
- class InterpOpts:
3980
- threaded: bool = False
3981
- debug: bool = False
3982
-
3983
- def __str__(self) -> str:
3984
- return ''.join(g for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items() if getattr(self, a))
3985
-
3986
- @classmethod
3987
- def parse(cls, s: str) -> 'InterpOpts':
3988
- return cls(**{INTERP_OPT_ATTRS_BY_GLYPH[g]: True for g in s})
3989
-
3990
- @classmethod
3991
- def parse_suffix(cls, s: str) -> ta.Tuple[str, 'InterpOpts']:
3992
- kw = {}
3993
- while s and (a := INTERP_OPT_ATTRS_BY_GLYPH.get(s[-1])):
3994
- s, kw[a] = s[:-1], True
3995
- return s, cls(**kw)
3996
-
3997
-
3998
- ##
3999
-
4000
-
4001
- @dc.dataclass(frozen=True)
4002
- class InterpVersion:
4003
- version: Version
4004
- opts: InterpOpts
4005
-
4006
- def __str__(self) -> str:
4007
- return str(self.version) + str(self.opts)
4008
-
4009
- @classmethod
4010
- def parse(cls, s: str) -> 'InterpVersion':
4011
- s, o = InterpOpts.parse_suffix(s)
4012
- v = Version(s)
4013
- return cls(
4014
- version=v,
4015
- opts=o,
4016
- )
4017
-
4018
- @classmethod
4019
- def try_parse(cls, s: str) -> ta.Optional['InterpVersion']:
4020
- try:
4021
- return cls.parse(s)
4022
- except (KeyError, InvalidVersion):
4023
- return None
4024
-
4025
-
4026
- ##
4027
-
4028
-
4029
- @dc.dataclass(frozen=True)
4030
- class InterpSpecifier:
4031
- specifier: Specifier
4032
- opts: InterpOpts
4033
-
4034
- def __str__(self) -> str:
4035
- return str(self.specifier) + str(self.opts)
4036
-
4037
- @classmethod
4038
- def parse(cls, s: str) -> 'InterpSpecifier':
4039
- s, o = InterpOpts.parse_suffix(s)
4040
- if not any(s.startswith(o) for o in Specifier.OPERATORS):
4041
- s = '~=' + s
4042
- if s.count('.') < 2:
4043
- s += '.0'
4044
- return cls(
4045
- specifier=Specifier(s),
4046
- opts=o,
4047
- )
4048
-
4049
- def contains(self, iv: InterpVersion) -> bool:
4050
- return self.specifier.contains(iv.version) and self.opts == iv.opts
4051
-
4052
- def __contains__(self, iv: InterpVersion) -> bool:
4053
- return self.contains(iv)
4054
-
4055
-
4056
- ##
4057
-
4058
-
4059
- @dc.dataclass(frozen=True)
4060
- class Interp:
4061
- exe: str
4062
- version: InterpVersion
4063
-
4064
-
4065
- ########################################
4066
- # ../../../omlish/logs/standard.py
4067
- """
4068
- TODO:
4069
- - structured
4070
- - prefixed
4071
- - debug
4072
- - optional noisy? noisy will never be lite - some kinda configure_standard callback mechanism?
4073
- """
4074
-
4075
-
4076
- ##
4077
-
4078
-
4079
- STANDARD_LOG_FORMAT_PARTS = [
4080
- ('asctime', '%(asctime)-15s'),
4081
- ('process', 'pid=%(process)s'),
4082
- ('thread', 'tid=%(thread)x'),
4083
- ('levelname', '%(levelname)s'),
4084
- ('name', '%(name)s'),
4085
- ('separator', '::'),
4086
- ('message', '%(message)s'),
4087
- ]
4088
-
4089
-
4090
- class StandardLogFormatter(logging.Formatter):
4091
- @staticmethod
4092
- def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
4093
- return ' '.join(v for k, v in parts)
4094
-
4095
- converter = datetime.datetime.fromtimestamp # type: ignore
4096
-
4097
- def formatTime(self, record, datefmt=None):
4098
- ct = self.converter(record.created)
4099
- if datefmt:
4100
- return ct.strftime(datefmt) # noqa
4101
- else:
4102
- t = ct.strftime('%Y-%m-%d %H:%M:%S')
4103
- return '%s.%03d' % (t, record.msecs) # noqa
4104
-
4105
-
4106
- ##
4107
-
4108
-
4109
- class StandardConfiguredLogHandler(ProxyLogHandler):
4110
- def __init_subclass__(cls, **kwargs):
4111
- raise TypeError('This class serves only as a marker and should not be subclassed.')
4112
-
4113
-
4114
- ##
4115
-
4116
-
4117
- @contextlib.contextmanager
4118
- def _locking_logging_module_lock() -> ta.Iterator[None]:
4119
- if hasattr(logging, '_acquireLock'):
4120
- logging._acquireLock() # noqa
4121
- try:
4122
- yield
4123
- finally:
4124
- logging._releaseLock() # type: ignore # noqa
4125
-
4126
- elif hasattr(logging, '_lock'):
4127
- # https://github.com/python/cpython/commit/74723e11109a320e628898817ab449b3dad9ee96
4128
- with logging._lock: # noqa
4129
- yield
4130
-
4131
- else:
4132
- raise Exception("Can't find lock in logging module")
4133
-
4134
-
4135
- def configure_standard_logging(
4136
- level: ta.Union[int, str] = logging.INFO,
4137
- *,
4138
- json: bool = False,
4139
- target: ta.Optional[logging.Logger] = None,
4140
- force: bool = False,
4141
- handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
4142
- ) -> ta.Optional[StandardConfiguredLogHandler]:
4143
- with _locking_logging_module_lock():
4144
- if target is None:
4145
- target = logging.root
4146
-
4147
- #
4148
-
4149
- if not force:
4150
- if any(isinstance(h, StandardConfiguredLogHandler) for h in list(target.handlers)):
4151
- return None
4152
-
4153
- #
4154
-
4155
- if handler_factory is not None:
4156
- handler = handler_factory()
4157
- else:
4158
- handler = logging.StreamHandler()
4159
-
4160
- #
4161
-
4162
- formatter: logging.Formatter
4163
- if json:
4164
- formatter = JsonLogFormatter()
4165
- else:
4166
- formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
4167
- handler.setFormatter(formatter)
4168
-
4169
- #
4170
-
4171
- handler.addFilter(TidLogFilter())
4172
-
4173
- #
4174
-
4175
- target.addHandler(handler)
4176
-
4177
- #
4178
-
4179
- if level is not None:
4180
- target.setLevel(level)
4181
-
4182
- #
4183
-
4184
- return StandardConfiguredLogHandler(handler)
4185
-
4186
-
4187
4578
  ########################################
4188
4579
  # ../../../omlish/subprocesses/wrap.py
4189
4580
  """
@@ -4225,13 +4616,13 @@ TODO:
4225
4616
  ##
4226
4617
 
4227
4618
 
4228
- class InterpProvider(abc.ABC):
4619
+ class InterpProvider(Abstract):
4229
4620
  name: ta.ClassVar[str]
4230
4621
 
4231
4622
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
4232
4623
  super().__init_subclass__(**kwargs)
4233
4624
 
4234
- if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
4625
+ if Abstract not in cls.__bases__ and 'name' not in cls.__dict__:
4235
4626
  sfx = 'InterpProvider'
4236
4627
  if not cls.__name__.endswith(sfx):
4237
4628
  raise NameError(cls)
@@ -4297,13 +4688,17 @@ class VerboseCalledProcessError(subprocess.CalledProcessError):
4297
4688
  return msg
4298
4689
 
4299
4690
 
4300
- class BaseSubprocesses(abc.ABC): # noqa
4301
- DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
4691
+ class BaseSubprocesses(Abstract):
4692
+ DEFAULT_LOGGER: ta.ClassVar[ta.Optional[LoggerLike]] = None
4693
+
4694
+ PIPE: ta.ClassVar[int] = subprocess.PIPE
4695
+ STDOUT: ta.ClassVar[int] = subprocess.STDOUT
4696
+ DEVNULL: ta.ClassVar[int] = subprocess.DEVNULL
4302
4697
 
4303
4698
  def __init__(
4304
4699
  self,
4305
4700
  *,
4306
- log: ta.Optional[logging.Logger] = None,
4701
+ log: ta.Optional[LoggerLike] = None,
4307
4702
  try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
4308
4703
  ) -> None:
4309
4704
  super().__init__()
@@ -4311,7 +4706,7 @@ class BaseSubprocesses(abc.ABC): # noqa
4311
4706
  self._log = log if log is not None else self.DEFAULT_LOGGER
4312
4707
  self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
4313
4708
 
4314
- def set_logger(self, log: ta.Optional[logging.Logger]) -> None:
4709
+ def set_logger(self, log: ta.Optional[LoggerLike]) -> None:
4315
4710
  self._log = log
4316
4711
 
4317
4712
  #
@@ -4562,7 +4957,7 @@ class InterpResolver:
4562
4957
  ##
4563
4958
 
4564
4959
 
4565
- class AbstractAsyncSubprocesses(BaseSubprocesses):
4960
+ class AbstractAsyncSubprocesses(BaseSubprocesses, Abstract):
4566
4961
  @abc.abstractmethod
4567
4962
  def run_(self, run: SubprocessRun) -> ta.Awaitable[SubprocessRunOutput]:
4568
4963
  raise NotImplementedError
@@ -4669,7 +5064,7 @@ class AsyncioProcessCommunicator:
4669
5064
  proc: asyncio.subprocess.Process,
4670
5065
  loop: ta.Optional[ta.Any] = None,
4671
5066
  *,
4672
- log: ta.Optional[logging.Logger] = None,
5067
+ log: ta.Optional[LoggerLike] = None,
4673
5068
  ) -> None:
4674
5069
  super().__init__()
4675
5070
 
@@ -4892,7 +5287,7 @@ class InterpInspector:
4892
5287
  def __init__(
4893
5288
  self,
4894
5289
  *,
4895
- log: ta.Optional[logging.Logger] = None,
5290
+ log: ta.Optional[LoggerLike] = None,
4896
5291
  ) -> None:
4897
5292
  super().__init__()
4898
5293
 
@@ -5056,7 +5451,7 @@ class Uv:
5056
5451
  self,
5057
5452
  config: UvConfig = UvConfig(),
5058
5453
  *,
5059
- log: ta.Optional[logging.Logger] = None,
5454
+ log: ta.Optional[LoggerLike] = None,
5060
5455
  ) -> None:
5061
5456
  super().__init__()
5062
5457
 
@@ -5148,7 +5543,7 @@ class SystemInterpProvider(InterpProvider):
5148
5543
  options: Options = Options(),
5149
5544
  *,
5150
5545
  inspector: ta.Optional[InterpInspector] = None,
5151
- log: ta.Optional[logging.Logger] = None,
5546
+ log: ta.Optional[LoggerLike] = None,
5152
5547
  ) -> None:
5153
5548
  super().__init__()
5154
5549
 
@@ -5328,7 +5723,7 @@ THREADED_PYENV_INSTALL_OPTS = PyenvInstallOpts(conf_opts=['--disable-gil'])
5328
5723
  #
5329
5724
 
5330
5725
 
5331
- class PyenvInstallOptsProvider(abc.ABC):
5726
+ class PyenvInstallOptsProvider(Abstract):
5332
5727
  @abc.abstractmethod
5333
5728
  def opts(self) -> ta.Awaitable[PyenvInstallOpts]:
5334
5729
  raise NotImplementedError
@@ -5519,19 +5914,49 @@ uv run pip
5519
5914
  uv run --python 3.11.6 pip
5520
5915
  uv venv --python 3.11.6 --seed barf
5521
5916
  python3 -m venv barf && barf/bin/pip install uv && barf/bin/uv venv --python 3.11.6 --seed barf2
5917
+ uv python find '3.13.10'
5918
+ uv python list --output-format=json
5522
5919
  """
5523
5920
 
5524
5921
 
5525
5922
  ##
5526
5923
 
5527
5924
 
5925
+ @dc.dataclass(frozen=True)
5926
+ class UvPythonListOutput:
5927
+ key: str
5928
+ version: str
5929
+
5930
+ @dc.dataclass(frozen=True)
5931
+ class VersionParts:
5932
+ major: int
5933
+ minor: int
5934
+ patch: int
5935
+
5936
+ version_parts: VersionParts
5937
+
5938
+ path: ta.Optional[str]
5939
+ symlink: ta.Optional[str]
5940
+
5941
+ url: str
5942
+
5943
+ os: str # emscripten linux macos
5944
+ variant: str # default freethreaded
5945
+ implementation: str # cpython graalpy pyodide pypy
5946
+ arch: str # aarch64 wasm32 x86_64
5947
+ libc: str # gnu musl none
5948
+
5949
+
5950
+ ##
5951
+
5952
+
5528
5953
  class UvInterpProvider(InterpProvider):
5529
5954
  def __init__(
5530
5955
  self,
5531
5956
  *,
5532
5957
  pyenv: Uv,
5533
5958
  inspector: InterpInspector,
5534
- log: ta.Optional[logging.Logger] = None,
5959
+ log: ta.Optional[LoggerLike] = None,
5535
5960
  ) -> None:
5536
5961
  super().__init__()
5537
5962
 
@@ -5545,6 +5970,12 @@ class UvInterpProvider(InterpProvider):
5545
5970
  async def get_installed_version(self, version: InterpVersion) -> Interp:
5546
5971
  raise NotImplementedError
5547
5972
 
5973
+ # async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
5974
+ # return []
5975
+
5976
+ # async def install_version(self, version: InterpVersion) -> Interp:
5977
+ # raise TypeError
5978
+
5548
5979
 
5549
5980
  ########################################
5550
5981
  # ../providers/inject.py
@@ -5588,7 +6019,7 @@ class PyenvInterpProvider(InterpProvider):
5588
6019
  *,
5589
6020
  pyenv: Pyenv,
5590
6021
  inspector: InterpInspector,
5591
- log: ta.Optional[logging.Logger] = None,
6022
+ log: ta.Optional[LoggerLike] = None,
5592
6023
  ) -> None:
5593
6024
  super().__init__()
5594
6025