omdev 0.0.0.dev157__py3-none-any.whl → 0.0.0.dev159__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/amalg/amalg.py +4 -4
- omdev/cache/data/cache.py +2 -2
- omdev/cache/data/manifests.py +1 -1
- omdev/cexts/cmake.py +1 -1
- omdev/cexts/scan.py +1 -1
- omdev/git/__init__.py +0 -0
- omdev/git/revisions.py +50 -0
- omdev/{git.py → git/status.py} +1 -112
- omdev/git/subtrees.py +100 -0
- omdev/interp/cli.py +3 -3
- omdev/interp/inspect.py +1 -1
- omdev/interp/pyenv.py +1 -1
- omdev/manifests/main.py +1 -1
- omdev/precheck/lite.py +1 -1
- omdev/precheck/main.py +1 -1
- omdev/pyproject/cli.py +4 -4
- omdev/pyproject/pkg.py +1 -1
- omdev/pyproject/venvs.py +1 -1
- omdev/revisions.py +2 -2
- omdev/scripts/interp.py +370 -293
- omdev/scripts/pyproject.py +497 -775
- omdev/tools/docker.py +1 -1
- omdev/tools/git.py +3 -3
- omdev/tools/mkrelimp.py +1 -1
- omdev/tools/sqlrepl.py +1 -1
- {omdev-0.0.0.dev157.dist-info → omdev-0.0.0.dev159.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev157.dist-info → omdev-0.0.0.dev159.dist-info}/RECORD +31 -28
- {omdev-0.0.0.dev157.dist-info → omdev-0.0.0.dev159.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev157.dist-info → omdev-0.0.0.dev159.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev157.dist-info → omdev-0.0.0.dev159.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev157.dist-info → omdev-0.0.0.dev159.dist-info}/top_level.txt +0 -0
omdev/scripts/pyproject.py
CHANGED
|
@@ -115,7 +115,7 @@ CallableVersionOperator = ta.Callable[['Version', str], bool]
|
|
|
115
115
|
# ../../omlish/argparse/cli.py
|
|
116
116
|
ArgparseCommandFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
|
|
117
117
|
|
|
118
|
-
# ../../omlish/
|
|
118
|
+
# ../../omlish/subprocesses.py
|
|
119
119
|
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
|
|
120
120
|
|
|
121
121
|
|
|
@@ -2328,6 +2328,13 @@ json_dump_compact: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON
|
|
|
2328
2328
|
json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_COMPACT_KWARGS)
|
|
2329
2329
|
|
|
2330
2330
|
|
|
2331
|
+
########################################
|
|
2332
|
+
# ../../../omlish/lite/logs.py
|
|
2333
|
+
|
|
2334
|
+
|
|
2335
|
+
log = logging.getLogger(__name__)
|
|
2336
|
+
|
|
2337
|
+
|
|
2331
2338
|
########################################
|
|
2332
2339
|
# ../../../omlish/lite/reflect.py
|
|
2333
2340
|
|
|
@@ -2450,6 +2457,116 @@ def format_num_bytes(num_bytes: int) -> str:
|
|
|
2450
2457
|
return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
|
|
2451
2458
|
|
|
2452
2459
|
|
|
2460
|
+
########################################
|
|
2461
|
+
# ../../../omlish/logs/filters.py
|
|
2462
|
+
|
|
2463
|
+
|
|
2464
|
+
class TidLogFilter(logging.Filter):
|
|
2465
|
+
def filter(self, record):
|
|
2466
|
+
record.tid = threading.get_native_id()
|
|
2467
|
+
return True
|
|
2468
|
+
|
|
2469
|
+
|
|
2470
|
+
########################################
|
|
2471
|
+
# ../../../omlish/logs/proxy.py
|
|
2472
|
+
|
|
2473
|
+
|
|
2474
|
+
class ProxyLogFilterer(logging.Filterer):
|
|
2475
|
+
def __init__(self, underlying: logging.Filterer) -> None: # noqa
|
|
2476
|
+
self._underlying = underlying
|
|
2477
|
+
|
|
2478
|
+
@property
|
|
2479
|
+
def underlying(self) -> logging.Filterer:
|
|
2480
|
+
return self._underlying
|
|
2481
|
+
|
|
2482
|
+
@property
|
|
2483
|
+
def filters(self):
|
|
2484
|
+
return self._underlying.filters
|
|
2485
|
+
|
|
2486
|
+
@filters.setter
|
|
2487
|
+
def filters(self, filters):
|
|
2488
|
+
self._underlying.filters = filters
|
|
2489
|
+
|
|
2490
|
+
def addFilter(self, filter): # noqa
|
|
2491
|
+
self._underlying.addFilter(filter)
|
|
2492
|
+
|
|
2493
|
+
def removeFilter(self, filter): # noqa
|
|
2494
|
+
self._underlying.removeFilter(filter)
|
|
2495
|
+
|
|
2496
|
+
def filter(self, record):
|
|
2497
|
+
return self._underlying.filter(record)
|
|
2498
|
+
|
|
2499
|
+
|
|
2500
|
+
class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
|
|
2501
|
+
def __init__(self, underlying: logging.Handler) -> None: # noqa
|
|
2502
|
+
ProxyLogFilterer.__init__(self, underlying)
|
|
2503
|
+
|
|
2504
|
+
_underlying: logging.Handler
|
|
2505
|
+
|
|
2506
|
+
@property
|
|
2507
|
+
def underlying(self) -> logging.Handler:
|
|
2508
|
+
return self._underlying
|
|
2509
|
+
|
|
2510
|
+
def get_name(self):
|
|
2511
|
+
return self._underlying.get_name()
|
|
2512
|
+
|
|
2513
|
+
def set_name(self, name):
|
|
2514
|
+
self._underlying.set_name(name)
|
|
2515
|
+
|
|
2516
|
+
@property
|
|
2517
|
+
def name(self):
|
|
2518
|
+
return self._underlying.name
|
|
2519
|
+
|
|
2520
|
+
@property
|
|
2521
|
+
def level(self):
|
|
2522
|
+
return self._underlying.level
|
|
2523
|
+
|
|
2524
|
+
@level.setter
|
|
2525
|
+
def level(self, level):
|
|
2526
|
+
self._underlying.level = level
|
|
2527
|
+
|
|
2528
|
+
@property
|
|
2529
|
+
def formatter(self):
|
|
2530
|
+
return self._underlying.formatter
|
|
2531
|
+
|
|
2532
|
+
@formatter.setter
|
|
2533
|
+
def formatter(self, formatter):
|
|
2534
|
+
self._underlying.formatter = formatter
|
|
2535
|
+
|
|
2536
|
+
def createLock(self):
|
|
2537
|
+
self._underlying.createLock()
|
|
2538
|
+
|
|
2539
|
+
def acquire(self):
|
|
2540
|
+
self._underlying.acquire()
|
|
2541
|
+
|
|
2542
|
+
def release(self):
|
|
2543
|
+
self._underlying.release()
|
|
2544
|
+
|
|
2545
|
+
def setLevel(self, level):
|
|
2546
|
+
self._underlying.setLevel(level)
|
|
2547
|
+
|
|
2548
|
+
def format(self, record):
|
|
2549
|
+
return self._underlying.format(record)
|
|
2550
|
+
|
|
2551
|
+
def emit(self, record):
|
|
2552
|
+
self._underlying.emit(record)
|
|
2553
|
+
|
|
2554
|
+
def handle(self, record):
|
|
2555
|
+
return self._underlying.handle(record)
|
|
2556
|
+
|
|
2557
|
+
def setFormatter(self, fmt):
|
|
2558
|
+
self._underlying.setFormatter(fmt)
|
|
2559
|
+
|
|
2560
|
+
def flush(self):
|
|
2561
|
+
self._underlying.flush()
|
|
2562
|
+
|
|
2563
|
+
def close(self):
|
|
2564
|
+
self._underlying.close()
|
|
2565
|
+
|
|
2566
|
+
def handleError(self, record):
|
|
2567
|
+
self._underlying.handleError(record)
|
|
2568
|
+
|
|
2569
|
+
|
|
2453
2570
|
########################################
|
|
2454
2571
|
# ../../cexts/magic.py
|
|
2455
2572
|
|
|
@@ -3207,6 +3324,83 @@ class SpecifierSet(BaseSpecifier):
|
|
|
3207
3324
|
return iter(filtered)
|
|
3208
3325
|
|
|
3209
3326
|
|
|
3327
|
+
########################################
|
|
3328
|
+
# ../reqs.py
|
|
3329
|
+
"""
|
|
3330
|
+
TODO:
|
|
3331
|
+
- embed pip._internal.req.parse_requirements, add additional env stuff? breaks compat with raw pip
|
|
3332
|
+
"""
|
|
3333
|
+
|
|
3334
|
+
|
|
3335
|
+
class RequirementsRewriter:
|
|
3336
|
+
def __init__(
|
|
3337
|
+
self,
|
|
3338
|
+
venv: ta.Optional[str] = None,
|
|
3339
|
+
) -> None:
|
|
3340
|
+
super().__init__()
|
|
3341
|
+
self._venv = venv
|
|
3342
|
+
|
|
3343
|
+
@cached_nullary
|
|
3344
|
+
def _tmp_dir(self) -> str:
|
|
3345
|
+
return tempfile.mkdtemp('-omlish-reqs')
|
|
3346
|
+
|
|
3347
|
+
VENV_MAGIC = '# @omlish-venv'
|
|
3348
|
+
|
|
3349
|
+
def rewrite_file(self, in_file: str) -> str:
|
|
3350
|
+
with open(in_file) as f:
|
|
3351
|
+
src = f.read()
|
|
3352
|
+
|
|
3353
|
+
in_lines = src.splitlines(keepends=True)
|
|
3354
|
+
out_lines = []
|
|
3355
|
+
|
|
3356
|
+
for l in in_lines:
|
|
3357
|
+
if self.VENV_MAGIC in l:
|
|
3358
|
+
lp, _, rp = l.partition(self.VENV_MAGIC)
|
|
3359
|
+
rp = rp.partition('#')[0]
|
|
3360
|
+
omit = False
|
|
3361
|
+
for v in rp.split():
|
|
3362
|
+
if v[0] == '!':
|
|
3363
|
+
if self._venv is not None and self._venv == v[1:]:
|
|
3364
|
+
omit = True
|
|
3365
|
+
break
|
|
3366
|
+
else:
|
|
3367
|
+
raise NotImplementedError
|
|
3368
|
+
|
|
3369
|
+
if omit:
|
|
3370
|
+
out_lines.append('# OMITTED: ' + l)
|
|
3371
|
+
continue
|
|
3372
|
+
|
|
3373
|
+
out_req = self.rewrite(l.rstrip('\n'), for_file=True)
|
|
3374
|
+
out_lines.append(out_req + '\n')
|
|
3375
|
+
|
|
3376
|
+
out_file = os.path.join(self._tmp_dir(), os.path.basename(in_file))
|
|
3377
|
+
if os.path.exists(out_file):
|
|
3378
|
+
raise Exception(f'file exists: {out_file}')
|
|
3379
|
+
|
|
3380
|
+
with open(out_file, 'w') as f:
|
|
3381
|
+
f.write(''.join(out_lines))
|
|
3382
|
+
log.info('Rewrote requirements file %s to %s', in_file, out_file)
|
|
3383
|
+
return out_file
|
|
3384
|
+
|
|
3385
|
+
def rewrite(self, in_req: str, *, for_file: bool = False) -> str:
|
|
3386
|
+
if in_req.strip().startswith('-r'):
|
|
3387
|
+
l = in_req.strip()
|
|
3388
|
+
lp, _, rp = l.partition(' ')
|
|
3389
|
+
if lp == '-r':
|
|
3390
|
+
inc_in_file, _, rest = rp.partition(' ')
|
|
3391
|
+
else:
|
|
3392
|
+
inc_in_file, rest = lp[2:], rp
|
|
3393
|
+
|
|
3394
|
+
inc_out_file = self.rewrite_file(inc_in_file)
|
|
3395
|
+
if for_file:
|
|
3396
|
+
return ' '.join(['-r ', inc_out_file, rest])
|
|
3397
|
+
else:
|
|
3398
|
+
return '-r' + inc_out_file
|
|
3399
|
+
|
|
3400
|
+
else:
|
|
3401
|
+
return in_req
|
|
3402
|
+
|
|
3403
|
+
|
|
3210
3404
|
########################################
|
|
3211
3405
|
# ../../../omlish/argparse/cli.py
|
|
3212
3406
|
"""
|
|
@@ -3482,311 +3676,41 @@ class ArgparseCli:
|
|
|
3482
3676
|
|
|
3483
3677
|
|
|
3484
3678
|
########################################
|
|
3485
|
-
# ../../../omlish/lite/
|
|
3679
|
+
# ../../../omlish/lite/marshal.py
|
|
3486
3680
|
"""
|
|
3487
3681
|
TODO:
|
|
3488
|
-
-
|
|
3489
|
-
-
|
|
3682
|
+
- pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
|
|
3683
|
+
- namedtuple
|
|
3684
|
+
- literals
|
|
3685
|
+
- newtypes?
|
|
3490
3686
|
"""
|
|
3491
3687
|
|
|
3492
3688
|
|
|
3493
|
-
log = logging.getLogger(__name__)
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
3689
|
##
|
|
3497
3690
|
|
|
3498
3691
|
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
return True
|
|
3692
|
+
@dc.dataclass(frozen=True)
|
|
3693
|
+
class ObjMarshalOptions:
|
|
3694
|
+
raw_bytes: bool = False
|
|
3695
|
+
nonstrict_dataclasses: bool = False
|
|
3504
3696
|
|
|
3505
3697
|
|
|
3506
|
-
|
|
3698
|
+
class ObjMarshaler(abc.ABC):
|
|
3699
|
+
@abc.abstractmethod
|
|
3700
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
|
3701
|
+
raise NotImplementedError
|
|
3507
3702
|
|
|
3703
|
+
@abc.abstractmethod
|
|
3704
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
|
3705
|
+
raise NotImplementedError
|
|
3508
3706
|
|
|
3509
|
-
class JsonLogFormatter(logging.Formatter):
|
|
3510
3707
|
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
'args': False,
|
|
3515
|
-
'levelname': False,
|
|
3516
|
-
'levelno': False,
|
|
3517
|
-
'pathname': False,
|
|
3518
|
-
'filename': False,
|
|
3519
|
-
'module': False,
|
|
3520
|
-
'exc_info': True,
|
|
3521
|
-
'exc_text': True,
|
|
3522
|
-
'stack_info': True,
|
|
3523
|
-
'lineno': False,
|
|
3524
|
-
'funcName': False,
|
|
3525
|
-
'created': False,
|
|
3526
|
-
'msecs': False,
|
|
3527
|
-
'relativeCreated': False,
|
|
3528
|
-
'thread': False,
|
|
3529
|
-
'threadName': False,
|
|
3530
|
-
'processName': False,
|
|
3531
|
-
'process': False,
|
|
3532
|
-
}
|
|
3708
|
+
class NopObjMarshaler(ObjMarshaler):
|
|
3709
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
|
3710
|
+
return o
|
|
3533
3711
|
|
|
3534
|
-
def
|
|
3535
|
-
|
|
3536
|
-
k: v
|
|
3537
|
-
for k, o in self.KEYS.items()
|
|
3538
|
-
for v in [getattr(record, k)]
|
|
3539
|
-
if not (o and v is None)
|
|
3540
|
-
}
|
|
3541
|
-
return json_dumps_compact(dct)
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
##
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
STANDARD_LOG_FORMAT_PARTS = [
|
|
3548
|
-
('asctime', '%(asctime)-15s'),
|
|
3549
|
-
('process', 'pid=%(process)-6s'),
|
|
3550
|
-
('thread', 'tid=%(thread)x'),
|
|
3551
|
-
('levelname', '%(levelname)s'),
|
|
3552
|
-
('name', '%(name)s'),
|
|
3553
|
-
('separator', '::'),
|
|
3554
|
-
('message', '%(message)s'),
|
|
3555
|
-
]
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
class StandardLogFormatter(logging.Formatter):
|
|
3559
|
-
|
|
3560
|
-
@staticmethod
|
|
3561
|
-
def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
|
|
3562
|
-
return ' '.join(v for k, v in parts)
|
|
3563
|
-
|
|
3564
|
-
converter = datetime.datetime.fromtimestamp # type: ignore
|
|
3565
|
-
|
|
3566
|
-
def formatTime(self, record, datefmt=None):
|
|
3567
|
-
ct = self.converter(record.created) # type: ignore
|
|
3568
|
-
if datefmt:
|
|
3569
|
-
return ct.strftime(datefmt) # noqa
|
|
3570
|
-
else:
|
|
3571
|
-
t = ct.strftime('%Y-%m-%d %H:%M:%S')
|
|
3572
|
-
return '%s.%03d' % (t, record.msecs) # noqa
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
##
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
class ProxyLogFilterer(logging.Filterer):
|
|
3579
|
-
def __init__(self, underlying: logging.Filterer) -> None: # noqa
|
|
3580
|
-
self._underlying = underlying
|
|
3581
|
-
|
|
3582
|
-
@property
|
|
3583
|
-
def underlying(self) -> logging.Filterer:
|
|
3584
|
-
return self._underlying
|
|
3585
|
-
|
|
3586
|
-
@property
|
|
3587
|
-
def filters(self):
|
|
3588
|
-
return self._underlying.filters
|
|
3589
|
-
|
|
3590
|
-
@filters.setter
|
|
3591
|
-
def filters(self, filters):
|
|
3592
|
-
self._underlying.filters = filters
|
|
3593
|
-
|
|
3594
|
-
def addFilter(self, filter): # noqa
|
|
3595
|
-
self._underlying.addFilter(filter)
|
|
3596
|
-
|
|
3597
|
-
def removeFilter(self, filter): # noqa
|
|
3598
|
-
self._underlying.removeFilter(filter)
|
|
3599
|
-
|
|
3600
|
-
def filter(self, record):
|
|
3601
|
-
return self._underlying.filter(record)
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
|
|
3605
|
-
def __init__(self, underlying: logging.Handler) -> None: # noqa
|
|
3606
|
-
ProxyLogFilterer.__init__(self, underlying)
|
|
3607
|
-
|
|
3608
|
-
_underlying: logging.Handler
|
|
3609
|
-
|
|
3610
|
-
@property
|
|
3611
|
-
def underlying(self) -> logging.Handler:
|
|
3612
|
-
return self._underlying
|
|
3613
|
-
|
|
3614
|
-
def get_name(self):
|
|
3615
|
-
return self._underlying.get_name()
|
|
3616
|
-
|
|
3617
|
-
def set_name(self, name):
|
|
3618
|
-
self._underlying.set_name(name)
|
|
3619
|
-
|
|
3620
|
-
@property
|
|
3621
|
-
def name(self):
|
|
3622
|
-
return self._underlying.name
|
|
3623
|
-
|
|
3624
|
-
@property
|
|
3625
|
-
def level(self):
|
|
3626
|
-
return self._underlying.level
|
|
3627
|
-
|
|
3628
|
-
@level.setter
|
|
3629
|
-
def level(self, level):
|
|
3630
|
-
self._underlying.level = level
|
|
3631
|
-
|
|
3632
|
-
@property
|
|
3633
|
-
def formatter(self):
|
|
3634
|
-
return self._underlying.formatter
|
|
3635
|
-
|
|
3636
|
-
@formatter.setter
|
|
3637
|
-
def formatter(self, formatter):
|
|
3638
|
-
self._underlying.formatter = formatter
|
|
3639
|
-
|
|
3640
|
-
def createLock(self):
|
|
3641
|
-
self._underlying.createLock()
|
|
3642
|
-
|
|
3643
|
-
def acquire(self):
|
|
3644
|
-
self._underlying.acquire()
|
|
3645
|
-
|
|
3646
|
-
def release(self):
|
|
3647
|
-
self._underlying.release()
|
|
3648
|
-
|
|
3649
|
-
def setLevel(self, level):
|
|
3650
|
-
self._underlying.setLevel(level)
|
|
3651
|
-
|
|
3652
|
-
def format(self, record):
|
|
3653
|
-
return self._underlying.format(record)
|
|
3654
|
-
|
|
3655
|
-
def emit(self, record):
|
|
3656
|
-
self._underlying.emit(record)
|
|
3657
|
-
|
|
3658
|
-
def handle(self, record):
|
|
3659
|
-
return self._underlying.handle(record)
|
|
3660
|
-
|
|
3661
|
-
def setFormatter(self, fmt):
|
|
3662
|
-
self._underlying.setFormatter(fmt)
|
|
3663
|
-
|
|
3664
|
-
def flush(self):
|
|
3665
|
-
self._underlying.flush()
|
|
3666
|
-
|
|
3667
|
-
def close(self):
|
|
3668
|
-
self._underlying.close()
|
|
3669
|
-
|
|
3670
|
-
def handleError(self, record):
|
|
3671
|
-
self._underlying.handleError(record)
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
##
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
class StandardLogHandler(ProxyLogHandler):
|
|
3678
|
-
pass
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
##
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
@contextlib.contextmanager
|
|
3685
|
-
def _locking_logging_module_lock() -> ta.Iterator[None]:
|
|
3686
|
-
if hasattr(logging, '_acquireLock'):
|
|
3687
|
-
logging._acquireLock() # noqa
|
|
3688
|
-
try:
|
|
3689
|
-
yield
|
|
3690
|
-
finally:
|
|
3691
|
-
logging._releaseLock() # type: ignore # noqa
|
|
3692
|
-
|
|
3693
|
-
elif hasattr(logging, '_lock'):
|
|
3694
|
-
# https://github.com/python/cpython/commit/74723e11109a320e628898817ab449b3dad9ee96
|
|
3695
|
-
with logging._lock: # noqa
|
|
3696
|
-
yield
|
|
3697
|
-
|
|
3698
|
-
else:
|
|
3699
|
-
raise Exception("Can't find lock in logging module")
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
def configure_standard_logging(
|
|
3703
|
-
level: ta.Union[int, str] = logging.INFO,
|
|
3704
|
-
*,
|
|
3705
|
-
json: bool = False,
|
|
3706
|
-
target: ta.Optional[logging.Logger] = None,
|
|
3707
|
-
force: bool = False,
|
|
3708
|
-
handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
|
|
3709
|
-
) -> ta.Optional[StandardLogHandler]:
|
|
3710
|
-
with _locking_logging_module_lock():
|
|
3711
|
-
if target is None:
|
|
3712
|
-
target = logging.root
|
|
3713
|
-
|
|
3714
|
-
#
|
|
3715
|
-
|
|
3716
|
-
if not force:
|
|
3717
|
-
if any(isinstance(h, StandardLogHandler) for h in list(target.handlers)):
|
|
3718
|
-
return None
|
|
3719
|
-
|
|
3720
|
-
#
|
|
3721
|
-
|
|
3722
|
-
if handler_factory is not None:
|
|
3723
|
-
handler = handler_factory()
|
|
3724
|
-
else:
|
|
3725
|
-
handler = logging.StreamHandler()
|
|
3726
|
-
|
|
3727
|
-
#
|
|
3728
|
-
|
|
3729
|
-
formatter: logging.Formatter
|
|
3730
|
-
if json:
|
|
3731
|
-
formatter = JsonLogFormatter()
|
|
3732
|
-
else:
|
|
3733
|
-
formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
|
|
3734
|
-
handler.setFormatter(formatter)
|
|
3735
|
-
|
|
3736
|
-
#
|
|
3737
|
-
|
|
3738
|
-
handler.addFilter(TidLogFilter())
|
|
3739
|
-
|
|
3740
|
-
#
|
|
3741
|
-
|
|
3742
|
-
target.addHandler(handler)
|
|
3743
|
-
|
|
3744
|
-
#
|
|
3745
|
-
|
|
3746
|
-
if level is not None:
|
|
3747
|
-
target.setLevel(level)
|
|
3748
|
-
|
|
3749
|
-
#
|
|
3750
|
-
|
|
3751
|
-
return StandardLogHandler(handler)
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
########################################
|
|
3755
|
-
# ../../../omlish/lite/marshal.py
|
|
3756
|
-
"""
|
|
3757
|
-
TODO:
|
|
3758
|
-
- pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
|
|
3759
|
-
- namedtuple
|
|
3760
|
-
- literals
|
|
3761
|
-
- newtypes?
|
|
3762
|
-
"""
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
##
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
@dc.dataclass(frozen=True)
|
|
3769
|
-
class ObjMarshalOptions:
|
|
3770
|
-
raw_bytes: bool = False
|
|
3771
|
-
nonstrict_dataclasses: bool = False
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
class ObjMarshaler(abc.ABC):
|
|
3775
|
-
@abc.abstractmethod
|
|
3776
|
-
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
|
3777
|
-
raise NotImplementedError
|
|
3778
|
-
|
|
3779
|
-
@abc.abstractmethod
|
|
3780
|
-
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
|
3781
|
-
raise NotImplementedError
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
class NopObjMarshaler(ObjMarshaler):
|
|
3785
|
-
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
|
3786
|
-
return o
|
|
3787
|
-
|
|
3788
|
-
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
|
3789
|
-
return o
|
|
3712
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
|
3713
|
+
return o
|
|
3790
3714
|
|
|
3791
3715
|
|
|
3792
3716
|
@dc.dataclass()
|
|
@@ -4205,12 +4129,66 @@ def is_debugger_attached() -> bool:
|
|
|
4205
4129
|
return any(frame[1].endswith('pydevd.py') for frame in inspect.stack())
|
|
4206
4130
|
|
|
4207
4131
|
|
|
4208
|
-
|
|
4132
|
+
LITE_REQUIRED_PYTHON_VERSION = (3, 8)
|
|
4209
4133
|
|
|
4210
4134
|
|
|
4211
|
-
def
|
|
4212
|
-
if sys.version_info <
|
|
4213
|
-
raise OSError(f'Requires python {
|
|
4135
|
+
def check_lite_runtime_version() -> None:
|
|
4136
|
+
if sys.version_info < LITE_REQUIRED_PYTHON_VERSION:
|
|
4137
|
+
raise OSError(f'Requires python {LITE_REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
|
|
4138
|
+
|
|
4139
|
+
|
|
4140
|
+
########################################
|
|
4141
|
+
# ../../../omlish/logs/json.py
|
|
4142
|
+
"""
|
|
4143
|
+
TODO:
|
|
4144
|
+
- translate json keys
|
|
4145
|
+
"""
|
|
4146
|
+
|
|
4147
|
+
|
|
4148
|
+
class JsonLogFormatter(logging.Formatter):
|
|
4149
|
+
KEYS: ta.Mapping[str, bool] = {
|
|
4150
|
+
'name': False,
|
|
4151
|
+
'msg': False,
|
|
4152
|
+
'args': False,
|
|
4153
|
+
'levelname': False,
|
|
4154
|
+
'levelno': False,
|
|
4155
|
+
'pathname': False,
|
|
4156
|
+
'filename': False,
|
|
4157
|
+
'module': False,
|
|
4158
|
+
'exc_info': True,
|
|
4159
|
+
'exc_text': True,
|
|
4160
|
+
'stack_info': True,
|
|
4161
|
+
'lineno': False,
|
|
4162
|
+
'funcName': False,
|
|
4163
|
+
'created': False,
|
|
4164
|
+
'msecs': False,
|
|
4165
|
+
'relativeCreated': False,
|
|
4166
|
+
'thread': False,
|
|
4167
|
+
'threadName': False,
|
|
4168
|
+
'processName': False,
|
|
4169
|
+
'process': False,
|
|
4170
|
+
}
|
|
4171
|
+
|
|
4172
|
+
def __init__(
|
|
4173
|
+
self,
|
|
4174
|
+
*args: ta.Any,
|
|
4175
|
+
json_dumps: ta.Optional[ta.Callable[[ta.Any], str]] = None,
|
|
4176
|
+
**kwargs: ta.Any,
|
|
4177
|
+
) -> None:
|
|
4178
|
+
super().__init__(*args, **kwargs)
|
|
4179
|
+
|
|
4180
|
+
if json_dumps is None:
|
|
4181
|
+
json_dumps = json_dumps_compact
|
|
4182
|
+
self._json_dumps = json_dumps
|
|
4183
|
+
|
|
4184
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
4185
|
+
dct = {
|
|
4186
|
+
k: v
|
|
4187
|
+
for k, o in self.KEYS.items()
|
|
4188
|
+
for v in [getattr(record, k)]
|
|
4189
|
+
if not (o and v is None)
|
|
4190
|
+
}
|
|
4191
|
+
return self._json_dumps(dct)
|
|
4214
4192
|
|
|
4215
4193
|
|
|
4216
4194
|
########################################
|
|
@@ -4405,84 +4383,129 @@ class PyprojectConfigPreparer:
|
|
|
4405
4383
|
|
|
4406
4384
|
|
|
4407
4385
|
########################################
|
|
4408
|
-
#
|
|
4386
|
+
# ../../../omlish/logs/standard.py
|
|
4409
4387
|
"""
|
|
4410
4388
|
TODO:
|
|
4411
|
-
-
|
|
4389
|
+
- structured
|
|
4390
|
+
- prefixed
|
|
4391
|
+
- debug
|
|
4392
|
+
- optional noisy? noisy will never be lite - some kinda configure_standard callback mechanism?
|
|
4412
4393
|
"""
|
|
4413
4394
|
|
|
4414
4395
|
|
|
4415
|
-
|
|
4416
|
-
def __init__(
|
|
4417
|
-
self,
|
|
4418
|
-
venv: ta.Optional[str] = None,
|
|
4419
|
-
) -> None:
|
|
4420
|
-
super().__init__()
|
|
4421
|
-
self._venv = venv
|
|
4396
|
+
##
|
|
4422
4397
|
|
|
4423
|
-
@cached_nullary
|
|
4424
|
-
def _tmp_dir(self) -> str:
|
|
4425
|
-
return tempfile.mkdtemp('-omlish-reqs')
|
|
4426
4398
|
|
|
4427
|
-
|
|
4399
|
+
STANDARD_LOG_FORMAT_PARTS = [
|
|
4400
|
+
('asctime', '%(asctime)-15s'),
|
|
4401
|
+
('process', 'pid=%(process)-6s'),
|
|
4402
|
+
('thread', 'tid=%(thread)x'),
|
|
4403
|
+
('levelname', '%(levelname)s'),
|
|
4404
|
+
('name', '%(name)s'),
|
|
4405
|
+
('separator', '::'),
|
|
4406
|
+
('message', '%(message)s'),
|
|
4407
|
+
]
|
|
4428
4408
|
|
|
4429
|
-
def rewrite_file(self, in_file: str) -> str:
|
|
4430
|
-
with open(in_file) as f:
|
|
4431
|
-
src = f.read()
|
|
4432
4409
|
|
|
4433
|
-
|
|
4434
|
-
|
|
4410
|
+
class StandardLogFormatter(logging.Formatter):
|
|
4411
|
+
@staticmethod
|
|
4412
|
+
def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
|
|
4413
|
+
return ' '.join(v for k, v in parts)
|
|
4435
4414
|
|
|
4436
|
-
|
|
4437
|
-
if self.VENV_MAGIC in l:
|
|
4438
|
-
lp, _, rp = l.partition(self.VENV_MAGIC)
|
|
4439
|
-
rp = rp.partition('#')[0]
|
|
4440
|
-
omit = False
|
|
4441
|
-
for v in rp.split():
|
|
4442
|
-
if v[0] == '!':
|
|
4443
|
-
if self._venv is not None and self._venv == v[1:]:
|
|
4444
|
-
omit = True
|
|
4445
|
-
break
|
|
4446
|
-
else:
|
|
4447
|
-
raise NotImplementedError
|
|
4415
|
+
converter = datetime.datetime.fromtimestamp # type: ignore
|
|
4448
4416
|
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4417
|
+
def formatTime(self, record, datefmt=None):
|
|
4418
|
+
ct = self.converter(record.created) # type: ignore
|
|
4419
|
+
if datefmt:
|
|
4420
|
+
return ct.strftime(datefmt) # noqa
|
|
4421
|
+
else:
|
|
4422
|
+
t = ct.strftime('%Y-%m-%d %H:%M:%S')
|
|
4423
|
+
return '%s.%03d' % (t, record.msecs) # noqa
|
|
4452
4424
|
|
|
4453
|
-
out_req = self.rewrite(l.rstrip('\n'), for_file=True)
|
|
4454
|
-
out_lines.append(out_req + '\n')
|
|
4455
4425
|
|
|
4456
|
-
|
|
4457
|
-
if os.path.exists(out_file):
|
|
4458
|
-
raise Exception(f'file exists: {out_file}')
|
|
4426
|
+
##
|
|
4459
4427
|
|
|
4460
|
-
with open(out_file, 'w') as f:
|
|
4461
|
-
f.write(''.join(out_lines))
|
|
4462
|
-
log.info('Rewrote requirements file %s to %s', in_file, out_file)
|
|
4463
|
-
return out_file
|
|
4464
4428
|
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4429
|
+
class StandardConfiguredLogHandler(ProxyLogHandler):
|
|
4430
|
+
def __init_subclass__(cls, **kwargs):
|
|
4431
|
+
raise TypeError('This class serves only as a marker and should not be subclassed.')
|
|
4432
|
+
|
|
4433
|
+
|
|
4434
|
+
##
|
|
4435
|
+
|
|
4436
|
+
|
|
4437
|
+
@contextlib.contextmanager
|
|
4438
|
+
def _locking_logging_module_lock() -> ta.Iterator[None]:
|
|
4439
|
+
if hasattr(logging, '_acquireLock'):
|
|
4440
|
+
logging._acquireLock() # noqa
|
|
4441
|
+
try:
|
|
4442
|
+
yield
|
|
4443
|
+
finally:
|
|
4444
|
+
logging._releaseLock() # type: ignore # noqa
|
|
4445
|
+
|
|
4446
|
+
elif hasattr(logging, '_lock'):
|
|
4447
|
+
# https://github.com/python/cpython/commit/74723e11109a320e628898817ab449b3dad9ee96
|
|
4448
|
+
with logging._lock: # noqa
|
|
4449
|
+
yield
|
|
4450
|
+
|
|
4451
|
+
else:
|
|
4452
|
+
raise Exception("Can't find lock in logging module")
|
|
4453
|
+
|
|
4454
|
+
|
|
4455
|
+
def configure_standard_logging(
|
|
4456
|
+
level: ta.Union[int, str] = logging.INFO,
|
|
4457
|
+
*,
|
|
4458
|
+
json: bool = False,
|
|
4459
|
+
target: ta.Optional[logging.Logger] = None,
|
|
4460
|
+
force: bool = False,
|
|
4461
|
+
handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
|
|
4462
|
+
) -> ta.Optional[StandardConfiguredLogHandler]:
|
|
4463
|
+
with _locking_logging_module_lock():
|
|
4464
|
+
if target is None:
|
|
4465
|
+
target = logging.root
|
|
4466
|
+
|
|
4467
|
+
#
|
|
4468
|
+
|
|
4469
|
+
if not force:
|
|
4470
|
+
if any(isinstance(h, StandardConfiguredLogHandler) for h in list(target.handlers)):
|
|
4471
|
+
return None
|
|
4472
|
+
|
|
4473
|
+
#
|
|
4474
|
+
|
|
4475
|
+
if handler_factory is not None:
|
|
4476
|
+
handler = handler_factory()
|
|
4477
|
+
else:
|
|
4478
|
+
handler = logging.StreamHandler()
|
|
4479
|
+
|
|
4480
|
+
#
|
|
4481
|
+
|
|
4482
|
+
formatter: logging.Formatter
|
|
4483
|
+
if json:
|
|
4484
|
+
formatter = JsonLogFormatter()
|
|
4485
|
+
else:
|
|
4486
|
+
formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
|
|
4487
|
+
handler.setFormatter(formatter)
|
|
4488
|
+
|
|
4489
|
+
#
|
|
4490
|
+
|
|
4491
|
+
handler.addFilter(TidLogFilter())
|
|
4492
|
+
|
|
4493
|
+
#
|
|
4494
|
+
|
|
4495
|
+
target.addHandler(handler)
|
|
4496
|
+
|
|
4497
|
+
#
|
|
4473
4498
|
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
return ' '.join(['-r ', inc_out_file, rest])
|
|
4477
|
-
else:
|
|
4478
|
-
return '-r' + inc_out_file
|
|
4499
|
+
if level is not None:
|
|
4500
|
+
target.setLevel(level)
|
|
4479
4501
|
|
|
4480
|
-
|
|
4481
|
-
|
|
4502
|
+
#
|
|
4503
|
+
|
|
4504
|
+
return StandardConfiguredLogHandler(handler)
|
|
4482
4505
|
|
|
4483
4506
|
|
|
4484
4507
|
########################################
|
|
4485
|
-
# ../../../omlish/
|
|
4508
|
+
# ../../../omlish/subprocesses.py
|
|
4486
4509
|
|
|
4487
4510
|
|
|
4488
4511
|
##
|
|
@@ -4533,8 +4556,8 @@ def subprocess_close(
|
|
|
4533
4556
|
##
|
|
4534
4557
|
|
|
4535
4558
|
|
|
4536
|
-
class
|
|
4537
|
-
DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] =
|
|
4559
|
+
class BaseSubprocesses(abc.ABC): # noqa
|
|
4560
|
+
DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
|
|
4538
4561
|
|
|
4539
4562
|
def __init__(
|
|
4540
4563
|
self,
|
|
@@ -4547,6 +4570,9 @@ class AbstractSubprocesses(abc.ABC): # noqa
|
|
|
4547
4570
|
self._log = log if log is not None else self.DEFAULT_LOGGER
|
|
4548
4571
|
self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
|
|
4549
4572
|
|
|
4573
|
+
def set_logger(self, log: ta.Optional[logging.Logger]) -> None:
|
|
4574
|
+
self._log = log
|
|
4575
|
+
|
|
4550
4576
|
#
|
|
4551
4577
|
|
|
4552
4578
|
def prepare_args(
|
|
@@ -4658,23 +4684,25 @@ class AbstractSubprocesses(abc.ABC): # noqa
|
|
|
4658
4684
|
##
|
|
4659
4685
|
|
|
4660
4686
|
|
|
4661
|
-
class
|
|
4687
|
+
class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
|
|
4688
|
+
@abc.abstractmethod
|
|
4662
4689
|
def check_call(
|
|
4663
4690
|
self,
|
|
4664
4691
|
*cmd: str,
|
|
4665
4692
|
stdout: ta.Any = sys.stderr,
|
|
4666
4693
|
**kwargs: ta.Any,
|
|
4667
4694
|
) -> None:
|
|
4668
|
-
|
|
4669
|
-
subprocess.check_call(cmd, **kwargs)
|
|
4695
|
+
raise NotImplementedError
|
|
4670
4696
|
|
|
4697
|
+
@abc.abstractmethod
|
|
4671
4698
|
def check_output(
|
|
4672
4699
|
self,
|
|
4673
4700
|
*cmd: str,
|
|
4674
4701
|
**kwargs: ta.Any,
|
|
4675
4702
|
) -> bytes:
|
|
4676
|
-
|
|
4677
|
-
|
|
4703
|
+
raise NotImplementedError
|
|
4704
|
+
|
|
4705
|
+
#
|
|
4678
4706
|
|
|
4679
4707
|
def check_output_str(
|
|
4680
4708
|
self,
|
|
@@ -4716,412 +4744,142 @@ class Subprocesses(AbstractSubprocesses):
|
|
|
4716
4744
|
return ret.decode().strip()
|
|
4717
4745
|
|
|
4718
4746
|
|
|
4719
|
-
|
|
4747
|
+
##
|
|
4720
4748
|
|
|
4721
4749
|
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4750
|
+
class Subprocesses(AbstractSubprocesses):
|
|
4751
|
+
def check_call(
|
|
4752
|
+
self,
|
|
4753
|
+
*cmd: str,
|
|
4754
|
+
stdout: ta.Any = sys.stderr,
|
|
4755
|
+
**kwargs: ta.Any,
|
|
4756
|
+
) -> None:
|
|
4757
|
+
with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
|
|
4758
|
+
subprocess.check_call(cmd, **kwargs)
|
|
4759
|
+
|
|
4760
|
+
def check_output(
|
|
4761
|
+
self,
|
|
4762
|
+
*cmd: str,
|
|
4763
|
+
**kwargs: ta.Any,
|
|
4764
|
+
) -> bytes:
|
|
4765
|
+
with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
|
|
4766
|
+
return subprocess.check_output(cmd, **kwargs)
|
|
4767
|
+
|
|
4768
|
+
|
|
4769
|
+
subprocesses = Subprocesses()
|
|
4730
4770
|
|
|
4731
4771
|
|
|
4732
4772
|
##
|
|
4733
4773
|
|
|
4734
4774
|
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4775
|
+
class AbstractAsyncSubprocesses(BaseSubprocesses):
|
|
4776
|
+
@abc.abstractmethod
|
|
4777
|
+
async def check_call(
|
|
4778
|
+
self,
|
|
4779
|
+
*cmd: str,
|
|
4780
|
+
stdout: ta.Any = sys.stderr,
|
|
4781
|
+
**kwargs: ta.Any,
|
|
4782
|
+
) -> None:
|
|
4783
|
+
raise NotImplementedError
|
|
4784
|
+
|
|
4785
|
+
@abc.abstractmethod
|
|
4786
|
+
async def check_output(
|
|
4787
|
+
self,
|
|
4788
|
+
*cmd: str,
|
|
4789
|
+
**kwargs: ta.Any,
|
|
4790
|
+
) -> bytes:
|
|
4791
|
+
raise NotImplementedError
|
|
4746
4792
|
|
|
4747
|
-
|
|
4748
|
-
raise TypeError(repo_subtrees)
|
|
4793
|
+
#
|
|
4749
4794
|
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4795
|
+
async def check_output_str(
|
|
4796
|
+
self,
|
|
4797
|
+
*cmd: str,
|
|
4798
|
+
**kwargs: ta.Any,
|
|
4799
|
+
) -> str:
|
|
4800
|
+
return (await self.check_output(*cmd, **kwargs)).decode().strip()
|
|
4753
4801
|
|
|
4754
|
-
|
|
4755
|
-
subprocess_maybe_shell_wrap_exec(
|
|
4756
|
-
'git',
|
|
4757
|
-
*git_opts,
|
|
4758
|
-
'clone',
|
|
4759
|
-
'-n',
|
|
4760
|
-
'--depth=1',
|
|
4761
|
-
'--filter=tree:0',
|
|
4762
|
-
*(['-b', branch] if branch else []),
|
|
4763
|
-
'--single-branch',
|
|
4764
|
-
repo_url,
|
|
4765
|
-
repo_dir,
|
|
4766
|
-
),
|
|
4767
|
-
cwd=base_dir,
|
|
4768
|
-
)
|
|
4802
|
+
#
|
|
4769
4803
|
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
),
|
|
4780
|
-
cwd=rd,
|
|
4781
|
-
)
|
|
4804
|
+
async def try_call(
|
|
4805
|
+
self,
|
|
4806
|
+
*cmd: str,
|
|
4807
|
+
**kwargs: ta.Any,
|
|
4808
|
+
) -> bool:
|
|
4809
|
+
if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
|
|
4810
|
+
return False
|
|
4811
|
+
else:
|
|
4812
|
+
return True
|
|
4782
4813
|
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4814
|
+
async def try_output(
|
|
4815
|
+
self,
|
|
4816
|
+
*cmd: str,
|
|
4817
|
+
**kwargs: ta.Any,
|
|
4818
|
+
) -> ta.Optional[bytes]:
|
|
4819
|
+
if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
|
|
4820
|
+
return None
|
|
4821
|
+
else:
|
|
4822
|
+
return ret
|
|
4823
|
+
|
|
4824
|
+
async def try_output_str(
|
|
4825
|
+
self,
|
|
4826
|
+
*cmd: str,
|
|
4827
|
+
**kwargs: ta.Any,
|
|
4828
|
+
) -> ta.Optional[str]:
|
|
4829
|
+
if (ret := await self.try_output(*cmd, **kwargs)) is None:
|
|
4830
|
+
return None
|
|
4831
|
+
else:
|
|
4832
|
+
return ret.decode().strip()
|
|
4833
|
+
|
|
4834
|
+
|
|
4835
|
+
########################################
|
|
4836
|
+
# ../../git/revisions.py
|
|
4792
4837
|
|
|
4793
4838
|
|
|
4794
4839
|
def get_git_revision(
|
|
4795
4840
|
*,
|
|
4796
4841
|
cwd: ta.Optional[str] = None,
|
|
4797
4842
|
) -> ta.Optional[str]:
|
|
4798
|
-
|
|
4843
|
+
subprocesses.check_output('git', '--version')
|
|
4799
4844
|
|
|
4800
4845
|
if cwd is None:
|
|
4801
4846
|
cwd = os.getcwd()
|
|
4802
4847
|
|
|
4803
4848
|
if subprocess.run( # noqa
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4849
|
+
subprocess_maybe_shell_wrap_exec(
|
|
4850
|
+
'git',
|
|
4851
|
+
'rev-parse',
|
|
4852
|
+
'--is-inside-work-tree',
|
|
4853
|
+
),
|
|
4854
|
+
stdout=subprocess.PIPE,
|
|
4855
|
+
stderr=subprocess.PIPE,
|
|
4811
4856
|
).returncode:
|
|
4812
4857
|
return None
|
|
4813
4858
|
|
|
4814
|
-
has_untracked = bool(
|
|
4859
|
+
has_untracked = bool(subprocesses.check_output(
|
|
4815
4860
|
'git',
|
|
4816
4861
|
'ls-files',
|
|
4817
4862
|
'.',
|
|
4818
4863
|
'--exclude-standard',
|
|
4819
4864
|
'--others',
|
|
4820
|
-
|
|
4865
|
+
cwd=cwd,
|
|
4866
|
+
).decode().strip())
|
|
4821
4867
|
|
|
4822
|
-
dirty_rev =
|
|
4868
|
+
dirty_rev = subprocesses.check_output(
|
|
4823
4869
|
'git',
|
|
4824
4870
|
'describe',
|
|
4825
4871
|
'--match=NeVeRmAtCh',
|
|
4826
4872
|
'--always',
|
|
4827
4873
|
'--abbrev=40',
|
|
4828
4874
|
'--dirty',
|
|
4829
|
-
), cwd=cwd).decode().strip()
|
|
4830
|
-
|
|
4831
|
-
return dirty_rev + ('-untracked' if has_untracked else '')
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
##
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
_GIT_STATUS_LINE_ESCAPE_CODES: ta.Mapping[str, str] = {
|
|
4838
|
-
'\\': '\\',
|
|
4839
|
-
'"': '"',
|
|
4840
|
-
'n': '\n',
|
|
4841
|
-
't': '\t',
|
|
4842
|
-
}
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
def yield_git_status_line_fields(l: str) -> ta.Iterator[str]:
|
|
4846
|
-
def find_any(chars: str, start: int = 0) -> int:
|
|
4847
|
-
ret = -1
|
|
4848
|
-
for c in chars:
|
|
4849
|
-
if (found := l.find(c, start)) >= 0 and (ret < 0 or ret > found):
|
|
4850
|
-
ret = found
|
|
4851
|
-
return ret
|
|
4852
|
-
|
|
4853
|
-
p = 0
|
|
4854
|
-
while True:
|
|
4855
|
-
if l[p] == '"':
|
|
4856
|
-
p += 1
|
|
4857
|
-
s = []
|
|
4858
|
-
while (n := find_any('\\"', p)) > 0:
|
|
4859
|
-
if (c := l[n]) == '\\':
|
|
4860
|
-
s.append(l[p:n])
|
|
4861
|
-
s.append(_GIT_STATUS_LINE_ESCAPE_CODES[l[n + 1]])
|
|
4862
|
-
p = n + 2
|
|
4863
|
-
elif c == '"':
|
|
4864
|
-
s.append(l[p:n])
|
|
4865
|
-
p = n
|
|
4866
|
-
break
|
|
4867
|
-
else:
|
|
4868
|
-
raise ValueError(l)
|
|
4869
|
-
|
|
4870
|
-
if l[p] != '"':
|
|
4871
|
-
raise ValueError(l)
|
|
4872
|
-
|
|
4873
|
-
yield ''.join(s)
|
|
4874
|
-
|
|
4875
|
-
p += 1
|
|
4876
|
-
if p == len(l):
|
|
4877
|
-
return
|
|
4878
|
-
elif l[p] != ' ':
|
|
4879
|
-
raise ValueError(l)
|
|
4880
|
-
|
|
4881
|
-
p += 1
|
|
4882
|
-
|
|
4883
|
-
else:
|
|
4884
|
-
if (e := l.find(' ', p)) < 0:
|
|
4885
|
-
yield l[p:]
|
|
4886
|
-
return
|
|
4887
|
-
|
|
4888
|
-
yield l[p:e]
|
|
4889
|
-
p = e + 1
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
"""
|
|
4893
|
-
When merge is occurring and was successful, or outside of a merge situation, X shows the status of the index and Y shows
|
|
4894
|
-
the status of the working tree:
|
|
4895
|
-
-------------------------------------------------
|
|
4896
|
-
X Y Meaning
|
|
4897
|
-
-------------------------------------------------
|
|
4898
|
-
[AMD] not updated
|
|
4899
|
-
M [ MTD] updated in index
|
|
4900
|
-
T [ MTD] type changed in index
|
|
4901
|
-
A [ MTD] added to index
|
|
4902
|
-
D deleted from index
|
|
4903
|
-
R [ MTD] renamed in index
|
|
4904
|
-
C [ MTD] copied in index
|
|
4905
|
-
[MTARC] index and work tree matches
|
|
4906
|
-
[ MTARC] M work tree changed since index
|
|
4907
|
-
[ MTARC] T type changed in work tree since index
|
|
4908
|
-
[ MTARC] D deleted in work tree
|
|
4909
|
-
R renamed in work tree
|
|
4910
|
-
C copied in work tree
|
|
4911
|
-
|
|
4912
|
-
When merge conflict has occurred and has not yet been resolved, X and Y show the state introduced by each head of the
|
|
4913
|
-
merge, relative to the common ancestor:
|
|
4914
|
-
-------------------------------------------------
|
|
4915
|
-
X Y Meaning
|
|
4916
|
-
-------------------------------------------------
|
|
4917
|
-
D D unmerged, both deleted
|
|
4918
|
-
A U unmerged, added by us
|
|
4919
|
-
U D unmerged, deleted by them
|
|
4920
|
-
U A unmerged, added by them
|
|
4921
|
-
D U unmerged, deleted by us
|
|
4922
|
-
A A unmerged, both added
|
|
4923
|
-
U U unmerged, both modified
|
|
4924
|
-
|
|
4925
|
-
When path is untracked, X and Y are always the same, since they are unknown to the index:
|
|
4926
|
-
-------------------------------------------------
|
|
4927
|
-
X Y Meaning
|
|
4928
|
-
-------------------------------------------------
|
|
4929
|
-
? ? untracked
|
|
4930
|
-
! ! ignored
|
|
4931
|
-
|
|
4932
|
-
Submodules have more state and instead report
|
|
4933
|
-
|
|
4934
|
-
- M = the submodule has a different HEAD than recorded in the index
|
|
4935
|
-
- m = the submodule has modified content
|
|
4936
|
-
- ? = the submodule has untracked files
|
|
4937
|
-
|
|
4938
|
-
This is since modified content or untracked files in a submodule cannot be added via git add in the superproject to
|
|
4939
|
-
prepare a commit. m and ? are applied recursively. For example if a nested submodule in a submodule contains an
|
|
4940
|
-
untracked file, this is reported as ? as well.
|
|
4941
|
-
""" # noqa
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
class GitStatusState(enum.Enum):
|
|
4945
|
-
UNMODIFIED = ' '
|
|
4946
|
-
MODIFIED = 'M'
|
|
4947
|
-
FILE_TYPE_CHANGED = 'T'
|
|
4948
|
-
ADDED = 'A'
|
|
4949
|
-
DELETED = 'D'
|
|
4950
|
-
RENAMED = 'R'
|
|
4951
|
-
COPIED = 'C'
|
|
4952
|
-
UPDATED_BUT_UNMERGED = 'U'
|
|
4953
|
-
UNTRACKED = '?'
|
|
4954
|
-
IGNORED = '!'
|
|
4955
|
-
SUBMODULE_MODIFIED_CONTENT = 'm'
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
_UNMERGED_GIT_STATUS_STATES: ta.FrozenSet[GitStatusState] = frozenset([
|
|
4959
|
-
GitStatusState.UPDATED_BUT_UNMERGED,
|
|
4960
|
-
])
|
|
4961
|
-
|
|
4962
|
-
_UNMERGED_GIT_STATUS_STATE_PAIRS: ta.FrozenSet[ta.Tuple[GitStatusState, GitStatusState]] = frozenset([
|
|
4963
|
-
(GitStatusState.ADDED, GitStatusState.ADDED),
|
|
4964
|
-
(GitStatusState.DELETED, GitStatusState.DELETED),
|
|
4965
|
-
])
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
@dc.dataclass(frozen=True)
|
|
4969
|
-
class GitStatusItem:
|
|
4970
|
-
x: GitStatusState
|
|
4971
|
-
y: GitStatusState
|
|
4972
|
-
|
|
4973
|
-
a: str
|
|
4974
|
-
b: ta.Optional[str]
|
|
4975
|
-
|
|
4976
|
-
@property
|
|
4977
|
-
def is_unmerged(self) -> bool:
|
|
4978
|
-
return (
|
|
4979
|
-
self.x in _UNMERGED_GIT_STATUS_STATE_PAIRS or
|
|
4980
|
-
self.y in _UNMERGED_GIT_STATUS_STATE_PAIRS or
|
|
4981
|
-
(self.x, self.y) in _UNMERGED_GIT_STATUS_STATE_PAIRS
|
|
4982
|
-
)
|
|
4983
|
-
|
|
4984
|
-
def __repr__(self) -> str:
|
|
4985
|
-
return (
|
|
4986
|
-
f'{self.__class__.__name__}('
|
|
4987
|
-
f'x={self.x.name}, '
|
|
4988
|
-
f'y={self.y.name}, '
|
|
4989
|
-
f'a={self.a!r}' +
|
|
4990
|
-
(f', b={self.b!r}' if self.b is not None else '') +
|
|
4991
|
-
')'
|
|
4992
|
-
)
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
def parse_git_status_line(l: str) -> GitStatusItem:
|
|
4996
|
-
if len(l) < 3 or l[2] != ' ':
|
|
4997
|
-
raise ValueError(l)
|
|
4998
|
-
x, y = l[0], l[1]
|
|
4999
|
-
|
|
5000
|
-
fields = list(yield_git_status_line_fields(l[3:]))
|
|
5001
|
-
if len(fields) == 1:
|
|
5002
|
-
a, b = fields[0], None
|
|
5003
|
-
elif len(fields) == 3:
|
|
5004
|
-
check.state(fields[1] == '->', l)
|
|
5005
|
-
a, b = fields[0], fields[2]
|
|
5006
|
-
else:
|
|
5007
|
-
raise ValueError(l)
|
|
5008
|
-
|
|
5009
|
-
return GitStatusItem(
|
|
5010
|
-
GitStatusState(x),
|
|
5011
|
-
GitStatusState(y),
|
|
5012
|
-
a,
|
|
5013
|
-
b,
|
|
5014
|
-
)
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
class GitStatus(ta.Sequence[GitStatusItem]):
|
|
5018
|
-
def __init__(self, lines: ta.Iterable[GitStatusItem]) -> None:
|
|
5019
|
-
super().__init__()
|
|
5020
|
-
|
|
5021
|
-
self._lst = list(lines)
|
|
5022
|
-
|
|
5023
|
-
by_x: ta.Dict[GitStatusState, list[GitStatusItem]] = {}
|
|
5024
|
-
by_y: ta.Dict[GitStatusState, list[GitStatusItem]] = {}
|
|
5025
|
-
|
|
5026
|
-
by_a: ta.Dict[str, GitStatusItem] = {}
|
|
5027
|
-
by_b: ta.Dict[str, GitStatusItem] = {}
|
|
5028
|
-
|
|
5029
|
-
for l in self._lst:
|
|
5030
|
-
by_x.setdefault(l.x, []).append(l)
|
|
5031
|
-
by_y.setdefault(l.y, []).append(l)
|
|
5032
|
-
|
|
5033
|
-
if l.a in by_a:
|
|
5034
|
-
raise KeyError(l.a)
|
|
5035
|
-
by_a[l.a] = l
|
|
5036
|
-
|
|
5037
|
-
if l.b is not None:
|
|
5038
|
-
if l.b in by_b:
|
|
5039
|
-
raise KeyError(l.b)
|
|
5040
|
-
by_b[l.b] = l
|
|
5041
|
-
|
|
5042
|
-
self._by_x = by_x
|
|
5043
|
-
self._by_y = by_y
|
|
5044
|
-
|
|
5045
|
-
self._by_a = by_a
|
|
5046
|
-
self._by_b = by_b
|
|
5047
|
-
|
|
5048
|
-
self._has_unmerged = any(l.is_unmerged for l in self)
|
|
5049
|
-
|
|
5050
|
-
#
|
|
5051
|
-
|
|
5052
|
-
def __iter__(self) -> ta.Iterator[GitStatusItem]:
|
|
5053
|
-
return iter(self._lst)
|
|
5054
|
-
|
|
5055
|
-
def __getitem__(self, index):
|
|
5056
|
-
return self._lst[index]
|
|
5057
|
-
|
|
5058
|
-
def __len__(self) -> int:
|
|
5059
|
-
return len(self._lst)
|
|
5060
|
-
|
|
5061
|
-
#
|
|
5062
|
-
|
|
5063
|
-
@property
|
|
5064
|
-
def by_x(self) -> ta.Mapping[GitStatusState, ta.Sequence[GitStatusItem]]:
|
|
5065
|
-
return self._by_x
|
|
5066
|
-
|
|
5067
|
-
@property
|
|
5068
|
-
def by_y(self) -> ta.Mapping[GitStatusState, ta.Sequence[GitStatusItem]]:
|
|
5069
|
-
return self._by_y
|
|
5070
|
-
|
|
5071
|
-
@property
|
|
5072
|
-
def by_a(self) -> ta.Mapping[str, GitStatusItem]:
|
|
5073
|
-
return self._by_a
|
|
5074
|
-
|
|
5075
|
-
@property
|
|
5076
|
-
def by_b(self) -> ta.Mapping[str, GitStatusItem]:
|
|
5077
|
-
return self._by_b
|
|
5078
|
-
|
|
5079
|
-
#
|
|
5080
|
-
|
|
5081
|
-
@property
|
|
5082
|
-
def has_unmerged(self) -> bool:
|
|
5083
|
-
return self._has_unmerged
|
|
5084
|
-
|
|
5085
|
-
@property
|
|
5086
|
-
def has_staged(self) -> bool:
|
|
5087
|
-
return any(l.x != GitStatusState.UNMODIFIED for l in self._lst)
|
|
5088
|
-
|
|
5089
|
-
@property
|
|
5090
|
-
def has_dirty(self) -> bool:
|
|
5091
|
-
return any(l.y != GitStatusState.UNMODIFIED for l in self._lst)
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
def parse_git_status(s: str) -> GitStatus:
|
|
5095
|
-
return GitStatus(parse_git_status_line(l) for l in s.splitlines())
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
def get_git_status(
|
|
5099
|
-
*,
|
|
5100
|
-
cwd: ta.Optional[str] = None,
|
|
5101
|
-
ignore_submodules: bool = False,
|
|
5102
|
-
verbose: bool = False,
|
|
5103
|
-
) -> GitStatus:
|
|
5104
|
-
if cwd is None:
|
|
5105
|
-
cwd = os.getcwd()
|
|
5106
|
-
|
|
5107
|
-
proc = subprocess.run( # type: ignore
|
|
5108
|
-
subprocess_maybe_shell_wrap_exec(
|
|
5109
|
-
'git',
|
|
5110
|
-
'status',
|
|
5111
|
-
'--porcelain=v1',
|
|
5112
|
-
*(['--ignore-submodules'] if ignore_submodules else []),
|
|
5113
|
-
),
|
|
5114
4875
|
cwd=cwd,
|
|
5115
|
-
|
|
5116
|
-
**(dict(stderr=subprocess.PIPE) if not verbose else {}),
|
|
5117
|
-
check=True,
|
|
5118
|
-
)
|
|
4876
|
+
).decode().strip()
|
|
5119
4877
|
|
|
5120
|
-
return
|
|
4878
|
+
return dirty_rev + ('-untracked' if has_untracked else '')
|
|
5121
4879
|
|
|
5122
4880
|
|
|
5123
4881
|
########################################
|
|
5124
|
-
# ../../../omlish/
|
|
4882
|
+
# ../../../omlish/asyncs/asyncio/subprocesses.py
|
|
5125
4883
|
|
|
5126
4884
|
|
|
5127
4885
|
##
|
|
@@ -5132,6 +4890,8 @@ class AsyncioProcessCommunicator:
|
|
|
5132
4890
|
self,
|
|
5133
4891
|
proc: asyncio.subprocess.Process,
|
|
5134
4892
|
loop: ta.Optional[ta.Any] = None,
|
|
4893
|
+
*,
|
|
4894
|
+
log: ta.Optional[logging.Logger] = None,
|
|
5135
4895
|
) -> None:
|
|
5136
4896
|
super().__init__()
|
|
5137
4897
|
|
|
@@ -5140,6 +4900,7 @@ class AsyncioProcessCommunicator:
|
|
|
5140
4900
|
|
|
5141
4901
|
self._proc = proc
|
|
5142
4902
|
self._loop = loop
|
|
4903
|
+
self._log = log
|
|
5143
4904
|
|
|
5144
4905
|
self._transport: asyncio.base_subprocess.BaseSubprocessTransport = check.isinstance(
|
|
5145
4906
|
proc._transport, # type: ignore # noqa
|
|
@@ -5155,19 +4916,19 @@ class AsyncioProcessCommunicator:
|
|
|
5155
4916
|
try:
|
|
5156
4917
|
if input is not None:
|
|
5157
4918
|
stdin.write(input)
|
|
5158
|
-
if self._debug:
|
|
5159
|
-
|
|
4919
|
+
if self._debug and self._log is not None:
|
|
4920
|
+
self._log.debug('%r communicate: feed stdin (%s bytes)', self, len(input))
|
|
5160
4921
|
|
|
5161
4922
|
await stdin.drain()
|
|
5162
4923
|
|
|
5163
4924
|
except (BrokenPipeError, ConnectionResetError) as exc:
|
|
5164
4925
|
# communicate() ignores BrokenPipeError and ConnectionResetError. write() and drain() can raise these
|
|
5165
4926
|
# exceptions.
|
|
5166
|
-
if self._debug:
|
|
5167
|
-
|
|
4927
|
+
if self._debug and self._log is not None:
|
|
4928
|
+
self._log.debug('%r communicate: stdin got %r', self, exc)
|
|
5168
4929
|
|
|
5169
|
-
if self._debug:
|
|
5170
|
-
|
|
4930
|
+
if self._debug and self._log is not None:
|
|
4931
|
+
self._log.debug('%r communicate: close stdin', self)
|
|
5171
4932
|
|
|
5172
4933
|
stdin.close()
|
|
5173
4934
|
|
|
@@ -5183,15 +4944,15 @@ class AsyncioProcessCommunicator:
|
|
|
5183
4944
|
check.equal(fd, 1)
|
|
5184
4945
|
stream = check.not_none(self._proc.stdout)
|
|
5185
4946
|
|
|
5186
|
-
if self._debug:
|
|
4947
|
+
if self._debug and self._log is not None:
|
|
5187
4948
|
name = 'stdout' if fd == 1 else 'stderr'
|
|
5188
|
-
|
|
4949
|
+
self._log.debug('%r communicate: read %s', self, name)
|
|
5189
4950
|
|
|
5190
4951
|
output = await stream.read()
|
|
5191
4952
|
|
|
5192
|
-
if self._debug:
|
|
4953
|
+
if self._debug and self._log is not None:
|
|
5193
4954
|
name = 'stdout' if fd == 1 else 'stderr'
|
|
5194
|
-
|
|
4955
|
+
self._log.debug('%r communicate: close %s', self, name)
|
|
5195
4956
|
|
|
5196
4957
|
transport.close()
|
|
5197
4958
|
|
|
@@ -5240,7 +5001,7 @@ class AsyncioProcessCommunicator:
|
|
|
5240
5001
|
##
|
|
5241
5002
|
|
|
5242
5003
|
|
|
5243
|
-
class AsyncioSubprocesses(
|
|
5004
|
+
class AsyncioSubprocesses(AbstractAsyncSubprocesses):
|
|
5244
5005
|
async def communicate(
|
|
5245
5006
|
self,
|
|
5246
5007
|
proc: asyncio.subprocess.Process,
|
|
@@ -5337,45 +5098,6 @@ class AsyncioSubprocesses(AbstractSubprocesses):
|
|
|
5337
5098
|
with self.prepare_and_wrap(*cmd, stdout=subprocess.PIPE, check=True, **kwargs) as (cmd, kwargs): # noqa
|
|
5338
5099
|
return check.not_none((await self.run(*cmd, **kwargs)).stdout)
|
|
5339
5100
|
|
|
5340
|
-
async def check_output_str(
|
|
5341
|
-
self,
|
|
5342
|
-
*cmd: str,
|
|
5343
|
-
**kwargs: ta.Any,
|
|
5344
|
-
) -> str:
|
|
5345
|
-
return (await self.check_output(*cmd, **kwargs)).decode().strip()
|
|
5346
|
-
|
|
5347
|
-
#
|
|
5348
|
-
|
|
5349
|
-
async def try_call(
|
|
5350
|
-
self,
|
|
5351
|
-
*cmd: str,
|
|
5352
|
-
**kwargs: ta.Any,
|
|
5353
|
-
) -> bool:
|
|
5354
|
-
if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
|
|
5355
|
-
return False
|
|
5356
|
-
else:
|
|
5357
|
-
return True
|
|
5358
|
-
|
|
5359
|
-
async def try_output(
|
|
5360
|
-
self,
|
|
5361
|
-
*cmd: str,
|
|
5362
|
-
**kwargs: ta.Any,
|
|
5363
|
-
) -> ta.Optional[bytes]:
|
|
5364
|
-
if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
|
|
5365
|
-
return None
|
|
5366
|
-
else:
|
|
5367
|
-
return ret
|
|
5368
|
-
|
|
5369
|
-
async def try_output_str(
|
|
5370
|
-
self,
|
|
5371
|
-
*cmd: str,
|
|
5372
|
-
**kwargs: ta.Any,
|
|
5373
|
-
) -> ta.Optional[str]:
|
|
5374
|
-
if (ret := await self.try_output(*cmd, **kwargs)) is None:
|
|
5375
|
-
return None
|
|
5376
|
-
else:
|
|
5377
|
-
return ret.decode().strip()
|
|
5378
|
-
|
|
5379
5101
|
|
|
5380
5102
|
asyncio_subprocesses = AsyncioSubprocesses()
|
|
5381
5103
|
|
|
@@ -7215,7 +6937,7 @@ class PyprojectCli(ArgparseCli):
|
|
|
7215
6937
|
|
|
7216
6938
|
|
|
7217
6939
|
async def _async_main(argv: ta.Optional[ta.Sequence[str]] = None) -> None:
|
|
7218
|
-
|
|
6940
|
+
check_lite_runtime_version()
|
|
7219
6941
|
configure_standard_logging()
|
|
7220
6942
|
|
|
7221
6943
|
await PyprojectCli(argv).async_cli_run()
|