import os import shlex import subprocess import platform from .log import PypeLogger as Logger # MSDN process creation flag (Windows only) CREATE_NO_WINDOW = 0x08000000 def execute(args, silent=False, cwd=None, env=None, shell=None): """Execute command as process. This will execute given command as process, monitor its output and log it appropriately. .. seealso:: :mod:`subprocess` module in Python. Args: args (list): list of arguments passed to process. silent (bool): control output of executed process. cwd (str): current working directory for process. env (dict): environment variables for process. shell (bool): use shell to execute, default is no. Returns: int: return code of process """ log_levels = ['DEBUG:', 'INFO:', 'ERROR:', 'WARNING:', 'CRITICAL:'] log = Logger().get_logger('execute') log.info("Executing ({})".format(" ".join(args))) popen = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1, cwd=cwd, env=env or os.environ, shell=shell ) # Blocks until finished while True: line = popen.stdout.readline() if line == '': break if silent: continue line_test = False for test_string in log_levels: if line.startswith(test_string): line_test = True break if not line_test: print(line[:-1]) log.info("Execution is finishing up ...") popen.wait() return popen.returncode def run_subprocess(*args, **kwargs): """Convenience method for getting output errors for subprocess. Output logged when process finish. Entered arguments and keyword arguments are passed to subprocess Popen. Args: *args: Variable length arument list passed to Popen. **kwargs : Arbitary keyword arguments passed to Popen. Is possible to pass `logging.Logger` object under "logger" if want to use different than lib's logger. Returns: str: Full output of subprocess concatenated stdout and stderr. Raises: RuntimeError: Exception is raised if process finished with nonzero return code. """ # Get environents from kwarg or use current process environments if were # not passed. env = kwargs.get("env") or os.environ # Make sure environment contains only strings filtered_env = {str(k): str(v) for k, v in env.items()} # Use lib's logger if was not passed with kwargs. logger = kwargs.pop("logger", None) if logger is None: logger = Logger.get_logger("run_subprocess") # set overrides kwargs['stdout'] = kwargs.get('stdout', subprocess.PIPE) kwargs['stderr'] = kwargs.get('stderr', subprocess.PIPE) kwargs['stdin'] = kwargs.get('stdin', subprocess.PIPE) kwargs['env'] = filtered_env proc = subprocess.Popen(*args, **kwargs) full_output = "" _stdout, _stderr = proc.communicate() if _stdout: _stdout = _stdout.decode("utf-8") full_output += _stdout logger.debug(_stdout) if _stderr: _stderr = _stderr.decode("utf-8") # Add additional line break if output already containt stdout if full_output: full_output += "\n" full_output += _stderr logger.warning(_stderr) if proc.returncode != 0: exc_msg = "Executing arguments was not successful: \"{}\"".format(args) if _stdout: exc_msg += "\n\nOutput:\n{}".format(_stdout) if _stderr: exc_msg += "Error:\n{}".format(_stderr) raise RuntimeError(exc_msg) return full_output def split_command_to_list(string_command): """Split string subprocess command to list. Should be able to split complex subprocess command to separated arguments: `"C:\\ffmpeg folder\\ffmpeg.exe" -i \"D:\\input.mp4\\" \"D:\\output.mp4\"` Should result into list: `["C:\ffmpeg folder\ffmpeg.exe", "-i", "D:\input.mp4", "D:\output.mp4"]` This may be required on few versions of python where subprocess can handle only list of arguments. To be able do that is using `shlex` python module. Args: string_command(str): Full subprocess command. Returns: list: Command separated into individual arguments. """ if platform.system().lower() == "windows": posix = False return shlex.split(string_command, posix=posix) def get_pype_execute_args(*args): """Arguments to run pype command. Arguments for subprocess when need to spawn new pype process. Which may be needed when new python process for pype scripts must be executed in build pype. ## Why is this needed? Pype executed from code has different executable set to virtual env python and must have path to script as first argument which is not needed for build pype. It is possible to pass any arguments that will be added after pype executables. """ pype_executable = os.environ["OPENPYPE_EXECUTABLE"] pype_args = [pype_executable] executable_filename = os.path.basename(pype_executable) if "python" in executable_filename.lower(): pype_args.append( os.path.join(os.environ["OPENPYPE_ROOT"], "start.py") ) if args: pype_args.extend(args) return pype_args