omdev 0.0.0.dev440__py3-none-any.whl → 0.0.0.dev495__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of omdev might be problematic. Click here for more details.

Files changed (148) hide show
  1. omdev/.omlish-manifests.json +18 -30
  2. omdev/README.md +51 -0
  3. omdev/__about__.py +11 -7
  4. omdev/amalg/gen/gen.py +49 -6
  5. omdev/amalg/gen/imports.py +1 -1
  6. omdev/amalg/gen/manifests.py +1 -1
  7. omdev/amalg/gen/resources.py +1 -1
  8. omdev/amalg/gen/srcfiles.py +13 -3
  9. omdev/amalg/gen/strip.py +1 -1
  10. omdev/amalg/gen/types.py +1 -1
  11. omdev/amalg/gen/typing.py +1 -1
  12. omdev/amalg/info.py +32 -0
  13. omdev/cache/data/actions.py +1 -1
  14. omdev/cache/data/specs.py +1 -1
  15. omdev/cexts/_boilerplate.cc +2 -3
  16. omdev/cexts/cmake.py +4 -1
  17. omdev/ci/cli.py +2 -3
  18. omdev/cli/clicli.py +37 -7
  19. omdev/cmdlog/cli.py +1 -2
  20. omdev/dataclasses/_dumping.py +1960 -0
  21. omdev/dataclasses/_template.py +22 -0
  22. omdev/dataclasses/cli.py +7 -2
  23. omdev/dataclasses/codegen.py +340 -60
  24. omdev/dataclasses/dumping.py +200 -0
  25. omdev/interp/cli.py +1 -1
  26. omdev/interp/types.py +3 -2
  27. omdev/interp/uv/provider.py +37 -0
  28. omdev/interp/venvs.py +1 -0
  29. omdev/irc/messages/base.py +50 -0
  30. omdev/irc/messages/formats.py +92 -0
  31. omdev/irc/messages/messages.py +775 -0
  32. omdev/irc/messages/parsing.py +99 -0
  33. omdev/irc/numerics/__init__.py +0 -0
  34. omdev/irc/numerics/formats.py +97 -0
  35. omdev/irc/numerics/numerics.py +865 -0
  36. omdev/irc/numerics/types.py +59 -0
  37. omdev/irc/protocol/LICENSE +11 -0
  38. omdev/irc/protocol/__init__.py +61 -0
  39. omdev/irc/protocol/consts.py +6 -0
  40. omdev/irc/protocol/errors.py +30 -0
  41. omdev/irc/protocol/message.py +21 -0
  42. omdev/irc/protocol/nuh.py +55 -0
  43. omdev/irc/protocol/parsing.py +158 -0
  44. omdev/irc/protocol/rendering.py +153 -0
  45. omdev/irc/protocol/tags.py +102 -0
  46. omdev/irc/protocol/utils.py +30 -0
  47. omdev/manifests/_dumping.py +125 -25
  48. omdev/manifests/main.py +1 -1
  49. omdev/markdown/__init__.py +0 -0
  50. omdev/markdown/incparse.py +116 -0
  51. omdev/markdown/tokens.py +51 -0
  52. omdev/packaging/marshal.py +8 -8
  53. omdev/packaging/requires.py +6 -6
  54. omdev/packaging/revisions.py +1 -1
  55. omdev/packaging/specifiers.py +2 -1
  56. omdev/packaging/versions.py +4 -4
  57. omdev/packaging/wheelfile.py +2 -0
  58. omdev/precheck/blanklines.py +66 -0
  59. omdev/precheck/caches.py +1 -1
  60. omdev/precheck/imports.py +14 -1
  61. omdev/precheck/main.py +4 -3
  62. omdev/precheck/unicode.py +39 -15
  63. omdev/py/asts/__init__.py +0 -0
  64. omdev/py/asts/parents.py +28 -0
  65. omdev/py/asts/toplevel.py +123 -0
  66. omdev/py/asts/visitors.py +18 -0
  67. omdev/py/attrdocs.py +1 -1
  68. omdev/py/bracepy.py +12 -4
  69. omdev/py/reprs.py +32 -0
  70. omdev/py/srcheaders.py +1 -1
  71. omdev/py/tokens/__init__.py +0 -0
  72. omdev/py/tools/mkrelimp.py +1 -1
  73. omdev/py/tools/pipdepup.py +686 -0
  74. omdev/pyproject/cli.py +1 -1
  75. omdev/pyproject/pkg.py +190 -45
  76. omdev/pyproject/reqs.py +31 -9
  77. omdev/pyproject/tools/__init__.py +0 -0
  78. omdev/pyproject/tools/aboutdeps.py +60 -0
  79. omdev/pyproject/venvs.py +8 -1
  80. omdev/rs/__init__.py +0 -0
  81. omdev/scripts/ci.py +752 -98
  82. omdev/scripts/interp.py +232 -39
  83. omdev/scripts/lib/inject.py +74 -27
  84. omdev/scripts/lib/logs.py +187 -43
  85. omdev/scripts/lib/marshal.py +67 -25
  86. omdev/scripts/pyproject.py +1369 -143
  87. omdev/tools/git/cli.py +10 -0
  88. omdev/tools/json/formats.py +2 -0
  89. omdev/tools/json/processing.py +5 -2
  90. omdev/tools/jsonview/cli.py +49 -65
  91. omdev/tools/jsonview/resources/jsonview.html.j2 +43 -0
  92. omdev/tools/pawk/README.md +195 -0
  93. omdev/tools/pawk/pawk.py +2 -2
  94. omdev/tools/pip.py +8 -0
  95. omdev/tui/__init__.py +0 -0
  96. omdev/tui/apps/__init__.py +0 -0
  97. omdev/tui/apps/edit/__init__.py +0 -0
  98. omdev/tui/apps/edit/main.py +167 -0
  99. omdev/tui/apps/irc/__init__.py +0 -0
  100. omdev/tui/apps/irc/__main__.py +4 -0
  101. omdev/tui/apps/irc/app.py +286 -0
  102. omdev/tui/apps/irc/client.py +187 -0
  103. omdev/tui/apps/irc/commands.py +175 -0
  104. omdev/tui/apps/irc/main.py +26 -0
  105. omdev/tui/apps/markdown/__init__.py +0 -0
  106. omdev/tui/apps/markdown/__main__.py +11 -0
  107. omdev/{ptk → tui/apps}/markdown/cli.py +5 -7
  108. omdev/tui/rich/__init__.py +46 -0
  109. omdev/tui/rich/console2.py +20 -0
  110. omdev/tui/rich/markdown2.py +186 -0
  111. omdev/tui/textual/__init__.py +265 -0
  112. omdev/tui/textual/app2.py +16 -0
  113. omdev/tui/textual/autocomplete/LICENSE +21 -0
  114. omdev/tui/textual/autocomplete/__init__.py +33 -0
  115. omdev/tui/textual/autocomplete/matching.py +226 -0
  116. omdev/tui/textual/autocomplete/paths.py +202 -0
  117. omdev/tui/textual/autocomplete/widget.py +612 -0
  118. omdev/tui/textual/debug/__init__.py +10 -0
  119. omdev/tui/textual/debug/dominfo.py +151 -0
  120. omdev/tui/textual/debug/screen.py +24 -0
  121. omdev/tui/textual/devtools.py +187 -0
  122. omdev/tui/textual/drivers2.py +55 -0
  123. omdev/tui/textual/logging2.py +20 -0
  124. omdev/tui/textual/types.py +45 -0
  125. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/METADATA +15 -9
  126. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/RECORD +135 -80
  127. omdev/ptk/__init__.py +0 -103
  128. omdev/ptk/apps/ncdu.py +0 -167
  129. omdev/ptk/confirm.py +0 -60
  130. omdev/ptk/markdown/LICENSE +0 -22
  131. omdev/ptk/markdown/__init__.py +0 -10
  132. omdev/ptk/markdown/__main__.py +0 -11
  133. omdev/ptk/markdown/border.py +0 -94
  134. omdev/ptk/markdown/markdown.py +0 -390
  135. omdev/ptk/markdown/parser.py +0 -42
  136. omdev/ptk/markdown/styles.py +0 -29
  137. omdev/ptk/markdown/tags.py +0 -299
  138. omdev/ptk/markdown/utils.py +0 -366
  139. omdev/pyproject/cexts.py +0 -110
  140. /omdev/{ptk/apps → irc}/__init__.py +0 -0
  141. /omdev/{tokens → irc/messages}/__init__.py +0 -0
  142. /omdev/{tokens → py/tokens}/all.py +0 -0
  143. /omdev/{tokens → py/tokens}/tokenizert.py +0 -0
  144. /omdev/{tokens → py/tokens}/utils.py +0 -0
  145. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/WHEEL +0 -0
  146. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/entry_points.txt +0 -0
  147. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/licenses/LICENSE +0 -0
  148. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/top_level.txt +0 -0
