ae-shell 0.3.17__tar.gz → 0.3.19__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.102 -->
1
+ <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.104 -->
2
2
  ### GNU GENERAL PUBLIC LICENSE
3
3
 
4
4
  Version 3, 29 June 2007
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ae_shell
3
- Version: 0.3.17
3
+ Version: 0.3.19
4
4
  Summary: ae namespace module portion shell: shell execution and environment helpers
5
5
  Home-page: https://gitlab.com/ae-group/ae_shell
6
6
  Author: AndiEcker
@@ -64,13 +64,13 @@ Dynamic: summary
64
64
 
65
65
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.110 -->
66
66
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.33 -->
67
- # shell 0.3.17
67
+ # shell 0.3.19
68
68
 
69
69
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/develop?logo=python)](
70
70
  https://gitlab.com/ae-group/ae_shell)
71
71
  [![LatestPyPIrelease](
72
- https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.17?logo=python)](
73
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.17)
72
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.19?logo=python)](
73
+ https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.19)
74
74
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](
75
75
  https://pypi.org/project/ae-shell/#history)
76
76
 
@@ -1,12 +1,12 @@
1
1
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.110 -->
2
2
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.33 -->
3
- # shell 0.3.17
3
+ # shell 0.3.19
4
4
 
5
5
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/develop?logo=python)](
6
6
  https://gitlab.com/ae-group/ae_shell)
7
7
  [![LatestPyPIrelease](
8
- https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.17?logo=python)](
9
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.17)
8
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.19?logo=python)](
9
+ https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.19)
10
10
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](
11
11
  https://pypi.org/project/ae-shell/#history)
12
12
 
@@ -12,32 +12,31 @@ to manage shell printouts, OS environment variables and to execute shell command
12
12
  * :func:`mask_token`: hide/mask tokens in a text block, to prevent to show them in logs and printouts.
13
13
  * :func:`output_line_split`: decode and split the specified shell/console output streams into line chunks.
14
14
  * :func:`output_zero_split`: decode and split the specified shell/console output streams separated by `NUL` (\\0) chars.
15
- * :func:`sh_exec`: execute command in the current working directory of the OS console/shell.
16
- * :func:`sh_exit_if_exec_err`: extended version of :func:`sh_exec` with automatically checks for errors
15
+ * :func:`run_cmd`: execute command in the current working directory of the OS console/shell.
16
+ * :func:`run_logged_cmd`: extended version of :func:`run_cmd` with logging and error checking.
17
17
  after a command is executed and handles application shutdown/termination gracefully.
18
18
 
19
- * :data:`STDERR_BEG_MARKER`: marker used in the console/shell printouts for the beginning of merged-in stderr output.
20
- * :data:`STDERR_END_MARKER`: marker used in the console/shell printouts for the end of merged-in stderr output.
19
+ * :data:`STDERR_BEG_MARKER`: marker used in the console/shell printouts for the beginning of merged-in `stderr` output.
20
+ * :data:`STDERR_END_MARKER`: marker used in the console/shell printouts for the end of merged-in `stderr` output.
21
21
  """
22
22
  import os
23
- import shlex
24
23
  import subprocess
25
24
 
26
- from collections.abc import Callable, Iterable, Iterator, MutableMapping
25
+ from collections.abc import Callable, Iterator, MutableMapping
27
26
  from contextlib import contextmanager
28
- from typing import Any, cast, overload
27
+ from typing import Any, Sequence, cast, overload
29
28
 
30
- from ae.base import UNSET, UnsetType, dummy_function, env_str, norm_name # type: ignore
29
+ from ae.base import UNSET, URI_SVC_SEP, UnsetType, dummy_function, env_str, norm_name # type: ignore
31
30
  from ae.system import load_env_var_defaults, os_env_venv # type: ignore
32
31
  from ae.core import main_app_instance, AppBase # type: ignore
33
32
  from ae.console import MAIN_SECTION_NAME, ConsoleApp # type: ignore
34
33
 
35
34
 
36
- __version__ = '0.3.17'
35
+ __version__ = '0.3.19'
37
36
 
38
37
 
39
- STDERR_BEG_MARKER = 'vvv STDERR vvv' #: :paramref:`ae.shell.sh_exec.output_lines` begin stderr lines marker
40
- STDERR_END_MARKER = '^^^ STDERR ^^^' #: end stderr lines marker in :paramref:`ae.shell.sh_exec.output_lines`
38
+ STDERR_BEG_MARKER = 'vvv STDERR vvv' #: :paramref:`ae.shell.run_cmd.output_lines` begin `stderr` lines marker
39
+ STDERR_END_MARKER = '^^^ STDERR ^^^' #: end `stderr` lines marker in :paramref:`ae.shell.run_cmd.output_lines`
41
40
 
42
41
 
43
42
  def debug_or_verbose(app_obj: ConsoleApp | UnsetType | None = None) -> bool:
@@ -119,18 +118,14 @@ def in_os_env(start_dir: str = "") -> Iterator[MutableMapping[str, str]]:
119
118
 
120
119
 
121
120
  @overload
122
- def mask_token(text: str) -> str: ...
121
+ def mask_token(text: str) -> str: ... # type: ignore[overload-overlap]
123
122
 
124
123
 
125
124
  @overload
126
- def mask_token(text: list[str]) -> list[str]: ...
125
+ def mask_token(text: Sequence[str]) -> list[str]: ...
127
126
 
128
127
 
129
- @overload
130
- def mask_token(text: str | list[str]) -> str | list[str]: ...
131
-
132
-
133
- def mask_token(text: str | list[str]) -> str | list[str]:
128
+ def mask_token(text: str | Sequence[str]) -> str | list[str]:
134
129
  """ hide most parts of any Codeberg/GitHub/GitHub URL tokens found in the specified text/-lines.
135
130
 
136
131
  :param text: text, specified either as str object or as a list of str objects (lines),
@@ -144,7 +139,7 @@ def mask_token(text: str | list[str]) -> str | list[str]:
144
139
  else:
145
140
  lines = list(text) # copy to not change text list content
146
141
 
147
- url_beg = 'https://' # PDV_REPO_HOST_PROTOCOL
142
+ url_beg = f"https{URI_SVC_SEP}" # PDV_REPO_HOST_PROTOCOL
148
143
  url_beg_len = len(url_beg)
149
144
  for tok_beg, tok_end in ((':', '@codeberg.org'), ('glpat-', '@gitlab.com'), ('ghp_', '@github.com')):
150
145
  for idx, line in enumerate(lines):
@@ -161,7 +156,7 @@ def mask_token(text: str | list[str]) -> str | list[str]:
161
156
  def output_line_split(output: bytes) -> list[str]:
162
157
  """ decode and split the specified shell/console output streams into line chunks.
163
158
 
164
- :param output: captured stdout/stderr output from an executed OS shell command.
159
+ :param output: captured `stdout`/`stderr` output from an executed OS shell command.
165
160
  :return: list of non-empty shell/console output lines, decoded into string.
166
161
  """
167
162
  return [line for line in output.decode().splitlines() if line.strip()]
@@ -170,83 +165,84 @@ def output_line_split(output: bytes) -> list[str]:
170
165
  def output_zero_split(output: bytes) -> list[str]:
171
166
  """ decode and split the specified shell/console output streams separated by `NUL` (\\0) characters.
172
167
 
173
- :param output: captured stdout/stderr output from an executed OS shell command (e.g. `env -0`).
168
+ :param output: captured `stdout`/`stderr` output from an executed OS shell command (e.g. `env -0`).
174
169
  :return: list of non-empty shell/console output chunks, decoded into string.
175
170
  """
176
171
  return [line for line in output.decode().split('\0') if line.strip()]
177
172
 
178
173
 
179
- # pylint: disable-next=too-many-arguments,too-many-positional-arguments
180
- def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: str = "",
181
- output_lines: list[str] | None = None, app_obj: AppBase | UnsetType | None = None, shell: bool = False,
182
- env_vars: dict[str, str] | UnsetType | None = UNSET, err_redirect: int | None = None,
183
- decoder_splitter: Callable[[bytes], list[str]] = output_line_split) -> int:
184
- """ execute command in the current working directory of the OS console/shell.
185
-
186
- :param command_line: command line string to execute on the console/shell. could contain command line args
187
- separated by whitespace characters (alternatively use :paramref:`.extra_args`).
188
- :param extra_args: optional iterable with extra command line arguments.
189
- :param console_input: optional string to be sent to the stdin stream of the console/shell.
190
- :param output_lines: specify a list to be extended with the lines printed on the console/shell stdout stream,
191
- and to also hide this output on the console. if and how the stderr stream get also
192
- hidden/redirected to this list can be controlled via the value passed in the argument
193
- :paramref:`.err_redirect`.
174
+ def run_cmd(*cmd_args: str,
175
+ output_lines: list[str] | None = None,
176
+ app_obj: AppBase | UnsetType | None = None,
177
+ decoder_splitter: Callable[[bytes], list[str]] = output_line_split,
178
+ **run_kwargs) -> int:
179
+ """ run command in the current working directory, capturing/logging errors and optionally returning console output.
180
+
181
+ :param cmd_args: command line string or args sequence of command name and arguments
182
+ to be run/executed on the console/shell via :func:`subprocess.run`.
183
+ :param output_lines: specify a list to be extended with the lines printed on the console/shell `stdout`
184
+ stream, and to also hide this output on the console. if and how the `stderr` stream get
185
+ also either hidden, captured or to be redirected to this list can be controlled via the
186
+ value passed in the kwarg :paramref:`~subprocess.Popen.stderr`: if this argument is a
187
+ list, and you specified the argument value :data:`subprocess.PIPE`, then the `stderr`
188
+ output will get added at the end of this list (enclosed between the list items
189
+ :data:`STDERR_BEG_MARKER` and :data:`STDERR_END_MARKER`). if this argument is a list,
190
+ and you specified the argument value :data:`subprocess.STDOUT` then the `stderr` outputs
191
+ will get merged without any markers into this list in the order they get printed.
192
+ specify :data:`subprocess.DEVNULL` in :paramref:`~subprocess.Popen.stderr` to hide any
193
+ `stderr` output onto the console/shell as well as in this list argument.
194
+ specifying `None` as :paramref:`~subprocess.Popen.stderr` argument value
195
+ (the default argument) then the `stderr` output will be printed only on the console.
194
196
  :param app_obj: optional :class:`~ae.core.AppBase`/:class:`~ae.console.ConsoleApp` instance, used for
