omdev 0.0.0.dev439__py3-none-any.whl → 0.0.0.dev486__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (134) hide show
  1. omdev/.omlish-manifests.json +18 -30
  2. omdev/__about__.py +9 -7
  3. omdev/amalg/gen/gen.py +49 -6
  4. omdev/amalg/gen/imports.py +1 -1
  5. omdev/amalg/gen/manifests.py +1 -1
  6. omdev/amalg/gen/resources.py +1 -1
  7. omdev/amalg/gen/srcfiles.py +13 -3
  8. omdev/amalg/gen/strip.py +1 -1
  9. omdev/amalg/gen/types.py +1 -1
  10. omdev/amalg/gen/typing.py +1 -1
  11. omdev/amalg/info.py +32 -0
  12. omdev/cache/data/actions.py +1 -1
  13. omdev/cache/data/specs.py +1 -1
  14. omdev/cexts/_boilerplate.cc +2 -3
  15. omdev/cexts/cmake.py +4 -1
  16. omdev/ci/cli.py +1 -2
  17. omdev/ci/github/api/v2/api.py +2 -0
  18. omdev/cmdlog/cli.py +1 -2
  19. omdev/dataclasses/_dumping.py +1960 -0
  20. omdev/dataclasses/_template.py +22 -0
  21. omdev/dataclasses/cli.py +6 -1
  22. omdev/dataclasses/codegen.py +340 -60
  23. omdev/dataclasses/dumping.py +200 -0
  24. omdev/interp/uv/provider.py +1 -0
  25. omdev/interp/venvs.py +1 -0
  26. omdev/irc/messages/base.py +50 -0
  27. omdev/irc/messages/formats.py +92 -0
  28. omdev/irc/messages/messages.py +775 -0
  29. omdev/irc/messages/parsing.py +99 -0
  30. omdev/irc/numerics/__init__.py +0 -0
  31. omdev/irc/numerics/formats.py +97 -0
  32. omdev/irc/numerics/numerics.py +865 -0
  33. omdev/irc/numerics/types.py +59 -0
  34. omdev/irc/protocol/LICENSE +11 -0
  35. omdev/irc/protocol/__init__.py +61 -0
  36. omdev/irc/protocol/consts.py +6 -0
  37. omdev/irc/protocol/errors.py +30 -0
  38. omdev/irc/protocol/message.py +21 -0
  39. omdev/irc/protocol/nuh.py +55 -0
  40. omdev/irc/protocol/parsing.py +158 -0
  41. omdev/irc/protocol/rendering.py +153 -0
  42. omdev/irc/protocol/tags.py +102 -0
  43. omdev/irc/protocol/utils.py +30 -0
  44. omdev/manifests/_dumping.py +125 -25
  45. omdev/markdown/__init__.py +0 -0
  46. omdev/markdown/incparse.py +116 -0
  47. omdev/markdown/tokens.py +51 -0
  48. omdev/packaging/marshal.py +8 -8
  49. omdev/packaging/requires.py +6 -6
  50. omdev/packaging/specifiers.py +2 -1
  51. omdev/packaging/versions.py +4 -4
  52. omdev/packaging/wheelfile.py +2 -0
  53. omdev/precheck/blanklines.py +66 -0
  54. omdev/precheck/caches.py +1 -1
  55. omdev/precheck/imports.py +14 -1
  56. omdev/precheck/main.py +4 -3
  57. omdev/precheck/unicode.py +39 -15
  58. omdev/py/asts/__init__.py +0 -0
  59. omdev/py/asts/parents.py +28 -0
  60. omdev/py/asts/toplevel.py +123 -0
  61. omdev/py/asts/visitors.py +18 -0
  62. omdev/py/attrdocs.py +6 -7
  63. omdev/py/bracepy.py +12 -4
  64. omdev/py/reprs.py +32 -0
  65. omdev/py/srcheaders.py +1 -1
  66. omdev/py/tokens/__init__.py +0 -0
  67. omdev/py/tools/mkrelimp.py +1 -1
  68. omdev/py/tools/pipdepup.py +629 -0
  69. omdev/pyproject/pkg.py +190 -45
  70. omdev/pyproject/reqs.py +31 -9
  71. omdev/pyproject/tools/__init__.py +0 -0
  72. omdev/pyproject/tools/aboutdeps.py +55 -0
  73. omdev/pyproject/venvs.py +8 -1
  74. omdev/rs/__init__.py +0 -0
  75. omdev/scripts/ci.py +400 -80
  76. omdev/scripts/interp.py +193 -35
  77. omdev/scripts/lib/__init__.py +0 -0
  78. omdev/scripts/{inject.py → lib/inject.py} +75 -28
  79. omdev/scripts/lib/logs.py +2079 -0
  80. omdev/scripts/{marshal.py → lib/marshal.py} +68 -26
  81. omdev/scripts/pyproject.py +941 -90
  82. omdev/tools/git/cli.py +12 -1
  83. omdev/tools/json/processing.py +5 -2
  84. omdev/tools/jsonview/cli.py +31 -5
  85. omdev/tools/pawk/pawk.py +2 -2
  86. omdev/tools/pip.py +8 -0
  87. omdev/tui/__init__.py +0 -0
  88. omdev/tui/apps/__init__.py +0 -0
  89. omdev/tui/apps/edit/__init__.py +0 -0
  90. omdev/tui/apps/edit/main.py +163 -0
  91. omdev/tui/apps/irc/__init__.py +0 -0
  92. omdev/tui/apps/irc/__main__.py +4 -0
  93. omdev/tui/apps/irc/app.py +278 -0
  94. omdev/tui/apps/irc/client.py +187 -0
  95. omdev/tui/apps/irc/commands.py +175 -0
  96. omdev/tui/apps/irc/main.py +26 -0
  97. omdev/tui/apps/markdown/__init__.py +0 -0
  98. omdev/tui/apps/markdown/__main__.py +11 -0
  99. omdev/{ptk → tui/apps}/markdown/cli.py +5 -7
  100. omdev/tui/rich/__init__.py +34 -0
  101. omdev/tui/rich/console2.py +20 -0
  102. omdev/tui/rich/markdown2.py +186 -0
  103. omdev/tui/textual/__init__.py +226 -0
  104. omdev/tui/textual/app2.py +11 -0
  105. omdev/tui/textual/autocomplete/LICENSE +21 -0
  106. omdev/tui/textual/autocomplete/__init__.py +33 -0
  107. omdev/tui/textual/autocomplete/matching.py +226 -0
  108. omdev/tui/textual/autocomplete/paths.py +202 -0
  109. omdev/tui/textual/autocomplete/widget.py +612 -0
  110. omdev/tui/textual/drivers2.py +55 -0
  111. {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/METADATA +11 -9
  112. {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/RECORD +121 -73
  113. omdev/ptk/__init__.py +0 -103
  114. omdev/ptk/apps/ncdu.py +0 -167
  115. omdev/ptk/confirm.py +0 -60
  116. omdev/ptk/markdown/LICENSE +0 -22
  117. omdev/ptk/markdown/__init__.py +0 -10
  118. omdev/ptk/markdown/__main__.py +0 -11
  119. omdev/ptk/markdown/border.py +0 -94
  120. omdev/ptk/markdown/markdown.py +0 -390
  121. omdev/ptk/markdown/parser.py +0 -42
  122. omdev/ptk/markdown/styles.py +0 -29
  123. omdev/ptk/markdown/tags.py +0 -299
  124. omdev/ptk/markdown/utils.py +0 -366
  125. omdev/pyproject/cexts.py +0 -110
  126. /omdev/{ptk/apps → irc}/__init__.py +0 -0
  127. /omdev/{tokens → irc/messages}/__init__.py +0 -0
  128. /omdev/{tokens → py/tokens}/all.py +0 -0
  129. /omdev/{tokens → py/tokens}/tokenizert.py +0 -0
  130. /omdev/{tokens → py/tokens}/utils.py +0 -0
  131. {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/WHEEL +0 -0
  132. {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/entry_points.txt +0 -0
  133. {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/licenses/LICENSE +0 -0
  134. {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,22 @@
1
+ # type: ignore
2
+ # ruff: noqa
3
+ # flake8: noqa
4
+ import dataclasses
5
+ import reprlib
6
+ import types
7
+
8
+
9
+ ##
10
+
11
+
12
+ REGISTRY = {}
13
+
14
+
15
+ def _register(**kwargs):
16
+ def inner(fn):
17
+ REGISTRY[kwargs['plan_repr']] = (kwargs, fn)
18
+ return fn
19
+ return inner
20
+
21
+
22
+ ##
omdev/dataclasses/cli.py CHANGED
@@ -12,7 +12,12 @@ class Cli(ap.Cli):
12
12
  ap.arg('roots', metavar='root', nargs='+'),
13
13
  )
14
14
  def codegen(self) -> None:
15
- DataclassCodeGen().run(self.args.roots)
15
+ import asyncio
16
+ asyncio.run(DataclassCodeGen(
17
+ # dump_inline=True,
18
+ ).run(
19
+ self.args.roots,
20
+ ))
16
21
 
17
22
 
18
23
  ##
@@ -1,27 +1,38 @@
1
1
  """
2
2
  TODO:
3
- - subdir conf files override parents, codegen those separately, don't duplicate
4
3
  - refactor dc gen to just Execute and Codegen
5
4
  - need to bubble up imports, preamble, deduped
6
- - still need plan repr / cmp
7
- - !! manifests for dataclass config?
8
- - more sparse / diffuse intent, not package-level
5
+ - _gen subdirs
6
+ - static analyze codegen kwarg if possible
7
+ - better ignore configurability than just tests dirs lol
9
8
  """
10
- import importlib
9
+ import ast
10
+ import asyncio
11
+ import hashlib
12
+ import inspect
13
+ import json
11
14
  import os.path
15
+ import shlex
16
+ import sys
17
+ import tempfile
18
+ import time
12
19
  import typing as ta
13
20
 
14
21
  from omlish import check
15
22
  from omlish import collections as col
23
+ from omlish import dataclasses as dc
16
24
  from omlish import lang
17
- from omlish.dataclasses.impl.configs import PackageConfig
18
- from omlish.dataclasses.impl.generation.compilation import OpCompiler
19
- from omlish.dataclasses.impl.generation.processor import Codegen as CodegenProcessingOption
20
- from omlish.dataclasses.impl.generation.processor import GeneratorProcessor
21
- from omlish.dataclasses.impl.processing.base import ProcessingContext
22
- from omlish.dataclasses.impl.processing.driving import processing_options_context
25
+ from omlish.asyncs.asyncio import all as au
26
+ from omlish.lite.marshal import unmarshal_obj
23
27
  from omlish.logs import all as logs
24
28
 
29
+ from ..py.asts.toplevel import TopLevelCall
30
+ from ..py.asts.toplevel import analyze_module_top_level
31
+ from ..py.reprs import textwrap_repr
32
+ from ..py.srcheaders import get_py_header_lines
33
+ from .dumping import DataclassCodegenDumperOutput
34
+ from .dumping import DumpedDataclassCodegen
35
+
25
36
 
26
37
  log = logs.get_module_logger(globals())
27
38
 
@@ -29,67 +40,336 @@ log = logs.get_module_logger(globals())
29
40
  ##
30
41
 
31
42
 
43
+ def _find_dir_py_files(dir_path: str) -> list[str]:
44
+ return sorted(
45
+ os.path.join(p, fn)
46
+ for p, dns, fns in os.walk(dir_path)
47
+ for fn in fns
48
+ if fn.endswith('.py')
49
+ )
50
+
51
+
52
+ @lang.cached_function
53
+ def _module_manifest_dumper_payload_src() -> str:
54
+ from . import _dumping
55
+ return inspect.getsource(_dumping)
56
+
57
+
58
+ def _is_generated_py_file(fp: str) -> bool:
59
+ with open(fp) as f:
60
+ gen_file_src = f.read()
61
+
62
+ gen_hdr_lines = get_py_header_lines(gen_file_src)
63
+ return any(hl.src.strip() == '# @omlish-generated' for hl in gen_hdr_lines)
64
+
65
+
66
+ #
67
+
68
+
32
69
  class DataclassCodeGen:
33
- def __init__(self) -> None:
70
+ DEFAULT_TARGET_LINE_WIDTH: ta.ClassVar[int] = 120
71
+
72
+ def __init__(
73
+ self,
74
+ *,
75
+ target_line_width: int | None = None,
76
+ dump_inline: bool = False,
77
+ subprocess_kwargs: ta.Mapping[str, ta.Any] | None = None,
78
+ ) -> None:
34
79
  super().__init__()
35
80
 
36
- def run_package_config(
81
+ self._target_line_width = target_line_width or self.DEFAULT_TARGET_LINE_WIDTH
82
+ self._dump_inline = dump_inline
83
+ self._subprocess_kwargs = subprocess_kwargs
84
+
85
+ #
86
+
87
+ @dc.dataclass(frozen=True)
88
+ class ConfiguredPackage:
89
+ name: str
90
+
91
+ init_file_path: str
92
+ init_module_name: str
93
+
94
+ init_package_call: TopLevelCall
95
+
96
+ def scan_py_file(self, file_path: str) -> ConfiguredPackage | None:
97
+ with open(file_path) as f:
98
+ source = f.read()
99
+
100
+ module = check.isinstance(ast.parse(source), ast.Module)
101
+ module_name = '.'.join([
102
+ *os.path.dirname(file_path).split(os.path.sep),
103
+ os.path.basename(file_path).removesuffix('.py'),
104
+ ])
105
+
106
+ tl = analyze_module_top_level(module, module_name)
107
+
108
+ init_calls: list[TopLevelCall] = []
109
+ for call in tl.calls:
110
+ if call.imp.spec != 'omlish.dataclasses':
111
+ continue
112
+ match call.node:
113
+ case ast.Call(func=ast.Attribute(attr='init_package')):
114
+ init_calls.append(call)
115
+
116
+ if not init_calls:
117
+ return None
118
+ if os.path.basename(file_path) != '__init__.py':
119
+ raise ValueError(f'File {file_path} has init_package call and is not an __init__.py: {init_calls!r}')
120
+ if len(init_calls) > 1:
121
+ raise ValueError(f'File {file_path} has multiple init_package calls: {init_calls!r}')
122
+
123
+ return DataclassCodeGen.ConfiguredPackage(
124
+ '.'.join(module_name.split('.')[:-1]),
125
+ file_path,
126
+ module_name,
127
+ check.single(init_calls),
128
+ )
129
+
130
+ def find_configured_packages(self, root_dirs: ta.Iterable[str]) -> list[ConfiguredPackage]:
131
+ check.not_isinstance(root_dirs, str)
132
+
133
+ py_files = (
134
+ fp
135
+ for rd in root_dirs
136
+ for fp in _find_dir_py_files(rd)
137
+ )
138
+
139
+ return [
140
+ cfg_pkg
141
+ for file_path in py_files
142
+ if (cfg_pkg := self.scan_py_file(file_path)) is not None
143
+ ]
144
+
145
+ #
146
+
147
+ async def _run_dumper_subprocess(
37
148
  self,
38
- pkg_root: str,
39
- config: PackageConfig,
149
+ cfg_pkg: ConfiguredPackage,
150
+ dumper_kwargs: ta.Mapping[str, ta.Any],
151
+ *,
152
+ shell_wrap: bool = True,
40
153
  ) -> None:
41
- if not config.codegen:
42
- return
43
-
44
- log.info('Running codegen on package: %s', pkg_root)
45
-
46
- sub_pkgs = sorted(lang.yield_importable(
47
- pkg_root,
48
- recursive=True,
49
- ))
50
-
51
- for sub_pkg in sub_pkgs:
52
- def callback(
53
- ctx: ProcessingContext,
54
- prepared: GeneratorProcessor.Prepared,
55
- comp: OpCompiler.CompileResult,
56
- ) -> None:
57
- print(ctx.cls)
58
- print(prepared.plans)
59
- print(comp.src)
60
-
61
- with processing_options_context(CodegenProcessingOption(callback)):
62
- print(f'{sub_pkg=}')
63
- try:
64
- importlib.import_module(sub_pkg)
65
- except ImportError as e:
66
- print(repr(e))
67
-
68
- def build_config_trie(
154
+ dumper_payload_src = _module_manifest_dumper_payload_src()
155
+
156
+ subproc_src = '\n\n'.join([
157
+ dumper_payload_src,
158
+ f'_DataclassCodegenDumper()(**{dumper_kwargs!r})\n',
159
+ ])
160
+
161
+ args = [
162
+ sys.executable,
163
+ '-c',
164
+ subproc_src,
165
+ ]
166
+
167
+ if shell_wrap:
168
+ args = ['sh', '-c', ' '.join(map(shlex.quote, args))]
169
+
170
+ proc = await asyncio.create_subprocess_exec(
171
+ *args,
172
+ **(self._subprocess_kwargs or {}),
173
+ )
174
+
175
+ if await proc.wait():
176
+ raise Exception('Subprocess failed')
177
+
178
+ async def _run_dumper_inline(
69
179
  self,
70
- root_dirs: ta.Iterable[str],
71
- ) -> col.Trie[str, PackageConfig]:
72
- check.not_isinstance(root_dirs, str)
180
+ cfg_pkg: ConfiguredPackage,
181
+ dumper_kwargs: ta.Mapping[str, ta.Any],
182
+ ) -> None:
183
+ from . import dumping
184
+
185
+ dumping._DataclassCodegenDumper()(**dumper_kwargs) # noqa
186
+
187
+ async def process_configured_package(
188
+ self,
189
+ cfg_pkg: ConfiguredPackage,
190
+ *,
191
+ warn_threshold_s: float | None = 10.,
192
+ ) -> None:
193
+ log.info(lambda: f'Running codegen on package: {cfg_pkg.name}')
73
194
 
74
- trie: col.Trie[str, PackageConfig] = col.Trie()
75
- for root_dir in root_dirs:
76
- for dp, _, fns in os.walk(root_dir): # noqa
77
- # if PACKAGE_CONFIG_FILE_NAME in fns:
78
- # with open(os.path.join(dp, PACKAGE_CONFIG_FILE_NAME)) as f:
79
- # config = PackageConfig.loads(f.read())
80
- # pkg_parts = dp.split(os.sep)
81
- # trie[pkg_parts] = config
82
- pass
195
+ out_dir = tempfile.mkdtemp()
196
+ out_file_path = os.path.join(out_dir, 'output.json')
83
197
 
84
- return trie
198
+ dumper_kwargs = dict(
199
+ init_file_path=cfg_pkg.init_file_path,
200
+ out_file_path=out_file_path,
201
+ )
85
202
 
86
- def run(
203
+ start_time = time.time()
204
+
205
+ if self._dump_inline:
206
+ await self._run_dumper_inline(cfg_pkg, dumper_kwargs)
207
+ else:
208
+ await self._run_dumper_subprocess(cfg_pkg, dumper_kwargs)
209
+
210
+ end_time = time.time()
211
+
212
+ if warn_threshold_s is not None and (elapsed_time := (end_time - start_time)) >= warn_threshold_s:
213
+ log.warning('Dataclass codegen took a long time: %s, %.2f s', cfg_pkg.name, elapsed_time)
214
+
215
+ with open(out_file_path) as f: # noqa
216
+ out_s = f.read()
217
+
218
+ output: DataclassCodegenDumperOutput = unmarshal_obj(json.loads(out_s), DataclassCodegenDumperOutput)
219
+
220
+ await self.process_dumper_output(cfg_pkg, output)
221
+
222
+ #
223
+
224
+ PROCESS_FN_NAME: ta.ClassVar[str] = '_process_dataclass'
225
+
226
+ async def process_dumper_output(
227
+ self,
228
+ cfg_pkg: ConfiguredPackage,
229
+ output: DataclassCodegenDumperOutput,
230
+ ) -> None:
231
+ gen_file_path = os.path.join(os.path.dirname(cfg_pkg.init_file_path), '_dataclasses.py')
232
+
233
+ if os.path.isfile(gen_file_path):
234
+ if not _is_generated_py_file(gen_file_path):
235
+ raise RuntimeError(f'Refusing to overwrite non-generated file: {gen_file_path!r}')
236
+
237
+ if not output.dumped:
238
+ os.unlink(gen_file_path)
239
+ return
240
+
241
+ #
242
+
243
+ lines = [
244
+ '# @omlish-generated',
245
+ ]
246
+
247
+ from . import _template
248
+ lines.extend(inspect.getsource(_template).strip().split('\n'))
249
+
250
+ #
251
+
252
+ processed_modules = set(output.processed_modules)
253
+
254
+ dumped_by_plan_repr: dict[str, list[DumpedDataclassCodegen]] = {}
255
+
256
+ seen_cls_name_tups: set[tuple[str, str]] = set()
257
+
258
+ for x in output.dumped:
259
+ if x.cls_module not in processed_modules:
260
+ continue
261
+
262
+ cls_name_tup = (x.cls_module, x.cls_qualname)
263
+ check.not_in(cls_name_tup, seen_cls_name_tups)
264
+ seen_cls_name_tups.add(cls_name_tup)
265
+
266
+ try:
267
+ lst = dumped_by_plan_repr[x.plan_repr]
268
+ except KeyError:
269
+ dumped_by_plan_repr[x.plan_repr] = [x]
270
+ continue
271
+
272
+ y = lst[0]
273
+ if set(x.refs) != set(y.refs):
274
+ raise RuntimeError(f'Mismatched refs: {x!r} != {y!r}')
275
+
276
+ lst.append(x)
277
+
278
+ #
279
+
280
+ # Sorted by first cls name for more stable diffs than say sha1
281
+ for grp in sorted(
282
+ dumped_by_plan_repr.values(),
283
+ key=lambda grp: sorted([(y.mod_name, y.cls_qualname) for y in grp])[0],
284
+ ):
285
+ x = grp[0]
286
+ pr = x.plan_repr
287
+ pr_sha1 = hashlib.sha1(pr.encode()).hexdigest() # noqa
288
+
289
+ fn_name = f'{self.PROCESS_FN_NAME}__{pr_sha1}'
290
+
291
+ lines.extend(['', ''])
292
+
293
+ lines.append(
294
+ '@_register(',
295
+ )
296
+
297
+ lines.extend([
298
+ f' plan_repr=(',
299
+ *[
300
+ f' {prl}'
301
+ for prl in textwrap_repr(x.plan_repr, self._target_line_width - 8)
302
+ ],
303
+ f' ),',
304
+ f' plan_repr_sha1={pr_sha1!r},',
305
+ ])
306
+
307
+ op_ref_idents = [r.ident for r in x.refs if r.kind == 'op']
308
+ if op_ref_idents:
309
+ lines.extend([
310
+ f' op_ref_idents=(',
311
+ *[
312
+ f' {r!r},'
313
+ for r in sorted(op_ref_idents)
314
+ ],
315
+ f' ),',
316
+ ])
317
+ else:
318
+ lines.append(
319
+ ' op_ref_idents=(),',
320
+ )
321
+
322
+ lines.extend([
323
+ f' cls_names=(',
324
+ *[
325
+ f' {cn!r},'
326
+ for cn in sorted([
327
+ (y.mod_name, y.cls_qualname)
328
+ for y in grp
329
+ ])
330
+ ],
331
+ f' ),',
332
+ ])
333
+
334
+ lines.append(
335
+ ')',
336
+ )
337
+
338
+ lines.extend([
339
+ f'def {fn_name}():',
340
+ ])
341
+
342
+ lines.extend([
343
+ f' {l}' if l.strip() else ''
344
+ for l in x.fn_lines
345
+ ])
346
+
347
+ lines.extend([
348
+ '',
349
+ f' return {x.fn_name}',
350
+ ])
351
+
352
+ lines.append('')
353
+
354
+ with open(gen_file_path, 'w') as f: # noqa
355
+ f.write('\n'.join(lines))
356
+
357
+ async def run(
87
358
  self,
88
359
  root_dirs: ta.Iterable[str],
360
+ *,
361
+ concurrency: int | None = 4,
89
362
  ) -> None:
90
363
  check.not_isinstance(root_dirs, str)
91
364
 
92
- config_trie = self.build_config_trie(root_dirs)
365
+ cfg_pkgs = self.find_configured_packages(root_dirs)
366
+
367
+ cfg_pkg_trie = col.Trie([ # noqa
368
+ (cfg_pkg.name.split('.'), cfg_pkg)
369
+ for cfg_pkg in cfg_pkgs
370
+ ])
93
371
 
94
- for pkg_parts, pkg_config in config_trie.iteritems(sort_children=True):
95
- self.run_package_config('.'.join(pkg_parts), pkg_config)
372
+ await au.wait_maybe_concurrent([
373
+ self.process_configured_package(cfg_pkg)
374
+ for cfg_pkg in cfg_pkgs
375
+ ], concurrency)
@@ -0,0 +1,200 @@
1
+ # ruff: noqa: UP006 UP007 UP037 UP045
2
+ # @omlish-lite
3
+ # @omlish-amalg _dumping.py
4
+ import dataclasses as dc
5
+ import os.path
6
+ import typing as ta
7
+
8
+ from omlish.lite.check import check
9
+ from omlish.lite.json import json_dumps_pretty
10
+ from omlish.lite.marshal import marshal_obj
11
+
12
+
13
+ ##
14
+
15
+
16
+ @dc.dataclass(frozen=True)
17
+ class DumpedDataclassCodegen:
18
+ mod_name: str
19
+
20
+ cls_module: str
21
+ cls_qualname: str
22
+
23
+ plan_repr: str
24
+
25
+ fn_name: str
26
+ fn_params: ta.Sequence[str]
27
+
28
+ hdr_lines: ta.Sequence[str]
29
+ fn_lines: ta.Sequence[str]
30
+
31
+ @dc.dataclass(frozen=True)
32
+ class Ref:
33
+ kind: ta.Literal['op', 'global']
34
+ ident: str
35
+
36
+ refs: ta.Sequence[Ref]
37
+
38
+
39
+ @dc.dataclass(frozen=True)
40
+ class DataclassCodegenDumperOutput:
41
+ init_file_path: str
42
+ out_file_path: str
43
+
44
+ processed_modules: ta.Sequence[str]
45
+ import_errors: ta.Mapping[str, str]
46
+
47
+ dumped: ta.Sequence[DumpedDataclassCodegen]
48
+
49
+
50
+ ##
51
+
52
+
53
+ class _DataclassCodegenDumper:
54
+ def __call__(
55
+ self,
56
+ *,
57
+ init_file_path: str,
58
+ out_file_path: str,
59
+ ) -> None:
60
+ from omlish.dataclasses.impl.configs import PACKAGE_CONFIG_CACHE # noqa
61
+ from omlish.dataclasses.impl.generation.compilation import OpCompiler # noqa
62
+ from omlish.dataclasses.impl.generation.globals import FnGlobal # noqa
63
+ from omlish.dataclasses.impl.generation.ops import OpRef # noqa
64
+ from omlish.dataclasses.impl.generation.processor import Codegen # noqa
65
+ from omlish.dataclasses.impl.generation.processor import GeneratorProcessor # noqa
66
+ from omlish.dataclasses.impl.processing.base import ProcessingContext # noqa
67
+ from omlish.dataclasses.impl.processing.driving import processing_options_context # noqa
68
+
69
+ cur_module: ta.Optional[str] = None
70
+
71
+ dumped: ta.List[DumpedDataclassCodegen] = []
72
+
73
+ def callback(
74
+ ctx: ProcessingContext,
75
+ prepared: GeneratorProcessor.Prepared,
76
+ comp: OpCompiler.CompileResult,
77
+ ) -> None:
78
+ d_refs: ta.List[DumpedDataclassCodegen.Ref] = []
79
+ for ref in comp.refs:
80
+ if isinstance(ref, OpRef):
81
+ d_refs.append(DumpedDataclassCodegen.Ref(
82
+ kind='op',
83
+ ident=ref.ident(),
84
+ ))
85
+ elif isinstance(ref, FnGlobal):
86
+ d_refs.append(DumpedDataclassCodegen.Ref(
87
+ kind='global',
88
+ ident=ref.ident,
89
+ ))
90
+ else:
91
+ raise TypeError(ref)
92
+
93
+ dumped.append(DumpedDataclassCodegen(
94
+ mod_name=check.not_none(cur_module),
95
+
96
+ cls_module=ctx.cls.__module__,
97
+ cls_qualname=ctx.cls.__qualname__,
98
+
99
+ plan_repr=repr(prepared.plans),
100
+
101
+ fn_name=comp.fn_name,
102
+ fn_params=comp.fn_params,
103
+
104
+ hdr_lines=comp.hdr_lines,
105
+ fn_lines=comp.fn_lines,
106
+
107
+ refs=d_refs,
108
+ ))
109
+
110
+ #
111
+
112
+ processed_modules: ta.List[str] = []
113
+
114
+ import_errors: ta.Dict[str, str] = {}
115
+
116
+ def process_module(spec: str) -> None:
117
+ nonlocal cur_module
118
+ check.none(cur_module)
119
+ cur_module = spec
120
+
121
+ try:
122
+ processed_modules.append(spec)
123
+
124
+ try:
125
+ __import__(spec)
126
+ except Exception as e: # noqa
127
+ import_errors[spec] = repr(e)
128
+
129
+ finally:
130
+ cur_module = None
131
+
132
+ def process_dir(dir_path: str) -> None:
133
+ spec = '.'.join(dir_path.split(os.path.sep))
134
+
135
+ process_module(spec)
136
+
137
+ pkg_cfg = PACKAGE_CONFIG_CACHE.get(spec)
138
+ if pkg_cfg is not None and not pkg_cfg.cfg.codegen:
139
+ return
140
+
141
+ #
142
+
143
+ dns: ta.List[str] = []
144
+ fns: ta.List[str] = []
145
+ for n in os.listdir(dir_path):
146
+ np = os.path.join(dir_path, n)
147
+ if os.path.isdir(np):
148
+ dns.append(n)
149
+ elif os.path.isfile(np):
150
+ fns.append(n)
151
+
152
+ for fn in sorted(fns):
153
+ if not fn.endswith('.py') or fn in ('conftest.py', '_dataclasses.py'):
154
+ continue
155
+
156
+ fp = os.path.join(dir_path, fn)
157
+
158
+ fpp = fp.split(os.path.sep)
159
+ check.state(fpp[-1].endswith('.py'))
160
+ fpp[-1] = fpp[-1][:-3]
161
+ if fpp[-1] == '__init__':
162
+ fpp.pop()
163
+ spec = '.'.join(fpp)
164
+
165
+ process_module(spec)
166
+
167
+ for dn in sorted(dns):
168
+ if dn == 'tests':
169
+ continue
170
+
171
+ dp = os.path.join(dir_path, dn)
172
+
173
+ if not os.path.isfile(os.path.join(dp, '__init__.py')):
174
+ continue
175
+
176
+ process_dir(dp)
177
+
178
+ #
179
+
180
+ with processing_options_context(Codegen(
181
+ style='aot',
182
+ force=True,
183
+ callback=callback,
184
+ )):
185
+ process_dir(os.path.dirname(init_file_path))
186
+
187
+ #
188
+
189
+ output = DataclassCodegenDumperOutput(
190
+ init_file_path=init_file_path,
191
+ out_file_path=out_file_path,
192
+
193
+ processed_modules=processed_modules,
194
+ import_errors=import_errors,
195
+
196
+ dumped=dumped,
197
+ )
198
+
199
+ with open(out_file_path, 'w') as f:
200
+ f.write(json_dumps_pretty(marshal_obj(output)))
@@ -4,6 +4,7 @@ uv run pip
4
4
  uv run --python 3.11.6 pip
5
5
  uv venv --python 3.11.6 --seed barf
6
6
  python3 -m venv barf && barf/bin/pip install uv && barf/bin/uv venv --python 3.11.6 --seed barf2
7
+ uv python find '3.13.10'
7
8
  """
8
9
  import typing as ta
9
10
 
omdev/interp/venvs.py CHANGED
@@ -21,6 +21,7 @@ from .types import InterpSpecifier
21
21
  class InterpVenvConfig:
22
22
  interp: ta.Optional[str] = None
23
23
  requires: ta.Optional[ta.Sequence[str]] = None
24
+ requires_pats: ta.Optional[ta.Sequence[str]] = None
24
25
  use_uv: ta.Optional[bool] = None
25
26
 
26
27