ae-shell 0.3.16__tar.gz → 0.3.17__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ae_shell
3
- Version: 0.3.16
3
+ Version: 0.3.17
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.16
67
+ # shell 0.3.17
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.16?logo=python)](
73
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.16)
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)
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.16
3
+ # shell 0.3.17
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.16?logo=python)](
9
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.16)
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)
10
10
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](
11
11
  https://pypi.org/project/ae-shell/#history)
12
12
 
@@ -5,17 +5,19 @@ shell execution and environment helpers
5
5
  this module is designed to provide a comprehensive set of constants and helper functions
6
6
  to manage shell printouts, OS environment variables and to execute shell commands.
7
7
 
8
- - :func:`debug_or_verbose`: checks if the application is running in debug or verbose mode.
9
- - :func:`get_domain_user_var`: retrieves an OS environment variable value for a specific domain and/or user.
10
- - :func:`hint`: provides a hint message for console printouts based on the provided arguments.
11
- - :func:`in_os_env`: context manager to temporarily add environment variables from the ``.env`` files onto `os.environ`.
12
- - :func:`mask_token`: hide/mask tokens in a text block, to prevent to show them in logs and printouts.
13
- - :func:`sh_exec`: execute command in the current working directory of the OS console/shell.
14
- - :func:`sh_exit_if_exec_err`: extended version of :func:`sh_exec` with automatically checks for errors
15
- after a command is executed and handles application termination gracefully.
16
-
17
- - :data:`STDERR_BEG_MARKER`: marker used in the console output for the beginning of merged-in stderr output.
18
- - :data:`STDERR_END_MARKER`: marker used in the console output for the end of merged-in stderr output.
8
+ * :func:`debug_or_verbose`: checks if the application is running in debug or verbose mode.
9
+ * :func:`get_domain_user_var`: retrieves an OS environment variable value for a specific domain and/or user.
10
+ * :func:`hint`: provides a hint message for console printouts based on the provided arguments.
11
+ * :func:`in_os_env`: context manager to temporarily add environment variables from the ``.env`` files onto `os.environ`.
12
+ * :func:`mask_token`: hide/mask tokens in a text block, to prevent to show them in logs and printouts.
13
+ * :func:`output_line_split`: decode and split the specified shell/console output streams into line chunks.
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
17
+ after a command is executed and handles application shutdown/termination gracefully.
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
21
  """
20
22
  import os
21
23
  import shlex
@@ -25,20 +27,20 @@ from collections.abc import Callable, Iterable, Iterator, MutableMapping
25
27
  from contextlib import contextmanager
26
28
  from typing import Any, cast, overload
27
29
 
28
- from ae.base import UNSET, dummy_function, env_str, norm_name # type: ignore
29
- from ae.system import active_venv, load_env_var_defaults # type: ignore
30
+ from ae.base import UNSET, UnsetType, dummy_function, env_str, norm_name # type: ignore
31
+ from ae.system import load_env_var_defaults, os_env_venv # type: ignore
30
32
  from ae.core import main_app_instance, AppBase # type: ignore
31
33
  from ae.console import MAIN_SECTION_NAME, ConsoleApp # type: ignore
32
34
 
33
35
 
34
- __version__ = '0.3.16'
36
+ __version__ = '0.3.17'
35
37
 
36
38
 
37
39
  STDERR_BEG_MARKER = 'vvv STDERR vvv' #: :paramref:`ae.shell.sh_exec.output_lines` begin stderr lines marker
38
40
  STDERR_END_MARKER = '^^^ STDERR ^^^' #: end stderr lines marker in :paramref:`ae.shell.sh_exec.output_lines`
39
41
 
40
42
 
41
- def debug_or_verbose(app_obj: ConsoleApp | None = None) -> bool:
43
+ def debug_or_verbose(app_obj: ConsoleApp | UnsetType | None = None) -> bool:
42
44
  """ determine if the current app runs in debug|verbose mode, while preventing early .get_option() call an app init.
43
45
 
44
46
  :param app_obj: optional ConsoleApp instance (def=main_app_instance()).
@@ -52,9 +54,11 @@ def debug_or_verbose(app_obj: ConsoleApp | None = None) -> bool:
52
54
  the debug mode can be activated via the :class:`~ae.console.ConsoleApp` option `debug_level`, specified either
53
55
  in a config file or via the command line options. the verbose mode get activated via the `more_verbose` option.
54
56
  """
55
- app_obj = app_obj or main_app_instance()
57
+ if app_obj is None:
58
+ app_obj = main_app_instance()
59
+
56
60
  return bool(
57
- not app_obj # prevent exception in early app startup and in test runs
61
+ not isinstance(app_obj, AppBase) # prevent exception in early app startup and unit test runs
58
62
  or app_obj.debug # main_app.debug_level > DEBUG_LEVEL_DISABLED
59
63
  or not isinstance(app_obj, ConsoleApp) # return True for pure ae.core.AppBase instances
60
64
  or not getattr(app_obj, '_parsed_arguments', None) # ConsoleApp._parsed_arguments args Namespace not created
@@ -101,10 +105,10 @@ def hint(command: str, action: Callable | str, message_suffix: str = "") -> str:
101
105
 
102
106
  @contextmanager
103
107
  def in_os_env(start_dir: str = "") -> Iterator[MutableMapping[str, str]]:
104
- """ temporarily add environment variables from the dotenv files that not exist in os.environ to it.
108
+ """ temporarily add environment variables from the ``.env`` files that not exist in :attr:`os.environ` to it.
105
109
 
106
110
  :param start_dir: path to the folder where the first dotenv file (with the highest priority) is stored.
107
- :return: yielding the os env variables that got added to os.environ in this temporary context.
111
+ :return: yielding the OS env variables that got added to os.environ in this temporary context.
108
112
  """
109
113
  loaded_env_vars = load_env_var_defaults(start_dir, os.environ)
110
114
  try:
@@ -122,6 +126,10 @@ def mask_token(text: str) -> str: ...
122
126
  def mask_token(text: list[str]) -> list[str]: ...
123
127
 
124
128
 
129
+ @overload
130
+ def mask_token(text: str | list[str]) -> str | list[str]: ...
131
+
132
+
125
133
  def mask_token(text: str | list[str]) -> str | list[str]:
126
134
  """ hide most parts of any Codeberg/GitHub/GitHub URL tokens found in the specified text/-lines.
127
135
 
@@ -150,10 +158,29 @@ def mask_token(text: str | list[str]) -> str | list[str]:
150
158
  return lines[0] if is_str_arg else lines
151
159
 
152
160
 
161
+ def output_line_split(output: bytes) -> list[str]:
162
+ """ decode and split the specified shell/console output streams into line chunks.
163
+
164
+ :param output: captured stdout/stderr output from an executed OS shell command.
165
+ :return: list of non-empty shell/console output lines, decoded into string.
166
+ """
167
+ return [line for line in output.decode().splitlines() if line.strip()]
168
+
169
+
170
+ def output_zero_split(output: bytes) -> list[str]:
171
+ """ decode and split the specified shell/console output streams separated by `NUL` (\\0) characters.
172
+
173
+ :param output: captured stdout/stderr output from an executed OS shell command (e.g. `env -0`).
174
+ :return: list of non-empty shell/console output chunks, decoded into string.
175
+ """
176
+ return [line for line in output.decode().split('\0') if line.strip()]
177
+
178
+
153
179
  # pylint: disable-next=too-many-arguments,too-many-positional-arguments
154
180
  def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: str = "",
155
- output_lines: list[str] | None = None, app_obj: AppBase | None = None, shell: bool = False,
156
- env_vars: dict[str, str] | None = None, err_redirect: int | None = None) -> int:
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:
157
184
  """ execute command in the current working directory of the OS console/shell.
158
185
 
159
186
  :param command_line: command line string to execute on the console/shell. could contain command line args
@@ -170,8 +197,18 @@ def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: st
170
197
  specify :data:`~ae.base.UNSET` to suppress any printing/logging output.
171
198
  :param shell: pass True to execute command in the default OS shell (for more info check the
172
199
  documentation of the parameter :paramref:`~subprocess.run.shell` of the
173
- :meth:`subprocess.run` function).
200
+ :func:`subprocess.run` function).
174
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.
175
212
  :param err_redirect: this argument controls if and how the output of the executed command onto the console