195
197
  logging. if not specified or None and if :func:`~ae.core.main_app_instance()` returns
196
198
  None then the Python :func:`print` function is used.
197
199
  specify :data:`~ae.base.UNSET` to suppress any printing/logging output.
198
- :param shell: pass True to execute command in the default OS shell (for more info check the
199
- documentation of the parameter :paramref:`~subprocess.run.shell` of the
200
- :func:`subprocess.run` function).
201
- :param env_vars: OS shell environment variables to be used instead of the console/bash defaults.
202
- if not specified or `UNSET` then an isolated dict copy of :attr:`os.environ` will get
203
- passed onto :func:`subprocess.run` in order to avoid potential runtime errors, if the
204
- parent process (or a concurrent thread) modifies :attr:`os.environ` during the creation
205
- of the subprocess. Additionally, with the convertion of the special :class:`_Environ`
206
- object into a standard dict, the execution will result slightly faster and avoids
207
- any special method overrides interfering with the child process creation. it also is
208
- preventing rare errors with multithread-processes that try to change OS env variable
209
- (e.g. Conda in relation with pip could lead to raise a `RuntimeError: dictionary
210
- changed size during iteration` exception).
211
- specify `None` in order to use the original/unisolated :attr:`os.environ` object.
212
- :param err_redirect: this argument controls if and how the output of the executed command onto the console
213
- stderr stream gets captured/redirected. it gets passed directly onto the
214
- :paramref:`~subprocess.run.stderr` argument of :func:`subprocess.run` function.
215
- if the argument of :paramref:`.output_lines` is a list, and you specified the
216
- argument value :data:`subprocess.PIPE`, then the stderr output will get added at the
217
- end of this list (enclosed between the list items :data:`STDERR_BEG_MARKER` and
218
- :data:`STDERR_END_MARKER`). if the argument of :paramref:`.output_lines` is a
219
- list, and you specified the argument value :data:`subprocess.STDOUT` then the stderr
220
- output will get merged without any markers into this list in the order they get printed.
221
- specify :data:`subprocess.DEVNULL` to hide any stderr output onto the console/shell
222
- as well as in the :paramref:`.output_lines` list. if you specify `None`
223
- (the default argument) then the stderr output will be printed only on the console.
224
- :param decoder_splitter: callable to decode and split the output from the stdout/stderr streams into a list
200
+ :param decoder_splitter: callable to decode and split the output from the `stdout`/`stderr` streams into a list
225
201
  of string chunks/lines, to be added to and returned by :paramref:`.output_lines`.