@@ -27,6 +27,7 @@ See:
27
27
  """
28
28
  import abc
29
29
  import argparse
30
+ import ast
30
31
  import asyncio
31
32
  import asyncio.base_subprocess
32
33
  import asyncio.subprocess
@@ -52,6 +53,7 @@ import itertools
52
53
  import json
53
54
  import logging
54
55
  import multiprocessing as mp
56
+ import operator
55
57
  import os
56
58
  import os.path
57
59
  import re
@@ -63,6 +65,7 @@ import subprocess
63
65
  import sys
64
66
  import tarfile
65
67
  import tempfile
68
+ import textwrap
66
69
  import threading
67
70
  import time
68
71
  import traceback
@@ -80,6 +83,84 @@ if sys.version_info < (3, 8):
80
83
  raise OSError(f'Requires python (3, 8), got {sys.version_info} from {sys.executable}') # noqa
81
84
 
82
85
 
86
+ def __omlish_amalg__(): # noqa
87
+ return dict(
88
+ src_files=[
89
+ dict(path='../magic/magic.py', sha1='16a7598eac927e7994d78b9f851dd6cd1fce34c9'),
90
+ dict(path='../magic/prepare.py', sha1='a9b6bd7408d86a52fab7aae2c522032fb251cb8e'),
91
+ dict(path='../magic/styles.py', sha1='60fc56d089669eaad72c5c134b91bc69d4bc107e'),
92
+ dict(path='../packaging/versions.py', sha1='71627ad600b3529b829b0e227b0952f2c63c7271'),
93
+ dict(path='../packaging/wheelfile.py', sha1='2e1113819aa85ed00e1fe25c71a2b3dbc0a383f5'),
94
+ dict(path='../../omlish/formats/toml/parser.py', sha1='73dac82289350ab951c4bcdbfe61167fa221f26f'),
95
+ dict(path='../../omlish/formats/toml/writer.py', sha1='6ea41d7e724bb1dcf6bd84b88993ff4e8798e021'),
96
+ dict(path='../../omlish/lite/abstract.py', sha1='a2fc3f3697fa8de5247761e9d554e70176f37aac'),
97
+ dict(path='../../omlish/lite/asyncs.py', sha1='b3f2251c56617ce548abf9c333ac996b63edb23e'),
98
+ dict(path='../../omlish/lite/cached.py', sha1='0c33cf961ac8f0727284303c7a30c5ea98f714f2'),
99
+ dict(path='../../omlish/lite/check.py', sha1='bb6b6b63333699b84462951a854d99ae83195b94'),
100
+ dict(path='../../omlish/lite/json.py', sha1='57eeddc4d23a17931e00284ffa5cb6e3ce089486'),
101
+ dict(path='../../omlish/lite/objects.py', sha1='9566bbf3530fd71fcc56321485216b592fae21e9'),
102
+ dict(path='../../omlish/lite/reflect.py', sha1='c4fec44bf144e9d93293c996af06f6c65fc5e63d'),
103
+ dict(path='../../omlish/lite/strings.py', sha1='89831ecbc34ad80e118a865eceb390ed399dc4d6'),
104
+ dict(path='../../omlish/lite/typing.py', sha1='c501ff8f9ed08202e8016eaa098526d4deede834'),
105
+ dict(path='../../omlish/logs/levels.py', sha1='91405563d082a5eba874da82aac89d83ce7b6152'),
106
+ dict(path='../../omlish/logs/std/filters.py', sha1='f36aab646d84d31e295b33aaaaa6f8b67ff38b3d'),
107
+ dict(path='../../omlish/logs/std/proxy.py', sha1='3e7301a2aa351127f9c85f61b2f85dcc3f15aafb'),
108
+ dict(path='../../omlish/logs/warnings.py', sha1='c4eb694b24773351107fcc058f3620f1dbfb6799'),
109
+ dict(path='../cexts/magic.py', sha1='4e5ce6732454f75c9dd27352959708d8fa7b1666'),
110
+ dict(path='../magic/find.py', sha1='436228a9cf1d8bab6b9234d09f72913b0960382f'),
111
+ dict(path='../packaging/specifiers.py', sha1='a56ab4e8c9b174adb523921f6280ac41e0fce749'),
112
+ dict(path='../../omlish/argparse/cli.py', sha1='f4dc3cd353d14386b5da0306768700e396afd2b3'),
113
+ dict(path='../../omlish/lite/marshal.py', sha1='96348f5f2a26dc27d842d33cc3927e9da163436b'),
114
+ dict(path='../../omlish/lite/maybes.py', sha1='bdf5136654ccd14b6a072588cad228925bdfbabd'),
115
+ dict(path='../../omlish/lite/runtime.py', sha1='2e752a27ae2bf89b1bb79b4a2da522a3ec360c70'),
116
+ dict(path='../../omlish/lite/timeouts.py', sha1='a0f673033a6943f242e35848d78a41892b9c62a1'),
117
+ dict(path='../../omlish/logs/infos.py', sha1='4dd104bd468a8c438601dd0bbda619b47d2f1620'),
118
+ dict(path='../../omlish/logs/protocols.py', sha1='05ca4d1d7feb50c4e3b9f22ee371aa7bf4b3dbd1'),
119
+ dict(path='../../omlish/logs/std/json.py', sha1='2a75553131e4d5331bb0cedde42aa183f403fc3b'),
120
+ dict(path='../interp/types.py', sha1='caf068a6e81fb6e221d777b341ac5777d92b8091'),
121
+ dict(path='../packaging/requires.py', sha1='5818353abd45135e0e638e28fa6247b24122231b'),
122
+ dict(path='../../omlish/asyncs/asyncio/timeouts.py', sha1='4d31b02b3c39b8f2fa7e94db36552fde6942e36a'),
123
+ dict(path='../../omlish/lite/inject.py', sha1='6f097e3170019a34ff6834d36fcc9cbeed3a7ab4'),
124
+ dict(path='../../omlish/logs/contexts.py', sha1='1000a6d5ddfb642865ca532e34b1d50759781cf0'),
125
+ dict(path='../../omlish/logs/std/standard.py', sha1='5c97c1b9f7ead58d6127d047b873398f708f288d'),
126
+ dict(path='../../omlish/subprocesses/run.py', sha1='8200e48f0c49d164df3503cd0143038d0c4d30aa'),
127
+ dict(path='../../omlish/subprocesses/wrap.py', sha1='8a9b7d2255481fae15c05f5624b0cdc0766f4b3f'),
128
+ dict(path='../interp/providers/base.py', sha1='f5d068c21f230d742e9015b033cd6320f4c68898'),
129
+ dict(path='../../omlish/logs/base.py', sha1='8d06faee05fead6b1dd98c9035a5b042af4aebb1'),
130
+ dict(path='../../omlish/logs/std/records.py', sha1='8bbf6ef9eccb3a012c6ca416ddf3969450fd8fc9'),
131
+ dict(path='../../omlish/subprocesses/base.py', sha1='cb9f668be5422fecb27222caabb67daac6c1bab9'),
132
+ dict(path='../interp/resolvers.py', sha1='817b8e76401cd7a19eb43ca54d65272e4c8a4b0e'),
133
+ dict(path='../../omlish/logs/asyncs.py', sha1='ab11b70033d9f2e9a4e70254185aa1c6130c6077'),
134
+ dict(path='../../omlish/logs/std/loggers.py', sha1='a569179445d6a8a942b5dcfad1d1f77702868803'),
135
+ dict(path='../../omlish/subprocesses/asyncs.py', sha1='bba44d524c24c6ac73168aee6343488414e5bf48'),
136
+ dict(path='../../omlish/subprocesses/sync.py', sha1='8434919eba4da67825773d56918fdc0cb2f1883b'),
137
+ dict(path='../git/revisions.py', sha1='a26b5afa568313e034b6b2d3a5d2dd0b065979d4'),
138
+ dict(path='../../omlish/asyncs/asyncio/subprocesses.py', sha1='b6b5f9ae3fd0b9c83593bad2e04a08f726e5904d'),
139
+ dict(path='../../omlish/logs/modules.py', sha1='dd7d5f8e63fe8829dfb49460f3929ab64b68ee14'),
140
+ dict(path='../interp/inspect.py', sha1='736287b4ec8d14a8c30afa0ba23996fdc0662caa'),
141
+ dict(path='../interp/pyenv/pyenv.py', sha1='d1f6e657c671c1b1a5b0e627284df656fe2d10d3'),
142
+ dict(path='../interp/uv/uv.py', sha1='8c6515cd6755efab3972da92a285e94ccb255515'),
143
+ dict(path='../packaging/revisions.py', sha1='9ba90e4a93b1bfcc93f6ca65dbaaf38f79929677'),
144
+ dict(path='reqs.py', sha1='822e265b0d2e6d9548ee24d3ac60c81066e40ee8'),
145
+ dict(path='../interp/providers/running.py', sha1='85c9cc69ff6fbd6c8cf78ed6262619a30856c2f1'),
146
+ dict(path='../interp/providers/system.py', sha1='9638a154475ca98775159d27739563ac7fb2eb16'),
147
+ dict(path='../interp/pyenv/install.py', sha1='4a10a19717364b4ba9f3b8bf1d12621cf21ba8b8'),
148
+ dict(path='../interp/uv/provider.py', sha1='3c3980878ad2b9fd2cd02172f9424954759c7f06'),
149
+ dict(path='pkg.py', sha1='a7b64fcf267ba385442393b90c9711af08ba9ac3'),
150
+ dict(path='../interp/providers/inject.py', sha1='7cc9ebf58cf2ec09545321456bd9da9f9a3a79fb'),
151
+ dict(path='../interp/pyenv/provider.py', sha1='377542ce01a35849e2a5b4a4dbafedc26882f983'),
152
+ dict(path='../interp/uv/inject.py', sha1='e95d058c2340baa5a3155ec3440f311d1daa10a8'),
153
+ dict(path='../interp/pyenv/inject.py', sha1='b8fb68f5a7cae86c70fe1bad6c29a8b2dfc985c3'),
154
+ dict(path='../interp/inject.py', sha1='b039abbadf0b096d2724182af2e0ebda2a230852'),
155
+ dict(path='../interp/default.py', sha1='a799969a0d3f4b57538587b13ceb08f6334ebc16'),
156
+ dict(path='../interp/venvs.py', sha1='9ba8f2c3131d7d519d5cf36ca69b75f9c6fe2b27'),
157
+ dict(path='configs.py', sha1='7b1c1ed034ecb728d67ff15e3bb2b21a218773c9'),
158
+ dict(path='venvs.py', sha1='9f1935171017aeb802da56e14d7f41d632a7aa25'),
159
+ dict(path='cli.py', sha1='77efd5e792baa941a79adef6b363751dbd6a0d3e'),
160
+ ],
161
+ )
162
+
163
+
83
164
  ########################################
84
165
 
85
166
 
@@ -96,8 +177,10 @@ TomlParseFloat = ta.Callable[[str], ta.Any] # ta.TypeAlias
96
177
  TomlKey = ta.Tuple[str, ...] # ta.TypeAlias
97
178
  TomlPos = int # ta.TypeAlias
98
179
 
99
- # ../../omlish/lite/cached.py
180
+ # ../../omlish/lite/abstract.py
100
181
  T = ta.TypeVar('T')
182
+
183
+ # ../../omlish/lite/cached.py
101
184
  CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
102
185
 
103
186
  # ../../omlish/lite/check.py
@@ -137,6 +220,11 @@ LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
137
220
  LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
138
221
  LoggingContextInfo = ta.Any # ta.TypeAlias
139
222
 
223
+ # ../packaging/requires.py
224
+ RequiresMarkerVar = ta.Union['RequiresVariable', 'RequiresValue'] # ta.TypeAlias
225
+ RequiresMarkerAtom = ta.Union['RequiresMarkerItem', ta.Sequence['RequiresMarkerAtom']] # ta.TypeAlias
226
+ RequiresMarkerList = ta.Sequence[ta.Union['RequiresMarkerList', 'RequiresMarkerAtom', str]] # ta.TypeAlias
227
+
140
228
  # ../../omlish/asyncs/asyncio/timeouts.py
141
229
  AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
142
230
 
@@ -361,12 +449,12 @@ class _BaseVersion:
361
449
 
362
450
  def __lt__(self, other: '_BaseVersion') -> bool:
363
451
  if not isinstance(other, _BaseVersion):
364
- return NotImplemented # type: ignore
452
+ return NotImplemented
365
453
  return self._key < other._key
366
454
 
367
455
  def __le__(self, other: '_BaseVersion') -> bool:
368
456
  if not isinstance(other, _BaseVersion):
369
- return NotImplemented # type: ignore
457
+ return NotImplemented
370
458
  return self._key <= other._key
371
459
 
372
460
  def __eq__(self, other: object) -> bool:
@@ -376,12 +464,12 @@ class _BaseVersion:
376
464
 
377
465
  def __ge__(self, other: '_BaseVersion') -> bool:
378
466
  if not isinstance(other, _BaseVersion):
379
- return NotImplemented # type: ignore
467
+ return NotImplemented
380
468
  return self._key >= other._key
381
469
 
382
470
  def __gt__(self, other: '_BaseVersion') -> bool:
383
471
  if not isinstance(other, _BaseVersion):
384
- return NotImplemented # type: ignore
472
+ return NotImplemented
385
473
  return self._key > other._key
386
474
 
387
475
  def __ne__(self, other: object) -> bool:
@@ -765,11 +853,13 @@ class WheelFile(zipfile.ZipFile):
765
853
  @staticmethod
766
854
  def _urlsafe_b64encode(data: bytes) -> bytes:
767
855
  """urlsafe_b64encode without padding"""
856
+
768
857
  return base64.urlsafe_b64encode(data).rstrip(b'=')
769
858
 
770
859
  @staticmethod
771
860
  def _urlsafe_b64decode(data: bytes) -> bytes:
772
861
  """urlsafe_b64decode without padding"""
862
+
773
863
  pad = b'=' * (4 - (len(data) & 3))
774
864
  return base64.urlsafe_b64decode(data + pad)
775
865
 
@@ -1959,25 +2049,49 @@ def is_abstract_method(obj: ta.Any) -> bool:
1959
2049
  return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
1960
2050
 
1961
2051
 
1962
- def update_abstracts(cls, *, force=False):
2052
+ def compute_abstract_methods(cls: type) -> ta.FrozenSet[str]:
2053
+ # ~> https://github.com/python/cpython/blob/f3476c6507381ca860eec0989f53647b13517423/Modules/_abc.c#L358
2054
+
2055
+ # Stage 1: direct abstract methods
2056
+
2057
+ abstracts = {
2058
+ a
2059
+ # Get items as a list to avoid mutation issues during iteration
2060
+ for a, v in list(cls.__dict__.items())
2061
+ if is_abstract_method(v)
2062
+ }
2063
+
2064
+ # Stage 2: inherited abstract methods
2065
+
2066
+ for base in cls.__bases__:
2067
+ # Get __abstractmethods__ from base if it exists
2068
+ if (base_abstracts := getattr(base, _ABSTRACT_METHODS_ATTR, None)) is None:
2069
+ continue
2070
+
2071
+ # Iterate over abstract methods in base
2072
+ for key in base_abstracts:
2073
+ # Check if this class has an attribute with this name
2074
+ try:
2075
+ value = getattr(cls, key)
2076
+ except AttributeError:
2077
+ # Attribute not found in this class, skip
2078
+ continue
2079
+
2080
+ # Check if it's still abstract
2081
+ if is_abstract_method(value):
2082
+ abstracts.add(key)
2083
+
2084
+ return frozenset(abstracts)
2085
+
2086
+
2087
+ def update_abstracts(cls: ta.Type[T], *, force: bool = False) -> ta.Type[T]:
1963
2088
  if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
1964
2089
  # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
1965
2090
  # implementation (especially during testing), and we want to handle both cases.
1966
2091
  return cls
1967
2092
 
1968
- abstracts: ta.Set[str] = set()
1969
-
1970
- for scls in cls.__bases__:
1971
- for name in getattr(scls, _ABSTRACT_METHODS_ATTR, ()):
1972
- value = getattr(cls, name, None)
1973
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
1974
- abstracts.add(name)
1975
-
1976
- for name, value in cls.__dict__.items():
1977
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
1978
- abstracts.add(name)
1979
-
1980
- setattr(cls, _ABSTRACT_METHODS_ATTR, frozenset(abstracts))
2093
+ abstracts = compute_abstract_methods(cls)
2094
+ setattr(cls, _ABSTRACT_METHODS_ATTR, abstracts)
1981
2095
  return cls
1982
2096
 
1983
2097
 
@@ -2031,23 +2145,26 @@ class Abstract:
2031
2145
  super().__init_subclass__(**kwargs)
2032
2146
 
2033
2147
  if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
2034
- ams = {a: cls for a, o in cls.__dict__.items() if is_abstract_method(o)}
2148
+ if ams := compute_abstract_methods(cls):
2149
+ amd = {
2150
+ a: mcls
2151
+ for mcls in cls.__mro__[::-1]
2152
+ for a in ams
2153
+ if a in mcls.__dict__
2154
+ }
2035
2155
 
2036
- seen = set(cls.__dict__)
2037
- for b in cls.__bases__:
2038
- ams.update({a: b for a in set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen}) # noqa
2039
- seen.update(dir(b))
2040
-
2041
- if ams:
2042
2156
  raise AbstractTypeError(
2043
2157
  f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
2044
2158
  ', '.join(sorted([
2045
2159
  '.'.join([
2046
- *([m] if (m := getattr(c, '__module__')) else []),
2047
- getattr(c, '__qualname__', getattr(c, '__name__')),
2160
+ *([
2161
+ *([m] if (m := getattr(c, '__module__')) else []),
2162
+ getattr(c, '__qualname__', getattr(c, '__name__')),
2163
+ ] if c is not None else '?'),
2048
2164
  a,
2049
2165
  ])
2050
- for a, c in ams.items()
2166
+ for a in ams
2167
+ for c in [amd.get(a)]
2051
2168
  ])),
2052
2169
  )
2053
2170
 
@@ -2064,6 +2181,150 @@ class Abstract:
2064
2181
  update_abstracts(cls, force=True)
2065
2182
 
2066
2183
 
2184
+ ########################################
2185
+ # ../../../omlish/lite/asyncs.py
2186
+
2187
+
2188
+ ##
2189
+
2190
+
2191
+ async def opt_await(aw: ta.Optional[ta.Awaitable[T]]) -> ta.Optional[T]:
2192
+ return (await aw if aw is not None else None)
2193
+
2194
+
2195
+ async def async_list(ai: ta.AsyncIterable[T]) -> ta.List[T]:
2196
+ return [v async for v in ai]
2197
+
2198
+
2199
+ async def async_enumerate(ai: ta.AsyncIterable[T]) -> ta.AsyncIterable[ta.Tuple[int, T]]:
2200
+ i = 0
2201
+ async for e in ai:
2202
+ yield (i, e)
2203
+ i += 1
2204
+
2205
+
2206
+ ##
2207
+
2208
+
2209
+ def as_async(fn: ta.Callable[..., T], *, wrap: bool = False) -> ta.Callable[..., ta.Awaitable[T]]:
2210
+ async def inner(*args, **kwargs):
2211
+ return fn(*args, **kwargs)
2212
+
2213
+ return functools.wraps(fn)(inner) if wrap else inner
2214
+
2215
+
2216
+ ##
2217
+
2218
+
2219
+ class SyncAwaitCoroutineNotTerminatedError(Exception):
2220
+ pass
2221
+
2222
+
2223
+ def sync_await(aw: ta.Awaitable[T]) -> T:
2224
+ """
2225
+ Allows for the synchronous execution of async functions which will never actually *externally* await anything. These
2226
+ functions are allowed to await any number of other functions - including contextmanagers and generators - so long as
2227
+ nothing ever actually 'leaks' out of the function, presumably to an event loop.
2228
+ """
2229
+
2230
+ ret = missing = object()
2231
+
2232
+ async def thunk():
2233
+ nonlocal ret
2234
+
2235
+ ret = await aw
2236
+
2237
+ cr = thunk()
2238
+ try:
2239
+ try:
2240
+ cr.send(None)
2241
+ except StopIteration:
2242
+ pass
2243
+
2244
+ if ret is missing or cr.cr_await is not None or cr.cr_running:
2245
+ raise SyncAwaitCoroutineNotTerminatedError('Not terminated')
2246
+
2247
+ finally:
2248
+ cr.close()
2249
+
2250
+ return ta.cast(T, ret)
2251
+
2252
+
2253
+ #
2254
+
2255
+
2256
+ def sync_aiter(ai: ta.AsyncIterator[T]) -> ta.Iterator[T]:
2257
+ while True:
2258
+ try:
2259
+ o = sync_await(ai.__anext__())
2260
+ except StopAsyncIteration:
2261
+ break
2262
+ yield o
2263
+
2264
+
2265
+ def sync_async_list(ai: ta.AsyncIterable[T]) -> ta.List[T]:
2266
+ """
2267
+ Uses `sync_await` to synchronously read the full contents of a function call returning an async iterator, given that
2268
+ the function never externally awaits anything.
2269
+ """
2270
+
2271
+ lst: ta.Optional[ta.List[T]] = None
2272
+
2273
+ async def inner():
2274
+ nonlocal lst
2275
+
2276
+ lst = [v async for v in ai]
2277
+
2278
+ sync_await(inner())
2279
+
2280
+ if not isinstance(lst, list):
2281
+ raise TypeError(lst)
2282
+
2283
+ return lst
2284
+
2285
+
2286
+ #
2287
+
2288
+
2289
+ @ta.final
2290
+ class SyncAwaitContextManager(ta.Generic[T]):
2291
+ def __init__(self, acm: ta.AsyncContextManager[T]) -> None:
2292
+ self._acm = acm
2293
+
2294
+ def __repr__(self) -> str:
2295
+ return f'{self.__class__.__name__}({self._acm!r})'
2296
+
2297
+ def __enter__(self) -> T:
2298
+ return sync_await(self._acm.__aenter__())
2299
+
2300
+ def __exit__(self, exc_type, exc_val, exc_tb):
2301
+ return sync_await(self._acm.__aexit__(exc_type, exc_val, exc_tb))
2302
+
2303
+
2304
+ sync_async_with = SyncAwaitContextManager
2305
+
2306
+
2307
+ ##
2308
+
2309
+
2310
+ @ta.final
2311
+ class SyncToAsyncContextManager(ta.Generic[T]):
2312
+ def __init__(self, cm: ta.ContextManager[T]) -> None:
2313
+ self._cm = cm
2314
+
2315
+ def __repr__(self) -> str:
2316
+ return f'{self.__class__.__name__}({self._cm!r})'
2317
+
2318
+ async def __aenter__(self) -> T:
2319
+ return self._cm.__enter__()
2320
+
2321
+ async def __aexit__(self, exc_type, exc_value, traceback, /):
2322
+ return self._cm.__exit__(exc_type, exc_value, traceback)
2323
+
2324
+
2325
+ as_async_context_manager = SyncToAsyncContextManager
2326
+
2327
+
2067
2328
  ########################################
2068
2329
  # ../../../omlish/lite/cached.py
2069
2330
 
@@ -2121,6 +2382,62 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
2121
2382
  return _AsyncCachedNullary(fn)
2122
2383
 
2123
2384
 
2385
+ ##
2386
+
2387
+
2388
+ cached_property = functools.cached_property
2389
+
2390
+
2391
+ class _cached_property: # noqa
2392
+ """Backported to pick up https://github.com/python/cpython/commit/056dfc71dce15f81887f0bd6da09d6099d71f979 ."""
2393
+
2394
+ def __init__(self, func):
2395
+ self.func = func
2396
+ self.attrname = None # noqa
2397
+ self.__doc__ = func.__doc__
2398
+ self.__module__ = func.__module__
2399
+
2400
+ _NOT_FOUND = object()
2401
+
2402
+ def __set_name__(self, owner, name):
2403
+ if self.attrname is None:
2404
+ self.attrname = name # noqa
2405
+ elif name != self.attrname:
2406
+ raise TypeError(
2407
+ f'Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r}).',
2408
+ )
2409
+
2410
+ def __get__(self, instance, owner=None):
2411
+ if instance is None:
2412
+ return self
2413
+ if self.attrname is None:
2414
+ raise TypeError('Cannot use cached_property instance without calling __set_name__ on it.')
2415
+
2416
+ try:
2417
+ cache = instance.__dict__
2418
+ except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
2419
+ raise TypeError(
2420
+ f"No '__dict__' attribute on {type(instance).__name__!r} instance to cache {self.attrname!r} property.",
2421
+ ) from None
2422
+
2423
+ val = cache.get(self.attrname, self._NOT_FOUND)
2424
+
2425
+ if val is self._NOT_FOUND:
2426
+ val = self.func(instance)
2427
+ try:
2428
+ cache[self.attrname] = val
2429
+ except TypeError:
2430
+ raise TypeError(
2431
+ f"The '__dict__' attribute on {type(instance).__name__!r} instance does not support item "
2432
+ f"assignment for caching {self.attrname!r} property.",
2433
+ ) from None
2434
+
2435
+ return val
2436
+
2437
+
2438
+ globals()['cached_property'] = _cached_property
2439
+
2440
+
2124
2441
  ########################################
