omdev 0.0.0.dev52__tar.gz → 0.0.0.dev54__tar.gz

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.
Files changed (130) hide show
  1. {omdev-0.0.0.dev52/omdev.egg-info → omdev-0.0.0.dev54}/PKG-INFO +2 -2
  2. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/main.py +25 -5
  3. omdev-0.0.0.dev54/omdev/cli/types.py +25 -0
  4. omdev-0.0.0.dev54/omdev/manifests/build.py +356 -0
  5. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/cli.py +1 -1
  6. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/pyproject.py +1 -1
  7. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54/omdev.egg-info}/PKG-INFO +2 -2
  8. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev.egg-info/requires.txt +1 -1
  9. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/pyproject.toml +2 -2
  10. omdev-0.0.0.dev52/omdev/cli/types.py +0 -17
  11. omdev-0.0.0.dev52/omdev/manifests/build.py +0 -331
  12. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/LICENSE +0 -0
  13. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/MANIFEST.in +0 -0
  14. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/README.rst +0 -0
  15. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/.manifests.json +0 -0
  16. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/__about__.py +0 -0
  17. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/__init__.py +0 -0
  18. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/amalg/__init__.py +0 -0
  19. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/amalg/__main__.py +0 -0
  20. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/amalg/amalg.py +0 -0
  21. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/bracepy.py +0 -0
  22. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/__init__.py +0 -0
  23. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/__init__.py +0 -0
  24. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/cache.py +0 -0
  25. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/contexts.py +0 -0
  26. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/currents.py +0 -0
  27. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/fns.py +0 -0
  28. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/resolvers.py +0 -0
  29. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/storage.py +0 -0
  30. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/types.py +0 -0
  31. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/__init__.py +0 -0
  32. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/actions.py +0 -0
  33. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/cache.py +0 -0
  34. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/consts.py +0 -0
  35. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/defaults.py +0 -0
  36. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/manifests.py +0 -0
  37. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/specs.py +0 -0
  38. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/__init__.py +0 -0
  39. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_boilerplate.cc +0 -0
  40. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/LICENSE +0 -0
  41. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/__init__.py +0 -0
  42. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/build_ext.py +0 -0
  43. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/compilers/__init__.py +0 -0
  44. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/compilers/ccompiler.py +0 -0
  45. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/compilers/options.py +0 -0
  46. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/compilers/unixccompiler.py +0 -0
  47. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/dir_util.py +0 -0
  48. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/errors.py +0 -0
  49. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/extension.py +0 -0
  50. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/file_util.py +0 -0
  51. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/modified.py +0 -0
  52. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/spawn.py +0 -0
  53. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/sysconfig.py +0 -0
  54. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/util.py +0 -0
  55. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/version.py +0 -0
  56. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/build.py +0 -0
  57. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/cmake.py +0 -0
  58. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/importhook.py +0 -0
  59. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/magic.py +0 -0
  60. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/scan.py +0 -0
  61. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/classdot.py +0 -0
  62. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/__init__.py +0 -0
  63. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/__main__.py +0 -0
  64. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/clicli.py +0 -0
  65. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/install.py +0 -0
  66. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/managers.py +0 -0
  67. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cmake.py +0 -0
  68. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/findimports.py +0 -0
  69. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/findmagic.py +0 -0
  70. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/git.py +0 -0
  71. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/__init__.py +0 -0
  72. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/__main__.py +0 -0
  73. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/cli.py +0 -0
  74. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/inspect.py +0 -0
  75. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/providers.py +0 -0
  76. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/pyenv.py +0 -0
  77. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/resolvers.py +0 -0
  78. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/standalone.py +0 -0
  79. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/system.py +0 -0
  80. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/types.py +0 -0
  81. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/manifests/__init__.py +0 -0
  82. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/manifests/load.py +0 -0
  83. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/manifests/types.py +0 -0
  84. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/mypy/__init__.py +0 -0
  85. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/mypy/debug.py +0 -0
  86. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/packaging/__init__.py +0 -0
  87. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/packaging/names.py +0 -0
  88. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/packaging/requires.py +0 -0
  89. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/packaging/specifiers.py +0 -0
  90. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/packaging/versions.py +0 -0
  91. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/__init__.py +0 -0
  92. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/__main__.py +0 -0
  93. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/base.py +0 -0
  94. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/git.py +0 -0
  95. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/lite.py +0 -0
  96. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/precheck.py +0 -0
  97. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/scripts.py +0 -0
  98. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/__init__.py +0 -0
  99. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/__main__.py +0 -0
  100. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/cexts.py +0 -0
  101. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/configs.py +0 -0
  102. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/pkg.py +0 -0
  103. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/reqs.py +0 -0
  104. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/revisions.py +0 -0
  105. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/__init__.py +0 -0
  106. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/bumpversion.py +0 -0
  107. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/execrss.py +0 -0
  108. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/exectime.py +0 -0
  109. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/importtrace.py +0 -0
  110. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/interp.py +0 -0
  111. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/secrets.py +0 -0
  112. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tokens.py +0 -0
  113. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/toml/__init__.py +0 -0
  114. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/toml/parser.py +0 -0
  115. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/toml/writer.py +0 -0
  116. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/__init__.py +0 -0
  117. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/dockertools.py +0 -0
  118. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/gittools.py +0 -0
  119. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/importscan.py +0 -0
  120. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/mkrelimp.py +0 -0
  121. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/piptools.py +0 -0
  122. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/proftools.py +0 -0
  123. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/rst.py +0 -0
  124. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/sqlrepl.py +0 -0
  125. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/wheelfile.py +0 -0
  126. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev.egg-info/SOURCES.txt +0 -0
  127. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev.egg-info/dependency_links.txt +0 -0
  128. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev.egg-info/entry_points.txt +0 -0
  129. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev.egg-info/top_level.txt +0 -0
  130. {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omdev
3
- Version: 0.0.0.dev52
3
+ Version: 0.0.0.dev54
4
4
  Summary: omdev
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: ~=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omlish==0.0.0.dev52
15
+ Requires-Dist: omlish==0.0.0.dev54
16
16
  Provides-Extra: all
17
17
  Requires-Dist: pycparser~=2.22; extra == "all"
18
18
  Requires-Dist: cffi~=1.17; extra == "all"
@@ -64,9 +64,10 @@ def _main() -> None:
64
64
 
65
65
  dct: dict[str, CliCmd] = {}
66
66
  for cc in ccs:
67
- if cc.cmd_name in dct:
68
- raise NameError(cc)
69
- dct[cc.cmd_name] = cc
67
+ for cn in [cc.cmd_name] if isinstance(cc.cmd_name, str) else cc.cmd_name:
68
+ if cn in dct:
69
+ raise NameError(cc)
70
+ dct[cn] = cc
70
71
 
71
72
  #
72
73
 
@@ -76,7 +77,26 @@ def _main() -> None:
76
77
 
77
78
  args = parser.parse_args()
78
79
  if not args.cmd:
79
- parser.print_help()
80
+ mdct: dict = {}
81
+ for cc in ccs:
82
+ if isinstance(cc.cmd_name, str) and cc.cmd_name[0] == '_':
83
+ continue
84
+ if isinstance(cc, CliFunc):
85
+ mdct.setdefault('-', []).append(cc)
86
+ elif isinstance(cc, CliModule):
87
+ mdct.setdefault(cc.mod_name.partition('.')[0], []).append(cc)
88
+ else:
89
+ raise TypeError(cc)
90
+
91
+ print('Subcommands:\n')
92
+ for m, l in sorted(mdct.items(), key=lambda t: (t[0] == '-', t[0])):
93
+ print(f' {m}')
94
+ for cc in sorted(l, key=lambda c: c.primary_name):
95
+ if isinstance(cc.cmd_name, str):
96
+ print(f' {cc.cmd_name}')
97
+ else:
98
+ print(f' {cc.cmd_name[0]} ({", ".join(cc.cmd_name[1:])})')
99
+ print()
80
100
  return
81
101
 
82
102
  #
@@ -84,7 +104,7 @@ def _main() -> None:
84
104
  cc = dct[args.cmd]
85
105
 
86
106
  if isinstance(cc, CliModule):
87
- sys.argv = [cc.cmd_name, *(args.args or ())]
107
+ sys.argv = [args.cmd, *(args.args or ())]
88
108
  runpy._run_module_as_main(cc.mod_name) # type: ignore # noqa
89
109
 
90
110
  elif isinstance(cc, CliFunc):
@@ -0,0 +1,25 @@
1
+ # ruff: noqa: UP007
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+
6
+ @dc.dataclass(frozen=True)
7
+ class CliCmd:
8
+ cmd_name: ta.Union[str, ta.Sequence[str]]
9
+
10
+ @property
11
+ def primary_name(self) -> str:
12
+ if isinstance(self.cmd_name, str):
13
+ return self.cmd_name
14
+ else:
15
+ return self.cmd_name[0]
16
+
17
+
18
+ @dc.dataclass(frozen=True)
19
+ class CliModule(CliCmd):
20
+ mod_name: str
21
+
22
+
23
+ @dc.dataclass(frozen=True)
24
+ class CliFunc(CliCmd):
25
+ fn: ta.Callable
@@ -0,0 +1,356 @@
1
+ """
2
+ TODO:
3
+ - separate build from cli
4
+ - parallelize & sort
5
+
6
+ See (entry_points):
7
+ - https://github.com/pytest-dev/pluggy/blob/main/src/pluggy/_manager.py#L405
8
+ - https://docs.pytest.org/en/7.1.x/how-to/writing_plugins.html#setuptools-entry-points
9
+ - https://packaging.python.org/en/latest/specifications/entry-points/
10
+ - https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/
11
+ - [project.entry-points.omlish-manifests] \n omdev = omdev
12
+ """
13
+ # ruff: noqa: UP006 UP007
14
+ import argparse
15
+ import asyncio
16
+ import collections
17
+ import dataclasses as dc
18
+ import inspect
19
+ import itertools
20
+ import json
21
+ import multiprocessing as mp
22
+ import os.path
23
+ import re
24
+ import shlex
25
+ import subprocess
26
+ import sys
27
+ import time
28
+ import typing as ta
29
+
30
+ from omlish.lite.cached import cached_nullary
31
+ from omlish.lite.json import json_dumps_pretty
32
+ from omlish.lite.logs import configure_standard_logging
33
+ from omlish.lite.logs import log
34
+
35
+ from .. import findmagic
36
+ from .load import ManifestLoader
37
+ from .types import Manifest
38
+ from .types import ManifestOrigin
39
+
40
+
41
+ T = ta.TypeVar('T')
42
+
43
+
44
+ ##
45
+
46
+
47
+ MANIFEST_MAGIC = '# @omlish-manifest'
48
+
49
+ _MANIFEST_GLOBAL_PAT = re.compile(r'^(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s*=.*')
50
+
51
+
52
+ def _dump_module_manifests(spec: str, *attrs: str) -> None:
53
+ import collections.abc
54
+ import dataclasses as dc # noqa
55
+ import importlib
56
+ import json
57
+
58
+ mod = importlib.import_module(spec)
59
+
60
+ out = {}
61
+ for attr in attrs:
62
+ manifest = getattr(mod, attr)
63
+
64
+ if dc.is_dataclass(manifest):
65
+ cls = type(manifest)
66
+ manifest_json = json.dumps(dc.asdict(manifest)) # type: ignore
67
+ manifest_dct = json.loads(manifest_json)
68
+
69
+ rt_manifest = cls(**manifest_dct) # type: ignore
70
+ if rt_manifest != manifest:
71
+ raise Exception(f'Manifest failed to roundtrip: {manifest} -> {manifest_dct} != {rt_manifest}')
72
+
73
+ key = f'${cls.__module__}.{cls.__qualname__}'
74
+ out[attr] = {key: manifest_dct}
75
+
76
+ elif isinstance(manifest, collections.abc.Mapping):
77
+ [(key, manifest_dct)] = manifest.items()
78
+ if not key.startswith('$'): # noqa
79
+ raise Exception(f'Bad key: {key}')
80
+
81
+ if not isinstance(manifest_dct, collections.abc.Mapping):
82
+ raise Exception(f'Bad value: {manifest_dct}')
83
+
84
+ manifest_json = json.dumps(manifest_dct)
85
+ rt_manifest_dct = json.loads(manifest_json)
86
+ if manifest_dct != rt_manifest_dct:
87
+ raise Exception(f'Manifest failed to roundtrip: {manifest_dct} != {rt_manifest_dct}')
88
+
89
+ out[attr] = {key: manifest_dct}
90
+
91
+ else:
92
+ raise TypeError(f'Manifest must be dataclass or mapping: {manifest!r}')
93
+
94
+ out_json = json.dumps(out, indent=None, separators=(',', ':'))
95
+ print(out_json)
96
+
97
+
98
+ @cached_nullary
99
+ def _payload_src() -> str:
100
+ return inspect.getsource(_dump_module_manifests)
101
+
102
+
103
+ class ManifestBuilder:
104
+ def __init__(
105
+ self,
106
+ base: str,
107
+ concurrency: int = 8,
108
+ *,
109
+ write: bool = False,
110
+ ) -> None:
111
+ super().__init__()
112
+
113
+ self._base = base
114
+ self._sem = asyncio.Semaphore(concurrency)
115
+ self._write = write
116
+
117
+ async def _spawn(self, fn: ta.Callable[..., ta.Awaitable[T]], *args: ta.Any, **kwargs: ta.Any) -> T:
118
+ await self._sem.acquire()
119
+ try:
120
+ try:
121
+ return await fn(*args, **kwargs)
122
+ except Exception: # noqa
123
+ log.exception('Exception in task: %s, %r, %r', fn, args, kwargs)
124
+ raise
125
+ finally:
126
+ self._sem.release()
127
+
128
+ async def build_module_manifests(
129
+ self,
130
+ file: str,
131
+ *,
132
+ shell_wrap: bool = True,
133
+ warn_threshold_s: ta.Optional[float] = 1.,
134
+ ) -> ta.Sequence[Manifest]:
135
+ log.info('Extracting manifests from file %s', file)
136
+
137
+ if not file.endswith('.py'):
138
+ raise Exception(file)
139
+
140
+ mod_name = file.rpartition('.')[0].replace(os.sep, '.')
141
+ mod_base = mod_name.split('.')[0]
142
+ if mod_base != (first_dir := file.split(os.path.sep)[0]):
143
+ raise Exception(f'Unexpected module base: {mod_base=} != {first_dir=}')
144
+
145
+ with open(os.path.join(self._base, file)) as f: # noqa
146
+ src = f.read()
147
+
148
+ origins: ta.List[ManifestOrigin] = []
149
+ lines = src.splitlines(keepends=True)
150
+ for i, l in enumerate(lines):
151
+ if l.startswith(MANIFEST_MAGIC):
152
+ if (m := _MANIFEST_GLOBAL_PAT.match(nl := lines[i + 1])) is None:
153
+ raise Exception(nl)
154
+
155
+ origins.append(ManifestOrigin(
156
+ module='.'.join(['', *mod_name.split('.')[1:]]),
157
+ attr=m.groupdict()['name'],
158
+
159
+ file=file,
160
+ line=i + 1,
161
+ ))
162
+
163
+ if not origins:
164
+ raise Exception('no manifests found')
165
+
166
+ if (dups := [k for k, v in collections.Counter(o.attr for o in origins).items() if v > 1]):
167
+ raise Exception(f'Duplicate attrs: {dups}')
168
+
169
+ attrs = [o.attr for o in origins]
170
+
171
+ subproc_src = '\n\n'.join([
172
+ _payload_src(),
173
+ f'_dump_module_manifests({mod_name!r}, {", ".join(repr(a) for a in attrs)})\n',
174
+ ])
175
+
176
+ args = [
177
+ sys.executable,
178
+ '-c',
179
+ subproc_src,
180
+ ]
181
+
182
+ if shell_wrap:
183
+ args = ['sh', '-c', ' '.join(map(shlex.quote, args))]
184
+
185
+ start_time = time.time()
186
+
187
+ proc = await asyncio.create_subprocess_exec(*args, stdout=subprocess.PIPE)
188
+ subproc_out, _ = await proc.communicate()
189
+ if proc.returncode:
190
+ raise Exception('Subprocess failed')
191
+
192
+ end_time = time.time()
193
+
194
+ if warn_threshold_s is not None and (elapsed_time := (end_time - start_time)) >= warn_threshold_s:
195
+ log.warning('Manifest extraction took a long time: %s, %.2f s', file, elapsed_time)
196
+
197
+ sp_lines = subproc_out.decode().strip().splitlines()
198
+ if len(sp_lines) != 1:
199
+ raise Exception('Unexpected subprocess output')
200
+
201
+ dct = json.loads(sp_lines[0])
202
+ if set(dct) != set(attrs):
203
+ raise Exception('Unexpected subprocess output keys')
204
+
205
+ out: ta.List[Manifest] = []
206
+
207
+ for o in origins:
208
+ value = dct[o.attr]
209
+
210
+ if not (
211
+ isinstance(value, ta.Mapping) and
212
+ len(value) == 1 and
213
+ all(isinstance(k, str) and k.startswith('$') and len(k) > 1 for k in value)
214
+ ):
215
+ raise TypeError(f'Manifests must be mappings of strings starting with $: {value!r}')
216
+
217
+ [(key, value_dct)] = value.items()
218
+ kb, _, kr = key[1:].partition('.') # noqa
219
+ if kb == mod_base: # noqa
220
+ key = f'$.{kr}'
221
+ value = {key: value_dct}
222
+
223
+ out.append(Manifest(
224
+ **dc.asdict(o),
225
+ value=value,
226
+ ))
227
+
228
+ return out
229
+
230
+ async def build_package_manifests(
231
+ self,
232
+ name: str,
233
+ ) -> ta.List[Manifest]:
234
+ pkg_dir = os.path.join(self._base, name)
235
+ if not os.path.isdir(pkg_dir) or not os.path.isfile(os.path.join(pkg_dir, '__init__.py')):
236
+ raise Exception(pkg_dir)
237
+
238
+ files = sorted(findmagic.find_magic(
239
+ [pkg_dir],
240
+ [MANIFEST_MAGIC],
241
+ ['py'],
242
+ ))
243
+ manifests: ta.List[Manifest] = list(itertools.chain.from_iterable(await asyncio.gather(*[
244
+ self._spawn(
245
+ self.build_module_manifests,
246
+ os.path.relpath(file, self._base),
247
+ )
248
+ for file in files
249
+ ])))
250
+
251
+ if self._write:
252
+ with open(os.path.join(pkg_dir, '.manifests.json'), 'w') as f: # noqa
253
+ f.write(json_dumps_pretty([dc.asdict(m) for m in manifests]))
254
+ f.write('\n')
255
+
256
+ return manifests
257
+
258
+
259
+ ##
260
+
261
+
262
+ def check_package_manifests(
263
+ name: str,
264
+ base: str,
265
+ ) -> None:
266
+ pkg_dir = os.path.join(base, name)
267
+ if not os.path.isdir(pkg_dir) or not os.path.isfile(os.path.join(pkg_dir, '__init__.py')):
268
+ raise Exception(pkg_dir)
269
+
270
+ manifests_file = os.path.join(pkg_dir, '.manifests.json')
271
+ if not os.path.isfile(manifests_file):
272
+ raise Exception(f'No manifests file: {manifests_file}')
273
+
274
+ with open(manifests_file) as f:
275
+ manifests_json = json.load(f)
276
+
277
+ ldr = ManifestLoader()
278
+ for entry in manifests_json:
279
+ manifest = Manifest(**entry)
280
+ [(key, value_dct)] = manifest.value.items()
281
+ if key.startswith('$.'):
282
+ key = f'${name}{key[1:]}'
283
+ cls = ldr.load_cls(key)
284
+ value = cls(**value_dct) # noqa
285
+
286
+
287
+ ##
288
+
289
+
290
+ if __name__ == '__main__':
291
+ def _get_base(args) -> str:
292
+ if args.base is not None:
293
+ base = args.base
294
+ else:
295
+ base = os.getcwd()
296
+ base = os.path.abspath(base)
297
+ if not os.path.isdir(base):
298
+ raise RuntimeError(base)
299
+ return base
300
+
301
+ def _gen_cmd(args) -> None:
302
+ base = _get_base(args)
303
+
304
+ jobs = args.jobs or int(max(mp.cpu_count() // 1.5, 1))
305
+ builder = ManifestBuilder(
306
+ base,
307
+ jobs,
308
+ write=args.write or False,
309
+ )
310
+
311
+ async def do():
312
+ return await asyncio.gather(*[
313
+ builder.build_package_manifests(pkg)
314
+ for pkg in args.package
315
+ ])
316
+
317
+ mss = asyncio.run(do())
318
+ if not args.quiet:
319
+ for ms in mss:
320
+ print(json_dumps_pretty([dc.asdict(m) for m in ms]))
321
+
322
+ def _check_cmd(args) -> None:
323
+ base = _get_base(args)
324
+
325
+ for pkg in args.package:
326
+ check_package_manifests(
327
+ pkg,
328
+ base,
329
+ )
330
+
331
+ def _main(argv=None) -> None:
332
+ configure_standard_logging('INFO')
333
+
334
+ parser = argparse.ArgumentParser()
335
+ subparsers = parser.add_subparsers()
336
+
337
+ parser_gen = subparsers.add_parser('gen')
338
+ parser_gen.add_argument('-b', '--base')
339
+ parser_gen.add_argument('-w', '--write', action='store_true')
340
+ parser_gen.add_argument('-q', '--quiet', action='store_true')
341
+ parser_gen.add_argument('-j', '--jobs', type=int)
342
+ parser_gen.add_argument('package', nargs='*')
343
+ parser_gen.set_defaults(func=_gen_cmd)
344
+
345
+ parser_check = subparsers.add_parser('check')
346
+ parser_check.add_argument('-b', '--base')
347
+ parser_check.add_argument('package', nargs='*')
348
+ parser_check.set_defaults(func=_check_cmd)
349
+
350
+ args = parser.parse_args(argv)
351
+ if not getattr(args, 'func', None):
352
+ parser.print_help()
353
+ else:
354
+ args.func(args)
355
+
356
+ _main()
@@ -348,7 +348,7 @@ def _pkg_cmd(args) -> None:
348
348
  ]
349
349
  pgs = list(itertools.chain.from_iterable([pg, *pg.children()] for pg in pgs))
350
350
 
351
- num_threads = args.jobs or max(mp.cpu_count() // 2, 1)
351
+ num_threads = args.jobs or int(max(mp.cpu_count() // 1.5, 1))
352
352
  futs: ta.List[cf.Future]
353
353
  with cf.ThreadPoolExecutor(num_threads) as ex:
354
354
  futs = [ex.submit(pg.gen) for pg in pgs]
@@ -5287,7 +5287,7 @@ def _pkg_cmd(args) -> None:
5287
5287
  ]
5288
5288
  pgs = list(itertools.chain.from_iterable([pg, *pg.children()] for pg in pgs))
5289
5289
 
5290
- num_threads = args.jobs or max(mp.cpu_count() // 2, 1)
5290
+ num_threads = args.jobs or int(max(mp.cpu_count() // 1.5, 1))
5291
5291
  futs: ta.List[cf.Future]
5292
5292
  with cf.ThreadPoolExecutor(num_threads) as ex:
5293
5293
  futs = [ex.submit(pg.gen) for pg in pgs]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omdev
3
- Version: 0.0.0.dev52
3
+ Version: 0.0.0.dev54
4
4
  Summary: omdev
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: ~=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omlish==0.0.0.dev52
15
+ Requires-Dist: omlish==0.0.0.dev54
16
16
  Provides-Extra: all
17
17
  Requires-Dist: pycparser~=2.22; extra == "all"
18
18
  Requires-Dist: cffi~=1.17; extra == "all"
@@ -1,4 +1,4 @@
1
- omlish==0.0.0.dev52
1
+ omlish==0.0.0.dev54
2
2
 
3
3
  [all]
4
4
  pycparser~=2.22
@@ -12,7 +12,7 @@ authors = [
12
12
  urls = {source = 'https://github.com/wrmsr/omlish'}
13
13
  license = {text = 'BSD-3-Clause'}
14
14
  requires-python = '~=3.12'
15
- version = '0.0.0.dev52'
15
+ version = '0.0.0.dev54'
16
16
  classifiers = [
17
17
  'License :: OSI Approved :: BSD License',
18
18
  'Development Status :: 2 - Pre-Alpha',
@@ -22,7 +22,7 @@ classifiers = [
22
22
  ]
23
23
  description = 'omdev'
24
24
  dependencies = [
25
- 'omlish == 0.0.0.dev52',
25
+ 'omlish == 0.0.0.dev54',
26
26
  ]
27
27
 
28
28
  [project.optional-dependencies]
@@ -1,17 +0,0 @@
1
- import dataclasses as dc
2
- import typing as ta
3
-
4
-
5
- @dc.dataclass(frozen=True)
6
- class CliCmd:
7
- cmd_name: str
8
-
9
-
10
- @dc.dataclass(frozen=True)
11
- class CliModule(CliCmd):
12
- mod_name: str
13
-
14
-
15
- @dc.dataclass(frozen=True)
16
- class CliFunc(CliCmd):
17
- fn: ta.Callable