202
+ :param run_kwargs: kwargs to be passed onto :func:`subprocess.run`, most of them unchanged, like e.g.
203
+ :paramref:`~subprocess.run.input`. some of them, will get adopted/changed before
204
+ they get passed onto :func:`subprocess.run`:
205
+
206
+ * :paramref:`~subprocess.run.check`: will get passed onto :func:`subprocess.run`
207
+ as `True` if not specified in this kwarg (in order to catch and log the
208
+ :class:`subprocess.CalledProcessError` exception in debug mode).
209
+ * :paramref:`~subprocess.Popen.env`: shell environment variables to be used instead of
210
+ the currently set OS shell variable values.
211
+ only if the value of this argument does not get specified or has the value `UNSET`,
212
+ then an isolated dict copy of :attr:`os.environ` will get passed onto
213
+ :func:`subprocess.run` in order to avoid potential runtime errors, caused by a parent
214
+ process (or a concurrent thread) if it modifies :attr:`os.environ` during the creation
215
+ of this subprocess. additionally, with the convertion of the special :class:`_Environ`
216
+ object into a standard dict, the execution will result slightly faster and avoids
217
+ any special method overrides interfering with the child process creation. it also is
218
+ preventing rare errors with multithread-processes that try to change OS env variable
219
+ (e.g. Conda in relation with pip could lead to raise a `RuntimeError: dictionary
220
+ changed size during iteration` exception). specify `None` as the :paramref:`.env`
221
+ value in order to use the original/unisolated :attr:`os.environ` object.
222
+ * :paramref:`~subprocess.Popen.stdout`: will be passed to :func:`subprocess.run`
223
+ as :data:`subprocess.PIPE` (instead of None) if a list instance got specified
224
+ as the :paramref:`.output_lines` argument.
225
+
226
226
  :return: return code of the executed command or 126 if execution raised any other exception.
