omdev 0.0.0.dev40__py3-none-any.whl → 0.0.0.dev43__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.

omdev/.manifests.json CHANGED
@@ -35,6 +35,18 @@
35
35
  }
36
36
  }
37
37
  },
38
+ {
39
+ "module": ".cli.clicmds",
40
+ "attr": "_CLI_MODULE",
41
+ "file": "omdev/cli/clicmds.py",
42
+ "line": 57,
43
+ "value": {
44
+ "$.cli.types.CliModule": {
45
+ "cmd_name": "cli",
46
+ "mod_name": "omdev.cli.clicmds"
47
+ }
48
+ }
49
+ },
38
50
  {
39
51
  "module": ".interp.__main__",
40
52
  "attr": "_CLI_MODULE",
@@ -71,6 +83,42 @@
71
83
  }
72
84
  }
73
85
  },
86
+ {
87
+ "module": ".scripts.execrss",
88
+ "attr": "_CLI_MODULE",
89
+ "file": "omdev/scripts/execrss.py",
90
+ "line": 11,
91
+ "value": {
92
+ "$.cli.types.CliModule": {
93
+ "cmd_name": "execrss",
94
+ "mod_name": "omdev.scripts.execrss"
95
+ }
96
+ }
97
+ },
98
+ {
99
+ "module": ".scripts.exectime",
100
+ "attr": "_CLI_MODULE",
101
+ "file": "omdev/scripts/exectime.py",
102
+ "line": 7,
103
+ "value": {
104
+ "$.cli.types.CliModule": {
105
+ "cmd_name": "exectime",
106
+ "mod_name": "omdev.scripts.exectime"
107
+ }
108
+ }
109
+ },
110
+ {
111
+ "module": ".scripts.importtrace",
112
+ "attr": "_CLI_MODULE",
113
+ "file": "omdev/scripts/importtrace.py",
114
+ "line": 481,
115
+ "value": {
116
+ "$.cli.types.CliModule": {
117
+ "cmd_name": "importtrace",
118
+ "mod_name": "omdev.scripts.importtrace"
119
+ }
120
+ }
121
+ },
74
122
  {
75
123
  "module": ".tools.dockertools",
76
124
  "attr": "_CLI_MODULE",
@@ -95,6 +143,18 @@
95
143
  }
96
144
  }
97
145
  },
