ominfra 0.0.0.dev223__py3-none-any.whl → 0.0.0.dev225__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.
@@ -42,7 +42,7 @@ from omlish.lite.check import check
42
42
  from omlish.lite.contextmanagers import ExitStacked
43
43
  from omlish.lite.logs import log
44
44
  from omlish.lite.runtime import is_debugger_attached
45
- from omlish.os.pidfile import Pidfile
45
+ from omlish.os.pidfiles.pidfile import Pidfile
46
46
 
47
47
  from ....journald.messages import JournalctlMessage # noqa
48
48
  from ....journald.tailer import JournalctlTailerWorker
@@ -410,8 +410,8 @@ import typing as ta
410
410
  from omlish.lite.cached import cached_nullary
411
411
  from omlish.lite.check import check
412
412
  from omlish.lite.logs import log
413
- from omlish.subprocesses import subprocess_close
414
- from omlish.subprocesses import subprocess_shell_wrap_exec
413
+ from omlish.subprocesses.utils import subprocess_close
414
+ from omlish.subprocesses.wrap import subprocess_shell_wrap_exec
415
415
 
416
416
  from ..threadworkers import ThreadWorker
417
417
  from .messages import JournalctlMessage # noqa
@@ -8,9 +8,9 @@ import typing as ta
8
8
 
9
9
  from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
10
10
  from omlish.lite.check import check
11
- from omlish.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
12
- from omlish.subprocesses import SubprocessChannelOption
13
- from omlish.subprocesses import subprocess_maybe_shell_wrap_exec
11
+ from omlish.subprocesses.base import SUBPROCESS_CHANNEL_OPTION_VALUES
12
+ from omlish.subprocesses.base import SubprocessChannelOption
13
+ from omlish.subprocesses.wrap import subprocess_maybe_shell_wrap_exec
14
14
 
15
15
  from .base import Command
16
16
  from .base import CommandExecutor
@@ -10,8 +10,8 @@ import typing as ta
10
10
  from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
11
11
  from omlish.lite.check import check
12
12
  from omlish.shlex import shlex_maybe_quote
13
- from omlish.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
14
- from omlish.subprocesses import SubprocessChannelOption
13
+ from omlish.subprocesses.base import SUBPROCESS_CHANNEL_OPTION_VALUES
14
+ from omlish.subprocesses.base import SubprocessChannelOption
15
15
 
16
16
 
17
17
  ##
@@ -86,9 +86,6 @@ AsyncExitStackedT = ta.TypeVar('AsyncExitStackedT', bound='AsyncExitStacked')
86
86
  # ../../../threadworkers.py
87
87
  ThreadWorkerT = ta.TypeVar('ThreadWorkerT', bound='ThreadWorker')
88
88
 
89
- # ../../../../omlish/subprocesses.py
90
- SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
91
-
92
89
 
93
90
  ########################################
94
91
  # ../../../../../omlish/configs/types.py
@@ -1945,42 +1942,107 @@ class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
1945
1942
 
1946
1943
 
1947
1944
  ########################################
1948
- # ../../../../../omlish/os/pidfile.py
1945
+ # ../../../../../omlish/os/pidfiles/pidfile.py
1946
+ """
1947
+ TODO:
1948
+ - reliable pid retrieval
1949
+ - contents are *ignored*, just advisory
1950
+ - check double-check:
1951
+ - 1) get pid of flock holder
1952
+ - 2) get pidfd to that
1953
+ - 3) recheck current pid of flock holder == that pid
1954
+ - racy as to if it's a different actual process as initial check, just with same pid, but due to 'identity' / semantic
1955
+ meaning of the named pidfile the processes are considered equivalent
1956
+ """
1957
+
1958
+
1959
+ ##
1949
1960
 
1950
1961
 
1951
1962
  class Pidfile:
1952
- def __init__(self, path: str) -> None:
1963
+ def __init__(
1964
+ self,
1965
+ path: str,
1966
+ *,
1967
+ inheritable: bool = True,
1968
+ ) -> None:
1953
1969
  super().__init__()
1954
- self._path = path
1955
1970
 
1956
- _f: ta.TextIO
1971
+ self._path = path
1972
+ self._inheritable = inheritable
1957
1973
 
1958
1974
  def __repr__(self) -> str:
1959
1975
  return f'{self.__class__.__name__}({self._path!r})'
1960
1976
 
1977
+ #
1978
+
1979
+ _f: ta.TextIO
1980
+
1981
+ def fileno(self) -> ta.Optional[int]:
1982
+ if hasattr(self, '_f'):
1983
+ return self._f.fileno()
1984
+ else:
1985
+ return None
1986
+
1987
+ #
1988
+
1961
1989
  def __enter__(self) -> 'Pidfile':
1962
1990
  fd = os.open(self._path, os.O_RDWR | os.O_CREAT, 0o600)
1991
+
1963
1992
  try:
1964
- os.set_inheritable(fd, True)
1993
+ if self._inheritable:
1994
+ os.set_inheritable(fd, True)
1995
+
1965
1996
  f = os.fdopen(fd, 'r+')
1997
+
1966
1998
  except Exception:
1967
1999
  try:
1968
2000
  os.close(fd)
1969
2001
  except Exception: # noqa
1970
2002
  pass
1971
2003
  raise
2004
+
1972
2005
  self._f = f
1973
2006
  return self
1974
2007
 
1975
2008
  def __exit__(self, exc_type, exc_val, exc_tb):
1976
- if hasattr(self, '_f'):
1977
- self._f.close()
1978
- del self._f
2009
+ self.close()
2010
+
2011
+ #
2012
+
2013
+ def __getstate__(self):
2014
+ state = self.__dict__.copy()
2015
+
2016
+ if '_f' in state:
2017
+ if os.get_inheritable(fd := state.pop('_f').fileno()):
2018
+ state['__fd'] = fd
2019
+
2020
+ return state
2021
+
2022
+ def __setstate__(self, state):
2023
+ if '_f' in state:
2024
+ raise RuntimeError
2025
+
2026
+ if '__fd' in state:
2027
+ state['_f'] = os.fdopen(state.pop('__fd'), 'r+')
2028
+
2029
+ self.__dict__.update(state)
2030
+
2031
+ #
2032
+
2033
+ def close(self) -> bool:
2034
+ if not hasattr(self, '_f'):
2035
+ return False
2036
+
2037
+ self._f.close()
2038
+ del self._f
2039
+ return True
1979
2040
 
1980
2041
  def try_lock(self) -> bool:
1981
2042
  try:
1982
2043
  fcntl.flock(self._f, fcntl.LOCK_EX | fcntl.LOCK_NB)
1983
2044
  return True
2045
+
1984
2046
  except OSError:
1985
2047
  return False
1986
2048
 
@@ -1988,27 +2050,57 @@ class Pidfile:
1988
2050
  if not self.try_lock():
1989
2051
  raise RuntimeError('Could not get lock')
1990
2052
 
2053
+ #
2054
+
1991
2055
  def write(self, pid: ta.Optional[int] = None) -> None:
1992
2056
  self.ensure_locked()
2057
+
1993
2058
  if pid is None:
1994
2059
  pid = os.getpid()
2060
+
2061
+ self._f.seek(0)
2062
+ self._f.truncate()
1995
2063
  self._f.write(f'{pid}\n')
1996
2064
  self._f.flush()
1997
2065
 
1998
2066
  def clear(self) -> None:
1999
2067
  self.ensure_locked()
2068
+
2000
2069
  self._f.seek(0)
2001
2070
  self._f.truncate()
2002
2071
 
2003
2072
  def read(self) -> int:
2004
2073
  if self.try_lock():
2005
2074
  raise RuntimeError('Got lock')