227
227
  """
228
- if shell:
229
- all_args: str | list[str] = command_line + (" " + " ".join(extra_args) if extra_args else "")
230
- else:
231
- all_args = shlex.split(command_line) + list(extra_args)
232
- masked_args = mask_token(all_args)
228
+ masked_args = mask_token(cmd_args)
229
+
233
230
  if app_obj is None:
234
231
  app_obj = main_app_instance()
235
232
  print_out = dummy_function if app_obj is UNSET else app_obj.print_out if isinstance(app_obj, AppBase) else print
236
233
  debug_out = dummy_function if app_obj is UNSET else app_obj.debug_out if isinstance(app_obj, AppBase) else print
237
- if env_vars is UNSET:
238
- env_vars = os.environ.copy()
234
+
235
+ run_kwargs.setdefault('check', True)
236
+ if run_kwargs.get('env', UNSET) is UNSET:
237
+ run_kwargs['env'] = os.environ.copy()
238
+ if isinstance(output_lines, list):
239
+ run_kwargs.setdefault('stdout', subprocess.PIPE)
239
240
 
240
241
  debug_out(f" . executing {masked_args} at {os.getcwd()=} in {os_env_venv()=}")
241
- result: subprocess.CompletedProcess | subprocess.CalledProcessError # having: stdout/stderr/returncode
242
+
243
+ result: subprocess.CompletedProcess | subprocess.CalledProcessError # having: stdout/stderr/returncode
242
244
  try:
243
- result = subprocess.run(all_args,
244
- stdout=subprocess.PIPE if isinstance(output_lines, list) else None,
245
- stderr=err_redirect,
246
- input=console_input.encode(),
247
- check=True,
248
- shell=shell,
249
- env=env_vars)
245
+ result = subprocess.run(cmd_args, **run_kwargs) # pylint: disable=subprocess-run-check
250
246
  except subprocess.CalledProcessError as exc:
251
247
  debug_out(f"**** subprocess.run({masked_args}) returned non-zero exit code {exc.returncode}; {exc=}")
252
248
  result = exc
@@ -257,7 +253,7 @@ def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: st
257
253
  if isinstance(output_lines, list):
258
254
  if result.stdout:
259
255
  output_lines.extend(decoder_splitter(result.stdout))
260
- if err_redirect == subprocess.PIPE and result.stderr:
256
+ if result.stderr and run_kwargs.get('stderr', None) == subprocess.PIPE:
261
257
  output_lines.append(STDERR_BEG_MARKER)
262
258
  output_lines.extend(decoder_splitter(result.stderr))
263
259
  output_lines.append(STDERR_END_MARKER)
@@ -265,36 +261,48 @@ def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: st
265
261
  return result.returncode
266
262
 
267
263
 
268
- # pylint: disable-next=too-many-arguments,too-many-positional-arguments
269
- def sh_exit_if_exec_err(err_code: int, command_line: str,
270
- extra_args: Iterable[str] = (), output_lines: list[str] | None = None, exit_on_err: bool = True,
271
- exit_msg: str = "", app_obj: ConsoleApp | UnsetType | None = None, shell: bool = False,
272
- env_vars: dict[str, str] | UnsetType | None = UNSET,
273
- err_redirect: int | None = subprocess.DEVNULL,
274
- decoder_splitter: Callable[[bytes], list[str]] = output_line_split) -> int:
275
- """ execute command in the current working directory, optionally capturing console output and exit app on error.
276
-
277
- :param err_code: error code to pass to the console as exit code if the command set an error code and
278
- value of the :paramref:`.exit_on_err` argument is `True`.
279
- :param command_line: command line string to execute. this argument could contain additional command line
280
- arguments, separated by whitespace characters. alternatively use the argument
281
- :paramref:`.extra_args` which allows to pass command line argument values,
282
- with containing space characters.
283
- :param extra_args: optional iterable of extra command line arguments.
284
- :param output_lines: optional list extended with the lines printed to stdout/stderr on execution.
285
- :param exit_on_err: pass False to not exit the app on error.
286
- :param exit_msg: additional text to print on stdout/console if the app debug level is greater or equal
287
- to 1 (:data:`~ae.core.DEBUG_LEVEL_ENABLED`) or if an error occurred.
288
- :param app_obj: :class:`~ae.console.ConsoleApp` instance, used for logging/force-ignorable error.
289
- :param shell: pass True to execute command in the default OS shell (see :func:`sh_exec`).
290
- :param env_vars: OS shell environment variables to be used instead of the console/bash defaults.
291
- :param err_redirect: control how the output on stderr gets captured, redirected and returned. see also
292
- :paramref:`sh_exec.err_redirect` for more details to the supported argument values.
293
- if this argument is not specified or has the value :data:`subprocess.DEVNULL` then the
294
- stderr output will get suppressed on the console and will also not get added to the
295
- list argument in :paramref:`.output_lines`.
296
- :param decoder_splitter: callable to decode and split the output from the stdout/stderr streams into a list
264
+ def run_logged_cmd(err_code: int, *cmd_args: str,
265
+ output_lines: list[str] | None = None,
266
+ exit_on_err: bool | str = True,
267
+ app_obj: ConsoleApp | UnsetType | None = None,
268
+ decoder_splitter: Callable[[bytes], list[str]] = output_line_split,
269
+ **run_kwargs) -> int:
270
+ """ run command in the current working directory, optionally capturing/logging console output and exiting on error.
271
+
272
+ :param err_code: error code to be passed onto the console as exit code if the command set an error code
273
+ and the value of the :paramref:`.exit_on_err` argument is `True` or a nonempty string.
274
+ :param cmd_args: command line string or a sequence of command name and separate line arguments
275
+ to be run/executed on the console/shell.
276
+ :param output_lines: optional list extended with the lines printed to `stdout`/`stderr` on execution.
277
+ see the :paramref:`~run_cmd.output_lines` argument of :func:`run_cmd` for more details.
278
+ :param exit_on_err: specifying `True` (the default) or a nonempty string will shut-down/quit/exit the
279
+ currently running app (the Python interpreter) if the executed command set an
280
+ error code. a nonempty string will get printed/logged to `stdout` before the exit.
281
+ pass `False` or an empty string to not exit the app if a command error occurred.
282
+ :param app_obj: optional :class:`~ae.console.ConsoleApp` instance, used for logging and error checking
283
+ (with the option to ignore errors if the app got started with the `--force` option).
284
+ if not specified or None and if :func:`~ae.core.main_app_instance()` returns
285
+ None then the Python :func:`print` function is used for logging.
286
+ specify :data:`~ae.base.UNSET` to suppress any printing/logging output.
287
+ :param decoder_splitter: callable to decode and split the output from the `stdout`/`stderr` streams into a list
297
288
  of string chunks/lines, to be added to and returned by :paramref:`.output_lines`.
289
+ :param run_kwargs: extra kwargs to be passed onto :func:`run_cmd` and :func:`subprocess.run`. some kwargs
290
+ like e.g. :paramref:`~subprocess.run.input` and :paramref:`~subprocess.Popen.shell`
291
+ will get passed unchanged onto :func:`run_cmd`, others like
292
+ :paramref:`~run_cmd.env` or :paramref:`~run_cmd.stderr`, will be first processed by
293
+ this function and/or :func:`run_cmd` before they get passed onto :func:`subprocess.run`:
294
+
295
+ * :paramref:`~subprocess.Popen.stderr`: controls how the output onto `stderr` will get
296
+ captured, redirected and/or returned. if this argument is not specified then the value
297
+ :data:`subprocess.DEVNULL` will get passed onto :func:`run_cmd` and
298
+ :func:`subprocess.run`, which is suppressing any output sent onto `stderr` on the
299
+ console as well as any addition of it to the list argument specified in
300
+ :paramref:`.output_lines`. if you specify the value :data:`subprocess.PIPE` or
301
+ :data:`subprocess.STDOUT` together with a list in the :paramref:`.output_lines`
302
+ argument, then any output onto `stderr` will get added to this list.
303
+ see also the description of the :paramref:`~run_cmd.output_lines` parameter of
304
+ :func:`run_cmd` for more details on the supported values of this parameter.
305
+
298
306
  :return: 0 on success, or if an error occurred the error number set by the executed command.
299
307
  """
300
308
  if output_lines is None:
@@ -302,22 +310,25 @@ def sh_exit_if_exec_err(err_code: int, command_line: str,
302
310
  output_len = 0
303
311
  else:
304
312
  output_len = len(output_lines)
313
+
305
314
  if app_obj is None:
306
- app_obj = cast(ConsoleApp, main_app_instance()) # calls app_obj./ConsoleApp.chk() method
315
+ app_obj = cast(ConsoleApp, main_app_instance()) # calls ConsoleApp./app_obj.chk() method
316
+
317
+ run_kwargs.setdefault('stderr', subprocess.DEVNULL)
307
318
 
308
- sh_err = sh_exec(command_line, extra_args=extra_args, output_lines=output_lines, app_obj=app_obj, shell=shell,
309
- env_vars=env_vars, err_redirect=err_redirect, decoder_splitter=decoder_splitter)
319
+ sh_err = run_cmd(*cmd_args, output_lines=output_lines, app_obj=app_obj, decoder_splitter=decoder_splitter,
320
+ **run_kwargs)
310
321
 
311
322
  if isinstance(app_obj, ConsoleApp) and (app_obj.debug or sh_err and exit_on_err):
312
323
  for line in output_lines[output_len:]:
313
324
  if app_obj.verbose or not line.startswith("LOG: "): # if verbose show mypy's endless (stderr) log entries
314
325
  app_obj.po(" " * 6 + line)
315
- command = mask_token(f"{command_line} " + " ".join('"' + _a + '"' if " " in _a else _a for _a in extra_args))
326
+ command = mask_token(cmd_args)
316
327
  if sh_err == 0:
317
328
  app_obj.dpo(f" = successfully executed {command=}")
318
329
  else:
319
- if exit_msg:
320
- app_obj.po(f" {exit_msg}")
321
- app_obj.chk(err_code, not exit_on_err, f"sh_exit_if_exec_err({err_code}, {command!r}) error {sh_err}")
330
+ if isinstance(exit_on_err, str):
331
+ app_obj.po(f" {exit_on_err}")
332
+ app_obj.chk(err_code, not bool(exit_on_err), f"run_logged_cmd({err_code}, {command!r}) error {sh_err}")
322
333
 
323
334
  return sh_err
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ae_shell
3
- Version: 0.3.17
3
+ Version: 0.3.19
4
4
  Summary: ae namespace module portion shell: shell execution and environment helpers
5
5
  Home-page: https://gitlab.com/ae-group/ae_shell
6
6
  Author: AndiEcker
@@ -64,13 +64,13 @@ Dynamic: summary
64
64
 
65
65
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.110 -->
66
66
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.33 -->
67
- # shell 0.3.17
67
+ # shell 0.3.19
68
68
 
69
69
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/develop?logo=python)](
70
70
  https://gitlab.com/ae-group/ae_shell)
71
71
  [![LatestPyPIrelease](
72
- https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.17?logo=python)](
73
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.17)
72
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.19?logo=python)](
73
+ https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.19)
74
74
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](
75
75
  https://pypi.org/project/ae-shell/#history)
76
76
 
@@ -1,4 +1,4 @@
1
- # THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.102
1
+ # THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.104
2
2
  [build-system]
3
3
  requires = ["setuptools>=42", "wheel"]
4
4
  build-backend = "setuptools.build_meta"
@@ -1,4 +1,4 @@
1
- # THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.102
1
+ # THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.104
2
2
  """ setup of ae namespace module portion shell: shell execution and environment helpers. """
3
3
  import pathlib
4
4
  import sys
@@ -77,7 +77,7 @@ setup_kwargs: dict[str, Any] = {
77
77
  },
78
78
  'python_requires': '>=3.12',
79
79
  'url': 'https://gitlab.com/ae-group/ae_shell',
80
- 'version': '0.3.17',
80
+ 'version': '0.3.19',
81
81
  'zip_safe': True,
82
82
  }
83
83
 
@@ -5,7 +5,7 @@ import sys
5
5
  import subprocess
6
6
  from unittest.mock import PropertyMock, patch
7
7
 
8
- from ae.base import UNSET, camel_to_snake, norm_name, os_path_join, write_file
8
+ from ae.base import UNSET, URI_SVC_SEP, camel_to_snake, norm_name, os_path_join, write_file
9
9
  from ae.system import load_env_var_defaults, os_env_venv
10
10
  from ae.core import DEBUG_LEVEL_DISABLED, DEBUG_LEVEL_ENABLED, DEBUG_LEVEL_VERBOSE
11
11
  from ae.console import MAIN_SECTION_NAME, ConsoleApp
@@ -14,7 +14,7 @@ from ae.console import MAIN_SECTION_NAME, ConsoleApp
14
14
  from ae.shell import (
15
15
  STDERR_BEG_MARKER, STDERR_END_MARKER,
16
16
  debug_or_verbose, get_domain_user_var, hint, in_os_env, mask_token,
17
- output_line_split, output_zero_split, sh_exec, sh_exit_if_exec_err)
17
+ output_line_split, output_zero_split, run_cmd, run_logged_cmd)
18
18
 
19
19
 
20
20
  class TestHelpers:
@@ -221,7 +221,7 @@ class TestHelpers:
221
221
  assert os.environ == os_env
222
222
 
223
223
  def test_mask_token(self):
224
- url_prefix_str = "https://" # PDV_REPO_HOST_PROTOCOL
224
+ url_prefix_str = f"https{URI_SVC_SEP}" # PDV_REPO_HOST_PROTOCOL
225
225
 
226
226
  token = "codeberg token does not have a prefix and only contains hex-digits followed by @ and codeberg.org"
227
227
  text = f"a text block containing a codeberg URL with a token: {url_prefix_str}UsaNäm:{token}@codeberg.org"
@@ -282,68 +282,68 @@ class TestShellExecutions:
282
282
  assert output_zero_split(STDOUT_LINE + b"\n" + STDERR_LINE + b"\r\n\r\0\r") == [
283
283
  STDOUT_LINE.decode() + "\n" + STDERR_LINE.decode() + "\r\n\r"]
284
284
 
285
- def test_sh_exec_catch_any_exception(self, capsys):
285
+ def test_run_cmd_catch_any_exception(self, capsys):
286
286
  with patch("subprocess.run", side_effect=Exception('broad tst exception')):
287
- assert sh_exec('any_cmd') == (126, )[0]
287
+ assert run_cmd('any_cmd') == (126, )[0]
288
288
 
289
289
  output = capsys.readouterr().out
290
290
  assert 'any_cmd' in output
291
291
  assert " raised exception " in output
292
292
  assert 'broad tst exception' in output
293
293
 
294
- def test_sh_exec_catch_exit_code_exception(self, capsys):
295
- assert sh_exec("exit 69", shell=True) == 69
294
+ def test_run_cmd_catch_exit_code_exception(self, capsys):
295
+ assert run_cmd("exit 69", shell=True) == 69
296
296
 
297
297
  output = capsys.readouterr().out
298
298
  assert " returned non-zero exit code " in output
299
299
 
300
- assert sh_exec(sys.executable, extra_args=["-c", "import sys; sys.exit(96)"]) == 96
300
+ assert run_cmd(sys.executable, "-c", "import sys; sys.exit(96)") == 96
301
301
 
302
302
  output = capsys.readouterr().out
303
303
  assert " returned non-zero exit code 96" in output
304
304
 
305
- def test_sh_exec_console_output(self, capsys, cons_app):
306
- sh_exec('any command')
305
+ def test_run_cmd_console_output(self, capsys, cons_app):
306
+ run_cmd('any command')
307
307
 
308
308
  out, err = capsys.readouterr()
309
- assert " . executing ['any', 'command']" in out
309
+ assert " . executing ['any command']" in out
310
310
  assert os.getcwd() in out
311
311
  assert os_env_venv() in out
312
- assert "**** subprocess.run(['any', 'command']) raised exception" in out
312
+ assert "**** subprocess.run(['any command']) raised exception" in out
313
313
  assert err == ""
314
314
 
