Source code for betty.subprocess

"""
Provide a subprocess API.
"""

import logging
import os
import subprocess
from asyncio import create_subprocess_exec, create_subprocess_shell
from asyncio.subprocess import Process
from collections.abc import Sequence
from pathlib import Path
from subprocess import PIPE


[docs] class SubprocessError(Exception): """ Raised when a subprocess failed. """ pass
[docs] class CalledSubprocessError(subprocess.CalledProcessError, SubprocessError): """ Raised when a subprocess was successfully invoked, but subsequently failed during its own execution. """ pass
[docs] class FileNotFound(FileNotFoundError, SubprocessError): """ Raised when a command could not be found. """ pass
[docs] async def run_process( runnee: Sequence[str], cwd: Path | None = None, shell: bool = False, ) -> Process: """ Run a command in a subprocess. :raise betty.subprocess.SubprocessError: """ command = " ".join(runnee) logger = logging.getLogger(__name__) logger.debug(f"Running subprocess `{command}`...") try: if shell: process = await create_subprocess_shell( " ".join(runnee), cwd=cwd, stderr=PIPE, stdout=PIPE ) else: process = await create_subprocess_exec( *runnee, cwd=cwd, stderr=PIPE, stdout=PIPE ) stdout, stderr = await process.communicate() except FileNotFoundError as error: logger.debug(str(error)) raise FileNotFound(str(error)) from None if process.returncode == 0: return process stdout_str = "\n".join(stdout.decode().split(os.linesep)) stderr_str = "\n".join(stderr.decode().split(os.linesep)) if stdout_str: logger.debug(f"Subprocess `{command}` stdout:\n{stdout_str}") if stderr_str: logger.debug(f"Subprocess `{command}` stderr:\n{stderr_str}") assert process.returncode is not None raise CalledSubprocessError( process.returncode, " ".join(runnee), stdout_str, stderr_str, )