2125
2442
  # ../../../omlish/lite/check.py
2126
2443
  """
@@ -2909,6 +3226,12 @@ def format_num_bytes(num_bytes: int) -> str:
2909
3226
 
2910
3227
  ##
2911
3228
  # A workaround for typing deficiencies (like `Argument 2 to NewType(...) must be subclassable`).
3229
+ #
3230
+ # Note that this problem doesn't happen at runtime - it happens in mypy:
3231
+ #
3232
+ # mypy <(echo "import typing as ta; MyCallback = ta.NewType('MyCallback', ta.Callable[[], None])")
3233
+ # /dev/fd/11:1:22: error: Argument 2 to NewType(...) must be subclassable (got "Callable[[], None]") [valid-newtype]
3234
+ #
2912
3235
 
2913
3236
 
2914
3237
  @dc.dataclass(frozen=True)
@@ -2954,6 +3277,24 @@ class Func3(ta.Generic[A0, A1, A2, T]):
2954
3277
  ##
2955
3278
 
2956
3279
 
3280
+ @dc.dataclass(frozen=True)
3281
+ class CachedFunc0(ta.Generic[T]):
3282
+ fn: ta.Callable[[], T]
3283
+
3284
+ def __call__(self) -> T:
3285
+ try:
3286
+ return object.__getattribute__(self, '_value')
3287
+ except AttributeError:
3288
+ pass
3289
+
3290
+ value = self.fn()
3291
+ object.__setattr__(self, '_value', value)
3292
+ return value
3293
+
3294
+
3295
+ ##
3296
+
3297
+
2957
3298
  _TYPING_ANNOTATIONS_ATTR = '__annotate__' if sys.version_info >= (3, 14) else '__annotations__'
2958
3299
 
2959
3300
 
@@ -3868,7 +4209,7 @@ class SpecifierSet(BaseSpecifier):
3868
4209
  if isinstance(other, str):
3869
4210
  other = SpecifierSet(other)
3870
4211
  elif not isinstance(other, SpecifierSet):
3871
- return NotImplemented # type: ignore
4212
+ return NotImplemented
3872
4213
 
3873
4214
  specifier = SpecifierSet()
3874
4215
  specifier._specs = frozenset(self._specs | other._specs)
@@ -3888,6 +4229,7 @@ class SpecifierSet(BaseSpecifier):
3888
4229
  if isinstance(other, (str, Specifier)):
3889
4230
  other = SpecifierSet(str(other))
3890
4231
  elif not isinstance(other, SpecifierSet):
4232
+
3891
4233
  return NotImplemented
3892
4234
 
3893
4235
  return self._specs == other._specs
@@ -3959,6 +4301,7 @@ TODO:
3959
4301
  - pre-run, post-run hooks
3960
4302
  - exitstack?
3961
4303
  - suggestion - difflib.get_close_matches
4304
+ - add_argument_group - group kw on ArgparseKwarg?
3962
4305
  """