315
315
  with patch('ae.console.ConsoleApp.debug_level', new_callable=PropertyMock, return_value=DEBUG_LEVEL_DISABLED):
316
- sh_exec('any command')
316
+ run_cmd('any command')
317
317
 
318
318
  out, err = capsys.readouterr()
319
319
  assert " . executing" not in out
320
- assert "**** subprocess.run(['any', 'command']) raised exception" in out
320
+ assert "**** subprocess.run(['any command']) raised exception" in out
321
321
  assert err == ""
322
322
 
323
323
  with patch('ae.console.ConsoleApp.debug_level', new_callable=PropertyMock, return_value=DEBUG_LEVEL_DISABLED):
324
- sh_exec('any command', app_obj=cons_app) # strange: patch('ae.core.AppBase.debug_level') does not work
324
+ run_cmd("any", "command", app_obj=cons_app) # strange: patch('ae.core.AppBase.debug_level') does not work
325
325
 
326
326
  out, err = capsys.readouterr()
327
327
  assert " . executing" not in out
328
328
  assert "**** subprocess.run(['any', 'command']) raised exception" in out
329
329
  assert err == ""
330
330
 
331
- sh_exec('any command', app_obj=UNSET)
331
+ run_cmd('any command', app_obj=UNSET)
332
332
 
333
333
  out, err = capsys.readouterr()
334
334
  assert out == ""
335
335
  assert err == ""
336
336
 
337
- def test_sh_exec_console_output_shell(self, capsys):
338
- ret = sh_exec("echo hello world", shell=True)
337
+ def test_run_cmd_console_output_shell(self, capsys):
338
+ ret = run_cmd("echo hello world", shell=True)
339
339
 
340
340
  out, err = capsys.readouterr()
341
341
  assert ret == 0
342
- assert " . executing echo hello world at " in out
342
+ assert " . executing ['echo hello world'] at " in out
343
343
  assert out.count("hello world") == 1 # strange: capsys does not get the echo command output w/ shell=True arg
344
344
  assert err == ""
345
345
 
346
- ret = sh_exec("echo hello world", app_obj=UNSET, shell=True)
346
+ ret = run_cmd("echo hello world", app_obj=UNSET, shell=True)
347
347
 
348
348
  out, err = capsys.readouterr()
349
349
  assert ret == 0
@@ -351,60 +351,43 @@ class TestShellExecutions:
351
351
  assert err == ""
352
352
 
353
353
  @patch.object(subprocess, 'run', autospec=True)
354
- def test_sh_exec_run_args(self, mock_method):
354
+ def test_run_cmd_run_args(self, mock_method):
355
355
  cmd_line = "cmd arg1 arg2"
356
356
  extra_args = ['extra_arg1', 'extra_arg2']
357
357
  env = os.environ.copy()
358
358
 
359
- sh_exec(cmd_line, tuple(extra_args))
360
-
361
- mock_method.assert_called_with(
362
- shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'', check=True, shell=False, env=env)
363
-
364
- sh_exec(cmd_line, shell=True)
359
+ run_cmd(*(shlex.split(cmd_line) + extra_args))
365
360
 
366
- mock_method.assert_called_with(
367
- cmd_line, stdout=None, stderr=None, input=b'', check=True, shell=True, env=env)
361
+ mock_method.assert_called_with(tuple(shlex.split(cmd_line) + extra_args), check=True, env=env)
368
362
 
369
- sh_exec(cmd_line, {_: "any" for _ in extra_args}, shell=True, err_redirect=subprocess.DEVNULL)
363
+ run_cmd(cmd_line, shell=True)
370
364
 
371
- mock_method.assert_called_with(
372
- cmd_line + " " + " ".join(extra_args), stdout=None, stderr=subprocess.DEVNULL, input=b'', check=True,
373
- shell=True, env=env)
365
+ mock_method.assert_called_with((cmd_line, ), check=True, shell=True, env=env)
374
366
 
375
- sh_exec(cmd_line, extra_args, console_input='con_inp')
367
+ run_cmd(cmd_line, *extra_args, input='con_inp')
376
368
 
377
- mock_method.assert_called_with(
378
- shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'con_inp',
379
- check=True, shell=False, env=env)
369
+ mock_method.assert_called_with((cmd_line, ) + tuple(extra_args), input='con_inp', check=True, env=env)
380
370
 
381
- sh_exec(cmd_line, extra_args, output_lines=[])
371
+ run_cmd(cmd_line, *extra_args, output_lines=[])
382
372
 
383
- mock_method.assert_called_with(
384
- shlex.split(cmd_line) + extra_args, stdout=subprocess.PIPE, stderr=None, input=b'',
385
- check=True, shell=False, env=env)
373
+ mock_method.assert_called_with((cmd_line, ) + tuple(extra_args), check=True, stdout=subprocess.PIPE, env=env)
386
374
 
387
- sh_exec(cmd_line, extra_args, console_input='con_inp', output_lines=[], err_redirect=subprocess.STDOUT)
375
+ run_cmd(cmd_line, *extra_args, input='con_inp', output_lines=[], stderr=subprocess.STDOUT)
388
376
 
389
- mock_method.assert_called_with(
390
- shlex.split(cmd_line) + extra_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, input=b'con_inp',
391
- check=True, shell=False, env=env)
377
+ mock_method.assert_called_with((cmd_line, ) + tuple(extra_args), input='con_inp',
378
+ stderr=subprocess.STDOUT, stdout=subprocess.PIPE, check=True, env=env)
392
379
 
393
380
  env_vars = {'A': "1", 'C': "tst_string value"}
394
381
 
395
- sh_exec(cmd_line, extra_args, env_vars=env_vars)
382
+ run_cmd(cmd_line, *extra_args, env=env_vars)
396
383
 
397
- mock_method.assert_called_with(
398
- shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'',
399
- check=True, shell=False, env=env_vars)
384
+ mock_method.assert_called_with((cmd_line, ) + tuple(extra_args), env=env_vars, check=True)
400
385
 
401
- sh_exec(cmd_line, extra_args, env_vars=None)
386
+ run_cmd(cmd_line, *extra_args, env=None)
402
387
 
403
- mock_method.assert_called_with(
404
- shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'',
405
- check=True, shell=False, env=None)
388
+ mock_method.assert_called_with((cmd_line, ) + tuple(extra_args), check=True, env=None)
406
389
 
