omdev 0.0.0.dev215__py3-none-any.whl → 0.0.0.dev217__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.
omdev/.manifests.json CHANGED
@@ -195,7 +195,7 @@
195
195
  "module": ".scripts.exectime",
196
196
  "attr": "_CLI_MODULE",
197
197
  "file": "omdev/scripts/exectime.py",
198
- "line": 7,
198
+ "line": 66,
199
199
  "value": {
200
200
  "$.cli.types.CliModule": {
201
201
  "cmd_name": "py/exectime",
omdev/precheck/lite.py CHANGED
@@ -25,13 +25,46 @@ log = logging.getLogger(__name__)
25
25
  class LitePython8Precheck(Precheck['LitePython8Precheck.Config']):
26
26
  @dc.dataclass(frozen=True)
27
27
  class Config(Precheck.Config):
28
- pass
28
+ python: str = '.venvs/8/bin/python'
29
+ concurrency: int = 4
29
30
 
30
31
  def __init__(self, context: PrecheckContext, config: Config = Config()) -> None:
31
32
  super().__init__(context, config)
32
33
 
33
34
  #
34
35
 
36
+ @dc.dataclass(frozen=True)
37
+ class _Target:
38
+ path: str
39
+ kind: ta.Literal['script', 'module']
40
+
41
+ async def _collect_targets(self) -> list[_Target]:
42
+ lst = []
43
+
44
+ for fp in magic.find_magic_files(
45
+ magic.PY_MAGIC_STYLE,
46
+ self._context.src_roots,
47
+ keys=['@omlish-lite'],
48
+ ):
49
+ with open(fp) as f: # noqa # FIXME
50
+ src = f.read()
51
+
52
+ is_script = '# @omlish-script' in src.splitlines()
53
+
54
+ if is_script:
55
+ lst.append(self._Target(fp, 'script'))
56
+
57
+ elif fp.endswith('__init__.py'):
58
+ for g in glob.glob(os.path.join(os.path.dirname(fp), '**/*.py'), recursive=True):
59
+ lst.append(self._Target(g, 'module'))
60
+
61
+ else:
62
+ lst.append(self._Target(fp, 'module'))
63
+
64
+ return lst
65
+
66
+ #
67
+
35
68
  @staticmethod
36
69
  def _load_file_module(fp: str) -> None:
37
70
  import os.path # noqa
@@ -71,7 +104,7 @@ class LitePython8Precheck(Precheck['LitePython8Precheck.Config']):
71
104
 
72
105
  proc = await asyncio.create_subprocess_exec(
73
106
  *subprocess_maybe_shell_wrap_exec(
74
- '.venvs/8/bin/python',
107
+ self._config.python,
75
108
  '-c',
76
109
  self._load_file_module_payload(),
77
110
  fp,
@@ -85,7 +118,7 @@ class LitePython8Precheck(Precheck['LitePython8Precheck.Config']):
85
118
 
86
119
  return vs
87
120
 
88
- async def _run_one_module(self, fp: str) -> list[Precheck.Violation]:
121
+ async def _run_module(self, fp: str) -> list[Precheck.Violation]:
89
122
  vs: list[Precheck.Violation] = []
90
123
 
91
124
  mod = fp.rpartition('.')[0].replace(os.sep, '.')
@@ -94,7 +127,7 @@ class LitePython8Precheck(Precheck['LitePython8Precheck.Config']):
94
127
 
95
128
  proc = await asyncio.create_subprocess_exec(
96
129
  *subprocess_maybe_shell_wrap_exec(
97
- '.venvs/8/bin/python',
130
+ self._config.python,
98
131
  '-c',
99
132
  f'import {mod}',
100
133
  ),
@@ -107,34 +140,29 @@ class LitePython8Precheck(Precheck['LitePython8Precheck.Config']):
107
140
 
108
141
  return vs
109
142
 
110
- async def _run_module(self, fp: str) -> list[Precheck.Violation]:
111
- vs: list[Precheck.Violation] = []
143
+ #
112
144
 
113
- if fp.endswith('__init__.py'):
114
- pfps = glob.glob(os.path.join(os.path.dirname(fp), '**/*.py'), recursive=True)
115
- else:
116
- pfps = [fp]
145
+ async def _run_one(self, tgt: _Target) -> list[Precheck.Violation]:
146
+ if tgt.kind == 'script':
147
+ return await self._run_script(tgt.path)
117
148
 
118
- for pfp in pfps:
119
- vs.extend(await self._run_one_module(pfp))
149
+ elif tgt.kind == 'module':
150
+ return await self._run_module(tgt.path)
120
151
 
121
- return vs
152
+ else:
153
+ raise RuntimeError(f'Unknown target kind: {tgt.kind}')
122
154
 
123
155
  async def run(self) -> ta.AsyncGenerator[Precheck.Violation, None]:
124
- for fp in magic.find_magic_files(
125
- magic.PY_MAGIC_STYLE,
126
- self._context.src_roots,
127
- keys=['@omlish-lite'],
128
- ):
129
- with open(fp) as f: # noqa # FIXME
130
- src = f.read()
156
+ tgts = await self._collect_targets()
131
157
 
132
- is_script = '# @omlish-script' in src.splitlines()
158
+ sem = asyncio.Semaphore(self._config.concurrency)
133
159
 
134
- if is_script:
135
- for v in await self._run_script(fp):
136
- yield v
160
+ async def run(tgt):
161
+ async with sem:
162
+ return await self._run_one(tgt)
137
163
 
138
- else:
139
- for v in await self._run_module(fp):
140
- yield v
164
+ tasks = [asyncio.create_task(run(tgt)) for tgt in tgts]
165
+
166
+ for coro in asyncio.as_completed(tasks):
167
+ for v in await coro:
168
+ yield v
omdev/precheck/main.py CHANGED
@@ -60,6 +60,7 @@ def _check_cmd(args) -> None:
60
60
  vs: list[Precheck.Violation] = []
61
61
 
62
62
  for pc in pcs:
63
+ log.info('Running precheck: %s', type(pc).__name__)
63
64
  async for v in pc.run():
64
65
  vs.append(v)
65
66
  print('*** VIOLATION ***')
omdev/scripts/ci.py CHANGED
@@ -167,7 +167,7 @@ def asyncio_once(fn: CallableT) -> CallableT:
167
167
  return ta.cast(CallableT, inner)
168
168
 
169
169
 
170
- def drain_tasks(loop=None):
170
+ def drain_asyncio_tasks(loop=None):
171
171
  if loop is None:
172
172
  loop = asyncio.get_running_loop()
173
173
 
@@ -182,7 +182,7 @@ def draining_asyncio_tasks() -> ta.Iterator[None]:
182
182
  yield
183
183
  finally:
184
184
  if loop is not None:
185
- drain_tasks(loop) # noqa
185
+ drain_asyncio_tasks(loop) # noqa
186
186
 
187
187
 
188
188
  async def asyncio_wait_concurrent(
@@ -2563,7 +2563,7 @@ TODO:
2563
2563
 
2564
2564
  STANDARD_LOG_FORMAT_PARTS = [
2565
2565
  ('asctime', '%(asctime)-15s'),
2566
- ('process', 'pid=%(process)-6s'),
2566
+ ('process', 'pid=%(process)s'),
2567
2567
  ('thread', 'tid=%(thread)x'),
2568
2568
  ('levelname', '%(levelname)s'),
2569
2569
  ('name', '%(name)s'),
@@ -2676,6 +2676,12 @@ def configure_standard_logging(
2676
2676
  ##
2677
2677
 
2678
2678
 
2679
+ # Valid channel type kwarg values:
2680
+ # - A special flag negative int
2681
+ # - A positive fd int
2682
+ # - A file-like object
2683
+ # - None
2684
+
2679
2685
  SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
2680
2686
  'pipe': subprocess.PIPE,
2681
2687
  'stdout': subprocess.STDOUT,
@@ -2721,6 +2727,25 @@ def subprocess_close(
2721
2727
  ##
2722
2728
 
2723
2729
 
2730
+ class VerboseCalledProcessError(subprocess.CalledProcessError):
2731
+ @classmethod
2732
+ def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
2733
+ return cls(
2734
+ e.returncode,
2735
+ e.cmd,
2736
+ output=e.output,
2737
+ stderr=e.stderr,
2738
+ )
2739
+
2740
+ def __str__(self) -> str:
2741
+ msg = super().__str__()
2742
+ if self.output is not None:
2743
+ msg += f' Output: {self.output!r}'
2744
+ if self.stderr is not None:
2745
+ msg += f' Stderr: {self.stderr!r}'
2746
+ return msg
2747
+
2748
+
2724
2749
  class BaseSubprocesses(abc.ABC): # noqa
2725
2750
  DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
2726
2751
 
@@ -2754,16 +2779,31 @@ class BaseSubprocesses(abc.ABC): # noqa
2754
2779
  if extra_env:
2755
2780
  self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
2756
2781
 
2782
+ #
2783
+
2757
2784
  if extra_env:
2758
2785
  env = {**(env if env is not None else os.environ), **extra_env}
2759
2786
 
2787
+ #
2788
+
2760
2789
  if quiet and 'stderr' not in kwargs:
2761
2790
  if self._log and not self._log.isEnabledFor(logging.DEBUG):
2762
2791
  kwargs['stderr'] = subprocess.DEVNULL
2763
2792
 
2793
+ for chk in ('stdout', 'stderr'):
2794
+ try:
2795
+ chv = kwargs[chk]
2796
+ except KeyError:
2797
+ continue
2798
+ kwargs[chk] = SUBPROCESS_CHANNEL_OPTION_VALUES.get(chv, chv)
2799
+
2800
+ #
2801
+
2764
2802
  if not shell:
2765
2803
  cmd = subprocess_maybe_shell_wrap_exec(*cmd)
2766
2804
 
2805
+ #
2806
+
2767
2807
  return cmd, dict(
2768
2808
  env=env,
2769
2809
  shell=shell,
@@ -2771,35 +2811,57 @@ class BaseSubprocesses(abc.ABC): # noqa
2771
2811
  )
2772
2812
 
2773
2813
  @contextlib.contextmanager
2774
- def wrap_call(self, *cmd: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
2814
+ def wrap_call(
2815
+ self,
2816
+ *cmd: ta.Any,
2817
+ raise_verbose: bool = False,
2818
+ **kwargs: ta.Any,
2819
+ ) -> ta.Iterator[None]:
2775
2820
  start_time = time.time()
2776
2821
  try:
2777
2822
  if self._log:
2778
2823
  self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
2824
+
2779
2825
  yield
2780
2826
 
2781
2827
  except Exception as exc: # noqa
2782
2828
  if self._log:
2783
2829
  self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
2830
+
2831
+ if (
2832
+ raise_verbose and
2833
+ isinstance(exc, subprocess.CalledProcessError) and
2834
+ not isinstance(exc, VerboseCalledProcessError) and
2835
+ (exc.output is not None or exc.stderr is not None)
2836
+ ):
2837
+ raise VerboseCalledProcessError.from_std(exc) from exc
2838
+
2784
2839
  raise
2785
2840
 
2786
2841
  finally:
2787
2842
  end_time = time.time()
2788
2843
  elapsed_s = end_time - start_time
2844
+
2789
2845
  if self._log:
2790
- self._log.debug('sSubprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
2846
+ self._log.debug('Subprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
2791
2847
 
2792
2848
  @contextlib.contextmanager
2793
2849
  def prepare_and_wrap(
2794
2850
  self,
2795
2851
  *cmd: ta.Any,
2852
+ raise_verbose: bool = False,
2796
2853
  **kwargs: ta.Any,
2797
2854
  ) -> ta.Iterator[ta.Tuple[
2798
2855
  ta.Tuple[ta.Any, ...],
2799
2856
  ta.Dict[str, ta.Any],
2800
2857
  ]]:
2801
2858
  cmd, kwargs = self.prepare_args(*cmd, **kwargs)
2802
- with self.wrap_call(*cmd, **kwargs):
2859
+
2860
+ with self.wrap_call(
2861
+ *cmd,
2862
+ raise_verbose=raise_verbose,
2863
+ **kwargs,
2864
+ ):
2803
2865
  yield cmd, kwargs
2804
2866
 
2805
2867
  #
omdev/scripts/exectime.py CHANGED
@@ -1,7 +1,66 @@
1
1
  #!/usr/bin/env python3
2
2
  # @omlish-script
3
- import sys
4
- import time
3
+
4
+
5
+ ##
6
+
7
+
8
+ def _run_one(src, pre=None):
9
+ import time
10
+
11
+ if pre:
12
+ exec(pre)
13
+
14
+ co = compile(src, '<string>', 'exec')
15
+ start = time.time_ns()
16
+ exec(co)
17
+ end = time.time_ns()
18
+
19
+ return end - start
20
+
21
+
22
+ ##
23
+
24
+
25
+ def _run(n, src, pre=None):
26
+ if n is None:
27
+ return _run_one(src, pre=pre)
28
+
29
+ #
30
+
31
+ import inspect
32
+
33
+ cmd = '\n'.join([
34
+ inspect.getsource(_run_one),
35
+ f'print(_run_one({src!r}, pre={pre!r}))',
36
+ ])
37
+
38
+ #
39
+
40
+ import subprocess
41
+ import sys
42
+
43
+ ts = []
44
+ for _ in range(n):
45
+ out = subprocess.check_output([sys.executable, '-c', cmd]).decode()
46
+ t = int(out.strip())
47
+ ts.append(t)
48
+
49
+ #
50
+
51
+ import statistics
52
+
53
+ o = {
54
+ # 'times': ts,
55
+ 'mean': statistics.mean(ts),
56
+ 'median': statistics.median(ts),
57
+ 'quantiles': statistics.quantiles(ts),
58
+ }
59
+
60
+ return o
61
+
62
+
63
+ ##
5
64
 
6
65
 
7
66
  # @omlish-manifest
@@ -11,23 +70,36 @@ _CLI_MODULE = {'$omdev.cli.types.CliModule': {
11
70
  }}
12
71
 
13
72
 
14
- def _main() -> None:
15
- if len(sys.argv) == 2:
16
- pre = None
17
- [src] = sys.argv[1:]
18
- elif len(sys.argv) == 3:
19
- [pre, src] = sys.argv[1:]
73
+ def _main():
74
+ import sys
75
+
76
+ args = sys.argv[1:]
77
+
78
+ n = None
79
+ if args:
80
+ try:
81
+ n = int(args[0])
82
+ except ValueError:
83
+ pass
84
+ else:
85
+ args.pop(0)
86
+
87
+ if len(args) > 1:
88
+ pre = args.pop(0)
20
89
  else:
90
+ pre = None
91
+
92
+ if len(args) != 1:
21
93
  raise Exception('Invalid arguments')
94
+ [src] = args
22
95
 
23
- if pre:
24
- exec(pre)
96
+ #
25
97
 
26
- co = compile(src, '<string>', 'exec')
27
- start = time.time_ns()
28
- exec(co)
29
- end = time.time_ns()
30
- print(end - start)
98
+ o = _run(n, src, pre=pre)
99
+
100
+ import json
101
+
102
+ print(json.dumps(o, indent=2))
31
103
 
32
104
 
33
105
  if __name__ == '__main__':
omdev/scripts/interp.py CHANGED
@@ -3500,7 +3500,7 @@ TODO:
3500
3500
 
3501
3501
  STANDARD_LOG_FORMAT_PARTS = [
3502
3502
  ('asctime', '%(asctime)-15s'),
3503
- ('process', 'pid=%(process)-6s'),
3503
+ ('process', 'pid=%(process)s'),
3504
3504
  ('thread', 'tid=%(thread)x'),
3505
3505
  ('levelname', '%(levelname)s'),
3506
3506
  ('name', '%(name)s'),
@@ -3613,6 +3613,12 @@ def configure_standard_logging(
3613
3613
  ##
3614
3614
 
3615
3615
 
3616
+ # Valid channel type kwarg values:
3617
+ # - A special flag negative int
3618
+ # - A positive fd int
3619
+ # - A file-like object
3620
+ # - None
3621
+
3616
3622
  SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
3617
3623
  'pipe': subprocess.PIPE,
3618
3624
  'stdout': subprocess.STDOUT,
@@ -3658,6 +3664,25 @@ def subprocess_close(
3658
3664
  ##
3659
3665
 
3660
3666
 
3667
+ class VerboseCalledProcessError(subprocess.CalledProcessError):
3668
+ @classmethod
3669
+ def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
3670
+ return cls(
3671
+ e.returncode,
3672
+ e.cmd,
3673
+ output=e.output,
3674
+ stderr=e.stderr,
3675
+ )
3676
+
3677
+ def __str__(self) -> str:
3678
+ msg = super().__str__()
3679
+ if self.output is not None:
3680
+ msg += f' Output: {self.output!r}'
3681
+ if self.stderr is not None:
3682
+ msg += f' Stderr: {self.stderr!r}'
3683
+ return msg
3684
+
3685
+
3661
3686
  class BaseSubprocesses(abc.ABC): # noqa
3662
3687
  DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
3663
3688
 
@@ -3691,16 +3716,31 @@ class BaseSubprocesses(abc.ABC): # noqa
3691
3716
  if extra_env:
3692
3717
  self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
3693
3718
 
3719
+ #
3720
+
3694
3721
  if extra_env:
3695
3722
  env = {**(env if env is not None else os.environ), **extra_env}
3696
3723
 
3724
+ #
3725
+
3697
3726
  if quiet and 'stderr' not in kwargs:
3698
3727
  if self._log and not self._log.isEnabledFor(logging.DEBUG):
3699
3728
  kwargs['stderr'] = subprocess.DEVNULL
3700
3729
 
3730
+ for chk in ('stdout', 'stderr'):
3731
+ try:
3732
+ chv = kwargs[chk]
3733
+ except KeyError:
3734
+ continue
3735
+ kwargs[chk] = SUBPROCESS_CHANNEL_OPTION_VALUES.get(chv, chv)
3736
+
3737
+ #
3738
+
3701
3739
  if not shell:
3702
3740
  cmd = subprocess_maybe_shell_wrap_exec(*cmd)
3703
3741
 
3742
+ #
3743
+
3704
3744
  return cmd, dict(
3705
3745
  env=env,
3706
3746
  shell=shell,
@@ -3708,35 +3748,57 @@ class BaseSubprocesses(abc.ABC): # noqa
3708
3748
  )
3709
3749
 
3710
3750
  @contextlib.contextmanager
3711
- def wrap_call(self, *cmd: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
3751
+ def wrap_call(
3752
+ self,
3753
+ *cmd: ta.Any,
3754
+ raise_verbose: bool = False,
3755
+ **kwargs: ta.Any,
3756
+ ) -> ta.Iterator[None]:
3712
3757
  start_time = time.time()
3713
3758
  try:
3714
3759
  if self._log:
3715
3760
  self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
3761
+
3716
3762
  yield
3717
3763
 
3718
3764
  except Exception as exc: # noqa
3719
3765
  if self._log:
3720
3766
  self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
3767
+
3768
+ if (
3769
+ raise_verbose and
3770
+ isinstance(exc, subprocess.CalledProcessError) and
3771
+ not isinstance(exc, VerboseCalledProcessError) and
3772
+ (exc.output is not None or exc.stderr is not None)
3773
+ ):
3774
+ raise VerboseCalledProcessError.from_std(exc) from exc
3775
+
3721
3776
  raise
3722
3777
 
3723
3778
  finally:
3724
3779
  end_time = time.time()
3725
3780
  elapsed_s = end_time - start_time
3781
+
3726
3782
  if self._log:
3727
- self._log.debug('sSubprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
3783
+ self._log.debug('Subprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
3728
3784
 
3729
3785
  @contextlib.contextmanager
3730
3786
  def prepare_and_wrap(
3731
3787
  self,
3732
3788
  *cmd: ta.Any,
3789
+ raise_verbose: bool = False,
3733
3790
  **kwargs: ta.Any,
3734
3791
  ) -> ta.Iterator[ta.Tuple[
3735
3792
  ta.Tuple[ta.Any, ...],
3736
3793
  ta.Dict[str, ta.Any],
3737
3794
  ]]:
3738
3795
  cmd, kwargs = self.prepare_args(*cmd, **kwargs)
3739
- with self.wrap_call(*cmd, **kwargs):
3796
+
3797
+ with self.wrap_call(
3798
+ *cmd,
3799
+ raise_verbose=raise_verbose,
3800
+ **kwargs,
3801
+ ):
3740
3802
  yield cmd, kwargs
3741
3803
 
3742
3804
  #
@@ -5740,7 +5740,7 @@ TODO:
5740
5740
 
5741
5741
  STANDARD_LOG_FORMAT_PARTS = [
5742
5742
  ('asctime', '%(asctime)-15s'),
5743
- ('process', 'pid=%(process)-6s'),
5743
+ ('process', 'pid=%(process)s'),
5744
5744
  ('thread', 'tid=%(thread)x'),
5745
5745
  ('levelname', '%(levelname)s'),
5746
5746
  ('name', '%(name)s'),
@@ -5853,6 +5853,12 @@ def configure_standard_logging(
5853
5853
  ##
5854
5854
 
5855
5855
 
5856
+ # Valid channel type kwarg values:
5857
+ # - A special flag negative int
5858
+ # - A positive fd int
5859
+ # - A file-like object
5860
+ # - None
5861
+
5856
5862
  SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
5857
5863
  'pipe': subprocess.PIPE,
5858
5864
  'stdout': subprocess.STDOUT,
@@ -5898,6 +5904,25 @@ def subprocess_close(
5898
5904
  ##
5899
5905
 
5900
5906
 
5907
+ class VerboseCalledProcessError(subprocess.CalledProcessError):
5908
+ @classmethod
5909
+ def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
5910
+ return cls(
5911
+ e.returncode,
5912
+ e.cmd,
5913
+ output=e.output,
5914
+ stderr=e.stderr,
5915
+ )
5916
+
5917
+ def __str__(self) -> str:
5918
+ msg = super().__str__()
5919
+ if self.output is not None:
5920
+ msg += f' Output: {self.output!r}'
5921
+ if self.stderr is not None:
5922
+ msg += f' Stderr: {self.stderr!r}'
5923
+ return msg
5924
+
5925
+
5901
5926
  class BaseSubprocesses(abc.ABC): # noqa
5902
5927
  DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
5903
5928
 
@@ -5931,16 +5956,31 @@ class BaseSubprocesses(abc.ABC): # noqa
5931
5956
  if extra_env:
5932
5957
  self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
5933
5958
 
5959
+ #
5960
+
5934
5961
  if extra_env:
5935
5962
  env = {**(env if env is not None else os.environ), **extra_env}
5936
5963
 
5964
+ #
5965
+
5937
5966
  if quiet and 'stderr' not in kwargs:
5938
5967
  if self._log and not self._log.isEnabledFor(logging.DEBUG):
5939
5968
  kwargs['stderr'] = subprocess.DEVNULL
5940
5969
 
5970
+ for chk in ('stdout', 'stderr'):
5971
+ try:
5972
+ chv = kwargs[chk]
5973
+ except KeyError:
5974
+ continue
5975
+ kwargs[chk] = SUBPROCESS_CHANNEL_OPTION_VALUES.get(chv, chv)
5976
+
5977
+ #
5978
+
5941
5979
  if not shell:
5942
5980
  cmd = subprocess_maybe_shell_wrap_exec(*cmd)
5943
5981
 
5982
+ #
5983
+
5944
5984
  return cmd, dict(
5945
5985
  env=env,
5946
5986
  shell=shell,
@@ -5948,35 +5988,57 @@ class BaseSubprocesses(abc.ABC): # noqa
5948
5988
  )
5949
5989
 
5950
5990
  @contextlib.contextmanager
5951
- def wrap_call(self, *cmd: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
5991
+ def wrap_call(
5992
+ self,
5993
+ *cmd: ta.Any,
5994
+ raise_verbose: bool = False,
5995
+ **kwargs: ta.Any,
5996
+ ) -> ta.Iterator[None]:
5952
5997
  start_time = time.time()
5953
5998
  try:
5954
5999
  if self._log:
5955
6000
  self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
6001
+
5956
6002
  yield
5957
6003
 
5958
6004
  except Exception as exc: # noqa
5959
6005
  if self._log:
5960
6006
  self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
6007
+
6008
+ if (
6009
+ raise_verbose and
6010
+ isinstance(exc, subprocess.CalledProcessError) and
6011
+ not isinstance(exc, VerboseCalledProcessError) and
6012
+ (exc.output is not None or exc.stderr is not None)
6013
+ ):
6014
+ raise VerboseCalledProcessError.from_std(exc) from exc
6015
+
5961
6016
  raise
5962
6017
 
5963
6018
  finally:
5964
6019
  end_time = time.time()
5965
6020
  elapsed_s = end_time - start_time
6021
+
5966
6022
  if self._log:
5967
- self._log.debug('sSubprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
6023
+ self._log.debug('Subprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
5968
6024
 
5969
6025
  @contextlib.contextmanager
5970
6026
  def prepare_and_wrap(
5971
6027
  self,
5972
6028
  *cmd: ta.Any,
6029
+ raise_verbose: bool = False,
5973
6030
  **kwargs: ta.Any,
5974
6031
  ) -> ta.Iterator[ta.Tuple[
5975
6032
  ta.Tuple[ta.Any, ...],
5976
6033
  ta.Dict[str, ta.Any],
5977
6034
  ]]:
5978
6035
  cmd, kwargs = self.prepare_args(*cmd, **kwargs)
5979
- with self.wrap_call(*cmd, **kwargs):
6036
+
6037
+ with self.wrap_call(
6038
+ *cmd,
6039
+ raise_verbose=raise_verbose,
6040
+ **kwargs,
6041
+ ):
5980
6042
  yield cmd, kwargs
5981
6043
 
5982
6044
  #
@@ -18,6 +18,7 @@ if ta.TYPE_CHECKING:
18
18
  import yaml
19
19
 
20
20
  from omlish.formats import dotenv
21
+ from omlish.formats import json5
21
22
  from omlish.formats import props
22
23
  from omlish.formats import xml
23
24
 
@@ -29,6 +30,7 @@ else:
29
30
  yaml = lang.proxy_import('yaml')
30
31
 
31
32
  dotenv = lang.proxy_import('omlish.formats.dotenv')
33
+ json5 = lang.proxy_import('omlish.formats.json5')
32
34
  props = lang.proxy_import('omlish.formats.props')
33
35
  xml = lang.proxy_import('omlish.formats.xml')
34
36
 
@@ -45,6 +47,8 @@ class Format:
45
47
  class Formats(enum.Enum):
46
48
  JSON = Format(['json'], json.load)
47
49
 
50
+ JSON5 = Format(['json5'], lambda f: json5.loads(f.read()))
51
+
48
52
  YAML = Format(['yaml', 'yml'], lambda f: yaml.safe_load(f))
49
53
 
50
54
  TOML = Format(['toml'], lambda f: tomllib.loads(f.read()))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: omdev
3
- Version: 0.0.0.dev215
3
+ Version: 0.0.0.dev217
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.dev215
15
+ Requires-Dist: omlish==0.0.0.dev217
16
16
  Provides-Extra: all
17
17
  Requires-Dist: black~=24.10; extra == "all"
18
18
  Requires-Dist: pycparser~=2.22; extra == "all"
@@ -1,4 +1,4 @@
1
- omdev/.manifests.json,sha256=VUg0T7DFMxatE-hsLqocMvGoNmELDiZZaudO1tcz-FE,9092
1
+ omdev/.manifests.json,sha256=02pFpcoefn9JQr0AIqt_6-BnWi49KF0baoGEKv8bjn0,9093
2
2
  omdev/__about__.py,sha256=j3vFclhFvyPICV6FK4aDApFzMCqJWxv9FaWwdwXrSgw,1215
3
3
  omdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  omdev/bracepy.py,sha256=I8EdqtDvxzAi3I8TuMEW-RBfwXfqKbwp06CfOdj3L1o,2743
@@ -150,8 +150,8 @@ omdev/precheck/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
150
150
  omdev/precheck/__main__.py,sha256=UEuS4z5-heIrwTtB-ONe1KeXJdqj8tYXMqWMpuO10so,165
151
151
  omdev/precheck/base.py,sha256=a_lGoFM-QhL8u8XDUYFhb-feEyfPbP4j8lcmNO51sHY,732
152
152
  omdev/precheck/git.py,sha256=APC5Ln7x0zDrQiGPRWPsBcVJK3vWhbU-brqR5M63JQA,849
153
- omdev/precheck/lite.py,sha256=eQjEBigjKj-y8xbDe0VnPPQYReGmxGERykyHi2cpEuM,3878
154
- omdev/precheck/main.py,sha256=QL2en3Vn8nnuRTnPeipK9ovc2KrXtv1CkT2YGPqxCio,2884
153
+ omdev/precheck/lite.py,sha256=kk41zbA2y1K0sMlbd6UyJP2mOW2I_iT6iaUW12F6mJY,4629
154
+ omdev/precheck/main.py,sha256=XkwQnC_4YH-0P9YYkVxKUHAAj0o6iXiCu6S-oU_WaQk,2948
155
155
  omdev/precheck/manifests.py,sha256=ulwuYeZ0vnRsj8uTUbQGifoBNwI82MAsJuffs3rVIak,760
156
156
  omdev/precheck/scripts.py,sha256=Xw9kkQzlDd_2V9av9qlaNpNZG9jZdy3TTo7x60MeR2I,1273
157
157
  omdev/ptk/__init__.py,sha256=QIu7cMeCKgNiXvIt7pXTESToJLuRMN0Qsxns_Z7ci0k,641
@@ -174,12 +174,12 @@ omdev/pyproject/resources/docker-dev.sh,sha256=DHkz5D18jok_oDolfg2mqrvGRWFoCe9GQ
174
174
  omdev/pyproject/resources/python.sh,sha256=jvrwddYw2KNttpuImLbdCdJK0HsUNMrHtTnmLvhxQxg,757
175
175
  omdev/scripts/__init__.py,sha256=MKCvUAEQwsIvwLixwtPlpBqmkMXLCnjjXyAXvVpDwVk,91
176
176
  omdev/scripts/bumpversion.py,sha256=Kn7fo73Hs8uJh3Hi3EIyLOlzLPWAC6dwuD_lZ3cIzuY,1064
177
- omdev/scripts/ci.py,sha256=yDx6VsztaY1VqYWF1i9Ywt2YTIseSc5wYtdvtmh5qyI,108870
177
+ omdev/scripts/ci.py,sha256=2bMl2vCMHdKQf2AmbcWV9Syv6RdizibYRDlT5Vg6q3E,110397
178
178
  omdev/scripts/execrss.py,sha256=mR0G0wERBYtQmVIn63lCIIFb5zkCM6X_XOENDFYDBKc,651
179
- omdev/scripts/exectime.py,sha256=sFb376GflU6s9gNX-2-we8hgH6w5MuQNS9g6i4SqJIo,610
179
+ omdev/scripts/exectime.py,sha256=S2O4MgtzTsFOY2IUJxsrnOIame9tEFc6aOlKP-F1JSg,1541
180
180
  omdev/scripts/importtrace.py,sha256=oa7CtcWJVMNDbyIEiRHej6ICfABfErMeo4_haIqe18Q,14041
181
- omdev/scripts/interp.py,sha256=7NrLbOkiDjBldnzpf-EpL8UTmU6U3lakvHqhSlY3X_U,141851
182
- omdev/scripts/pyproject.py,sha256=HDors8tvpSgCUzESwQ2E-Gx5jLisaYoOApOTCgmQ5P0,245595
181
+ omdev/scripts/interp.py,sha256=40KDm-mW8v7Mmhi4WByV9XzWPgwvTfKYsFKlojXR4k0,143362
182
+ omdev/scripts/pyproject.py,sha256=TG3Ne6h1W2rwT8nBG9E7QCmN_adPIO24QYaSJ7JGreU,247106
183
183
  omdev/scripts/slowcat.py,sha256=lssv4yrgJHiWfOiHkUut2p8E8Tq32zB-ujXESQxFFHY,2728
184
184
  omdev/scripts/tmpexec.py,sha256=WTYcf56Tj2qjYV14AWmV8SfT0u6Y8eIU6cKgQRvEK3c,1442
185
185
  omdev/tokens/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -203,7 +203,7 @@ omdev/tools/sqlrepl.py,sha256=wAjrfXNrRV63-NJCC2HlGQnFh7lUH0bHMnOjYotQqFs,5753
203
203
  omdev/tools/json/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
204
204
  omdev/tools/json/__main__.py,sha256=wqpkN_NsQyNwKW4qjVj8ADJ4_C98KhrFBtE-Z1UamfU,168
205
205
  omdev/tools/json/cli.py,sha256=EubIMT-n2XsjWBZjSy2fWXqijlwrIhLsfbkg3SZzi28,9586
206
- omdev/tools/json/formats.py,sha256=S8O0-A2_b_kXUevBme4KlSG0V5Nl-WXp8emRub1zMmA,1753
206
+ omdev/tools/json/formats.py,sha256=RgtPdcs294o9n9czjafHppg1iSzD-olsIc3v8ApM9Os,1908
207
207
  omdev/tools/json/io.py,sha256=sfj2hJS9Hy3aUR8a_lLzOrYcmL9fSKyvOHiofdUASsI,1427
208
208
  omdev/tools/json/parsing.py,sha256=YOeTRY6Gd89EfcHvqXO5PRWJ3IgRCpNnI54Lb_N3v2k,2183
209
209
  omdev/tools/json/processing.py,sha256=iFm5VqaxJ97WHaun2ed7NEjMxhFeJqf28bLNfoDJft0,1209
@@ -211,9 +211,9 @@ omdev/tools/json/rendering.py,sha256=tMcjOW5edfozcMSTxxvF7WVTsbYLoe9bCKFh50qyaGw
211
211
  omdev/tools/pawk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
212
212
  omdev/tools/pawk/__main__.py,sha256=VCqeRVnqT1RPEoIrqHFSu4PXVMg4YEgF4qCQm90-eRI,66
213
213
  omdev/tools/pawk/pawk.py,sha256=zsEkfQX0jF5bn712uqPAyBSdJt2dno1LH2oeSMNfXQI,11424
214
- omdev-0.0.0.dev215.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
215
- omdev-0.0.0.dev215.dist-info/METADATA,sha256=oNR4sVjlhZ4v-B5bp8h4XPFlDnI8aniDWCUq9WLk-ZM,1638
216
- omdev-0.0.0.dev215.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
217
- omdev-0.0.0.dev215.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
218
- omdev-0.0.0.dev215.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
219
- omdev-0.0.0.dev215.dist-info/RECORD,,
214
+ omdev-0.0.0.dev217.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
215
+ omdev-0.0.0.dev217.dist-info/METADATA,sha256=lNWmIe7pfG4nlRGuyYkGYlDR5GbC5JBbwrLMiHFIfNk,1638
216
+ omdev-0.0.0.dev217.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
217
+ omdev-0.0.0.dev217.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
218
+ omdev-0.0.0.dev217.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
219
+ omdev-0.0.0.dev217.dist-info/RECORD,,