Skip to content

Commit 4794806

Browse files
committed
Deprecate execute_async
Low level method, which normally should not be used from outside: we can change backend without affecting other API parts. Signed-off-by: Aleksei Stepanov <penguinolog@gmail.com>
1 parent 87af91a commit 4794806

File tree

10 files changed

+75
-30
lines changed

10 files changed

+75
-30
lines changed

exec_helpers/_ssh_client_base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ def keepalive(self, enforce: bool = True) -> "typing.ContextManager[None]":
400400
return _KeepAliveContext(ssh=self, enforce=enforce)
401401

402402
# noinspection PyMethodOverriding
403-
def execute_async( # pylint: disable=arguments-differ
403+
def _execute_async( # pylint: disable=arguments-differ
404404
self,
405405
command: str,
406406
stdin: typing.Union[bytes, str, bytearray, None] = None,
@@ -743,7 +743,7 @@ def get_result(remote: "SSHClientBase") -> exec_result.ExecResult:
743743
:param remote: SSH connection instance
744744
:returns: execution result
745745
"""
746-
async_result: SshExecuteAsyncResult = remote.execute_async(
746+
async_result: SshExecuteAsyncResult = remote._execute_async( # pylint: disable=protected-access
747747
command, stdin=stdin, log_mask_re=log_mask_re, **kwargs
748748
)
749749

exec_helpers/api.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import re
2929
import threading
3030
import typing
31+
import warnings
3132

3233
# Exec-Helpers Implementation
3334
from exec_helpers import constants
@@ -229,8 +230,53 @@ def _prepare_command(self, cmd: str, chroot_path: typing.Optional[str] = None) -
229230
return f"chroot {chroot_path if chroot_path else self._chroot_path} {cmd}"
230231
return cmd
231232

233+
def execute_async( # pylint: disable=missing-param-doc,differing-param-doc,differing-type-doc
234+
self, *args: typing.Any, **kwargs: typing.Any
235+
) -> ExecuteAsyncResult:
236+
"""Execute command in async mode and return remote interface with IO objects.
237+
238+
:param command: Command for execution
239+
:type command: str
240+
:param stdin: pass STDIN text to the process
241+
:type stdin: typing.Union[bytes, str, bytearray, None]
242+
:param open_stdout: open STDOUT stream for read
243+
:type open_stdout: bool
244+
:param open_stderr: open STDERR stream for read
245+
:type open_stderr: bool
246+
:param verbose: produce verbose log record on command call
247+
:type verbose: bool
248+
:param log_mask_re: regex lookup rule to mask command for logger.
249+
all MATCHED groups will be replaced by '<*masked*>'
250+
:type log_mask_re: typing.Optional[str]
251+
:param chroot_path: chroot path override
252+
:type chroot_path: typing.Optional[str]
253+
:param kwargs: additional parameters for call.
254+
:type kwargs: typing.Any
255+
:return: NamedTuple with control interface and file-like objects for STDIN/STDERR/STDOUT
256+
:rtype: typing.NamedTuple(
257+
'ExecuteAsyncResult',
258+
[
259+
('interface', typing.Any),
260+
('stdin', typing.Optional[typing.Any]),
261+
('stderr', typing.Optional[typing.Any]),
262+
('stdout', typing.Optional[typing.Any]),
263+
("started", datetime.datetime),
264+
]
265+
)
266+
267+
.. versionchanged:: 1.2.0 open_stdout and open_stderr flags
268+
.. versionchanged:: 1.2.0 stdin data
269+
.. versionchanged:: 2.1.0 Use typed NamedTuple as result
270+
.. versionchanged:: 4.1.0 support chroot
271+
"""
272+
warnings.warn(
273+
"execute_async public usage is deprecated and will be disallowed at the next major release.",
274+
DeprecationWarning,
275+
)
276+
return self._execute_async(*args, **kwargs)
277+
232278
@abc.abstractmethod
233-
def execute_async(
279+
def _execute_async(
234280
self,
235281
command: str,
236282
stdin: typing.Union[bytes, str, bytearray, None] = None,
@@ -347,7 +393,7 @@ def execute(
347393
.. versionchanged:: 1.2.0 default timeout 1 hour
348394
.. versionchanged:: 2.1.0 Allow parallel calls
349395
"""
350-
async_result: ExecuteAsyncResult = self.execute_async(
396+
async_result: ExecuteAsyncResult = self._execute_async(
351397
command, verbose=verbose, log_mask_re=log_mask_re, stdin=stdin, **kwargs
352398
)
353399

exec_helpers/async_api/api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ async def _exec_command( # type: ignore
134134
"""
135135

136136
@abc.abstractmethod
137-
async def execute_async( # type: ignore
137+
async def _execute_async( # type: ignore
138138
self,
139139
command: str,
140140
stdin: typing.Union[str, bytes, bytearray, None] = None,
@@ -208,7 +208,7 @@ async def execute( # type: ignore
208208
:rtype: ExecResult
209209
:raises ExecHelperTimeoutError: Timeout exceeded
210210
"""
211-
async_result: api.ExecuteAsyncResult = await self.execute_async(
211+
async_result: api.ExecuteAsyncResult = await self._execute_async(
212212
command, verbose=verbose, log_mask_re=log_mask_re, stdin=stdin, **kwargs
213213
)
214214

exec_helpers/async_api/subprocess_runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ async def poll_stderr() -> None:
163163

164164
# pylint: disable=arguments-differ
165165
# noinspection PyMethodOverriding
166-
async def execute_async( # type: ignore
166+
async def _execute_async( # type: ignore
167167
self,
168168
command: str,
169169
stdin: typing.Union[str, bytes, bytearray, None] = None,

exec_helpers/subprocess_runner.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,11 @@ def close_streams() -> None:
169169
stdout_future.cancel()
170170
stderr_future.cancel()
171171
_, not_done = concurrent.futures.wait([stdout_future, stderr_future], timeout=1)
172-
if not_done:
173-
if async_result.interface.returncode:
174-
self.logger.critical(
175-
f"Process {command!s} was closed with exit code {async_result.interface.returncode!s}, "
176-
f"but FIFO buffers are still open"
177-
)
172+
if not_done and async_result.interface.returncode:
173+
self.logger.critical(
174+
f"Process {command!s} was closed with exit code {async_result.interface.returncode!s}, "
175+
f"but FIFO buffers are still open"
176+
)
178177
result.set_timestamp()
179178
close_streams()
180179

@@ -183,7 +182,7 @@ def close_streams() -> None:
183182
raise exceptions.ExecHelperTimeoutError(result=result, timeout=timeout) # type: ignore
184183

185184
# noinspection PyMethodOverriding
186-
def execute_async( # pylint: disable=arguments-differ
185+
def _execute_async( # pylint: disable=arguments-differ
187186
self,
188187
command: str,
189188
stdin: typing.Union[str, bytes, bytearray, None] = None,

test/async_api/test_subprocess.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ def logger(mocker):
202202
async def test_001_execute_async(create_subprocess_shell, logger, run_parameters) -> None:
203203
"""Test low level API."""
204204
runner = exec_helpers.async_api.Subprocess()
205-
res = await runner.execute_async(
205+
res = await runner._execute_async(
206206
command,
207207
stdin=run_parameters["stdin"],
208208
open_stdout=run_parameters["open_stdout"],

test/test_ssh_client_execute.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ def get_patched_execute_async_retval(
235235
)
236236

237237
return mocker.patch(
238-
"exec_helpers.ssh_client.SSHClient.execute_async",
238+
"exec_helpers.ssh_client.SSHClient._execute_async",
239239
side_effect=[
240240
get_patched_execute_async_retval(**run_parameters),
241241
get_patched_execute_async_retval(**run_parameters),
@@ -261,7 +261,7 @@ def test_001_execute_async(ssh, paramiko_ssh_client, ssh_transport_channel, chan
261261
if "height" in run_parameters:
262262
kwargs["height"] = run_parameters["height"]
263263

264-
res = ssh.execute_async(
264+
res = ssh._execute_async(
265265
command, stdin=run_parameters["stdin"], open_stdout=open_stdout, open_stderr=open_stderr, **kwargs
266266
)
267267
assert isinstance(res, exec_helpers.SshExecuteAsyncResult)

test/test_ssh_client_execute_async_special.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def exec_result():
116116
def test_001_execute_async_sudo(ssh, ssh_transport_channel):
117117
ssh.sudo_mode = True
118118

119-
ssh.execute_async(command)
119+
ssh._execute_async(command)
120120
ssh_transport_channel.assert_has_calls(
121121
(
122122
mock.call.makefile_stderr("rb"),
@@ -129,11 +129,11 @@ def test_002_execute_async_with_sudo_enforce(ssh, ssh_transport_channel):
129129
assert ssh.sudo_mode is False
130130

131131
with ssh.sudo(enforce=True):
132-
ssh.execute_async(command)
132+
ssh._execute_async(command)
133133
ssh_transport_channel.assert_has_calls(
134134
(
135135
mock.call.makefile_stderr("rb"),
136-
mock.call.exec_command(f'sudo -S bash -c \'eval "$(base64 -d <(echo "{encoded_cmd}"))"\''),
136+
mock.call.exec_command(f'sudo -S bash -c \"eval {shlex.quote(cmd_execute)}\"'),
137137
)
138138
)
139139

@@ -142,15 +142,15 @@ def test_003_execute_async_with_no_sudo_enforce(ssh, ssh_transport_channel):
142142
ssh.sudo_mode = True
143143

144144
with ssh.sudo(enforce=False):
145-
ssh.execute_async(command)
145+
ssh._execute_async(command)
146146
ssh_transport_channel.assert_has_calls((mock.call.makefile_stderr("rb"), mock.call.exec_command(f"{command}\n")))
147147

148148

149149
def test_004_execute_async_with_sudo_none_enforce(ssh, ssh_transport_channel):
150150
ssh.sudo_mode = False
151151

152152
with ssh.sudo():
153-
ssh.execute_async(command)
153+
ssh._execute_async(command)
154154
ssh_transport_channel.assert_has_calls((mock.call.makefile_stderr("rb"), mock.call.exec_command(f"{command}\n")))
155155

156156

@@ -159,11 +159,11 @@ def test_005_execute_async_sudo_password(ssh, ssh_transport_channel, mocker):
159159

160160
ssh.sudo_mode = True
161161

162-
res = ssh.execute_async(command)
162+
res = ssh._execute_async(command)
163163
ssh_transport_channel.assert_has_calls(
164164
(
165165
mock.call.makefile_stderr("rb"),
166-
mock.call.exec_command(f'sudo -S bash -c \'eval "$(base64 -d <(echo "{encoded_cmd}"))"\''),
166+
mock.call.exec_command(f'sudo -S bash -c \"eval {shlex.quote(cmd_execute)}\"'),
167167
)
168168
)
169169

@@ -221,15 +221,15 @@ def test_010_check_stdin_closed(paramiko_ssh_client, chan_makefile, auto_add_pol
221221
stdin_val = "this is a line"
222222

223223
ssh = exec_helpers.SSHClient(host=host, port=port, auth=exec_helpers.SSHAuth(username=username, password=password))
224-
ssh.execute_async(command=print_stdin, stdin=stdin_val)
224+
ssh._execute_async(command=print_stdin, stdin=stdin_val)
225225

226226
log = get_logger(ssh.__class__.__name__).getChild(f"{host}:{port}")
227227
log.warning.assert_called_once_with("STDIN Send failed: closed channel")
228228

229229

230230
def test_011_execute_async_chroot_cmd(ssh, ssh_transport_channel):
231231
"""Command-only chroot path."""
232-
ssh.execute_async(command, chroot_path='/')
232+
ssh._execute_async(command, chroot_path='/')
233233
ssh_transport_channel.assert_has_calls(
234234
(
235235
mock.call.makefile_stderr("rb"),
@@ -241,7 +241,7 @@ def test_011_execute_async_chroot_cmd(ssh, ssh_transport_channel):
241241
def test_012_execute_async_chroot_context(ssh, ssh_transport_channel):
242242
"""Context-managed chroot path."""
243243
with ssh.chroot('/'):
244-
ssh.execute_async(command)
244+
ssh._execute_async(command)
245245
ssh_transport_channel.assert_has_calls(
246246
(
247247
mock.call.makefile_stderr("rb"),
@@ -255,7 +255,7 @@ def test_013_execute_async_no_chroot_context(ssh, ssh_transport_channel):
255255
ssh._chroot_path = "/"
256256

257257
with ssh.chroot(None):
258-
ssh.execute_async(command)
258+
ssh._execute_async(command)
259259
ssh_transport_channel.assert_has_calls(
260260
(
261261
mock.call.makefile_stderr("rb"),

test/test_ssh_client_execute_special.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def test_005_execute_timeout_fail(ssh, ssh_transport_channel, exec_result) -> No
183183

184184

185185
def test_006_execute_together_exceptions(ssh, ssh2, mocker) -> None:
186-
mocker.patch("exec_helpers.ssh_client.SSHClient.execute_async", side_effect=RuntimeError)
186+
mocker.patch("exec_helpers.ssh_client.SSHClient._execute_async", side_effect=RuntimeError)
187187
remotes = [ssh, ssh2]
188188

189189
with pytest.raises(exec_helpers.ParallelCallExceptions) as e:

test/test_subprocess.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def create_mock(
177177
def test_001_execute_async(popen, subprocess_logger, run_parameters) -> None:
178178
"""Test low level API."""
179179
runner = exec_helpers.Subprocess()
180-
res = runner.execute_async(
180+
res = runner._execute_async(
181181
command,
182182
stdin=run_parameters["stdin"],
183183
open_stdout=run_parameters["open_stdout"],

0 commit comments

Comments
 (0)