Mercurial > libervia-backend
comparison libervia/backend/tools/common/async_process.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 601e72332907 |
children | 9308b2d15fd2 |
comparison
equal
deleted
inserted
replaced
4269:64a85ce8be70 | 4270:0d7bb4df2343 |
---|---|
24 from twisted.internet import defer, reactor, protocol | 24 from twisted.internet import defer, reactor, protocol |
25 from twisted.python.failure import Failure | 25 from twisted.python.failure import Failure |
26 from libervia.backend.core.i18n import _ | 26 from libervia.backend.core.i18n import _ |
27 from libervia.backend.core import exceptions | 27 from libervia.backend.core import exceptions |
28 from libervia.backend.core.log import getLogger | 28 from libervia.backend.core.log import getLogger |
29 | |
29 log = getLogger(__name__) | 30 log = getLogger(__name__) |
30 | 31 |
31 | 32 |
32 class CommandProtocol(protocol.ProcessProtocol): | 33 class CommandProtocol(protocol.ProcessProtocol): |
33 """handle an external command""" | 34 """handle an external command""" |
35 | |
34 # name of the command (unicode) | 36 # name of the command (unicode) |
35 name = None | 37 name = None |
36 # full path to the command (bytes) | 38 # full path to the command (bytes) |
37 command = None | 39 command = None |
38 # True to activate logging of command outputs (bool) | 40 # True to activate logging of command outputs (bool) |
45 """ | 47 """ |
46 self._stdin = stdin | 48 self._stdin = stdin |
47 self._deferred = deferred | 49 self._deferred = deferred |
48 self.data = [] | 50 self.data = [] |
49 self.err_data = [] | 51 self.err_data = [] |
50 self.cmd_args: list[str]|None = None | 52 self.cmd_args: list[str] | None = None |
51 self.cmd_kwargs: dict[str, Any]|None = None | 53 self.cmd_kwargs: dict[str, Any] | None = None |
52 | 54 |
53 @property | 55 @property |
54 def command_name(self): | 56 def command_name(self): |
55 """returns command name or empty string if it can't be guessed""" | 57 """returns command name or empty string if it can't be guessed""" |
56 if self.name is not None: | 58 if self.name is not None: |
57 return self.name | 59 return self.name |
58 elif self.command is not None: | 60 elif self.command is not None: |
59 return os.path.splitext(os.path.basename(self.command))[0].decode('utf-8', | 61 return os.path.splitext(os.path.basename(self.command))[0].decode( |
60 'ignore') | 62 "utf-8", "ignore" |
63 ) | |
61 else: | 64 else: |
62 return '' | 65 return "" |
63 | 66 |
64 def connectionMade(self): | 67 def connectionMade(self): |
65 if self._stdin is not None: | 68 if self._stdin is not None: |
66 self.transport.write(self._stdin) | 69 self.transport.write(self._stdin) |
67 self.transport.closeStdin() | 70 self.transport.closeStdin() |
68 | 71 |
69 def outReceived(self, data): | 72 def outReceived(self, data): |
70 if self.log: | 73 if self.log: |
71 log.info(data.decode('utf-8', 'replace')) | 74 log.info(data.decode("utf-8", "replace")) |
72 self.data.append(data) | 75 self.data.append(data) |
73 | 76 |
74 def errReceived(self, data): | 77 def errReceived(self, data): |
75 if self.log: | 78 if self.log: |
76 log.warning(data.decode('utf-8', 'replace')) | 79 log.warning(data.decode("utf-8", "replace")) |
77 self.err_data.append(data) | 80 self.err_data.append(data) |
78 | 81 |
79 def processEnded(self, reason): | 82 def processEnded(self, reason): |
80 data = b''.join(self.data) | 83 data = b"".join(self.data) |
81 if (reason.value.exitCode == 0): | 84 if reason.value.exitCode == 0: |
82 log.debug(f'{self.command_name!r} command succeed') | 85 log.debug(f"{self.command_name!r} command succeed") |
83 # we don't use "replace" on purpose, we want an exception if decoding | 86 # we don't use "replace" on purpose, we want an exception if decoding |
84 # is not working properly | 87 # is not working properly |
85 self._deferred.callback(data) | 88 self._deferred.callback(data) |
86 else: | 89 else: |
87 err_data = b''.join(self.err_data) | 90 err_data = b"".join(self.err_data) |
88 | 91 |
89 assert self.cmd_args is not None | 92 assert self.cmd_args is not None |
90 assert self.cmd_kwargs is not None | 93 assert self.cmd_kwargs is not None |
91 msg = ( | 94 msg = _( |
92 _( | 95 "Can't complete {name} command (error code: {code}):\n" |
93 "Can't complete {name} command (error code: {code}):\n" | 96 "Executed command: {command}\n" |
94 "Executed command: {command}\n" | 97 "Keyword arguments:\n" |
95 "Keyword arguments:\n" | 98 "{command_kw}\n\n" |
96 "{command_kw}\n\n" | 99 "stderr:\n{stderr}\n{stdout}\n" |
97 "stderr:\n{stderr}\n{stdout}\n" | 100 ).format( |
98 ) | 101 name=self.command_name, |
99 .format( | 102 code=reason.value.exitCode, |
100 name = self.command_name, | 103 command=" ".join(self.cmd_args), |
101 code = reason.value.exitCode, | 104 command_kw="\n".join( |
102 command = " ".join(self.cmd_args), | 105 f" - {k} = {v!r}" for k, v in self.cmd_kwargs.items() |
103 command_kw = "\n".join( | 106 ), |
104 f" - {k} = {v!r}" for k,v in self.cmd_kwargs.items() | 107 stderr=err_data.decode(errors="replace"), |
105 ), | 108 stdout="stdout: " + data.decode(errors="replace") if data else "", |
106 stderr= err_data.decode(errors='replace'), | |
107 stdout = "stdout: " + data.decode(errors='replace') | |
108 if data else '', | |
109 ) | |
110 ) | 109 ) |
111 self._deferred.errback(Failure(exceptions.CommandException( | 110 self._deferred.errback( |
112 msg, data, err_data))) | 111 Failure(exceptions.CommandException(msg, data, err_data)) |
112 ) | |
113 | 113 |
114 @classmethod | 114 @classmethod |
115 def run(cls, *args, **kwargs): | 115 def run(cls, *args, **kwargs): |
116 """Create a new CommandProtocol and execute the given command. | 116 """Create a new CommandProtocol and execute the given command. |
117 | 117 |
125 @return ((D)bytes): stdout in case of success | 125 @return ((D)bytes): stdout in case of success |
126 @raise RuntimeError: command returned a non zero status | 126 @raise RuntimeError: command returned a non zero status |
127 stdin and stdout will be given as arguments | 127 stdin and stdout will be given as arguments |
128 | 128 |
129 """ | 129 """ |
130 stdin = kwargs.pop('stdin', None) | 130 stdin = kwargs.pop("stdin", None) |
131 if stdin is not None: | 131 if stdin is not None: |
132 stdin = stdin.encode('utf-8') | 132 stdin = stdin.encode("utf-8") |
133 verbose = kwargs.pop('verbose', False) | 133 verbose = kwargs.pop("verbose", False) |
134 args = list(args) | 134 args = list(args) |
135 d = defer.Deferred() | 135 d = defer.Deferred() |
136 prot = cls(d, stdin=stdin) | 136 prot = cls(d, stdin=stdin) |
137 if verbose: | 137 if verbose: |
138 prot.log = True | 138 prot.log = True |
139 if cls.command is None: | 139 if cls.command is None: |
140 if not args: | 140 if not args: |
141 raise ValueError( | 141 raise ValueError( |
142 "You must either specify cls.command or use a full path to command " | 142 "You must either specify cls.command or use a full path to command " |
143 "to execute as first argument") | 143 "to execute as first argument" |
144 ) | |
144 command = args.pop(0) | 145 command = args.pop(0) |
145 if prot.name is None: | 146 if prot.name is None: |
146 name = os.path.splitext(os.path.basename(command))[0] | 147 name = os.path.splitext(os.path.basename(command))[0] |
147 prot.name = name | 148 prot.name = name |
148 else: | 149 else: |
153 if "env" not in kwargs: | 154 if "env" not in kwargs: |
154 # we pass parent environment by default | 155 # we pass parent environment by default |
155 # FIXME: `None` doesn't seem to work, despite what documentation says, to be | 156 # FIXME: `None` doesn't seem to work, despite what documentation says, to be |
156 # checked and reported upstream if confirmed. | 157 # checked and reported upstream if confirmed. |
157 kwargs["env"] = os.environ | 158 kwargs["env"] = os.environ |
158 reactor.spawnProcess(prot, | 159 reactor.spawnProcess(prot, command, cmd_args, **kwargs) |
159 command, | |
160 cmd_args, | |
161 **kwargs) | |
162 return d | 160 return d |
163 | 161 |
164 | 162 |
165 run = CommandProtocol.run | 163 run = CommandProtocol.run |