407
- def test_sh_exec_run_returned_values(self):
390
+ def test_run_cmd_run_returned_values(self):
408
391
  def _run_return(*_args, **_kwargs):
409
392
  """ mock to simulate subprocess.run return object. """
410
393
  class _Return:
@@ -416,7 +399,7 @@ class TestShellExecutions:
416
399
  with patch('ae.shell.subprocess.run', new=_run_return): # @patch.object(subprocess, 'run', new=_run_return)
417
400
  output_lines = []
418
401
 
419
- assert sh_exec("cmd_line", output_lines=output_lines, err_redirect=subprocess.PIPE) == RETURN_CODE
402
+ assert run_cmd("cmd_line", output_lines=output_lines, stderr=subprocess.PIPE) == RETURN_CODE
420
403
 
421
404
  assert output_lines[0] == STDOUT_LINE.decode()
422
405
  assert output_lines[1] == STDERR_BEG_MARKER
@@ -425,7 +408,7 @@ class TestShellExecutions:
425
408
 
426
409
  output_lines = []
427
410
 
428
- assert sh_exec("cmd_line", output_lines=output_lines) == RETURN_CODE
411
+ assert run_cmd("cmd_line", output_lines=output_lines) == RETURN_CODE
429
412
 
430
413
  assert len(output_lines) == 1
431
414
  assert output_lines[0] == STDOUT_LINE.decode()
@@ -435,7 +418,7 @@ class TestShellExecutions:
435
418
 
436
419
  output_lines = ['first line', 'second line']
437
420
 
438
- assert sh_exec("cmd_line", output_lines=output_lines, err_redirect=subprocess.STDOUT) == RETURN_CODE
421
+ assert run_cmd("cmd_line", output_lines=output_lines, err_redirect=subprocess.STDOUT) == RETURN_CODE
439
422
 
440
423
  assert len(output_lines) == 3
441
424
  assert output_lines[0] == 'first line'
@@ -445,11 +428,10 @@ class TestShellExecutions:
445
428
  assert STDERR_BEG_MARKER not in output_lines
446
429
  assert STDERR_END_MARKER not in output_lines
447
430
 
448
- def test_sh_exec_stderr_redirection(self, capfd, cons_app): # pytest/capsys replaces sys.stdout/.stderr
449
- args = {'command_line': sys.executable,
450
- 'extra_args': ["-c", "import sys; print('tst_std_err', file=sys.stderr); print('tst_std_out')"]}
431
+ def test_run_cmd_stderr_redirection(self, capfd, cons_app): # pytest/capsys replaces sys.stdout/.stderr
432
+ args = [sys.executable, "-c", "import sys; print('tst_std_err', file=sys.stderr); print('tst_std_out')"]
451
433
 
452
- assert sh_exec(**args) == 0
434
+ assert run_cmd(*args) == 0
453
435
 
454
436
  out, err = capfd.readouterr()
455
437
  assert out.count('tst_std_out') == 2
@@ -458,7 +440,7 @@ class TestShellExecutions:
458
440
 
459
441
  redirected = []
460
442
 
461
- assert sh_exec(**args, output_lines=redirected) == 0
443
+ assert run_cmd(*args, output_lines=redirected) == 0
462
444
 
463
445
  out, err = capfd.readouterr()
464
446
  assert out.count('tst_std_out') == 1
@@ -468,7 +450,7 @@ class TestShellExecutions:
468
450
 
469
451
  redirected = []
470
452
 
471
- assert sh_exec(**args, output_lines=redirected, err_redirect=subprocess.DEVNULL) == 0
453
+ assert run_cmd(*args, output_lines=redirected, stderr=subprocess.DEVNULL) == 0
472
454
 
473
455
  out, err = capfd.readouterr()
474
456
  assert err == ''
@@ -476,7 +458,7 @@ class TestShellExecutions:
476
458
 
477
459
  redirected = []
478
460
 
479
- assert sh_exec(**args, output_lines=redirected, err_redirect=subprocess.PIPE) == 0
461
+ assert run_cmd(*args, output_lines=redirected, stderr=subprocess.PIPE) == 0
480
462
 
481
463
  out, err = capfd.readouterr()
482
464
  assert err == ''
@@ -484,18 +466,17 @@ class TestShellExecutions:
484
466
 
485
467
  redirected = []
486
468
 
487
- assert sh_exec(**args, output_lines=redirected, err_redirect=subprocess.STDOUT) == 0
469
+ assert run_cmd(*args, output_lines=redirected, stderr=subprocess.STDOUT) == 0
488
470
 
489
471
  out, err = capfd.readouterr()
490
472
  assert err == ''
491
473
  assert redirected == ['tst_std_err', 'tst_std_out']
492
474
 
493
- def test_sh_exit_if_exec_err_any_command(self, capsys, cons_app):
475
+ def test_run_logged_cmd_any_command(self, capsys, cons_app):
494
476
  output = ['old output']
495
477
 
496
478
  with patch('ae.console.ConsoleApp.debug', new_callable=PropertyMock, return_value=True): # with app_obj kwarg
497
- ret = sh_exit_if_exec_err(0, 'git --version', output_lines=output,
498
- exit_on_err=False, exit_msg='tst exit message', app_obj=cons_app)
479
+ ret = run_logged_cmd(0, "git", "--version", output_lines=output, exit_on_err=False, app_obj=cons_app)
499
480
 
500
481
  assert ret == 0
501
482
  assert len(output) >= 2
@@ -503,49 +484,46 @@ class TestShellExecutions:
503
484
  assert isinstance(output[1], str) # e.g. == 'git version 2.55.0'
504
485
  out, err = capsys.readouterr()
505
486
  assert 'old output' not in out
506
- assert 'git --version' in out
507
- assert 'tst exit message' not in out
487
+ assert "\n . executing ['git', '--version'] at os.getcwd()='" in out
508
488
  assert err == ""
509
489
 
510
- def test_sh_exit_if_exec_err_any_invalid_command(self, capsys, cons_app, patched_shutdown_wrapper):
511
- ret = sh_exit_if_exec_err(693, 'tst_command_line', exit_on_err=False, exit_msg='tst exit message')
490
+ def test_run_logged_cmd_any_invalid_command(self, capsys, cons_app, patched_shutdown_wrapper):
491
+ ret = run_logged_cmd(693, 'tst_command_line', exit_on_err=False)
512
492
 