2075
+
2006
2076
  self._f.seek(0)
2007
- return int(self._f.read())
2077
+ return int(self._f.read()) # FIXME: could be empty or hold old value, race w proc start
2008
2078
 
2009
2079
  def kill(self, sig: int = signal.SIGTERM) -> None:
2010
2080
  pid = self.read()
2011
- os.kill(pid, sig) # FIXME: Still racy
2081
+ os.kill(pid, sig) # FIXME: Still racy - pidfd_send_signal?
2082
+
2083
+
2084
+ ########################################
2085
+ # ../../../../../omlish/subprocesses/utils.py
2086
+
2087
+
2088
+ ##
2089
+
2090
+
2091
+ def subprocess_close(
2092
+ proc: subprocess.Popen,
2093
+ timeout: ta.Optional[float] = None,
2094
+ ) -> None:
2095
+ # TODO: terminate, sleep, kill
2096
+ if proc.stdout:
2097
+ proc.stdout.close()
2098
+ if proc.stderr:
2099
+ proc.stderr.close()
2100
+ if proc.stdin:
2101
+ proc.stdin.close()
2102
+
2103
+ proc.wait(timeout)
2012
2104
 
2013
2105
 
2014
2106
  ########################################
@@ -4245,23 +4337,7 @@ def configure_standard_logging(
4245
4337
 
4246
4338
 
4247
4339
  ########################################
4248
- # ../../../../../omlish/subprocesses.py
4249
-
4250
-
4251
- ##
4252
-
4253
-
4254
- # Valid channel type kwarg values:
4255
- # - A special flag negative int
4256
- # - A positive fd int
4257
- # - A file-like object
4258
- # - None
4259
-
4260
- SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
4261
- 'pipe': subprocess.PIPE,
4262
- 'stdout': subprocess.STDOUT,
4263
- 'devnull': subprocess.DEVNULL,
4264
- }
4340
+ # ../../../../../omlish/subprocesses/wrap.py
4265
4341
 
4266
4342
 
4267
4343
  ##
@@ -4281,446 +4357,6 @@ def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
4281
4357
  return cmd
4282
4358
 
4283
4359
 