176
213
  stderr stream gets captured/redirected. it gets passed directly onto the
177
214
  :paramref:`~subprocess.run.stderr` argument of :func:`subprocess.run` function.
@@ -184,18 +221,23 @@ def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: st
184
221
  specify :data:`subprocess.DEVNULL` to hide any stderr output onto the console/shell
185
222
  as well as in the :paramref:`.output_lines` list. if you specify `None`
186
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
225
+ of string chunks/lines, to be added to and returned by :paramref:`.output_lines`.
187
226
  :return: return code of the executed command or 126 if execution raised any other exception.
188
227
  """
189
- all_args = command_line + (" " + " ".join(extra_args) if extra_args else "") if shell else (
190
- shlex.split(command_line) + list(extra_args))
191
- # noinspection PyTypeChecker
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)
192
232
  masked_args = mask_token(all_args)
193
233
  if app_obj is None:
194
234
  app_obj = main_app_instance()
195
- print_out = app_obj.po if app_obj else dummy_function if app_obj is UNSET else print
196
- debug_out = app_obj.dpo if app_obj else dummy_function if app_obj is UNSET else print
235
+ print_out = dummy_function if app_obj is UNSET else app_obj.print_out if isinstance(app_obj, AppBase) else print
236
+ 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()
197
239
 
198
- debug_out(f" . executing {masked_args} at {os.getcwd()=} in {active_venv()=}")
240
+ debug_out(f" . executing {masked_args} at {os.getcwd()=} in {os_env_venv()=}")
199
241
  result: subprocess.CompletedProcess | subprocess.CalledProcessError # having: stdout/stderr/returncode
200
242
  try:
201
243
  result = subprocess.run(all_args,
@@ -214,10 +256,10 @@ def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: st
214
256
 
215
257
  if isinstance(output_lines, list):
216
258
  if result.stdout:
217
- output_lines.extend([line for line in result.stdout.decode().splitlines() if line.strip()])
259
+ output_lines.extend(decoder_splitter(result.stdout))
218
260
  if err_redirect == subprocess.PIPE and result.stderr:
219
261
  output_lines.append(STDERR_BEG_MARKER)
220
- output_lines.extend([line for line in result.stderr.decode().splitlines() if line.strip()])
262
+ output_lines.extend(decoder_splitter(result.stderr))
221
263
  output_lines.append(STDERR_END_MARKER)
222
264
 
223
265
  return result.returncode
@@ -226,8 +268,10 @@ def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: st
226
268
  # pylint: disable-next=too-many-arguments,too-many-positional-arguments
227
269
  def sh_exit_if_exec_err(err_code: int, command_line: str,
228
270
  extra_args: Iterable[str] = (), output_lines: list[str] | None = None, exit_on_err: bool = True,
229
- exit_msg: str = "", app_obj: ConsoleApp | None = None, shell: bool = False,
230
- env_vars: dict[str, str] | None = None, err_redirect: int | None = subprocess.DEVNULL) -> int:
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:
231
275
  """ execute command in the current working directory, optionally capturing console output and exit app on error.