513
493
  assert ret == (126, )[0]
514
494
  out, err = capsys.readouterr()
515
495
  assert out.count('tst_command_line') == 3
516
- assert 'tst exit message' in out
517
496
  assert err == ""
518
497
 
519
- def test_sh_exit_if_exec_err_caught_shutdown_exception(self, capsys, cons_app, patched_shutdown_wrapper):
520
- ret = patched_shutdown_wrapper(sh_exit_if_exec_err, 693, 'tst_command_line', exit_msg='tst exit message')
498
+ def test_run_logged_cmd_caught_shutdown_exception(self, capsys, cons_app, patched_shutdown_wrapper):
499
+ ret = patched_shutdown_wrapper(run_logged_cmd, 693, 'tst_command_line', exit_on_err='tst exit message')
521
500
 
522
501
  assert len(ret) == 1
523
502
  assert ret[0]['exit_code'] == 693 # 1st arg == error code
524
503
  assert 'tst_command_line' in ret[0]['error_message']
525
- assert 'sh_exit_if_exec_err(' in ret[0]['error_message']
504
+ assert 'run_logged_cmd(' in ret[0]['error_message']
526
505
  assert "(693, " in ret[0]['error_message']
527
506
  out, err = capsys.readouterr()
528
507
  assert out.count('tst_command_line') == 3
529
508
  assert 'tst exit message' in out
530
509
  assert err == ""
531
510
 
532
- def test_sh_exit_if_exec_err_exception(self, capsys, cons_app):
511
+ def test_run_logged_cmd_exception(self, capsys, cons_app):
533
512
  output = ['old output']
534
513
 
535
- ret = sh_exit_if_exec_err(693, "", output_lines=output, exit_on_err=False, exit_msg='tst exit message')
514
+ ret = run_logged_cmd(693, "", output_lines=output, exit_on_err=False)
536
515
 
537
516
  assert ret == (126, )[0]
538
517
  assert output == ['old output']
539
518
  out, err = capsys.readouterr()
540
- assert f". executing [] at os.getcwd()='{os.getcwd()}' in os_env_venv()='{os_env_venv()}'" in out
541
- assert 'tst exit message' in out
519
+ assert f". executing [''] at os.getcwd()='{os.getcwd()}' in os_env_venv()='{os_env_venv()}'" in out
542
520
  assert err == ""
543
521
 
544
- def test_sh_exit_if_exec_err_with_app(self, capsys, cons_app):
522
+ def test_run_logged_cmd_with_app(self, capsys, cons_app):
545
523
  output = []
546
524
 
547
525
  with patch('ae.console.ConsoleApp.debug', new_callable=PropertyMock, return_value=False):
548
- ret = sh_exit_if_exec_err(0, "_err", output_lines=output, exit_on_err=False, err_redirect=subprocess.STDOUT)
526
+ ret = run_logged_cmd(0, "_err", output_lines=output, exit_on_err=False, stderr=subprocess.STDOUT)
549
527
 
550
528
  assert ret == (126, )[0]
551
529
  assert output == []
@@ -553,26 +531,24 @@ class TestShellExecutions:
553
531
  assert out.count('_err') == 3
554
532
  assert err == ""
555
533
 
556
- def test_sh_exit_if_exec_err_with_app_kwarg_and_exit_msg(self, capsys, cons_app):
534
+ def test_run_logged_cmd_with_app_kwarg_and_empty_exit_on_err_msg(self, capsys, cons_app):
557
535
  output = []
558
536
 
559
537
  with patch('ae.console.ConsoleApp.debug', new_callable=PropertyMock, return_value=True): # with app_obj kwarg
560
- ret = sh_exit_if_exec_err(0, 'error-command', output_lines=output,
561
- exit_on_err=False, exit_msg='tst exit message', app_obj=cons_app)
538
+ ret = run_logged_cmd(0, 'error-command', output_lines=output, exit_on_err="", app_obj=cons_app)
562
539
 
563
540
  assert ret == (126, )[0]
564
541
  assert output == []
565
542
  out, err = capsys.readouterr()
566
- assert out.count('error-command') == 3
567
- assert 'tst exit message' in out
543
+ assert out.count('error-command') == 3 # extra empty line
568
544
  assert err == ""
569
545
 
570
- def test_sh_exit_if_exec_err_with_app_extending_output(self, capsys, cons_app):
546
+ def test_run_logged_cmd_with_app_extending_output(self, capsys, cons_app):
571
547
  output = ['any old line output']
572
548
 
573
- with (patch('ae.shell.sh_exec', new=lambda *_, **kwargs: kwargs['output_lines'].append('new out line') or 0),
549
+ with (patch('ae.shell.run_cmd', new=lambda *_, **kwargs: kwargs['output_lines'].append('new out line') or 0),
574
550
  patch('ae.console.ConsoleApp.debug', new_callable=PropertyMock, return_value=True)):
575
- ret = sh_exit_if_exec_err(369, "any_cmd", output_lines=output) # extended output_lines and ret==0
551
+ ret = run_logged_cmd(369, "any_cmd", output_lines=output) # extended output_lines and ret==0
576
552
 
577
553
  assert ret == 0
578
554
  assert output == ['any old line output', 'new out line']
@@ -583,14 +559,13 @@ class TestShellExecutions:
583
559
  assert out.count('any_cmd') == 1
584
560
  assert err == ""
585
561
 
586
- def test_sh_exit_if_exec_err_without_app(self, capsys):
562
+ def test_run_logged_cmd_without_app(self, capsys):
587
563
  output = []
588
564
 
589
- ret = sh_exit_if_exec_err(0, "_err_cmd", output_lines=output, exit_on_err=False, exit_msg='tst exit message')
565
+ ret = run_logged_cmd(0, "_err_cmd", output_lines=output, exit_on_err=False)
590
566
 
591
567
  assert ret == (126, )[0]
592
568
  assert output == []
593
569
  out, err = capsys.readouterr()
594
570
  assert out.count('_err_cmd') == 3
595
- assert 'tst exit message' not in out
596
571
  assert err == ""
File without changes