comparison libervia/backend/tools/common/async_process.py @ 4251:601e72332907

tools (common/async_process): show command and arguments used in error message in case a failure.
author Goffi <goffi@goffi.org>
date Fri, 31 May 2024 11:08:23 +0200
parents 730f542e4ad0
children 0d7bb4df2343
comparison
equal deleted inserted replaced
4250:4b6b812f485a 4251:601e72332907
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 """tools to launch process in a async way (using Twisted)""" 20 """tools to launch process in a async way (using Twisted)"""
21 21
22 import os.path 22 import os.path
23 from typing import Any
23 from twisted.internet import defer, reactor, protocol 24 from twisted.internet import defer, reactor, protocol
24 from twisted.python.failure import Failure 25 from twisted.python.failure import Failure
25 from libervia.backend.core.i18n import _ 26 from libervia.backend.core.i18n import _
26 from libervia.backend.core import exceptions 27 from libervia.backend.core import exceptions
27 from libervia.backend.core.log import getLogger 28 from libervia.backend.core.log import getLogger
44 """ 45 """
45 self._stdin = stdin 46 self._stdin = stdin
46 self._deferred = deferred 47 self._deferred = deferred
47 self.data = [] 48 self.data = []
48 self.err_data = [] 49 self.err_data = []
50 self.cmd_args: list[str]|None = None
51 self.cmd_kwargs: dict[str, Any]|None = None
49 52
50 @property 53 @property
51 def command_name(self): 54 def command_name(self):
52 """returns command name or empty string if it can't be guessed""" 55 """returns command name or empty string if it can't be guessed"""
53 if self.name is not None: 56 if self.name is not None:
81 # is not working properly 84 # is not working properly
82 self._deferred.callback(data) 85 self._deferred.callback(data)
83 else: 86 else:
84 err_data = b''.join(self.err_data) 87 err_data = b''.join(self.err_data)
85 88
86 msg = (_("Can't complete {name} command (error code: {code}):\n" 89 assert self.cmd_args is not None
87 "stderr:\n{stderr}\n{stdout}\n") 90 assert self.cmd_kwargs is not None
88 .format(name = self.command_name, 91 msg = (
89 code = reason.value.exitCode, 92 _(
90 stderr= err_data.decode(errors='replace'), 93 "Can't complete {name} command (error code: {code}):\n"
91 stdout = "stdout: " + data.decode(errors='replace') 94 "Executed command: {command}\n"
92 if data else '', 95 "Keyword arguments:\n"
93 )) 96 "{command_kw}\n\n"
97 "stderr:\n{stderr}\n{stdout}\n"
98 )
99 .format(
100 name = self.command_name,
101 code = reason.value.exitCode,
102 command = " ".join(self.cmd_args),
103 command_kw = "\n".join(
104 f" - {k} = {v!r}" for k,v in self.cmd_kwargs.items()
105 ),
106 stderr= err_data.decode(errors='replace'),
107 stdout = "stdout: " + data.decode(errors='replace')
108 if data else '',
109 )
110 )
94 self._deferred.errback(Failure(exceptions.CommandException( 111 self._deferred.errback(Failure(exceptions.CommandException(
95 msg, data, err_data))) 112 msg, data, err_data)))
96 113
97 @classmethod 114 @classmethod
98 def run(cls, *args, **kwargs): 115 def run(cls, *args, **kwargs):
129 name = os.path.splitext(os.path.basename(command))[0] 146 name = os.path.splitext(os.path.basename(command))[0]
130 prot.name = name 147 prot.name = name
131 else: 148 else:
132 command = cls.command 149 command = cls.command
133 cmd_args = [command] + args 150 cmd_args = [command] + args
151 prot.cmd_args = cmd_args
152 prot.cmd_kwargs = kwargs
134 if "env" not in kwargs: 153 if "env" not in kwargs:
135 # we pass parent environment by default 154 # we pass parent environment by default
136 # FIXME: `None` doesn't seem to work, despite what documentation says, to be 155 # FIXME: `None` doesn't seem to work, despite what documentation says, to be
137 # checked and reported upstream if confirmed. 156 # checked and reported upstream if confirmed.
138 kwargs["env"] = os.environ 157 kwargs["env"] = os.environ