232
276
 
233
277
  :param err_code: error code to pass to the console as exit code if the command set an error code and
@@ -242,13 +286,15 @@ def sh_exit_if_exec_err(err_code: int, command_line: str,
242
286
  :param exit_msg: additional text to print on stdout/console if the app debug level is greater or equal
243
287
  to 1 (:data:`~ae.core.DEBUG_LEVEL_ENABLED`) or if an error occurred.
244
288
  :param app_obj: :class:`~ae.console.ConsoleApp` instance, used for logging/force-ignorable error.
245
- :param shell: pass True to execute command in the default OS shell (see :meth:`subprocess.run`).
289
+ :param shell: pass True to execute command in the default OS shell (see :func:`sh_exec`).
246
290
  :param env_vars: OS shell environment variables to be used instead of the console/bash defaults.
247
291
  :param err_redirect: control how the output on stderr gets captured, redirected and returned. see also
248
292
  :paramref:`sh_exec.err_redirect` for more details to the supported argument values.
249
293
  if this argument is not specified or has the value :data:`subprocess.DEVNULL` then the
250
294
  stderr output will get suppressed on the console and will also not get added to the
251
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
297
+ of string chunks/lines, to be added to and returned by :paramref:`.output_lines`.
252
298
  :return: 0 on success, or if an error occurred the error number set by the executed command.
253
299
  """
254
300
  if output_lines is None:
@@ -256,12 +302,13 @@ def sh_exit_if_exec_err(err_code: int, command_line: str,
256
302
  output_len = 0
257
303
  else:
258
304
  output_len = len(output_lines)
259
- app_obj = app_obj or cast(ConsoleApp, main_app_instance()) # calls app_obj./ConsoleApp.chk() method
305
+ if app_obj is None:
306
+ app_obj = cast(ConsoleApp, main_app_instance()) # calls app_obj./ConsoleApp.chk() method
260
307
 
261
308
  sh_err = sh_exec(command_line, extra_args=extra_args, output_lines=output_lines, app_obj=app_obj, shell=shell,
262
- env_vars=env_vars, err_redirect=err_redirect)
309
+ env_vars=env_vars, err_redirect=err_redirect, decoder_splitter=decoder_splitter)
263
310
 
264
- if app_obj and (app_obj.debug or sh_err and exit_on_err):
311
+ if isinstance(app_obj, ConsoleApp) and (app_obj.debug or sh_err and exit_on_err):
265
312
  for line in output_lines[output_len:]:
266
313
  if app_obj.verbose or not line.startswith("LOG: "): # if verbose show mypy's endless (stderr) log entries
267
314
  app_obj.po(" " * 6 + line)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ae_shell
3
- Version: 0.3.16
3
+ Version: 0.3.17
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.16
67
+ # shell 0.3.17
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.16?logo=python)](
73
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.16)
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)
74
74
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](
75
75
  https://pypi.org/project/ae-shell/#history)
76
76
 
@@ -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.16',
80
+ 'version': '0.3.17',
81
81
  'zip_safe': True,
82
82
  }
83
83
 
@@ -6,14 +6,15 @@ import subprocess
6
6
  from unittest.mock import PropertyMock, patch
7
7
 
8
8
  from ae.base import UNSET, camel_to_snake, norm_name, os_path_join, write_file
9
- from ae.system import load_env_var_defaults, active_venv
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
12
12
 
13
13
 
14
14
  from ae.shell import (
15
15
  STDERR_BEG_MARKER, STDERR_END_MARKER,
16
- debug_or_verbose, get_domain_user_var, hint, in_os_env, mask_token, sh_exec, sh_exit_if_exec_err)
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
18
 
18
19
 
19
20
  class TestHelpers:
@@ -257,6 +258,30 @@ STDERR_LINE = b'std___err'
257
258
 
258
259
 
259
260
  class TestShellExecutions:
261
+ def test_output_line_split(self):
262
+ assert output_line_split(STDOUT_LINE) == [STDOUT_LINE.decode()]
263
+
264
+ assert output_line_split(STDOUT_LINE + b"\n" + STDERR_LINE + b"\n") == [STDOUT_LINE.decode(),
265
+ STDERR_LINE.decode()]
266
+
267
+ assert output_line_split(STDOUT_LINE + b"\n\0" + STDERR_LINE + b"\0") == [
268
+ STDOUT_LINE.decode(), "\0" + STDERR_LINE.decode() + "\0"]
269
+
270
+ assert output_line_split(STDOUT_LINE + b"\r" + STDERR_LINE + b"\r\n\r\0\r") == [
271
+ STDOUT_LINE.decode(), STDERR_LINE.decode(), "\0"]
272
+
273
+ def test_output_zero_split(self):
274
+ assert output_zero_split(STDOUT_LINE) == [STDOUT_LINE.decode()]
275
+
276
+ assert output_zero_split(STDOUT_LINE + b"\0" + STDERR_LINE + b"\0") == [STDOUT_LINE.decode(),
277
+ STDERR_LINE.decode()]
278
+
279
+ assert output_zero_split(STDOUT_LINE + b"\0" + STDERR_LINE + b" = multi\nval\rlines\r\n" + STDERR_LINE) == [
280
+ STDOUT_LINE.decode(), STDERR_LINE.decode() + " = multi\nval\rlines\r\n" + STDERR_LINE.decode()]
281
+
282
+ assert output_zero_split(STDOUT_LINE + b"\n" + STDERR_LINE + b"\r\n\r\0\r") == [
283
+ STDOUT_LINE.decode() + "\n" + STDERR_LINE.decode() + "\r\n\r"]
284
+
260
285
  def test_sh_exec_catch_any_exception(self, capsys):
261
286
  with patch("subprocess.run", side_effect=Exception('broad tst exception')):
262
287
  assert sh_exec('any_cmd') == (126, )[0]
@@ -283,7 +308,7 @@ class TestShellExecutions:
283
308
  out, err = capsys.readouterr()
284
309
  assert " . executing ['any', 'command']" in out
285
310
  assert os.getcwd() in out
286
- assert active_venv() in out
311
+ assert os_env_venv() in out
287
312
  assert "**** subprocess.run(['any', 'command']) raised exception" in out
288
313
  assert err == ""
289
314
 
@@ -329,40 +354,41 @@ class TestShellExecutions:
329
354
  def test_sh_exec_run_args(self, mock_method):
330
355
  cmd_line = "cmd arg1 arg2"
331
356
  extra_args = ['extra_arg1', 'extra_arg2']
357
+ env = os.environ.copy()
332
358
 
333
359
  sh_exec(cmd_line, tuple(extra_args))
334
360
 
335
361
  mock_method.assert_called_with(
336
- shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'', check=True, shell=False, env=None)
362
+ shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'', check=True, shell=False, env=env)
337
363
 
338
364
  sh_exec(cmd_line, shell=True)
339
365
 
340
366
  mock_method.assert_called_with(
341
- cmd_line, stdout=None, stderr=None, input=b'', check=True, shell=True, env=None)
367
+ cmd_line, stdout=None, stderr=None, input=b'', check=True, shell=True, env=env)
342
368
 
343
369
  sh_exec(cmd_line, {_: "any" for _ in extra_args}, shell=True, err_redirect=subprocess.DEVNULL)
344
370
 
345
371
  mock_method.assert_called_with(
346
372
  cmd_line + " " + " ".join(extra_args), stdout=None, stderr=subprocess.DEVNULL, input=b'', check=True,
347
- shell=True, env=None)
373
+ shell=True, env=env)
348
374
 
349
375
  sh_exec(cmd_line, extra_args, console_input='con_inp')
350
376
 
351
377
  mock_method.assert_called_with(
352
378
  shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'con_inp',
353
- check=True, shell=False, env=None)
379
+ check=True, shell=False, env=env)
354
380
 
355
381
  sh_exec(cmd_line, extra_args, output_lines=[])
356
382
 
357
383
  mock_method.assert_called_with(
358
384
  shlex.split(cmd_line) + extra_args, stdout=subprocess.PIPE, stderr=None, input=b'',
359
- check=True, shell=False, env=None)
385
+ check=True, shell=False, env=env)
360
386
 
361
387
  sh_exec(cmd_line, extra_args, console_input='con_inp', output_lines=[], err_redirect=subprocess.STDOUT)
362
388
 
363
389
  mock_method.assert_called_with(
364
390
  shlex.split(cmd_line) + extra_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, input=b'con_inp',
365
- check=True, shell=False, env=None)
391
+ check=True, shell=False, env=env)
366
392
 
367
393
  env_vars = {'A': "1", 'C': "tst_string value"}
368
394
 
@@ -372,6 +398,12 @@ class TestShellExecutions:
372
398
  shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'',
373
399
  check=True, shell=False, env=env_vars)
374
400
 
401
+ sh_exec(cmd_line, extra_args, env_vars=None)
402
+
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)
406
+
375
407
  def test_sh_exec_run_returned_values(self):
376
408
  def _run_return(*_args, **_kwargs):
377
409
  """ mock to simulate subprocess.run return object. """
@@ -505,7 +537,7 @@ class TestShellExecutions:
505
537
  assert ret == (126, )[0]
506
538
  assert output == ['old output']
507
539
  out, err = capsys.readouterr()
508
- assert f". executing [] at os.getcwd()='{os.getcwd()}' in active_venv()='{active_venv()}'" in out
540
+ assert f". executing [] at os.getcwd()='{os.getcwd()}' in os_env_venv()='{os_env_venv()}'" in out
509
541
  assert 'tst exit message' in out
510
542
  assert err == ""
511
543
 
File without changes
File without changes
File without changes