3963
4306
 
3964
4307
 
@@ -3969,6 +4312,7 @@ TODO:
3969
4312
  class ArgparseArg:
3970
4313
  args: ta.Sequence[ta.Any]
3971
4314
  kwargs: ta.Mapping[str, ta.Any]
4315
+ group: ta.Optional[str] = None
3972
4316
  dest: ta.Optional[str] = None
3973
4317
 
3974
4318
  def __get__(self, instance, owner=None):
@@ -3978,7 +4322,11 @@ class ArgparseArg:
3978
4322
 
3979
4323
 
3980
4324
  def argparse_arg(*args, **kwargs) -> ArgparseArg:
3981
- return ArgparseArg(args, kwargs)
4325
+ return ArgparseArg(
4326
+ args=args,
4327
+ group=kwargs.pop('group', None),
4328
+ kwargs=kwargs,
4329
+ )
3982
4330
 
3983
4331
 
3984
4332
  def argparse_arg_(*args, **kwargs) -> ta.Any:
@@ -4148,6 +4496,10 @@ class ArgparseCli:
4148
4496
  subparser.set_defaults(_cmd=obj)
4149
4497
 
4150
4498
  elif isinstance(obj, ArgparseArg):
4499
+ if obj.group is not None:
4500
+ # FIXME: add_argument_group
4501
+ raise NotImplementedError
4502
+
4151
4503
  if att in anns:
4152
4504
  ann_kwargs = _get_argparse_arg_ann_kwargs(anns[att])
4153
4505
  obj.kwargs = {**ann_kwargs, **obj.kwargs}
@@ -4193,7 +4545,7 @@ class ArgparseCli:
4193
4545
 
4194
4546
  if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
4195
4547
  msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
4196
- if (parser := self.get_parser()).exit_on_error:
4548
+ if (parser := self.get_parser()).exit_on_error: # noqa
4197
4549
  parser.error(msg)
4198
4550
  else:
4199
4551
  raise argparse.ArgumentError(None, msg)
@@ -4213,7 +4565,10 @@ class ArgparseCli:
4213
4565
  return fn()
4214
4566
 
4215
4567
  def cli_run_and_exit(self) -> ta.NoReturn:
4216
- sys.exit(rc if isinstance(rc := self.cli_run(), int) else 0)
4568
+ rc = self.cli_run()
4569
+ if not isinstance(rc, int):
4570
+ rc = 0
4571
+ raise SystemExit(rc)
4217
4572
 
4218
4573
  def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
4219
4574
  if exit:
@@ -5165,8 +5520,6 @@ class _JustMaybe(_Maybe[T]):
5165
5520
  __slots__ = ('_v', '_hash')
5166
5521
 
5167
5522
  def __init__(self, v: T) -> None:
5168
- super().__init__()
5169
-
5170
5523
  self._v = v
5171
5524
 
5172
5525
  @property
@@ -5224,6 +5577,13 @@ class _EmptyMaybe(_Maybe[T]):
5224
5577
  Maybe._empty = _EmptyMaybe() # noqa
5225
5578
 
5226
5579
 
5580
+ ##
5581
+
5582
+
5583
+ setattr(Maybe, 'just', _JustMaybe) # noqa
5584
+ setattr(Maybe, 'empty', functools.partial(operator.attrgetter('_empty'), Maybe))
5585
+
5586
+
5227
5587
  ########################################
5228
5588
  # ../../../omlish/lite/runtime.py
5229
5589
 
@@ -5970,9 +6330,10 @@ class InterpSpecifier:
5970
6330
  def parse(cls, s: str) -> 'InterpSpecifier':
5971
6331
  s, o = InterpOpts.parse_suffix(s)
5972
6332
  if not any(s.startswith(o) for o in Specifier.OPERATORS):
5973
- s = '~=' + s
5974
6333
  if s.count('.') < 2:
5975
- s += '.0'
6334
+ s = '~=' + s + '.0'
6335
+ else:
6336
+ s = '==' + s
5976
6337
  return cls(
5977
6338
  specifier=Specifier(s),
5978
6339
  opts=o,
@@ -5995,62 +6356,544 @@ class Interp:
5995
6356
 
5996
6357
 
5997
6358
  ########################################
5998
- # ../../../omlish/asyncs/asyncio/timeouts.py
6359
+ # ../../packaging/requires.py
6360
+ # Copyright (c) Donald Stufft and individual contributors.
6361
+ # All rights reserved.
6362
+ #
6363
+ # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
6364
+ # following conditions are met:
6365
+ #
6366
+ # 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
6367
+ # following disclaimer.
6368
+ #
6369
+ # 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
6370
+ # following disclaimer in the documentation and/or other materials provided with the distribution.
6371
+ #
6372
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
6373
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
6374
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
6375
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
6376
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
6377
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
6378
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This file is dual licensed under the terms of the
6379
+ # Apache License, Version 2.0, and the BSD License. See the LICENSE file in the root of this repository for complete
6380
+ # details.
6381
+ # https://github.com/pypa/packaging/blob/cf2cbe2aec28f87c6228a6fb136c27931c9af407/src/packaging/_parser.py#L65
5999
6382
 
6000
6383
 
6001
6384
  ##
6002
6385
 
6003
6386
 
6004
- def asyncio_maybe_timeout(
6005
- fut: AwaitableT,
6006
- timeout: TimeoutLike = None,
6007
- ) -> AwaitableT:
6008
- if timeout is not None:
6009
- fut = asyncio.wait_for(fut, Timeout.of(timeout)()) # type: ignore
6010
- return fut
6387
+ @dc.dataclass()
6388
+ class RequiresToken:
6389
+ name: str
6390
+ text: str
6391
+ position: int
6011
6392
 
6012
6393
 
6013
- ########################################
6014
- # ../../../omlish/lite/inject.py
6394
+ class RequiresParserSyntaxError(Exception):
6395
+ def __init__(
6396
+ self,
6397
+ message: str,
6398
+ *,
6399
+ source: str,
6400
+ span: ta.Tuple[int, int],
6401
+ ) -> None:
6402
+ self.span = span
6403
+ self.message = message
6404
+ self.source = source
6015
6405
 
6406
+ super().__init__()
6016
6407
 
6017
- ###
6018
- # types
6408
+ def __str__(self) -> str:
6409
+ marker = ' ' * self.span[0] + '~' * (self.span[1] - self.span[0]) + '^'
6410
+ return '\n '.join([self.message, self.source, marker])
6411
+
6412
+
6413
+ REQUIRES_DEFAULT_RULES: ta.Dict[str, ta.Union[str, ta.Pattern[str]]] = {
6414
+ 'LEFT_PARENTHESIS': r'\(',
6415
+ 'RIGHT_PARENTHESIS': r'\)',
6416
+ 'LEFT_BRACKET': r'\[',
6417
+ 'RIGHT_BRACKET': r'\]',
6418
+ 'SEMICOLON': r';',
6419
+ 'COMMA': r',',
6420
+ 'QUOTED_STRING': re.compile(
6421
+ r"""
6422
+ (
6423
+ ('[^']*')
6424
+ |
6425
+ ("[^"]*")
6426
+ )
6427
+ """,
6428
+ re.VERBOSE,
6429
+ ),
6430
+ 'OP': r'(===|==|~=|!=|<=|>=|<|>)',
6431
+ 'BOOLOP': r'\b(or|and)\b',
6432
+ 'IN': r'\bin\b',
6433
+ 'NOT': r'\bnot\b',
6434
+ 'VARIABLE': re.compile(
6435
+ r"""
6436
+ \b(
6437
+ python_version
6438
+ |python_full_version
6439
+ |os[._]name
6440
+ |sys[._]platform
6441
+ |platform_(release|system)
6442
+ |platform[._](version|machine|python_implementation)
6443
+ |python_implementation
6444
+ |implementation_(name|version)
6445
+ |extra
6446
+ )\b
6447
+ """,
6448
+ re.VERBOSE,
6449
+ ),
6450
+ 'SPECIFIER': re.compile(
6451
+ Specifier._operator_regex_str + Specifier._version_regex_str, # noqa
6452
+ re.VERBOSE | re.IGNORECASE,
6453
+ ),
6454
+ 'AT': r'\@',
6455
+ 'URL': r'[^ \t]+',
6456
+ 'IDENTIFIER': r'\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b',
6457
+ 'VERSION_PREFIX_TRAIL': r'\.\*',
6458
+ 'VERSION_LOCAL_LABEL_TRAIL': r'\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*',
6459
+ 'WS': r'[ \t]+',
6460
+ 'END': r'$',
6461
+ }
6019
6462
 
6020
6463
 
6021
- @dc.dataclass(frozen=True)
6022
- class InjectorKey(ta.Generic[T]):
6023
- # Before PEP-560 typing.Generic was a metaclass with a __new__ that takes a 'cls' arg, so instantiating a dataclass
6024
- # with kwargs (such as through dc.replace) causes `TypeError: __new__() got multiple values for argument 'cls'`.
6025
- # See:
6026
- # - https://github.com/python/cpython/commit/d911e40e788fb679723d78b6ea11cabf46caed5a
6027
- # - https://gist.github.com/wrmsr/4468b86efe9f373b6b114bfe85b98fd3
6028
- cls_: InjectorKeyCls
6464
+ class RequiresTokenizer:
6465
+ def __init__(
6466
+ self,
6467
+ source: str,
6468
+ *,
6469
+ rules: ta.Dict[str, ta.Union[str, ta.Pattern[str]]],
6470
+ ) -> None:
6471
+ super().__init__()
6029
6472
 
6030
- tag: ta.Any = None
6031
- array: bool = False
6473
+ self.source = source
6474
+ self.rules: ta.Dict[str, ta.Pattern[str]] = {name: re.compile(pattern) for name, pattern in rules.items()}
6475
+ self.next_token: ta.Optional[RequiresToken] = None
6476
+ self.position = 0
6032
6477
 
6478
+ def consume(self, name: str) -> None:
6479
+ if self.check(name):
6480
+ self.read()
6033
6481
 
6034
- def is_valid_injector_key_cls(cls: ta.Any) -> bool:
6035
- return isinstance(cls, type) or is_new_type(cls)
6482
+ def check(self, name: str, *, peek: bool = False) -> bool:
6483
+ check.state(self.next_token is None, f'Cannot check for {name!r}, already have {self.next_token!r}')
6484
+ check.state(name in self.rules, f'Unknown token name: {name!r}')
6036
6485
 
6486
+ expression = self.rules[name]
6037
6487
 
6038
- def check_valid_injector_key_cls(cls: T) -> T:
6039
- if not is_valid_injector_key_cls(cls):
6040
- raise TypeError(cls)
6041
- return cls
6488
+ match = expression.match(self.source, self.position)
6489
+ if match is None:
6490
+ return False
6491
+ if not peek:
6492
+ self.next_token = RequiresToken(name, match[0], self.position)
6493
+ return True
6042
6494
 
6495
+ def expect(self, name: str, *, expected: str) -> RequiresToken:
6496
+ if not self.check(name):
6497
+ raise self.raise_syntax_error(f'Expected {expected}')
6498
+ return self.read()
6043
6499
 
6044
- ##
6500
+ def read(self) -> RequiresToken:
6501
+ token = self.next_token
6502
+ check.state(token is not None)
6045
6503
 
6504
+ self.position += len(check.not_none(token).text)
6505
+ self.next_token = None
6046
6506
 
6047
- class InjectorProvider(Abstract):
6048
- @abc.abstractmethod
6049
- def provider_fn(self) -> InjectorProviderFn:
6050
- raise NotImplementedError
6507
+ return check.not_none(token)
6051
6508
 
6509
+ def raise_syntax_error(
6510
+ self,
6511
+ message: str,
6512
+ *,
6513
+ span_start: ta.Optional[int] = None,
6514
+ span_end: ta.Optional[int] = None,
6515
+ ) -> ta.NoReturn:
6516
+ span = (
6517
+ self.position if span_start is None else span_start,
6518
+ self.position if span_end is None else span_end,
6519
+ )
6520
+ raise RequiresParserSyntaxError(
6521
+ message,
6522
+ source=self.source,
6523
+ span=span,
6524
+ )
6052
6525
 
6053
- ##
6526
+ @contextlib.contextmanager
6527
+ def enclosing_tokens(self, open_token: str, close_token: str, *, around: str) -> ta.Iterator[None]:
6528
+ if self.check(open_token):
6529
+ open_position = self.position
6530
+ self.read()
6531
+ else:
6532
+ open_position = None
6533
+
6534
+ yield
6535
+
6536
+ if open_position is None:
6537
+ return
6538
+
6539
+ if not self.check(close_token):
6540
+ self.raise_syntax_error(
6541
+ f'Expected matching {close_token} for {open_token}, after {around}',
6542
+ span_start=open_position,
6543
+ )
6544
+
6545
+ self.read()
6546
+
6547
+
6548
+ @dc.dataclass(frozen=True)
6549
+ class RequiresNode:
6550
+ value: str
6551
+
6552
+ def __str__(self) -> str:
6553
+ return self.value
6554
+
6555
+ def __repr__(self) -> str:
6556
+ return f"<{self.__class__.__name__}('{self}')>"
6557
+
6558
+ def serialize(self) -> str:
6559
+ raise NotImplementedError
6560
+
6561
+
6562
+ @dc.dataclass(frozen=True)
6563
+ class RequiresVariable(RequiresNode):
6564
+ def serialize(self) -> str:
6565
+ return str(self)
6566
+
6567
+
6568
+ @dc.dataclass(frozen=True)
6569
+ class RequiresValue(RequiresNode):
6570
+ def serialize(self) -> str:
6571
+ return f'"{self}"'
6572
+
6573
+
6574
+ @dc.dataclass(frozen=True)
6575
+ class RequiresOp(RequiresNode):
6576
+ def serialize(self) -> str:
6577
+ return str(self)
6578
+
6579
+
6580
+ class RequiresMarkerItem(ta.NamedTuple):
6581
+ l: ta.Union[RequiresVariable, RequiresValue]
6582
+ op: RequiresOp
6583
+ r: ta.Union[RequiresVariable, RequiresValue]
6584
+
6585
+
6586
+ class ParsedRequirement(ta.NamedTuple):
6587
+ name: str
6588
+ url: str
6589
+ extras: ta.List[str]
6590
+ specifier: str
6591
+ marker: ta.Optional[RequiresMarkerList]
6592
+
6593
+
6594
+ def parse_requirement(source: str) -> ParsedRequirement:
6595
+ return _parse_requirement(RequiresTokenizer(source, rules=REQUIRES_DEFAULT_RULES))
6596
+
6597
+
6598
+ def _parse_requirement(tokenizer: RequiresTokenizer) -> ParsedRequirement:
6599
+ tokenizer.consume('WS')
6600
+
6601
+ name_token = tokenizer.expect('IDENTIFIER', expected='package name at the start of dependency specifier')
6602
+ name = name_token.text
6603
+ tokenizer.consume('WS')
6604
+
6605
+ extras = _parse_requires_extras(tokenizer)
6606
+ tokenizer.consume('WS')
6607
+
6608
+ url, specifier, marker = _parse_requirement_details(tokenizer)
6609
+ tokenizer.expect('END', expected='end of dependency specifier')
6610
+
6611
+ return ParsedRequirement(name, url, extras, specifier, marker)
6612
+
6613
+
6614
+ def _parse_requirement_details(tokenizer: RequiresTokenizer) -> ta.Tuple[str, str, ta.Optional[RequiresMarkerList]]:
6615
+ specifier = ''
6616
+ url = ''
6617
+ marker = None
6618
+
6619
+ if tokenizer.check('AT'):
6620
+ tokenizer.read()
6621
+ tokenizer.consume('WS')
6622
+
6623
+ url_start = tokenizer.position
6624
+ url = tokenizer.expect('URL', expected='URL after @').text
6625
+ if tokenizer.check('END', peek=True):
6626
+ return (url, specifier, marker)
6627
+
6628
+ tokenizer.expect('WS', expected='whitespace after URL')
6629
+
6630
+ # The input might end after whitespace.
6631
+ if tokenizer.check('END', peek=True):
6632
+ return (url, specifier, marker)
6633
+
6634
+ marker = _parse_requirement_marker(
6635
+ tokenizer, span_start=url_start, after='URL and whitespace',
6636
+ )
6637
+ else:
6638
+ specifier_start = tokenizer.position
6639
+ specifier = _parse_requires_specifier(tokenizer)
6640
+ tokenizer.consume('WS')
6641
+
6642
+ if tokenizer.check('END', peek=True):
6643
+ return (url, specifier, marker)
6644
+
6645
+ marker = _parse_requirement_marker(
6646
+ tokenizer,
6647
+ span_start=specifier_start,
6648
+ after=(
6649
+ 'version specifier'
6650
+ if specifier
6651
+ else 'name and no valid version specifier'
6652
+ ),
6653
+ )
6654
+
6655
+ return (url, specifier, marker)
6656
+
6657
+
6658
+ def _parse_requirement_marker(
6659
+ tokenizer: RequiresTokenizer, *, span_start: int, after: str,
6660
+ ) -> RequiresMarkerList:
6661
+ if not tokenizer.check('SEMICOLON'):
6662
+ tokenizer.raise_syntax_error(
6663
+ f'Expected end or semicolon (after {after})',
6664
+ span_start=span_start,
6665
+ )
6666
+ tokenizer.read()
6667
+
6668
+ marker = _parse_requires_marker(tokenizer)
6669
+ tokenizer.consume('WS')
6670
+
6671
+ return marker
6672
+
6673
+
6674
+ def _parse_requires_extras(tokenizer: RequiresTokenizer) -> ta.List[str]:
6675
+ if not tokenizer.check('LEFT_BRACKET', peek=True):
6676
+ return []
6677
+
6678
+ with tokenizer.enclosing_tokens(
6679
+ 'LEFT_BRACKET',
6680
+ 'RIGHT_BRACKET',
6681
+ around='extras',
6682
+ ):
6683
+ tokenizer.consume('WS')
6684
+ extras = _parse_requires_extras_list(tokenizer)
6685
+ tokenizer.consume('WS')
6686
+
6687
+ return extras
6688
+
6689
+
6690
+ def _parse_requires_extras_list(tokenizer: RequiresTokenizer) -> ta.List[str]:
6691
+ extras: ta.List[str] = []
6692
+
6693
+ if not tokenizer.check('IDENTIFIER'):
6694
+ return extras
6695
+
6696
+ extras.append(tokenizer.read().text)
6697
+
6698
+ while True:
6699
+ tokenizer.consume('WS')
6700
+ if tokenizer.check('IDENTIFIER', peek=True):
6701
+ tokenizer.raise_syntax_error('Expected comma between extra names')
6702
+ elif not tokenizer.check('COMMA'):
6703
+ break
6704
+
6705
+ tokenizer.read()
6706
+ tokenizer.consume('WS')
6707
+
6708
+ extra_token = tokenizer.expect('IDENTIFIER', expected='extra name after comma')
6709
+ extras.append(extra_token.text)
6710
+
6711
+ return extras
6712
+
6713
+
6714
+ def _parse_requires_specifier(tokenizer: RequiresTokenizer) -> str:
6715
+ with tokenizer.enclosing_tokens(
6716
+ 'LEFT_PARENTHESIS',
6717
+ 'RIGHT_PARENTHESIS',
6718
+ around='version specifier',
6719
+ ):
6720
+ tokenizer.consume('WS')
6721
+ parsed_specifiers = _parse_requires_version_many(tokenizer)
6722
+ tokenizer.consume('WS')
6723
+
6724
+ return parsed_specifiers
6725
+
6726
+
6727
+ def _parse_requires_version_many(tokenizer: RequiresTokenizer) -> str:
6728
+ parsed_specifiers = ''
6729
+ while tokenizer.check('SPECIFIER'):
6730
+ span_start = tokenizer.position
6731
+ parsed_specifiers += tokenizer.read().text
6732
+ if tokenizer.check('VERSION_PREFIX_TRAIL', peek=True):
6733
+ tokenizer.raise_syntax_error(
6734
+ '.* suffix can only be used with `==` or `!=` operators',
6735
+ span_start=span_start,
6736
+ span_end=tokenizer.position + 1,
6737
+ )
6738
+ if tokenizer.check('VERSION_LOCAL_LABEL_TRAIL', peek=True):
6739
+ tokenizer.raise_syntax_error(
6740
+ 'Local version label can only be used with `==` or `!=` operators',
6741
+ span_start=span_start,
6742
+ span_end=tokenizer.position,
6743
+ )
6744
+ tokenizer.consume('WS')
6745
+ if not tokenizer.check('COMMA'):
6746
+ break
6747
+ parsed_specifiers += tokenizer.read().text
6748
+ tokenizer.consume('WS')
6749
+
6750
+ return parsed_specifiers
6751
+
6752
+
6753
+ def parse_requires_marker(source: str) -> RequiresMarkerList:
6754
+ return _parse_requires_full_marker(RequiresTokenizer(source, rules=REQUIRES_DEFAULT_RULES))
6755
+
6756
+
6757
+ def _parse_requires_full_marker(tokenizer: RequiresTokenizer) -> RequiresMarkerList:
6758
+ retval = _parse_requires_marker(tokenizer)
6759
+ tokenizer.expect('END', expected='end of marker expression')
6760
+ return retval
6761
+
6762
+
6763
+ def _parse_requires_marker(tokenizer: RequiresTokenizer) -> RequiresMarkerList:
6764
+ expression = [_parse_requires_marker_atom(tokenizer)]
6765
+ while tokenizer.check('BOOLOP'):
6766
+ token = tokenizer.read()
6767
+ expr_right = _parse_requires_marker_atom(tokenizer)
6768
+ expression.extend((token.text, expr_right))
6769
+ return expression
6770
+
6771
+
6772
+ def _parse_requires_marker_atom(tokenizer: RequiresTokenizer) -> RequiresMarkerAtom:
6773
+ tokenizer.consume('WS')
6774
+ if tokenizer.check('LEFT_PARENTHESIS', peek=True):
6775
+ with tokenizer.enclosing_tokens(
6776
+ 'LEFT_PARENTHESIS',
6777
+ 'RIGHT_PARENTHESIS',
6778
+ around='marker expression',
6779
+ ):
6780
+ tokenizer.consume('WS')
6781
+ marker: RequiresMarkerAtom = _parse_requires_marker(tokenizer)
6782
+ tokenizer.consume('WS')
6783
+ else:
6784
+ marker = _parse_requires_marker_item(tokenizer)
6785
+ tokenizer.consume('WS')
6786
+ return marker
6787
+
6788
+
6789
+ def _parse_requires_marker_item(tokenizer: RequiresTokenizer) -> RequiresMarkerItem:
6790
+ tokenizer.consume('WS')
6791
+ marker_var_left = _parse_requires_marker_var(tokenizer)
6792
+ tokenizer.consume('WS')
6793
+ marker_op = _parse_requires_marker_op(tokenizer)
6794
+ tokenizer.consume('WS')
6795
+ marker_var_right = _parse_requires_marker_var(tokenizer)
6796
+ tokenizer.consume('WS')
6797
+ return RequiresMarkerItem(marker_var_left, marker_op, marker_var_right)
6798
+
6799
+
6800
+ def _parse_requires_marker_var(tokenizer: RequiresTokenizer) -> RequiresMarkerVar:
6801
+ if tokenizer.check('VARIABLE'):
6802
+ return process_requires_env_var(tokenizer.read().text.replace('.', '_'))
6803
+ elif tokenizer.check('QUOTED_STRING'):
6804
+ return process_requires_python_str(tokenizer.read().text)
6805
+ else:
6806
+ tokenizer.raise_syntax_error(message='Expected a marker variable or quoted string')
6807
+ raise RuntimeError # noqa
6808
+
6809
+
6810
+ def process_requires_env_var(env_var: str) -> RequiresVariable:
6811
+ if env_var in ('platform_python_implementation', 'python_implementation'):
6812
+ return RequiresVariable('platform_python_implementation')
6813
+ else:
6814
+ return RequiresVariable(env_var)
6815
+
6816
+
6817
+ def process_requires_python_str(python_str: str) -> RequiresValue:
6818
+ value = ast.literal_eval(python_str)
6819
+ return RequiresValue(str(value))
6820
+
6821
+
6822
+ def _parse_requires_marker_op(tokenizer: RequiresTokenizer) -> RequiresOp:
6823
+ if tokenizer.check('IN'):
6824
+ tokenizer.read()
6825
+ return RequiresOp('in')
6826
+ elif tokenizer.check('NOT'):
6827
+ tokenizer.read()
6828
+ tokenizer.expect('WS', expected="whitespace after 'not'")
6829
+ tokenizer.expect('IN', expected="'in' after 'not'")
6830
+ return RequiresOp('not in')
6831
+ elif tokenizer.check('OP'):
6832
+ return RequiresOp(tokenizer.read().text)
6833
+ else:
6834
+ return tokenizer.raise_syntax_error(
6835
+ 'Expected marker operator, one of '
6836
+ '<=, <, !=, ==, >=, >, ~=, ===, in, not in',
6837
+ )
6838
+
6839
+
6840
+ ########################################
6841
+ # ../../../omlish/asyncs/asyncio/timeouts.py
6842
+
6843
+
6844
+ ##
6845
+
6846
+
6847
+ def asyncio_maybe_timeout(
6848
+ fut: AwaitableT,
6849
+ timeout: TimeoutLike = None,
6850
+ ) -> AwaitableT:
6851
+ if timeout is not None:
6852
+ fut = asyncio.wait_for(fut, Timeout.of(timeout)()) # type: ignore
6853
+ return fut
6854
+
6855
+
6856
+ ########################################
6857
+ # ../../../omlish/lite/inject.py
6858
+
6859
+
6860
+ ###
6861
+ # types
6862
+
6863
+
6864
+ @dc.dataclass(frozen=True)
6865
+ class InjectorKey(ta.Generic[T]):
6866
+ # Before PEP-560 typing.Generic was a metaclass with a __new__ that takes a 'cls' arg, so instantiating a dataclass
6867
+ # with kwargs (such as through dc.replace) causes `TypeError: __new__() got multiple values for argument 'cls'`.
6868
+ # See:
6869
+ # - https://github.com/python/cpython/commit/d911e40e788fb679723d78b6ea11cabf46caed5a
6870
+ # - https://gist.github.com/wrmsr/4468b86efe9f373b6b114bfe85b98fd3
6871
+ cls_: InjectorKeyCls
6872
+
6873
+ tag: ta.Any = None
6874
+ array: bool = False
6875
+
6876
+
6877
+ def is_valid_injector_key_cls(cls: ta.Any) -> bool:
6878
+ return isinstance(cls, type) or is_new_type(cls)
6879
+
6880
+
6881
+ def check_valid_injector_key_cls(cls: T) -> T:
6882
+ if not is_valid_injector_key_cls(cls):
6883
+ raise TypeError(cls)
6884
+ return cls
6885
+
6886
+
6887
+ ##
6888
+
6889
+
6890
+ class InjectorProvider(Abstract):
6891
+ @abc.abstractmethod
6892
+ def provider_fn(self) -> InjectorProviderFn:
6893
+ raise NotImplementedError
6894
+
6895
+
6896
+ ##
6054
6897
 
6055
6898
 
6056
6899
  @dc.dataclass(frozen=True)
@@ -7201,6 +8044,9 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
7201
8044
  self._infos[type(info)] = info
7202
8045
  return self
7203
8046
 
8047
+ def get_infos(self) -> ta.Mapping[ta.Type[LoggingContextInfo], LoggingContextInfo]:
8048
+ return self._infos
8049
+
7204
8050
  def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
7205
8051
  return self._infos.get(ty)
7206
8052
 
@@ -7223,7 +8069,7 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
7223
8069
  _stack_offset: int
7224
8070
  _stack_info: bool
7225
8071
 
7226
- def inc_stack_offset(self, ofs: int = 1) -> 'CaptureLoggingContext':
8072
+ def inc_stack_offset(self, ofs: int = 1) -> 'CaptureLoggingContextImpl':
7227
8073
  if hasattr(self, '_stack_offset'):
7228
8074
  self._stack_offset += ofs
7229
8075
  return self
@@ -7255,10 +8101,9 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
7255
8101
 
7256
8102
 
7257
8103
  ########################################
7258
- # ../../../omlish/logs/standard.py
8104
+ # ../../../omlish/logs/std/standard.py
7259
8105
  """
7260
8106
  TODO:
7261
- - !! move to std !!
7262
8107
  - structured
7263
8108
  - prefixed
7264
8109
  - debug
@@ -7628,6 +8473,11 @@ class AnyLogger(Abstract, ta.Generic[T]):
7628
8473
 
7629
8474
  ##
7630
8475
 
8476
+ # This will be 1 for [Sync]Logger and 0 for AsyncLogger - in sync loggers these methods remain present on the stack,
8477
+ # in async loggers they return a coroutine to be awaited and thus aren't actually present when said coroutine is
8478
+ # awaited.
8479
+ _level_proxy_method_stack_offset: int
8480
+
7631
8481
  @ta.overload
7632
8482
  def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
7633
8483
  ...
@@ -7642,7 +8492,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
7642
8492
 
7643
8493
  @ta.final
7644
8494
  def log(self, level: LogLevel, *args, **kwargs):
7645
- return self._log(CaptureLoggingContextImpl(level, stack_offset=1), *args, **kwargs)
8495
+ return self._log(
8496
+ CaptureLoggingContextImpl(
8497
+ level,
8498
+ stack_offset=self._level_proxy_method_stack_offset,
8499
+ ),
8500
+ *args,
8501
+ **kwargs,
8502
+ )
7646
8503
 
7647
8504
  #
7648
8505
 
@@ -7660,7 +8517,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
7660
8517
 
7661
8518
  @ta.final
7662
8519
  def debug(self, *args, **kwargs):
7663
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.DEBUG, stack_offset=1), *args, **kwargs)
8520
+ return self._log(
8521
+ CaptureLoggingContextImpl(
8522
+ NamedLogLevel.DEBUG,
8523
+ stack_offset=self._level_proxy_method_stack_offset,
8524
+ ),
8525
+ *args,
8526
+ **kwargs,
8527
+ )
7664
8528
 
7665
8529
  #
7666
8530
 
@@ -7678,7 +8542,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
7678
8542
 
7679
8543
  @ta.final
7680
8544
  def info(self, *args, **kwargs):
7681
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.INFO, stack_offset=1), *args, **kwargs)
8545
+ return self._log(
8546
+ CaptureLoggingContextImpl(
8547
+ NamedLogLevel.INFO,
8548
+ stack_offset=self._level_proxy_method_stack_offset,
8549
+ ),
8550
+ *args,
8551
+ **kwargs,
8552
+ )
7682
8553
 
7683
8554
  #
7684
8555
 
@@ -7696,7 +8567,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
7696
8567
 
7697
8568
  @ta.final
7698
8569
  def warning(self, *args, **kwargs):
7699
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.WARNING, stack_offset=1), *args, **kwargs)
8570
+ return self._log(
8571
+ CaptureLoggingContextImpl(
8572
+ NamedLogLevel.WARNING,
8573
+ stack_offset=self._level_proxy_method_stack_offset,
8574
+ ),
8575
+ *args,
8576
+ **kwargs,
8577
+ )
7700
8578
 
7701
8579
  #
7702
8580
 
@@ -7714,7 +8592,14 @@ class AnyLogger(Abstract, ta.Generic[T]):
7714
8592
 
7715
8593
  @ta.final
7716
8594
  def error(self, *args, **kwargs):
7717
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.ERROR, stack_offset=1), *args, **kwargs)
8595
+ return self._log(
8596
+ CaptureLoggingContextImpl(
8597
+ NamedLogLevel.ERROR,
8598
+ stack_offset=self._level_proxy_method_stack_offset,
8599
+ ),
8600
+ *args,
8601
+ **kwargs,
8602
+ )
7718
8603
 
7719
8604
  #
7720
8605
 
@@ -7732,7 +8617,15 @@ class AnyLogger(Abstract, ta.Generic[T]):
7732
8617
 
7733
8618
  @ta.final
7734
8619
  def exception(self, *args, exc_info: LoggingExcInfoArg = True, **kwargs):
7735
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.ERROR, exc_info=exc_info, stack_offset=1), *args, **kwargs) # noqa
8620
+ return self._log(
8621
+ CaptureLoggingContextImpl(
8622
+ NamedLogLevel.ERROR,
8623
+ exc_info=exc_info,
8624
+ stack_offset=self._level_proxy_method_stack_offset,
8625
+ ),
8626
+ *args,
8627
+ **kwargs,
8628
+ )
7736
8629
 
7737
8630
  #
7738
8631
 
@@ -7750,24 +8643,53 @@ class AnyLogger(Abstract, ta.Generic[T]):
7750
8643
 
7751
8644
  @ta.final
7752
8645
  def critical(self, *args, **kwargs):
7753
- return self._log(CaptureLoggingContextImpl(NamedLogLevel.CRITICAL, stack_offset=1), *args, **kwargs)
8646
+ return self._log(
8647
+ CaptureLoggingContextImpl(
8648
+ NamedLogLevel.CRITICAL,
8649
+ stack_offset=self._level_proxy_method_stack_offset,
8650
+ ),
8651
+ *args,
8652
+ **kwargs,
8653
+ )
7754
8654
 
7755
8655
  ##
7756
8656
 
7757
8657
  @abc.abstractmethod
7758
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
8658
+ def _log(
8659
+ self,
8660
+ ctx: CaptureLoggingContext,
8661
+ msg: ta.Union[str, tuple, LoggingMsgFn],
8662
+ *args: ta.Any,
8663
+ **kwargs: ta.Any,
8664
+ ) -> T:
7759
8665
  raise NotImplementedError
7760
8666
 
7761
8667
 
7762
8668
  class Logger(AnyLogger[None], Abstract):
8669
+ _level_proxy_method_stack_offset: int = 1
8670
+
7763
8671
  @abc.abstractmethod
7764
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
8672
+ def _log(
8673
+ self,
8674
+ ctx: CaptureLoggingContext,
8675
+ msg: ta.Union[str, tuple, LoggingMsgFn],
8676
+ *args: ta.Any,
8677
+ **kwargs: ta.Any,
8678
+ ) -> None:
7765
8679
  raise NotImplementedError
7766
8680
 
7767
8681
 
7768
8682
  class AsyncLogger(AnyLogger[ta.Awaitable[None]], Abstract):
8683
+ _level_proxy_method_stack_offset: int = 0
8684
+
7769
8685
  @abc.abstractmethod
7770
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> ta.Awaitable[None]: # noqa
8686
+ def _log(
8687
+ self,
8688
+ ctx: CaptureLoggingContext,
8689
+ msg: ta.Union[str, tuple, LoggingMsgFn],
8690
+ *args: ta.Any,
8691
+ **kwargs: ta.Any,
8692
+ ) -> ta.Awaitable[None]:
7771
8693
  raise NotImplementedError
7772
8694
 
7773
8695
 
@@ -7782,13 +8704,25 @@ class AnyNopLogger(AnyLogger[T], Abstract):
7782
8704
 
7783
8705
  @ta.final
7784
8706
  class NopLogger(AnyNopLogger[None], Logger):
7785
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
8707
+ def _log(
8708
+ self,
8709
+ ctx: CaptureLoggingContext,
8710
+ msg: ta.Union[str, tuple, LoggingMsgFn],
8711
+ *args: ta.Any,
8712
+ **kwargs: ta.Any,
8713
+ ) -> None:
7786
8714
  pass
7787
8715
 
7788
8716
 
7789
8717
  @ta.final
7790
8718
  class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
7791
- async def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
8719
+ async def _log(
8720
+ self,
8721
+ ctx: CaptureLoggingContext,
8722
+ msg: ta.Union[str, tuple, LoggingMsgFn],
8723
+ *args: ta.Any,
8724
+ **kwargs: ta.Any,
8725
+ ) -> None:
7792
8726
  pass
7793
8727
 
7794
8728
 
@@ -8495,6 +9429,10 @@ class VerboseCalledProcessError(subprocess.CalledProcessError):
8495
9429
  class BaseSubprocesses(Abstract):
8496
9430
  DEFAULT_LOGGER: ta.ClassVar[ta.Optional[LoggerLike]] = None
8497
9431
 
9432
+ PIPE: ta.ClassVar[int] = subprocess.PIPE
9433
+ STDOUT: ta.ClassVar[int] = subprocess.STDOUT
9434
+ DEVNULL: ta.ClassVar[int] = subprocess.DEVNULL
9435
+
8498
9436
  def __init__(
8499
9437
  self,
8500
9438
  *,
@@ -8750,6 +9688,70 @@ class InterpResolver:
8750
9688
  print(f' {si}')
8751
9689
 
8752
9690
 
9691
+ ########################################
9692
+ # ../../../omlish/logs/asyncs.py
9693
+
9694
+
9695
+ ##
9696
+
9697
+
9698
+ class AsyncLoggerToLogger(Logger):
9699
+ def __init__(self, u: AsyncLogger) -> None:
9700
+ super().__init__()
9701
+
9702
+ self._u = u
9703
+
9704
+ def get_effective_level(self) -> LogLevel:
9705
+ return self._u.get_effective_level()
9706
+
9707
+ def _log(
9708
+ self,
9709
+ ctx: CaptureLoggingContext,
9710
+ msg: ta.Union[str, tuple, LoggingMsgFn],
9711
+ *args: ta.Any,
9712
+ **kwargs: ta.Any,
9713
+ ) -> None:
9714
+ # Nope out early to avoid sync_await if possible - don't bother in the LoggerToAsyncLogger.
9715
+ if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
9716
+ return
9717
+
9718
+ # Note: we hardcode the stack offset of sync_await (which is 2 - sync_await + sync_await.thunk). In non-lite
9719
+ # code, lang.sync_await uses a cext if present to avoid being on the py stack, which would obviously complicate
9720
+ # this, but this is lite code so we will always have the non-c version.
9721
+ sync_await(
9722
+ self._u._log( # noqa
9723
+ check.isinstance(ctx, CaptureLoggingContextImpl).inc_stack_offset(3),
9724
+ msg,
9725
+ *args,
9726
+ **kwargs,
9727
+ ),
9728
+ )
9729
+
9730
+
9731
+ class LoggerToAsyncLogger(AsyncLogger):
9732
+ def __init__(self, u: Logger) -> None:
9733
+ super().__init__()
9734
+
9735
+ self._u = u
9736
+
9737
+ def get_effective_level(self) -> LogLevel:
9738
+ return self._u.get_effective_level()
9739
+
9740
+ async def _log(
9741
+ self,
9742
+ ctx: CaptureLoggingContext,
9743
+ msg: ta.Union[str, tuple, LoggingMsgFn],
9744
+ *args: ta.Any,
9745
+ **kwargs: ta.Any,
9746
+ ) -> None:
9747
+ return self._u._log( # noqa
9748
+ check.isinstance(ctx, CaptureLoggingContextImpl).inc_stack_offset(),
9749
+ msg,
9750
+ *args,
9751
+ **kwargs,
9752
+ )
9753
+
9754
+
8753
9755
  ########################################
8754
9756
  # ../../../omlish/logs/std/loggers.py
8755
9757
 
@@ -8773,7 +9775,12 @@ class StdLogger(Logger):
8773
9775
  def get_effective_level(self) -> LogLevel:
8774
9776
  return self._std.getEffectiveLevel()
8775
9777
 
8776
- def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
9778
+ def _log(
9779
+ self,
9780
+ ctx: CaptureLoggingContext,
9781
+ msg: ta.Union[str, tuple, LoggingMsgFn],
9782
+ *args: ta.Any,
9783
+ ) -> None:
8777
9784
  if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
8778
9785
  return
8779
9786
 
@@ -9292,8 +10299,23 @@ asyncio_subprocesses = AsyncioSubprocesses()
9292
10299
  ##
9293
10300
 
9294
10301
 
10302
+ def _get_module_std_logger(mod_globals: ta.Mapping[str, ta.Any]) -> logging.Logger:
10303
+ return logging.getLogger(mod_globals.get('__name__'))
10304
+
10305
+
9295
10306
  def get_module_logger(mod_globals: ta.Mapping[str, ta.Any]) -> Logger:
9296
- return StdLogger(logging.getLogger(mod_globals.get('__name__'))) # noqa
10307
+ return StdLogger(_get_module_std_logger(mod_globals))
10308
+
10309
+
10310
+ def get_module_async_logger(mod_globals: ta.Mapping[str, ta.Any]) -> AsyncLogger:
10311
+ return LoggerToAsyncLogger(get_module_logger(mod_globals))
10312
+
10313
+
10314
+ def get_module_loggers(mod_globals: ta.Mapping[str, ta.Any]) -> ta.Tuple[Logger, AsyncLogger]:
10315
+ return (
10316
+ log := get_module_logger(mod_globals),
10317
+ LoggerToAsyncLogger(log),
10318
+ )
9297
10319
 
9298
10320
 
9299
10321
  ########################################
@@ -9684,11 +10706,14 @@ log = get_module_logger(globals()) # noqa
9684
10706
  class RequirementsRewriter:
9685
10707
  def __init__(
9686
10708
  self,
10709
+ *,
9687
10710
  venv: ta.Optional[str] = None,
10711
+ only_pats: ta.Optional[ta.Sequence[re.Pattern]] = None,
9688
10712
  ) -> None:
9689
10713
  super().__init__()
9690
10714
 
9691
10715
  self._venv = venv
10716
+ self._only_pats = only_pats
9692
10717
 
9693
10718
  @cached_nullary
9694
10719
  def _tmp_dir(self) -> str:
@@ -9704,17 +10729,32 @@ class RequirementsRewriter:
9704
10729
  out_lines = []
9705
10730
 
9706
10731
  for l in in_lines:
9707
- if self.VENV_MAGIC in l:
9708
- lp, _, rp = l.partition(self.VENV_MAGIC)
9709
- rp = rp.partition('#')[0]
10732
+ if l.split('#')[0].strip():
9710
10733
  omit = False
9711
- for v in rp.split():
9712
- if v[0] == '!':
9713
- if self._venv is not None and self._venv == v[1:]:
9714
- omit = True
9715
- break
10734
+
10735
+ if self.VENV_MAGIC in l:
10736
+ lp, _, rp = l.partition(self.VENV_MAGIC)
10737
+ rp = rp.partition('#')[0]
10738
+ for v in rp.split():
10739
+ if v[0] == '!':
10740
+ if self._venv is not None and self._venv == v[1:]:
10741
+ omit = True
10742
+ break
10743
+ else:
10744
+ raise NotImplementedError
10745
+
10746
+ if (
10747
+ not omit and
10748
+ (ops := self._only_pats) is not None and
10749
+ not l.strip().startswith('-')
10750
+ ):
10751
+ try:
10752
+ pr = parse_requirement(l.split('#')[0].strip())
10753
+ except RequiresParserSyntaxError:
10754
+ pass
9716
10755
  else:
9717
- raise NotImplementedError
10756
+ if not any(op.fullmatch(pr.name) for op in ops):
10757
+ omit = True
9718
10758
 
9719
10759
  if omit:
9720
10760
  out_lines.append('# OMITTED: ' + l)
@@ -10171,12 +11211,42 @@ uv run pip
10171
11211
  uv run --python 3.11.6 pip
10172
11212
  uv venv --python 3.11.6 --seed barf
10173
11213
  python3 -m venv barf && barf/bin/pip install uv && barf/bin/uv venv --python 3.11.6 --seed barf2
11214
+ uv python find '3.13.10'
11215
+ uv python list --output-format=json
10174
11216
  """
10175
11217
 
10176
11218
 
10177
11219
  ##
10178
11220
 
10179
11221
 
11222
+ @dc.dataclass(frozen=True)
11223
+ class UvPythonListOutput:
11224
+ key: str
11225
+ version: str
11226
+
11227
+ @dc.dataclass(frozen=True)
11228
+ class VersionParts:
11229
+ major: int
11230
+ minor: int
11231
+ patch: int
11232
+
11233
+ version_parts: VersionParts
11234
+
11235
+ path: ta.Optional[str]
11236
+ symlink: ta.Optional[str]
11237
+
11238
+ url: str
11239
+
11240
+ os: str # emscripten linux macos
11241
+ variant: str # default freethreaded
11242
+ implementation: str # cpython graalpy pyodide pypy
11243
+ arch: str # aarch64 wasm32 x86_64
11244
+ libc: str # gnu musl none
11245
+
11246
+
11247
+ ##
11248
+
11249
+
10180
11250
  class UvInterpProvider(InterpProvider):
10181
11251
  def __init__(
10182
11252
  self,
@@ -10197,6 +11267,12 @@ class UvInterpProvider(InterpProvider):
10197
11267
  async def get_installed_version(self, version: InterpVersion) -> Interp:
10198
11268
  raise NotImplementedError
10199
11269
 
11270
+ # async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
11271
+ # return []
11272
+
11273
+ # async def install_version(self, version: InterpVersion) -> Interp:
11274
+ # raise TypeError
11275
+
10200
11276
 
10201
11277
  ########################################
10202
11278
  # ../pkg.py
@@ -10273,7 +11349,7 @@ class BasePyprojectPackageGenerator(Abstract):
10273
11349
 
10274
11350
  def _write_git_ignore(self) -> None:
10275
11351
  with open(os.path.join(self._pkg_dir(), '.gitignore'), 'w') as f:
10276
- f.write('\n'.join(self._GIT_IGNORE))
11352
+ f.write('\n'.join([*self._GIT_IGNORE, '']))
10277
11353
 
10278
11354
  #
10279
11355
 
@@ -10442,6 +11518,7 @@ class BasePyprojectPackageGenerator(Abstract):
10442
11518
  )
10443
11519
 
10444
11520
  if output_dir is not None:
11521
+ log.info(lambda: f'Copying {dist_dir} to {output_dir}')
10445
11522
  for fn in os.listdir(dist_dir):
10446
11523
  shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(output_dir, fn))