4284
- ##
4285
-
4286
-
4287
- def subprocess_close(
4288
- proc: subprocess.Popen,
4289
- timeout: ta.Optional[float] = None,
4290
- ) -> None:
4291
- # TODO: terminate, sleep, kill
4292
- if proc.stdout:
4293
- proc.stdout.close()
4294
- if proc.stderr:
4295
- proc.stderr.close()
4296
- if proc.stdin:
4297
- proc.stdin.close()
4298
-
4299
- proc.wait(timeout)
4300
-
4301
-
4302
- ##
4303
-
4304
-
4305
- class VerboseCalledProcessError(subprocess.CalledProcessError):
4306
- @classmethod
4307
- def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
4308
- return cls(
4309
- e.returncode,
4310
- e.cmd,
4311
- output=e.output,
4312
- stderr=e.stderr,
4313
- )
4314
-
4315
- def __str__(self) -> str:
4316
- msg = super().__str__()
4317
- if self.output is not None:
4318
- msg += f' Output: {self.output!r}'
4319
- if self.stderr is not None:
4320
- msg += f' Stderr: {self.stderr!r}'
4321
- return msg
4322
-
4323
-
4324
- class BaseSubprocesses(abc.ABC): # noqa
4325
- DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
4326
-
4327
- def __init__(
4328
- self,
4329
- *,
4330
- log: ta.Optional[logging.Logger] = None,
4331
- try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
4332
- ) -> None:
4333
- super().__init__()
4334
-
4335
- self._log = log if log is not None else self.DEFAULT_LOGGER
4336
- self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
4337
-
4338
- def set_logger(self, log: ta.Optional[logging.Logger]) -> None:
4339
- self._log = log
4340
-
4341
- #
4342
-
4343
- def prepare_args(
4344
- self,
4345
- *cmd: str,
4346
- env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
4347
- extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
4348
- quiet: bool = False,
4349
- shell: bool = False,
4350
- **kwargs: ta.Any,
4351
- ) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
4352
- if self._log:
4353
- self._log.debug('Subprocesses.prepare_args: cmd=%r', cmd)
4354
- if extra_env:
4355
- self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
4356
-
4357
- #
4358
-
4359
- if extra_env:
4360
- env = {**(env if env is not None else os.environ), **extra_env}
4361
-
4362
- #
4363
-
4364
- if quiet and 'stderr' not in kwargs:
4365
- if self._log and not self._log.isEnabledFor(logging.DEBUG):
4366
- kwargs['stderr'] = subprocess.DEVNULL
4367
-
4368
- for chk in ('stdout', 'stderr'):
4369
- try:
4370
- chv = kwargs[chk]
4371
- except KeyError:
4372
- continue
4373
- kwargs[chk] = SUBPROCESS_CHANNEL_OPTION_VALUES.get(chv, chv)
4374
-
4375
- #
4376
-
4377
- if not shell:
4378
- cmd = subprocess_maybe_shell_wrap_exec(*cmd)
4379
-
4380
- #
4381
-
4382
- return cmd, dict(
4383
- env=env,
4384
- shell=shell,
4385
- **kwargs,
4386
- )
4387
-
4388
- @contextlib.contextmanager
4389
- def wrap_call(
4390
- self,
4391
- *cmd: ta.Any,
4392
- raise_verbose: bool = False,
4393
- **kwargs: ta.Any,
4394
- ) -> ta.Iterator[None]:
4395
- start_time = time.time()
4396
- try:
4397
- if self._log:
4398
- self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
4399
-
4400
- yield
4401
-
4402
- except Exception as exc: # noqa
4403
- if self._log:
4404
- self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
4405
-
4406
- if (
4407
- raise_verbose and
4408
- isinstance(exc, subprocess.CalledProcessError) and
4409
- not isinstance(exc, VerboseCalledProcessError) and
4410
- (exc.output is not None or exc.stderr is not None)
4411
- ):
4412
- raise VerboseCalledProcessError.from_std(exc) from exc
4413
-
4414
- raise
4415
-
4416
- finally:
4417
- end_time = time.time()
4418
- elapsed_s = end_time - start_time
4419
-
4420
- if self._log:
4421
- self._log.debug('Subprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
4422
-
4423
- @contextlib.contextmanager
4424
- def prepare_and_wrap(
4425
- self,
4426
- *cmd: ta.Any,
4427
- raise_verbose: bool = False,
4428
- **kwargs: ta.Any,
4429
- ) -> ta.Iterator[ta.Tuple[
4430
- ta.Tuple[ta.Any, ...],
4431
- ta.Dict[str, ta.Any],
4432
- ]]:
4433
- cmd, kwargs = self.prepare_args(*cmd, **kwargs)
4434
-
4435
- with self.wrap_call(
4436
- *cmd,
4437
- raise_verbose=raise_verbose,
4438
- **kwargs,
4439
- ):
4440
- yield cmd, kwargs
4441
-
4442
- #
4443
-
4444
- DEFAULT_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
4445
- FileNotFoundError,
4446
- subprocess.CalledProcessError,
4447
- )
4448
-
4449
- def try_fn(
4450
- self,
4451
- fn: ta.Callable[..., T],
4452
- *cmd: str,
4453
- try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
4454
- **kwargs: ta.Any,
4455
- ) -> ta.Union[T, Exception]:
4456
- if try_exceptions is None:
4457
- try_exceptions = self._try_exceptions
4458
-
4459
- try:
4460
- return fn(*cmd, **kwargs)
4461
-
4462
- except try_exceptions as e: # noqa
4463
- if self._log and self._log.isEnabledFor(logging.DEBUG):
4464
- self._log.exception('command failed')
4465
- return e
4466
-
4467
- async def async_try_fn(
4468
- self,
4469
- fn: ta.Callable[..., ta.Awaitable[T]],
4470
- *cmd: ta.Any,
4471
- try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
4472
- **kwargs: ta.Any,
4473
- ) -> ta.Union[T, Exception]:
4474
- if try_exceptions is None:
4475
- try_exceptions = self._try_exceptions
4476
-
4477
- try:
4478
- return await fn(*cmd, **kwargs)
4479
-
4480
- except try_exceptions as e: # noqa
4481
- if self._log and self._log.isEnabledFor(logging.DEBUG):
4482
- self._log.exception('command failed')
4483
- return e
4484
-
4485
-
4486
- ##
4487
-
4488
-
4489
- @dc.dataclass(frozen=True)
4490
- class SubprocessRun:
4491
- cmd: ta.Sequence[str]
4492
- input: ta.Any = None
4493
- timeout: ta.Optional[float] = None
4494
- check: bool = False
4495
- capture_output: ta.Optional[bool] = None
4496
- kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
4497
-
4498
-
4499
- @dc.dataclass(frozen=True)
4500
- class SubprocessRunOutput(ta.Generic[T]):
4501
- proc: T
4502
-
4503
- returncode: int # noqa
4504
-
4505
- stdout: ta.Optional[bytes] = None
4506
- stderr: ta.Optional[bytes] = None
4507
-
4508
-
4509
- class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
4510
- @abc.abstractmethod
4511
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
4512
- raise NotImplementedError
4513
-
4514
- def run(
4515
- self,
4516
- *cmd: str,
4517
- input: ta.Any = None, # noqa
4518
- timeout: ta.Optional[float] = None,
4519
- check: bool = False,
4520
- capture_output: ta.Optional[bool] = None,
4521
- **kwargs: ta.Any,
4522
- ) -> SubprocessRunOutput:
4523
- return self.run_(SubprocessRun(
4524
- cmd=cmd,
4525
- input=input,
4526
- timeout=timeout,
4527
- check=check,
4528
- capture_output=capture_output,
4529
- kwargs=kwargs,
4530
- ))
4531
-
4532
- #
4533
-
4534
- @abc.abstractmethod
4535
- def check_call(
4536
- self,
4537
- *cmd: str,
4538
- stdout: ta.Any = sys.stderr,
4539
- **kwargs: ta.Any,
4540
- ) -> None:
4541
- raise NotImplementedError
4542
-
4543
- @abc.abstractmethod
4544
- def check_output(
4545
- self,
4546
- *cmd: str,
4547
- **kwargs: ta.Any,
4548
- ) -> bytes:
4549
- raise NotImplementedError
4550
-
4551
- #
4552
-
4553
- def check_output_str(
4554
- self,
4555
- *cmd: str,
4556
- **kwargs: ta.Any,
4557
- ) -> str:
4558
- return self.check_output(*cmd, **kwargs).decode().strip()
4559
-
4560
- #
4561
-
4562
- def try_call(
4563
- self,
4564
- *cmd: str,
4565
- **kwargs: ta.Any,
4566
- ) -> bool:
4567
- if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
4568
- return False
4569
- else:
4570
- return True
4571
-
4572
- def try_output(
4573
- self,
4574
- *cmd: str,
4575
- **kwargs: ta.Any,
4576
- ) -> ta.Optional[bytes]:
4577
- if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
4578
- return None
4579
- else:
4580
- return ret
4581
-
4582
- def try_output_str(
4583
- self,
4584
- *cmd: str,
4585
- **kwargs: ta.Any,
4586
- ) -> ta.Optional[str]:
4587
- if (ret := self.try_output(*cmd, **kwargs)) is None:
4588
- return None
4589
- else:
4590
- return ret.decode().strip()
4591
-
4592
-
4593
- ##
4594
-
4595
-
4596
- class Subprocesses(AbstractSubprocesses):
4597
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
4598
- proc = subprocess.run(
4599
- run.cmd,
4600
- input=run.input,
4601
- timeout=run.timeout,
4602
- check=run.check,
4603
- capture_output=run.capture_output or False,
4604
- **(run.kwargs or {}),
4605
- )
4606
-
4607
- return SubprocessRunOutput(
4608
- proc=proc,
4609
-
4610
- returncode=proc.returncode,
4611
-
4612
- stdout=proc.stdout, # noqa
4613
- stderr=proc.stderr, # noqa
4614
- )
4615
-
4616
- def check_call(
4617
- self,
4618
- *cmd: str,
4619
- stdout: ta.Any = sys.stderr,
4620
- **kwargs: ta.Any,
4621
- ) -> None:
4622
- with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
4623
- subprocess.check_call(cmd, **kwargs)
4624
-
4625
- def check_output(
4626
- self,
4627
- *cmd: str,
4628
- **kwargs: ta.Any,
4629
- ) -> bytes:
4630
- with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
4631
- return subprocess.check_output(cmd, **kwargs)
4632
-
4633
-
4634
- subprocesses = Subprocesses()
4635
-
4636
-
4637
- ##
4638
-
4639
-
4640
- class AbstractAsyncSubprocesses(BaseSubprocesses):
4641
- @abc.abstractmethod
4642
- async def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
4643
- raise NotImplementedError
4644
-
4645
- def run(
4646
- self,
4647
- *cmd: str,
4648
- input: ta.Any = None, # noqa
4649
- timeout: ta.Optional[float] = None,
4650
- check: bool = False,
4651
- capture_output: ta.Optional[bool] = None,
4652
- **kwargs: ta.Any,
4653
- ) -> ta.Awaitable[SubprocessRunOutput]:
4654
- return self.run_(SubprocessRun(
4655
- cmd=cmd,
4656
- input=input,
4657
- timeout=timeout,
4658
- check=check,
4659
- capture_output=capture_output,
4660
- kwargs=kwargs,
4661
- ))
4662
-
4663
- #
4664
-
4665
- @abc.abstractmethod
4666
- async def check_call(
4667
- self,
4668
- *cmd: str,
4669
- stdout: ta.Any = sys.stderr,
4670
- **kwargs: ta.Any,
4671
- ) -> None:
4672
- raise NotImplementedError
4673
-
4674
- @abc.abstractmethod
4675
- async def check_output(
4676
- self,
4677
- *cmd: str,
4678
- **kwargs: ta.Any,
4679
- ) -> bytes:
4680
- raise NotImplementedError
4681
-
4682
- #
4683
-
4684
- async def check_output_str(
4685
- self,
4686
- *cmd: str,
4687
- **kwargs: ta.Any,
4688
- ) -> str:
4689
- return (await self.check_output(*cmd, **kwargs)).decode().strip()
4690
-
4691
- #
4692
-
4693
- async def try_call(
4694
- self,
4695
- *cmd: str,
4696
- **kwargs: ta.Any,
4697
- ) -> bool:
4698
- if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
4699
- return False
4700
- else:
4701
- return True
4702
-
4703
- async def try_output(
4704
- self,
4705
- *cmd: str,
4706
- **kwargs: ta.Any,
4707
- ) -> ta.Optional[bytes]:
4708
- if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
4709
- return None
4710
- else:
4711
- return ret
4712
-
4713
- async def try_output_str(
4714
- self,
4715
- *cmd: str,
4716
- **kwargs: ta.Any,
4717
- ) -> ta.Optional[str]:
4718
- if (ret := await self.try_output(*cmd, **kwargs)) is None:
4719
- return None
4720
- else:
4721
- return ret.decode().strip()
4722
-
4723
-
4724
4360
  ########################################
4725
4361
  # ../poster.py
4726
4362
  """