146
+ {
147
+ "module": ".tools.importscan",
148
+ "attr": "_CLI_MODULE",
149
+ "file": "omdev/tools/importscan.py",
150
+ "line": 165,
151
+ "value": {
152
+ "$.cli.types.CliModule": {
153
+ "cmd_name": "importscan",
154
+ "mod_name": "omdev.tools.importscan"
155
+ }
156
+ }
157
+ },
98
158
  {
99
159
  "module": ".tools.piptools",
100
160
  "attr": "_CLI_MODULE",
omdev/cache/data/cache.py CHANGED
@@ -10,11 +10,14 @@ TODO:
10
10
  - chaining? or is this compcache..
11
11
  - download resume ala hf_hub
12
12
  """
13
+ import contextlib
13
14
  import logging
14
15
  import os.path
15
16
  import shutil
16
17
  import subprocess
17
18
  import tempfile
19
+ import typing as ta
20
+ import urllib.error
18
21
  import urllib.parse
19
22
  import urllib.request
20
23
 
@@ -40,6 +43,57 @@ log = logging.getLogger(__name__)
40
43
  ##
41
44
 
42
45
 
46
+ def _url_retrieve(
47
+ req: urllib.request.Request,
48
+ out_file: str | None = None,
49
+ ) -> tuple[str, ta.Any]:
50
+ p = urllib.parse.urlparse(req.full_url)
51
+
52
+ with contextlib.ExitStack() as es:
53
+ fp = es.enter_context(contextlib.closing(urllib.request.urlopen(req))) # noqa
54
+
55
+ headers = fp.info()
56
+
57
+ # Just return the local path and the "headers" for file:// URLs. No sense in performing a copy unless requested.
58
+ if p.scheme == 'file' and not out_file:
59
+ return os.path.normpath(p.path), headers
60
+
61
+ success = False
62
+
63
+ tfp: ta.Any
64
+ if out_file:
65
+ tfp = es.enter_context(open(out_file, 'wb'))
66
+
67
+ else:
68
+ tfp = es.enter_context(tempfile.NamedTemporaryFile(delete=False))
69
+ out_file = tfp.name
70
+
71
+ def _cleanup():
72
+ if not success and out_file:
73
+ os.unlink(out_file)
74
+
75
+ es.enter_context(lang.defer(_cleanup)) # noqa
76
+
77
+ result = out_file, headers
78
+ size = -1
79
+ read = 0
80
+ if 'content-length' in headers:
81
+ size = int(headers['Content-Length'])
82
+
83
+ while block := fp.read(1024 * 8):
84
+ read += len(block)
85
+ tfp.write(block)
86
+
87
+ if size >= 0 and read < size:
88
+ raise urllib.error.ContentTooShortError(
89
+ f'retrieval incomplete: got only {read} out of {size} bytes',
90
+ result, # type: ignore
91
+ )
92
+
93
+ success = True
94
+ return result # type: ignore
95
+
96
+
43
97
  class Cache:
44
98
  def __init__(self, base_dir: str) -> None:
45
99
  super().__init__()
@@ -49,16 +103,32 @@ class Cache:
49
103
 
50
104
  #
51
105
 
52
- def _fetch_url(self, url: str, out_file: str) -> None:
106
+ def _fetch_url(
107
+ self,
108
+ url: str,
109
+ out_file: str,
110
+ *,
111
+ headers: ta.Mapping[str, str] | None = None,
112
+ ) -> None:
53
113
  log.info('Fetching url: %s -> %s', url, out_file)
54
114
 
55
- urllib.request.urlretrieve(url, out_file) # noqa
115
+ _url_retrieve(
116
+ urllib.request.Request( # noqa
117
+ url,
118
+ **(dict(headers=headers) if headers is not None else {}), # type: ignore
119
+ ),
120
+ out_file,
121
+ )
56
122
 
57
123
  def _fetch_into(self, spec: Spec, data_dir: str) -> None:
58
124
  log.info('Fetching spec: %s %r -> %s', spec.digest, spec, data_dir)
59
125
 
60
126
  if isinstance(spec, UrlSpec):
61
- self._fetch_url(spec.url, os.path.join(data_dir, spec.file_name_or_default))
127
+ self._fetch_url(
128
+ spec.url,
129
+ os.path.join(data_dir, spec.file_name_or_default),
130
+ headers=spec.headers,
131
+ )
62
132
 
63
133
  elif isinstance(spec, GithubContentSpec):
64
134
  for repo_file in spec.files:
@@ -104,7 +174,7 @@ class Cache:
104
174
 
105
175
  def _perform_action(self, action: Action, data_dir: str) -> None:
106
176
  if isinstance(action, ExtractAction):
107
- for f in action.files:
177
+ for f in [action.files] if isinstance(action.files, str) else action.files:
108
178
  file = os.path.join(data_dir, f)
109
179
  if not os.path.isfile(file):
110
180
  raise Exception(f'Not file: {file}')
omdev/cache/data/specs.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import hashlib
2
+ import operator
2
3
  import typing as ta
3
4
  import urllib.parse
4
5
  import urllib.request
@@ -59,6 +60,10 @@ class UrlSpec(Spec):
59
60
  url: str = dc.xfield(validate=lambda u: bool(urllib.parse.urlparse(u)))
60
61
  file_name: str | None = None
61
62
 
63
+ _: dc.KW_ONLY
64
+
65
+ headers: ta.Mapping[str, str] | None = dc.field(default=None) | msh.update_field_metadata(omit_if=operator.not_)
66
+
62
67
  @cached.property
63
68
  def file_name_or_default(self) -> str:
64
69
  if self.file_name is not None:
omdev/cli/clicmds.py ADDED
@@ -0,0 +1,83 @@
1
+ import argparse
2
+ import inspect
3
+ import os
4
+ import subprocess
5
+ import sys
6
+
7
+ from omlish import __about__
8
+
9
+ from . import install
10
+ from .types import CliModule
11
+
12
+
13
+ def _print_version(args) -> None:
14
+ print(__about__.__version__)
15
+
16
+
17
+ def _print_revision(args) -> None:
18
+ print(__about__.__revision__)
19
+
20
+
21
+ def _reinstall(args) -> None:
22
+ mod_name = globals()['__spec__'].name
23
+ tool_name = '.'.join([mod_name.partition('.')[0], 'tools', 'piptools'])
24
+
25
+ out = subprocess.check_output([
26
+ sys.executable,
27
+ '-m',
28
+ tool_name,
29
+ 'list-root-dists',
30
+ ]).decode()
31
+
32
+ deps = sorted(
33
+ ({s for l in out.splitlines() if (s := l.strip())} | set(args.extra_deps or []))
34
+ - {install.DEFAULT_CLI_PKG} # noqa
35
+ )
36
+
37
+ if deps:
38
+ print('Reinstalling with following additional dependencies:')
39
+ print('\n'.join(' ' + d for d in deps))
40
+ else:
41
+ print('No additional dependencies detected.')
42
+ print()
43
+ print('Continue with reinstall? (ctrl-c to cancel)')
44
+ input()
45
+
46
+ install_src = inspect.getsource(install)
47
+
48
+ os.execl(
49
+ sys.executable,
50
+ sys.executable,
51
+ '-c',
52
+ install_src,
53
+ *deps,
54
+ )
55
+
56
+
57
+ # @omlish-manifest
58
+ _CLI_MODULE = CliModule('cli', __name__)
59
+
60
+
61
+ def _main(argv=None) -> None:
62
+ parser = argparse.ArgumentParser()
63
+ subparsers = parser.add_subparsers()
64
+
65
+ parser_version = subparsers.add_parser('version')
66
+ parser_version.set_defaults(func=_print_version)
67
+
68
+ parser_revision = subparsers.add_parser('revision')
69
+ parser_revision.set_defaults(func=_print_revision)
70
+
71
+ parser_reinstall = subparsers.add_parser('reinstall')
72
+ parser_reinstall.add_argument('extra_deps', nargs='*')
73
+ parser_reinstall.set_defaults(func=_reinstall)
74
+
75
+ args = parser.parse_args(argv)
76
+ if not getattr(args, 'func', None):
77
+ parser.print_help()
78
+ else:
79
+ args.func(args)
80
+
81
+
82
+ if __name__ == '__main__':
83
+ _main()
omdev/cli/main.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """
2
2
  TODO:
3
+ - cache ldr.discover() somehow if in uvx/pipx - very slow
4
+ - <venv-root>/.omdev-cli-manifest-cache.json - {pkg_name: manifests_json}
3
5
  - allow manually specifying manifest packages
4
6
  - https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#creating-executable-scripts
5
7
  - https://packaging.python.org/en/latest/specifications/entry-points/#entry-points
@@ -8,8 +10,8 @@ import argparse
8
10
  import os
9
11
  import runpy
10
12
  import sys
13
+ import typing as ta
11
14
 
12
- from omlish import __about__
13
15
  from omlish import check
14
16
 
15
17
  from ..manifests.load import ManifestLoader
@@ -21,18 +23,7 @@ from .types import CliModule
21
23
  ##
22
24
 
23
25
 
24
- def _print_version() -> None:
25
- print(__about__.__version__)
26
-
27
-
28
- def _print_revision() -> None:
29
- print(__about__.__revision__)
30
-
31
-
32
- _CLI_FUNCS = [
33
- CliFunc('version', _print_version),
34
- CliFunc('revision', _print_revision),
35
- ]
26
+ _CLI_FUNCS: ta.Sequence[CliFunc] = []
36
27
 
37
28
 
38
29
  ##
@@ -46,6 +37,7 @@ def _main() -> None:
46
37
  ldr = ManifestLoader.from_entry_point(globals())
47
38
 
48
39
  pkgs = ldr.discover()
40
+
49
41
  if not pkgs:
50
42
  pkgs = []
51
43
  for n in os.listdir(os.getcwd()):
omdev/git.py CHANGED
@@ -73,11 +73,15 @@ def get_git_revision(
73
73
  if cwd is None:
74
74
  cwd = os.getcwd()
75
75
 
76
- if subprocess.run([ # noqa
77
- 'git',
78
- 'rev-parse',
79
- '--is-inside-work-tree',
80
- ], stderr=subprocess.PIPE).returncode:
76
+ if subprocess.run( # noqa
77
+ [
78
+ 'git',
79
+ 'rev-parse',
80
+ '--is-inside-work-tree',
81
+ ],
82
+ stdout=subprocess.PIPE,
83
+ stderr=subprocess.PIPE,
84
+ ).returncode:
81
85
  return None
82
86
 
83
87
  has_untracked = bool(subprocess.check_output([
omdev/manifests/load.py CHANGED
@@ -1,7 +1,12 @@
1
+ """
2
+ Should be kept somewhat lightweight - used in cli entrypoints.
3
+
4
+ TODO:
5
+ - persisted caching support - {pkg_name: manifests}
6
+ """
1
7
  # ruff: noqa: UP006 UP007
2
8
  import dataclasses as dc
3
9
  import importlib.machinery
4
- import importlib.metadata
5
10
  import importlib.resources
6
11
  import json
7
12
  import typing as ta
@@ -74,19 +79,7 @@ class ManifestLoader:
74
79
  self._cls_cache[key] = cls
75
80
  return cls
76
81
 
77
- def load_raw(self, pkg_name: str) -> ta.Optional[ta.Sequence[Manifest]]:
78
- try:
79
- return self._raw_cache[pkg_name]
80
- except KeyError:
81
- pass
82
-
83
- t = importlib.resources.files(pkg_name).joinpath('.manifests.json')
84
- if not t.is_file():
85
- self._raw_cache[pkg_name] = None
86
- return None
87
-
88
- src = t.read_text('utf-8')
89
- obj = json.loads(src)
82
+ def load_contents(self, obj: ta.Any, pkg_name: str) -> ta.Sequence[Manifest]:
90
83
  if not isinstance(obj, (list, tuple)):
91
84
  raise TypeError(obj)
92
85
 
@@ -105,6 +98,26 @@ class ManifestLoader:
105
98
 
106
99
  lst.append(m)
107
100
 
101
+ return lst
102
+
103
+ def load_raw(self, pkg_name: str) -> ta.Optional[ta.Sequence[Manifest]]:
104
+ try:
105
+ return self._raw_cache[pkg_name]
106
+ except KeyError:
107
+ pass
108
+
109
+ t = importlib.resources.files(pkg_name).joinpath('.manifests.json')
110
+ if not t.is_file():
111
+ self._raw_cache[pkg_name] = None
112
+ return None
113
+
114
+ src = t.read_text('utf-8')
115
+ obj = json.loads(src)
116
+ if not isinstance(obj, (list, tuple)):
117
+ raise TypeError(obj)
118
+
119
+ lst = self.load_contents(obj, pkg_name)
120
+
108
121
  self._raw_cache[pkg_name] = lst
109
122
  return lst
110
123
 
@@ -143,6 +156,9 @@ class ManifestLoader:
143
156
  ENTRY_POINT_GROUP = 'omlish.manifests'
144
157
 
145
158
  def discover(self) -> ta.Sequence[str]:
159
+ # This is a fat dep so do it late.
160
+ import importlib.metadata
161
+
146
162
  return [
147
163
  ep.value
148
164
  for ep in importlib.metadata.entry_points(group=self.ENTRY_POINT_GROUP)
omdev/pyproject/cli.py CHANGED
@@ -47,6 +47,7 @@ from ..toml.parser import toml_loads
47
47
  from .configs import PyprojectConfig
48
48
  from .configs import PyprojectConfigPreparer
49
49
  from .configs import VenvConfig
50
+ from .pkg import BasePyprojectPackageGenerator
50
51
  from .pkg import PyprojectPackageGenerator
51
52
  from .reqs import RequirementsRewriter
52
53
 
@@ -338,25 +339,36 @@ def _pkg_cmd(args) -> None:
338
339
  if run_build:
339
340
  os.makedirs(build_output_dir, exist_ok=True)
340
341
 
341
- num_threads = max(mp.cpu_count() // 2, 1)
342
+ pgs: ta.List[BasePyprojectPackageGenerator] = [
343
+ PyprojectPackageGenerator(
344
+ dir_name,
345
+ pkgs_root,
346
+ )
347
+ for dir_name in run.cfg().pkgs
348
+ ]
349
+ pgs = list(itertools.chain.from_iterable([pg, *pg.children()] for pg in pgs))
350
+
351
+ num_threads = args.jobs or max(mp.cpu_count() // 2, 1)
352
+ futs: ta.List[cf.Future]
342
353
  with cf.ThreadPoolExecutor(num_threads) as ex:
343
- futs = [
344
- ex.submit(functools.partial(
345
- PyprojectPackageGenerator(
346
- dir_name,
347
- pkgs_root,
348
- ).gen,
349
- PyprojectPackageGenerator.GenOpts(
350
- run_build=run_build,
351
- build_output_dir=build_output_dir,
352
- add_revision=add_revision,
353
- ),
354
- ))
355
- for dir_name in run.cfg().pkgs
356
- ]
354
+ futs = [ex.submit(pg.gen) for pg in pgs]
357
355
  for fut in futs:
358
356
  fut.result()
359
357
 
358
+ if run_build:
359
+ futs = [
360
+ ex.submit(functools.partial(
361
+ pg.build,
362
+ build_output_dir,
363
+ BasePyprojectPackageGenerator.BuildOpts(
364
+ add_revision=add_revision,
365
+ ),
366
+ ))
367
+ for pg in pgs
368
+ ]
369
+ for fut in futs:
370
+ fut.result()
371
+
360
372
  else:
361
373
  raise Exception(f'unknown subcommand: {cmd}')
362
374
 
@@ -380,6 +392,7 @@ def _build_parser() -> argparse.ArgumentParser:
380
392
  parser_resolve = subparsers.add_parser('pkg')
381
393
  parser_resolve.add_argument('-b', '--build', action='store_true')
382
394
  parser_resolve.add_argument('-r', '--revision', action='store_true')
395
+ parser_resolve.add_argument('-j', '--jobs', type=int)
383
396
  parser_resolve.add_argument('cmd', nargs='?')
384
397
  parser_resolve.add_argument('args', nargs=argparse.REMAINDER)
385
398
  parser_resolve.set_defaults(func=_pkg_cmd)
omdev/pyproject/pkg.py CHANGED
@@ -186,12 +186,33 @@ class BasePyprojectPackageGenerator(abc.ABC):
186
186
 
187
187
  #
188
188
 
189
- def _run_build(
189
+ def children(self) -> ta.Sequence['BasePyprojectPackageGenerator']:
190
+ return []
191
+
192
+ #
193
+
194
+ def gen(self) -> str:
195
+ log.info('Generating pyproject package: %s -> %s (%s)', self._dir_name, self._pkgs_root, self._pkg_suffix)
196
+
197
+ self._pkg_dir()
198
+ self._write_git_ignore()
199
+ self._symlink_source_dir()
200
+ self._write_file_contents()
201
+ self._symlink_standard_files()
202
+
203
+ return self._pkg_dir()
204
+
205
+ #
206
+
207
+ @dc.dataclass(frozen=True)
208
+ class BuildOpts:
209
+ add_revision: bool = False
210
+ test: bool = False
211
+
212
+ def build(
190
213
  self,
191
- build_output_dir: ta.Optional[str] = None,
192
- *,
193
- add_revision: bool = False,
194
- test: bool = False,
214
+ output_dir: ta.Optional[str] = None,
215
+ opts: BuildOpts = BuildOpts(),
195
216
  ) -> None:
196
217
  subprocess_check_call(
197
218
  sys.executable,
@@ -202,10 +223,10 @@ class BasePyprojectPackageGenerator(abc.ABC):
202
223
 
203
224
  dist_dir = os.path.join(self._pkg_dir(), 'dist')
204
225
 
205
- if add_revision:
226
+ if opts.add_revision:
206
227
  GitRevisionAdder().add_to(dist_dir)
207
228
 
208
- if test:
229
+ if opts.test:
209
230
  for fn in os.listdir(dist_dir):
210
231
  tmp_dir = tempfile.mkdtemp()
211
232
 
@@ -224,34 +245,9 @@ class BasePyprojectPackageGenerator(abc.ABC):
224
245
  cwd=tmp_dir,
225
246
  )
226
247
 
227
- if build_output_dir is not None:
248
+ if output_dir is not None:
228
249
  for fn in os.listdir(dist_dir):
229
- shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(build_output_dir, fn))
230
-
231
- #
232
-
233
- @dc.dataclass(frozen=True)
234
- class GenOpts:
235
- run_build: bool = False
236
- build_output_dir: ta.Optional[str] = None
237
- add_revision: bool = False
238
-
239
- def gen(self, opts: GenOpts = GenOpts()) -> str:
240
- log.info('Generating pyproject package: %s -> %s (%s)', self._dir_name, self._pkgs_root, self._pkg_suffix)
241
-
242
- self._pkg_dir()
243
- self._write_git_ignore()
244
- self._symlink_source_dir()
245
- self._write_file_contents()
246
- self._symlink_standard_files()
247
-
248
- if opts.run_build:
249
- self._run_build(
250
- opts.build_output_dir,
251
- add_revision=opts.add_revision,
252
- )
253
-
254
- return self._pkg_dir()
250
+ shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(output_dir, fn))
255
251
 
256
252
 
257
253
  #
@@ -377,24 +373,25 @@ class PyprojectPackageGenerator(BasePyprojectPackageGenerator):
377
373
 
378
374
  #
379
375
 
380
- def gen(self, opts: BasePyprojectPackageGenerator.GenOpts = BasePyprojectPackageGenerator.GenOpts()) -> str:
381
- ret = super().gen(opts)
376
+ @cached_nullary
377
+ def children(self) -> ta.Sequence[BasePyprojectPackageGenerator]:
378
+ out: ta.List[BasePyprojectPackageGenerator] = []
382
379
 
383
380
  if self.build_specs().setuptools.get('cexts'):
384
- _PyprojectCextPackageGenerator(
381
+ out.append(_PyprojectCextPackageGenerator(
385
382
  self._dir_name,
386
383
  self._pkgs_root,
387
384
  pkg_suffix='-cext',
388
- ).gen(opts)
385
+ ))
389
386
 
390
387
  if self.build_specs().pyproject.get('cli_scripts'):
391
- _PyprojectCliPackageGenerator(
388
+ out.append(_PyprojectCliPackageGenerator(
392
389
  self._dir_name,
393
390
  self._pkgs_root,
394
391
  pkg_suffix='-cli',
395
- ).gen(opts)
392
+ ))
396
393
 
397
- return ret
394
+ return out
398
395
 
399
396
 
400
397
  #
omdev/scripts/execrss.py CHANGED
@@ -8,6 +8,13 @@ def _get_rss() -> int:
8
8
  return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
9
9
 
10
10
 
11
+ # @omlish-manifest
12
+ _CLI_MODULE = {'$omdev.cli.types.CliModule': {
13
+ 'cmd_name': 'execrss',
14
+ 'mod_name': __name__,
15
+ }}
16
+
17
+
11
18
  def _main() -> None:
12
19
  [src] = sys.argv[1:]
13
20
  start = _get_rss()
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env python3
2
+ # @omlish-script
3
+ import sys
4
+ import time
5
+
6
+
7
+ # @omlish-manifest
8
+ _CLI_MODULE = {'$omdev.cli.types.CliModule': {
9
+ 'cmd_name': 'exectime',
10
+ 'mod_name': __name__,
11
+ }}
12
+
13
+
14
+ def _main() -> None:
15
+ [src] = sys.argv[1:]
16
+ co = compile(src, '<string>', 'exec')
17
+ start = time.time_ns()
18
+ exec(co)
19
+ end = time.time_ns()
20
+ print(end - start)
21
+
22
+
23
+ if __name__ == '__main__':
24
+ _main()
@@ -478,6 +478,13 @@ class SqliteWriter:
478
478
  ##
479
479
 
480
480
 
481
+ # @omlish-manifest
482
+ _CLI_MODULE = {'$omdev.cli.types.CliModule': {
483
+ 'cmd_name': 'importtrace',
484
+ 'mod_name': __name__,
485
+ }}
486
+
487
+
481
488
  def _main() -> None:
482
489
  if sys.version_info < REQUIRED_PYTHON_VERSION:
483
490
  raise EnvironmentError(f'Requires python {REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
@@ -242,11 +242,15 @@ def get_git_revision(
242
242
  if cwd is None:
243
243
  cwd = os.getcwd()
244
244
 
245
- if subprocess.run([ # noqa
246
- 'git',
247
- 'rev-parse',
248
- '--is-inside-work-tree',
249
- ], stderr=subprocess.PIPE).returncode:
245
+ if subprocess.run( # noqa
246
+ [
247
+ 'git',
248
+ 'rev-parse',
249
+ '--is-inside-work-tree',
250
+ ],
251
+ stdout=subprocess.PIPE,
252
+ stderr=subprocess.PIPE,
253
+ ).returncode:
250
254
  return None
251
255
 
252
256
  has_untracked = bool(subprocess.check_output([
@@ -3879,12 +3883,33 @@ class BasePyprojectPackageGenerator(abc.ABC):
3879
3883
 
3880
3884
  #
3881
3885
 
3882
- def _run_build(
3886
+ def children(self) -> ta.Sequence['BasePyprojectPackageGenerator']:
3887
+ return []
3888
+
3889
+ #
3890
+
3891
+ def gen(self) -> str:
3892
+ log.info('Generating pyproject package: %s -> %s (%s)', self._dir_name, self._pkgs_root, self._pkg_suffix)
3893
+
3894
+ self._pkg_dir()
3895
+ self._write_git_ignore()
3896
+ self._symlink_source_dir()
3897
+ self._write_file_contents()
3898
+ self._symlink_standard_files()
3899
+
3900
+ return self._pkg_dir()
3901
+
3902
+ #
3903
+
3904
+ @dc.dataclass(frozen=True)
3905
+ class BuildOpts:
3906
+ add_revision: bool = False
3907
+ test: bool = False
3908
+
3909
+ def build(
3883
3910
  self,
3884
- build_output_dir: ta.Optional[str] = None,
3885
- *,
3886
- add_revision: bool = False,
3887
- test: bool = False,
3911
+ output_dir: ta.Optional[str] = None,
3912
+ opts: BuildOpts = BuildOpts(),
3888
3913
  ) -> None:
3889
3914
  subprocess_check_call(
3890
3915
  sys.executable,
@@ -3895,10 +3920,10 @@ class BasePyprojectPackageGenerator(abc.ABC):
3895
3920
 
3896
3921
  dist_dir = os.path.join(self._pkg_dir(), 'dist')
3897
3922
 
3898
- if add_revision:
3923
+ if opts.add_revision:
3899
3924
  GitRevisionAdder().add_to(dist_dir)
3900
3925
 
3901
- if test:
3926
+ if opts.test:
3902
3927
  for fn in os.listdir(dist_dir):
3903
3928
  tmp_dir = tempfile.mkdtemp()
3904
3929
 
@@ -3917,34 +3942,9 @@ class BasePyprojectPackageGenerator(abc.ABC):
3917
3942
  cwd=tmp_dir,
3918
3943
  )
3919
3944
 
3920
- if build_output_dir is not None:
3945
+ if output_dir is not None:
3921
3946
  for fn in os.listdir(dist_dir):
3922
- shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(build_output_dir, fn))
3923
-
3924
- #
3925
-
3926
- @dc.dataclass(frozen=True)
3927
- class GenOpts:
3928
- run_build: bool = False
3929
- build_output_dir: ta.Optional[str] = None
3930
- add_revision: bool = False
3931
-
3932
- def gen(self, opts: GenOpts = GenOpts()) -> str:
3933
- log.info('Generating pyproject package: %s -> %s (%s)', self._dir_name, self._pkgs_root, self._pkg_suffix)
3934
-
3935
- self._pkg_dir()
3936
- self._write_git_ignore()
3937
- self._symlink_source_dir()
3938
- self._write_file_contents()
3939
- self._symlink_standard_files()
3940
-
3941
- if opts.run_build:
3942
- self._run_build(
3943
- opts.build_output_dir,
3944
- add_revision=opts.add_revision,
3945
- )
3946
-
3947
- return self._pkg_dir()
3947
+ shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(output_dir, fn))
3948
3948
 
3949
3949
 
3950
3950
  #
@@ -4070,24 +4070,25 @@ class PyprojectPackageGenerator(BasePyprojectPackageGenerator):
4070
4070
 
4071
4071
  #
4072
4072
 
4073
- def gen(self, opts: BasePyprojectPackageGenerator.GenOpts = BasePyprojectPackageGenerator.GenOpts()) -> str:
4074
- ret = super().gen(opts)
4073
+ @cached_nullary
4074
+ def children(self) -> ta.Sequence[BasePyprojectPackageGenerator]:
4075
+ out: ta.List[BasePyprojectPackageGenerator] = []
4075
4076
 
4076
4077
  if self.build_specs().setuptools.get('cexts'):
4077
- _PyprojectCextPackageGenerator(
4078
+ out.append(_PyprojectCextPackageGenerator(
4078
4079
  self._dir_name,
4079
4080
  self._pkgs_root,
4080
4081
  pkg_suffix='-cext',
4081
- ).gen(opts)
4082
+ ))
4082
4083
 
4083
4084
  if self.build_specs().pyproject.get('cli_scripts'):
4084
- _PyprojectCliPackageGenerator(
4085
+ out.append(_PyprojectCliPackageGenerator(
4085
4086
  self._dir_name,
4086
4087
  self._pkgs_root,
4087
4088
  pkg_suffix='-cli',
4088
- ).gen(opts)
4089
+ ))
4089
4090
 
4090
- return ret
4091
+ return out
4091
4092
 
4092
4093
 
4093
4094
  #
@@ -5275,25 +5276,36 @@ def _pkg_cmd(args) -> None:
5275
5276
  if run_build:
5276
5277
  os.makedirs(build_output_dir, exist_ok=True)
5277
5278
 
5278
- num_threads = max(mp.cpu_count() // 2, 1)
5279
+ pgs: ta.List[BasePyprojectPackageGenerator] = [
5280
+ PyprojectPackageGenerator(
5281
+ dir_name,
5282
+ pkgs_root,
5283
+ )
5284
+ for dir_name in run.cfg().pkgs
5285
+ ]
5286
+ pgs = list(itertools.chain.from_iterable([pg, *pg.children()] for pg in pgs))
5287
+
5288
+ num_threads = args.jobs or max(mp.cpu_count() // 2, 1)
5289
+ futs: ta.List[cf.Future]
5279
5290
  with cf.ThreadPoolExecutor(num_threads) as ex:
5280
- futs = [
5281
- ex.submit(functools.partial(
5282
- PyprojectPackageGenerator(
5283
- dir_name,
5284
- pkgs_root,
5285
- ).gen,
5286
- PyprojectPackageGenerator.GenOpts(
5287
- run_build=run_build,
5288
- build_output_dir=build_output_dir,
5289
- add_revision=add_revision,
5290
- ),
5291
- ))
5292
- for dir_name in run.cfg().pkgs
5293
- ]
5291
+ futs = [ex.submit(pg.gen) for pg in pgs]
5294
5292
  for fut in futs:
5295
5293
  fut.result()
5296
5294
 
5295
+ if run_build:
5296
+ futs = [
5297
+ ex.submit(functools.partial(
5298
+ pg.build,
5299
+ build_output_dir,
5300
+ BasePyprojectPackageGenerator.BuildOpts(
5301
+ add_revision=add_revision,
5302
+ ),
5303
+ ))
5304
+ for pg in pgs
5305
+ ]
5306
+ for fut in futs:
5307
+ fut.result()
5308
+
5297
5309
  else:
5298
5310
  raise Exception(f'unknown subcommand: {cmd}')
5299
5311
 
@@ -5317,6 +5329,7 @@ def _build_parser() -> argparse.ArgumentParser:
5317
5329
  parser_resolve = subparsers.add_parser('pkg')
5318
5330
  parser_resolve.add_argument('-b', '--build', action='store_true')
5319
5331
  parser_resolve.add_argument('-r', '--revision', action='store_true')
5332
+ parser_resolve.add_argument('-j', '--jobs', type=int)
5320
5333
  parser_resolve.add_argument('cmd', nargs='?')
5321
5334
  parser_resolve.add_argument('args', nargs=argparse.REMAINDER)
5322
5335
  parser_resolve.set_defaults(func=_pkg_cmd)
omdev/tools/importscan.py CHANGED
@@ -17,6 +17,8 @@ import typing as ta
17
17
  from omlish import concurrent as cu
18
18
  from omlish import lang
19
19
 
20
+ from ..cli import CliModule
21
+
20
22
 
21
23
  ##
22
24
 
@@ -160,6 +162,10 @@ def run(
160
162
  ##
161
163
 
162
164
 
165
+ # @omlish-manifest
166
+ _CLI_MODULE = CliModule('importscan', __name__)
167
+
168
+
163
169
  def _main() -> None:
164
170
  parser = argparse.ArgumentParser()
165
171
  parser.add_argument('-j', '--jobs', type=int)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omdev
3
- Version: 0.0.0.dev40
3
+ Version: 0.0.0.dev43
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.dev40
15
+ Requires-Dist: omlish ==0.0.0.dev43
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
- omdev/.manifests.json,sha256=tZR-7JWuphPfRzUGk5qhIhRP8qJpZOu3Z-D12VjgQFo,2595
1
+ omdev/.manifests.json,sha256=c-eZ9NasJYDz6P7LeyEV_-NLPoVpI3ph8LcO8Hg0JNY,3921
2
2
  omdev/__about__.py,sha256=LqSNNFFcT84xW3W8fIOJ78kPYJKFLIXZyDX-AJREvN0,1005
3
3
  omdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  omdev/bracepy.py,sha256=HwBK5XmlOsF_juTel25fRLJK9vHSJCWXuCc-OZlevRQ,2619
@@ -6,7 +6,7 @@ omdev/classdot.py,sha256=K0YMTngaC6uuEKhDb95tFzW33Re_YEdgIWBBeze4PTI,1628
6
6
  omdev/cmake.py,sha256=Diy2ry65806dQP125DAstD3w46z_wszMH7PwC2-6iik,4578
7
7
  omdev/findimports.py,sha256=P8v4I1tm6g-PEWJiNwAKxErvWwL-Nop83vAuwq1kR5A,2246
8
8
  omdev/findmagic.py,sha256=DhBYHHP_dzwM5pIh21xnQPnkZ2YmAXCjithsr7X0ScU,2357
9
- omdev/git.py,sha256=OzP4xHVboaa7GhS-mg4F3lYWf3HLa5aMm6V6PtIw_3U,2137
9
+ omdev/git.py,sha256=riM2KqSpQPC3N89uipd7Vm3kVgv4EYYDBU9tV1Z8juM,2208
10
10
  omdev/revisions.py,sha256=U657hf4zeEN32y3g4CzqCAodx_HlfkHj2cIIKALNFDo,5009
11
11
  omdev/secrets.py,sha256=ja0VsCB01MHxYwn5OHjFeXV9cRah9AQl-0uJzZELpic,256
12
12
  omdev/tokens.py,sha256=GusxQ1Cd_eiScuR8XTTtc9QFhOgYviYGBZmFnn3Hj7s,756
@@ -25,11 +25,11 @@ omdev/cache/compute/storage.py,sha256=woCUqHg8ZrwLEejRG3zu1L5ZXxGNNXveh3E8FnlEkj
25
25
  omdev/cache/compute/types.py,sha256=NpCTTJHDmpERjrbO6dh9TEzHuP6-vOuoX3ym9sA0ukc,2639
26
26
  omdev/cache/data/__init__.py,sha256=SQXtugLceRif463rcoklpQ33pxYLgEIm0xiI6NvOI6M,301
27
27
  omdev/cache/data/actions.py,sha256=KVYb3tBYP5c0g-wK1bXih_K7L0ER9UINKChhfc7mwKQ,1071
28
- omdev/cache/data/cache.py,sha256=WSsbFyFRT_IQFYQCrmUpaTvs9DRglLmCnhguOzdJ6p4,5753
28
+ omdev/cache/data/cache.py,sha256=NRi1WJHVixt9tL_JpOPZqOow8aIaapXCMwIkk2JlM-k,7719
29
29
  omdev/cache/data/consts.py,sha256=d6W_aeMqgah6PmPYi9RA8Be54oQ4BcNCy8kDQ7FlB_Q,26
30
30
  omdev/cache/data/defaults.py,sha256=HrapVUIf9Ozu3qSfRPyQj-vx-dz6Yyedjb-k3yV4CW8,277
31
31
  omdev/cache/data/manifests.py,sha256=CupK71fL3_PnDzUqjrWLNt64KfGKF-K4ycMkT5p0gPA,979
32
- omdev/cache/data/specs.py,sha256=h2yGkDAZ5tGpqJ280QyglFodTNf_WP1GHJo6koQ-sTk,2313
32
+ omdev/cache/data/specs.py,sha256=4eQ1o8Oxd7AWkcCGy1FgH2Sa0B9uqhp1UFO45eUAH1c,2466
33
33
  omdev/cexts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  omdev/cexts/_boilerplate.cc,sha256=sbpXEgdFrkdzZXgaNWFFNN27fL9TZu6VrwvMY4-nnFM,1726
35
35
  omdev/cexts/build.py,sha256=F3z1-CjDlEM-Gzi5IunKUBO52qdH_pMsFylobTdGJnI,2654
@@ -55,8 +55,9 @@ omdev/cexts/_distutils/compilers/options.py,sha256=H7r5IcLvga5Fs3jjXWIT-6ap3JBdu
55
55
  omdev/cexts/_distutils/compilers/unixccompiler.py,sha256=o1h8QuyupLntv4F21_XjzAZmCiwwxJuTmOirvBSL-Qw,15419
56
56
  omdev/cli/__init__.py,sha256=V_l6VP1SZMlJbO-8CJwSuO9TThOy2S_oaPepNYgIrbE,37
57
57
  omdev/cli/__main__.py,sha256=d23loR_cKfTYZwYiqpt_CmKI7dd5WcYFgIYzqMep75E,68
58
+ omdev/cli/clicmds.py,sha256=Qt75JOZ5nnI1OSvKFkNy25Zi1iaNx_hcPlK3PkgWgYY,1923
58
59
  omdev/cli/install.py,sha256=-pFczwyJn48IeqCswbJzrV15ZslQUNIcREiiOGSjDk0,3933
59
- omdev/cli/main.py,sha256=63IcpB8y2j3xp0I6kF6hdqqJ2bzrMsGElWNtcEsI8nU,1946
60
+ omdev/cli/main.py,sha256=rGc8SIk-czOS9eILZbzu27nx3ezjc54fn880OWskpYE,1880
60
61
  omdev/cli/types.py,sha256=7_Owg0P8C8oOObSuOp6aEYSjkEukVFxTT00SRy1bLHM,250
61
62
  omdev/interp/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
62
63
  omdev/interp/__main__.py,sha256=GMCqeGYltgt5dlJzHxY9gqisa8cRkrPfmZYuZnjg4WI,162
@@ -70,7 +71,7 @@ omdev/interp/system.py,sha256=bI-JhX4GVJqW7wMxnIa-DGJWnCLmFcIsnl9pc1RGY2g,3513
70
71
  omdev/interp/types.py,sha256=EMN3StEMkFoQAMUIZd7JYL4uUWqzFGY-2hTL8EBznYM,2437
71
72
  omdev/manifests/__init__.py,sha256=P2B0dpT8D7l5lJwRGPA92IcQj6oeXfd90X5-q9BJrKg,51
72
73
  omdev/manifests/build.py,sha256=_anZfhIAwUQo0J4BfKvDsvivrST2U5VigJaFKbJKXMQ,9067
73
- omdev/manifests/load.py,sha256=1QznPL4zNlu1eIFkoGrO78coIQCk9LlMC8FJ6PcvhNM,4373
74
+ omdev/manifests/load.py,sha256=LtEsluDdd8CkNGj0QGBxee3twBn095Fru0xz2mtr7uk,4788
74
75
  omdev/manifests/types.py,sha256=Jv6PAdVLPb9Hh4y6vDhPlWuMNBBViin1bC_u83jfsH4,234
75
76
  omdev/mypy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
77
  omdev/mypy/debug.py,sha256=WcZw-3Z1njg_KFGqi3DB6RuqbBa3dLArJnjVCuY1Mn0,3003
@@ -89,30 +90,31 @@ omdev/precheck/scripts.py,sha256=qq6MXkxgrYngPg5pWnXH4uRSuRkP3mFqbeml1UmvGBc,126
89
90
  omdev/pyproject/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
90
91
  omdev/pyproject/__main__.py,sha256=gn3Rl1aYPYdiTtEqa9ifi0t-e4ZwPY0vhJ4UXvYdJDY,165
91
92
  omdev/pyproject/cexts.py,sha256=x13piOOnNrYbA17qZLDVuR0p1sqhgEwpk4FtImX-klM,4281
92
- omdev/pyproject/cli.py,sha256=hE2bjfXmJy1xnadSyIcFjF3kYYTqNK4Y5dqTjnMtjVs,10909
93
+ omdev/pyproject/cli.py,sha256=QnlptoCJQ0yvJ1C2mXEYfOMS6agRQMn2nGS9KgWoJ5U,11358
93
94
  omdev/pyproject/configs.py,sha256=K9H5cGwVLgHi8wKwtYvlXHZ9ThtmnI4jo8JAb-t1-70,2859
94
- omdev/pyproject/pkg.py,sha256=-8meXIJSVkXrDJDsqEcdJ05bssAnTlLp2Y7bElcxI-g,14753
95
+ omdev/pyproject/pkg.py,sha256=rNKzJOIgPDrtT2i14Pebldoboz45w00sKb5l_kYFaRI,14562
95
96
  omdev/pyproject/reqs.py,sha256=jgDH_nmOkcgxKshbeFt-byPAuGfwoxHuyzaolGu-lms,2299
96
97
  omdev/scripts/__init__.py,sha256=MKCvUAEQwsIvwLixwtPlpBqmkMXLCnjjXyAXvVpDwVk,91
97
98
  omdev/scripts/bumpversion.py,sha256=Kn7fo73Hs8uJh3Hi3EIyLOlzLPWAC6dwuD_lZ3cIzuY,1064
98
- omdev/scripts/execrss.py,sha256=HzDNmwXOO8fMwIRXw9q8CUnVfLFCQASyU2tfY_y2Vf8,324
99
+ omdev/scripts/execrss.py,sha256=d6purJqU99OkMcNxCS1kG2CMfSsw7wjnvBQW7SjyJ70,448
100
+ omdev/scripts/exectime.py,sha256=LRVIJsnvGYUqH-zfCdFnrQCZv1KLXJPtWBWplXMDwhI,407
101
+ omdev/scripts/importtrace.py,sha256=bp6lnrDdh0zPR0cmI3nvlU5QPWybiImpe9SNoJFOrac,13558
99
102
  omdev/scripts/interp.py,sha256=CB9Eg8qPULFIQmMDsbLTGZ74c3xASWrQJ9KriLSb19A,70949
100
- omdev/scripts/pyproject.py,sha256=zLDXrPavD4MzWTTwOKEPPL8y71_XmLlFf7r7AYXObq0,157080
103
+ omdev/scripts/pyproject.py,sha256=lk0Gk6chRLHYRpES32uXDgxsHti4Ui8oGGyJrJu_dnk,157362
101
104
  omdev/toml/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
102
105
  omdev/toml/parser.py,sha256=84bn09uhYHwQGyfww6Rw6y1RxPAE_HDltODOSakcqDM,29186
103
106
  omdev/toml/writer.py,sha256=lk3on3YXVbWuLJa-xsOzOhs1bBAT1vXqw4mBbluZl_w,3040
104
107
  omdev/tools/__init__.py,sha256=iVJAOQ0viGTQOm0DLX4uZLro-9jOioYJGLg9s0kDx1A,78
105
108
  omdev/tools/dockertools.py,sha256=x00GV8j1KReMXwxJ641GlcsVwHoWeuzdIKVBp36BqwU,5298
106
109
  omdev/tools/gittools.py,sha256=s_-cxh-4Nv2a_bJtHZ6AiKF8kx2IjDrt_GOH8Wjz-3M,669
107
- omdev/tools/importscan.py,sha256=XRLiasVSaTIp-jnO0-Nfhi0t6gnv_hVy5j2nVfEvuMI,3831
108
- omdev/tools/importtrace.py,sha256=oDry9CwIv5h96wSaTVKJ0qQ5vMGxYE5oBtfF-GYNLJs,13430
110
+ omdev/tools/importscan.py,sha256=usF35AjdMZacpe8nfP-wfzxelExT5sEQUORNcBKqr5M,3929
109
111
  omdev/tools/piptools.py,sha256=lhwzGXD-v0KFEQNyvzvdO2Kw1OA_2AfGPBs_rIkz8iE,2772
110
112
  omdev/tools/proftools.py,sha256=xKSm_yPoCnfsvS3iT9MblDqFMuZmGfI3_koGj8amMyU,145
111
113
  omdev/tools/rst.py,sha256=6dWk8QZHoGiLSuBw3TKsXZjjFK6wWBEtPi9krdCLKKg,977
112
114
  omdev/tools/sqlrepl.py,sha256=tmFZh80-xsGM62dyQ7_UGLebChrj7IHbIPYBWDJMgVk,5741
113
- omdev-0.0.0.dev40.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
114
- omdev-0.0.0.dev40.dist-info/METADATA,sha256=NwNqYM23XZBQMNs1KcxHQ5imSOLX7UhGWIyHrkcrmbo,1252
115
- omdev-0.0.0.dev40.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
116
- omdev-0.0.0.dev40.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
117
- omdev-0.0.0.dev40.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
118
- omdev-0.0.0.dev40.dist-info/RECORD,,
115
+ omdev-0.0.0.dev43.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
116
+ omdev-0.0.0.dev43.dist-info/METADATA,sha256=5Hv8QOnRyAlCu0hmOy2f4I772ceLh29Q1XPQmufh8fw,1252
117
+ omdev-0.0.0.dev43.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
118
+ omdev-0.0.0.dev43.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
119
+ omdev-0.0.0.dev43.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
120
+ omdev-0.0.0.dev43.dist-info/RECORD,,