10447
11524
 
@@ -10497,7 +11574,11 @@ class PyprojectPackageGenerator(BasePyprojectPackageGenerator):
10497
11574
  st = dict(specs.setuptools)
10498
11575
  pyp_dct['tool.setuptools'] = st
10499
11576
 
10500
- st.pop('cexts', None)
11577
+ for k in [
11578
+ 'cext',
11579
+ 'rs',
11580
+ ]:
11581
+ st.pop(k, None)
10501
11582
 
10502
11583
  #
10503
11584
 
@@ -10570,13 +11651,20 @@ class PyprojectPackageGenerator(BasePyprojectPackageGenerator):
10570
11651
  def children(self) -> ta.Sequence[BasePyprojectPackageGenerator]:
10571
11652
  out: ta.List[BasePyprojectPackageGenerator] = []
10572
11653
 
10573
- if self.build_specs().setuptools.get('cexts'):
11654
+ if self.build_specs().setuptools.get('cext'):
10574
11655
  out.append(_PyprojectCextPackageGenerator(
10575
11656
  self._dir_name,
10576
11657
  self._pkgs_root,
10577
11658
  pkg_suffix='-cext',
10578
11659
  ))
10579
11660
 
11661
+ if self.build_specs().setuptools.get('rs'):
11662
+ out.append(_PyprojectRsPackageGenerator(
11663
+ self._dir_name,
11664
+ self._pkgs_root,
11665
+ pkg_suffix='-rs',
11666
+ ))
11667
+
10580
11668
  if self.build_specs().pyproject.get('cli_scripts'):
10581
11669
  out.append(_PyprojectCliPackageGenerator(
10582
11670
  self._dir_name,
@@ -10587,10 +11675,67 @@ class PyprojectPackageGenerator(BasePyprojectPackageGenerator):
10587
11675
  return out
10588
11676
 
10589
11677
 
10590
- #
11678
+ ##
11679
+
11680
+
11681
+ class _PyprojectExtensionPackageGenerator(BasePyprojectPackageGenerator, Abstract):
11682
+ #
11683
+
11684
+ def _build_project_dict(self) -> ta.Dict[str, ta.Any]:
11685
+ prj = dict(self.build_specs().pyproject)
11686
+
11687
+ prj['dependencies'] = [f'{prj["name"]} == {prj["version"]}']
11688
+ prj['name'] += self._pkg_suffix
11689
+
11690
+ for k in [
11691
+ 'optional_dependencies',
11692
+ 'entry_points',
11693
+ 'scripts',
11694
+ 'cli_scripts',
11695
+ ]:
11696
+ prj.pop(k, None)
11697
+
11698
+ return prj
11699
+
11700
+ def _build_setuptools_dict(self) -> ta.Dict[str, ta.Any]:
11701
+ st = dict(self.build_specs().setuptools)
11702
+
11703
+ for k in [
11704
+ 'cext',
11705
+ 'rs',
11706
+
11707
+ 'find_packages',
11708
+ 'package_data',
11709
+ 'manifest_in',
11710
+ ]:
11711
+ st.pop(k, None)
11712
+
11713
+ return st
11714
+
11715
+ #
11716
+
11717
+ @dc.dataclass(frozen=True)
11718
+ class FileContents:
11719
+ pyproject_dct: ta.Mapping[str, ta.Any]
11720
+ setup_py: str
11721
+
11722
+ @abc.abstractmethod
11723
+ def file_contents(self) -> FileContents:
11724
+ raise NotImplementedError
11725
+
11726
+ #
10591
11727
 
11728
+ def _write_file_contents(self) -> None:
11729
+ fc = self.file_contents()
10592
11730
 
10593
- class _PyprojectCextPackageGenerator(BasePyprojectPackageGenerator):
11731
+ with open(os.path.join(self._pkg_dir(), 'pyproject.toml'), 'w') as f:
11732
+ TomlWriter(f).write_root(fc.pyproject_dct)
11733
+
11734
+ with open(os.path.join(self._pkg_dir(), 'setup.py'), 'w') as f:
11735
+ f.write(fc.setup_py)
11736
+
11737
+
11738
+ class _PyprojectCextPackageGenerator(_PyprojectExtensionPackageGenerator):
10594
11739
  @cached_nullary
10595
11740
  def find_cext_srcs(self) -> ta.Sequence[str]:
10596
11741
  return sorted(find_magic_files(
@@ -10601,14 +11746,10 @@ class _PyprojectCextPackageGenerator(BasePyprojectPackageGenerator):
10601
11746
 
10602
11747
  #
10603
11748
 
10604
- @dc.dataclass(frozen=True)
10605
- class FileContents:
10606
- pyproject_dct: ta.Mapping[str, ta.Any]
10607
- setup_py: str
10608
-
10609
11749
  @cached_nullary
10610
- def file_contents(self) -> FileContents:
10611
- specs = self.build_specs()
11750
+ def file_contents(self) -> _PyprojectExtensionPackageGenerator.FileContents:
11751
+ prj = self._build_project_dict()
11752
+ st = self._build_setuptools_dict()
10612
11753
 
10613
11754
  #
10614
11755
 
@@ -10619,33 +11760,9 @@ class _PyprojectCextPackageGenerator(BasePyprojectPackageGenerator):
10619
11760
  'build-backend': 'setuptools.build_meta',
10620
11761
  }
10621
11762
 
10622
- prj = specs.pyproject
10623
- prj['dependencies'] = [f'{prj["name"]} == {prj["version"]}']
10624
- prj['name'] += self._pkg_suffix
10625
- for k in [
10626
- 'optional_dependencies',
10627
- 'entry_points',
10628
- 'scripts',
10629
- 'cli_scripts',
10630
- ]:
10631
- prj.pop(k, None)
10632
-
10633
11763
  pyp_dct['project'] = prj
10634
-
10635
- #
10636
-
10637
- st = dict(specs.setuptools)
10638
11764
  pyp_dct['tool.setuptools'] = st
10639
11765
 
10640
- for k in [
10641
- 'cexts',
10642
-
10643
- 'find_packages',
10644
- 'package_data',
10645
- 'manifest_in',
10646
- ]:
10647
- st.pop(k, None)
10648
-
10649
11766
  pyp_dct['tool.setuptools.packages.find'] = {
10650
11767
  'include': [],
10651
11768
  }
@@ -10655,12 +11772,17 @@ class _PyprojectCextPackageGenerator(BasePyprojectPackageGenerator):
10655
11772
  ext_lines = []
10656
11773
 
10657
11774
  for ext_src in self.find_cext_srcs():
11775
+ ext_lang = ext_src.rpartition('.')[2]
11776
+ compile_args = {
11777
+ 'c': ['-std=c11'],
11778
+ 'cc': ['-std=c++20'],
11779
+ }.get(ext_lang, [])
10658
11780
  ext_name = ext_src.rpartition('.')[0].replace(os.sep, '.')
10659
11781
  ext_lines.extend([
10660
11782
  'st.Extension(',
10661
11783
  f" name='{ext_name}',",
10662
11784
  f" sources=['{ext_src}'],",
10663
- " extra_compile_args=['-std=c++20'],",
11785
+ f' extra_compile_args={compile_args!r},',
10664
11786
  '),',
10665
11787
  ])
10666
11788
 
@@ -10671,7 +11793,7 @@ class _PyprojectCextPackageGenerator(BasePyprojectPackageGenerator):
10671
11793
  'st.setup(',
10672
11794
  ' ext_modules=[',
10673
11795
  *[' ' + l for l in ext_lines],
10674
- ' ]',
11796
+ ' ],',
10675
11797
  ')',
10676
11798
  '',
10677
11799
  ])
@@ -10683,14 +11805,110 @@ class _PyprojectCextPackageGenerator(BasePyprojectPackageGenerator):
10683
11805
  src,
10684
11806
  )
10685
11807
 
10686
- def _write_file_contents(self) -> None:
10687
- fc = self.file_contents()
10688
11808
 
10689
- with open(os.path.join(self._pkg_dir(), 'pyproject.toml'), 'w') as f:
10690
- TomlWriter(f).write_root(fc.pyproject_dct)
11809
+ class _PyprojectRsPackageGenerator(_PyprojectExtensionPackageGenerator):
11810
+ @cached_nullary
11811
+ def find_rs_dirs(self) -> ta.Sequence[str]:
11812
+ return sorted(
11813
+ dp
11814
+ for dp, dns, fns in os.walk(self._dir_name)
11815
+ for fn in fns
11816
+ if fn == '.omlish-rs-ext'
11817
+ )
10691
11818
 
10692
- with open(os.path.join(self._pkg_dir(), 'setup.py'), 'w') as f:
10693
- f.write(fc.setup_py)
11819
+ #
11820
+
11821
+ @staticmethod
11822
+ def _sdist_patch_body() -> None:
11823
+ def _patch_sdist():
11824
+ def _sdist_add_defaults(old, self):
11825
+ import os.path
11826
+
11827
+ old(self)
11828
+
11829
+ if self.distribution.rust_extensions and len(self.distribution.rust_extensions) > 0:
11830
+ build_rust = self.get_finalized_command('build_rust') # noqa
11831
+ for ext in build_rust.extensions:
11832
+ ext_dir = os.path.dirname(ext.path)
11833
+ for n in os.listdir(ext_dir):
11834
+ if n.startswith('.') or n == 'target':
11835
+ continue
11836
+ p = os.path.join(ext_dir, n)
11837
+ if os.path.isfile(p):
11838
+ self.filelist.append(p)
11839
+ elif os.path.isdir(p):
11840
+ self.filelist.extend(os.path.join(dp, f) for dp, dn, fn in os.walk(p) for f in fn)
11841
+
11842
+ # Sadly, we can't just subclass sdist and override it via cmdclass because manifest_maker calls
11843
+ # `sdist.add_defaults` as an unbound function, not a bound method:
11844
+ # https://github.com/pypa/setuptools/blob/9c4d383631d3951fcae0afd73b5d08ff5a262976/setuptools/command/egg_info.py#L581
11845
+ from setuptools.command.sdist import sdist # noqa
11846
+ sdist.add_defaults = (lambda old: lambda sdist: _sdist_add_defaults(old, sdist))(sdist.add_defaults) # noqa
11847
+
11848
+ _patch_sdist()
11849
+
11850
+ @cached_nullary
11851
+ def sdist_patch_code(self) -> str:
11852
+ return textwrap.dedent(''.join(inspect.getsource(self._sdist_patch_body).splitlines(keepends=True)[2:])).strip()
11853
+
11854
+ #
11855
+
11856
+ @cached_nullary
11857
+ def file_contents(self) -> _PyprojectExtensionPackageGenerator.FileContents:
11858
+ prj = self._build_project_dict()
11859
+ st = self._build_setuptools_dict()
11860
+
11861
+ #
11862
+
11863
+ pyp_dct = {}
11864
+
11865
+ pyp_dct['build-system'] = {
11866
+ 'requires': ['setuptools', 'setuptools-rust'],
11867
+ 'build-backend': 'setuptools.build_meta',
11868
+ }
11869
+
11870
+ pyp_dct['project'] = prj
11871
+ pyp_dct['tool.setuptools'] = st
11872
+
11873
+ pyp_dct['tool.setuptools.packages.find'] = {
11874
+ 'include': [],
11875
+ }
11876
+
11877
+ #
11878
+
11879
+ ext_lines: list = []
11880
+
11881
+ for ext_dir in self.find_rs_dirs(): # noqa
11882
+ ext_name = ext_dir.replace(os.sep, '.')
11883
+ ext_lines.extend([
11884
+ 'st_rs.RustExtension(',
11885
+ f" '{ext_name}',",
11886
+ f" path='{ext_dir}/Cargo.toml',",
11887
+ '),',
11888
+ ])
11889
+
11890
+ src = '\n'.join([
11891
+ 'import setuptools as st',
11892
+ 'import setuptools_rust as st_rs',
11893
+ '',
11894
+ '',
11895
+ self.sdist_patch_code(),
11896
+ '',
11897
+ '',
11898
+ 'st.setup(',
11899
+ ' rust_extensions=[',
11900
+ *[' ' + l for l in ext_lines],
11901
+ ' ],',
11902
+ ')',
11903
+ '',
11904
+ ])
11905
+
11906
+ #
11907
+
11908
+ return self.FileContents(
11909
+ pyp_dct,
11910
+ src,
11911
+ )
10694
11912
 
10695
11913
 
10696
11914
  ##
@@ -10735,7 +11953,8 @@ class _PyprojectCliPackageGenerator(BasePyprojectPackageGenerator):
10735
11953
  pyp_dct['tool.setuptools'] = st
10736
11954
 
10737
11955
  for k in [
10738
- 'cexts',
11956
+ 'cext',
11957
+ 'rs',
10739
11958
 
10740
11959
  'find_packages',
10741
11960
  'package_data',
@@ -11020,6 +12239,7 @@ def get_default_interp_resolver() -> InterpResolver:
11020
12239
  class InterpVenvConfig:
11021
12240
  interp: ta.Optional[str] = None
11022
12241
  requires: ta.Optional[ta.Sequence[str]] = None
12242
+ requires_pats: ta.Optional[ta.Sequence[str]] = None
11023
12243
  use_uv: ta.Optional[bool] = None
11024
12244
 
11025
12245
 
@@ -11244,7 +12464,13 @@ class Venv:
11244
12464
 
11245
12465
  @cached_nullary
11246
12466
  def _iv(self) -> InterpVenv:
11247
- rr = RequirementsRewriter(self._name)
12467
+ rr = RequirementsRewriter(
12468
+ venv=self._name,
12469
+ only_pats=(
12470
+ [re.compile(p) for p in self._cfg.requires_pats]
12471
+ if self._cfg.requires_pats is not None else None
12472
+ ),
12473
+ )
11248
12474
 
11249
12475
  return InterpVenv(
11250
12476
  self.dir_name,