ae-shell 0.3.5__tar.gz → 0.3.7__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.5
3
+ Version: 0.3.7
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
@@ -63,15 +63,15 @@ Dynamic: provides-extra
63
63
  Dynamic: requires-python
64
64
  Dynamic: summary
65
65
 
66
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.100 -->
67
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.21 -->
68
- # shell 0.3.5
66
+ <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.101 -->
67
+ <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.22 -->
68
+ # shell 0.3.7
69
69
 
70
70
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/develop?logo=python)](
71
71
  https://gitlab.com/ae-group/ae_shell)
72
72
  [![LatestPyPIrelease](
73
- https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.5?logo=python)](
74
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.5)
73
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.7?logo=python)](
74
+ https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.7)
75
75
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](
76
76
  https://pypi.org/project/ae-shell/#history)
77
77
 
@@ -1,12 +1,12 @@
1
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.100 -->
2
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.21 -->
3
- # shell 0.3.5
1
+ <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.101 -->
2
+ <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.22 -->
3
+ # shell 0.3.7
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.5?logo=python)](
9
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.5)
8
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.7?logo=python)](
9
+ https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.7)
10
10
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](
11
11
  https://pypi.org/project/ae-shell/#history)
12
12
 
@@ -174,6 +174,7 @@ this section includes various other utility functions and classes.
174
174
  - :func:`get_main_app`: retrieves the main application instance, or a mock instance if one does not exist.
175
175
  - :func:`get_pypi_versions`: determines all available release versions of a package on PyPI.
176
176
  - :func:`hint`: provides a hint message based on the provided arguments.
177
+ - :func:`mask_token`: hide/mask tokens in a text block, to prevent to show them in logs or error messages.
177
178
  - :func:`prg_git_project_path`: determines the project root path from the current working directory.
178
179
 
179
180
  - :class:`MockedMainApp`: a mock class for a main application instance.
@@ -192,7 +193,7 @@ import tempfile
192
193
 
193
194
  from contextlib import contextmanager
194
195
  from urllib.parse import urlparse
195
- from typing import Optional, Iterator, Iterable, cast, Callable, Any, Union, MutableMapping
196
+ from typing import Any, Callable, Iterable, Iterator, MutableMapping, Optional, Union, cast, overload
196
197
 
197
198
  import requests
198
199
  from packaging.version import Version
@@ -204,7 +205,7 @@ from ae.core import main_app_instance
204
205
  from ae.console import MAIN_SECTION_NAME, ConsoleApp # type: ignore
205
206
 
206
207
 
207
- __version__ = '0.3.5'
208
+ __version__ = '0.3.7'
208
209
 
209
210
 
210
211
  COMMIT_MSG_FILE_NAME = '.commit_msg.txt' #: name of the file containing the commit message
@@ -1033,6 +1034,41 @@ def in_venv(name: str = "") -> Iterator[None]:
1033
1034
  activate_venv(old_venv)
1034
1035
 
1035
1036
 
1037
+ @overload
1038
+ def mask_token(text: str) -> str: ...
1039
+
1040
+
1041
+ @overload
1042
+ def mask_token(text: list[str]) -> list[str]: ...
1043
+
1044
+
1045
+ def mask_token(text: Union[str, list[str]]) -> Union[str, list[str]]:
1046
+ """ hide most parts of any GitHub/GitHub tokens found in the specified text/-lines.
1047
+
1048
+ :param text: text block, specified either as str object or as a list of str objects (lines),
1049
+ to detect tokens within, to hide/mask the most part of them.
1050
+ :return: text block with without the complete tokens.
1051
+
1052
+ .. note:: see also :func:`ae.base.mask_url` to hide passwords and tokens in URLs.
1053
+ """
1054
+ if is_str_arg := isinstance(text, str):
1055
+ lines = [text]
1056
+ else:
1057
+ lines = list(text) # copy to not change text list content
1058
+
1059
+ for tok_beg, tok_end in (('glpat-', '@gitlab.com'), ('ghp_', '@github.com')):
1060
+ for idx, line in enumerate(lines):
1061
+ while tok_beg in line: # hide the GitLab/GitHub private token, e.g. from git-push-urls with authentication
1062
+ start = line.index(tok_beg)
1063
+ end = line.find(tok_end, start)
1064
+ if end == -1:
1065
+ break
1066
+ line = line[:start + 3] + "***-masked-token-***" + line[end - 3:]
1067
+ lines[idx] = line
1068
+
1069
+ return lines[0] if is_str_arg else lines
1070
+
1071
+
1036
1072
  def owner_project_from_url(remote_url: str) -> str:
1037
1073
  """ determine the owner and project name path from the specified git remote repository url.
1038
1074
 
@@ -1069,7 +1105,7 @@ def project_name_version(imp_or_pkg_name: str, packages_versions: Iterable[str])
1069
1105
 
1070
1106
 
1071
1107
  # pylint: disable-next=too-many-arguments,too-many-positional-arguments
1072
- def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "",
1108
+ def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: str = "",
1073
1109
  lines_output: Optional[list[str]] = None, main_app: Optional[Any] = None, shell: bool = False,
1074
1110
  env_vars: Optional[dict[str, str]] = None) -> int:
1075
1111
  """ execute command in the current working directory of the OS console/shell.
@@ -1093,7 +1129,7 @@ def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "
1093
1129
  merge_err = bool(lines_output) # == -''- and len(lines_output) > 0
1094
1130
  print_out = main_app.po if main_app else print if main_app is None else dummy_function
1095
1131
  debug_out = main_app.dpo if main_app else dummy_function
1096
- debug_out(f" . executing at {os.getcwd()}: {args}")
1132
+ debug_out(f" . executing at {os.getcwd()}: {mask_token(args)}")
1097
1133
 
1098
1134
  result: Union[subprocess.CompletedProcess, subprocess.CalledProcessError] # having: stdout/stderr/returncode
1099
1135
  try:
@@ -1105,10 +1141,10 @@ def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "
1105
1141
  shell=shell,
1106
1142
  env=env_vars)
1107
1143
  except subprocess.CalledProcessError as ex: # pragma: no cover
1108
- debug_out(f"**** subprocess.run({args=}) returned non-zero exit code {ex.returncode}; {ex=}")
1144
+ debug_out(f"**** subprocess.run({mask_token(args)}) returned non-zero exit code {ex.returncode}; {ex=}")
1109
1145
  result = ex
1110
1146
  except Exception as ex: # pylint: disable=broad-except # pragma: no cover
1111
- print_out(f"**** subprocess.run({args}) raised exception {ex}")
1147
+ print_out(f"**** subprocess.run({mask_token(args)}) raised exception {ex}")
1112
1148
  return 126
1113
1149
 
1114
1150
  if ret_out:
@@ -1125,7 +1161,7 @@ def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "
1125
1161
 
1126
1162
  # pylint: disable-next=too-many-arguments,too-many-positional-arguments
1127
1163
  def sh_exit_if_exec_err(err_code: int, command_line: str,
1128
- extra_args: Iterable = (), lines_output: Optional[list[str]] = None,
1164
+ extra_args: Iterable[str] = (), lines_output: Optional[list[str]] = None,
1129
1165
  exit_on_err: bool = True, exit_msg: str = "", shell: bool = False,
1130
1166
  env_vars: Optional[dict[str, str]] = None) -> int:
1131
1167
  """ execute command in the current working directory of the OS console/shell, dump error, and exit app if needed.
@@ -1160,19 +1196,19 @@ def sh_exit_if_exec_err(err_code: int, command_line: str,
1160
1196
  main_app.po(" " * 6 + line)
1161
1197
  msg = f"command: {command_line} " + " ".join('"' + arg + '"' if " " in arg else arg for arg in extra_args)
1162
1198
  if not sh_err:
1163
- main_app.dpo(f" = successfully executed {msg}")
1199
+ main_app.dpo(f" = successfully executed {mask_token(msg)}")
1164
1200
  else:
1165
1201
  if exit_msg:
1166
1202
  main_app.po(f" {exit_msg}")
1167
- check_if(err_code, not exit_on_err, f"sh_exit_if_exec_err error {sh_err} in {msg}") # app exit
1203
+ check_if(err_code, not exit_on_err, f"sh_exit_if_exec_err error {sh_err} in {mask_token(msg)}") # app exit
1168
1204
 
1169
1205
  return sh_err
1170
1206
 
1171
1207
 
1172
1208
  # pylint: disable-next=too-many-arguments,too-many-positional-arguments,too-many-locals
1173
1209
  def sh_exit_if_git_err(err_code: int, command_line: str,
1174
- extra_args: Iterable = (), lines_output: Optional[list[str]] = None, exit_on_err: bool = False,
1175
- log_enable_dir: str = "") -> list[str]:
1210
+ extra_args: Iterable[str] = (), lines_output: Optional[list[str]] = None,
1211
+ exit_on_err: bool = False, log_enable_dir: str = "") -> list[str]:
1176
1212
  """ execute git command with optional git trace output, returning the stdout lines cleaned from any trace messages.
1177
1213
 
1178
1214
  :param err_code: error code to pass to the console as exit code if :paramref:`.exit_on_err` is True.
@@ -1211,8 +1247,9 @@ def sh_exit_if_git_err(err_code: int, command_line: str,
1211
1247
  sh_log(command_line, extra_args=extra_args, cl_err=cl_err, lines_output=lines_output, log_file_paths=log_files)
1212
1248
 
1213
1249
  if cl_err: # if cl_err and exit_on_err then it would have exit the Python interpreter (so never would run to here)
1214
- main_app.vpo(f" # ignored error {cl_err} of {command_line=} with {extra_args=} and git trace {env_vars=}")
1215
- lines_output.insert(0, EXEC_GIT_ERR_PREFIX + str(cl_err) + f" in {command_line=} with {extra_args=}")
1250
+ cmd_line = mask_token([command_line] + list(extra_args))
1251
+ main_app.vpo(f" # ignored error {cl_err} of `{cmd_line}` and git trace {env_vars=}")
1252
+ lines_output.insert(0, EXEC_GIT_ERR_PREFIX + str(cl_err) + f" in {cmd_line}")
1216
1253
 
1217
1254
  if STDERR_BEG_MARKER in lines_output and ( # output marker only if stderr not got merged/called w/ lines_output==[]
1218
1255
  git_debug or any(os.environ.get(_, "0") in ("true", "1", "2") for _ in git_trace_vars)):
@@ -1224,7 +1261,7 @@ def sh_exit_if_git_err(err_code: int, command_line: str,
1224
1261
  main_app.po(sep + lines_output[line_no])
1225
1262
  lines_output[:] = lines_output[:start] # del output[start:]
1226
1263
 
1227
- return lines_output
1264
+ return list(mask_token(lines_output))
1228
1265
 
1229
1266
 
1230
1267
  # pylint: disable-next=too-many-arguments,too-many-positional-arguments
@@ -1250,11 +1287,7 @@ def sh_log(comment_or_command: str, extra_args: Iterable[str] = (), cl_err: int
1250
1287
  (f" * {cl_err=}" + sep if cl_err else "") +
1251
1288
  (" " + (sep + " ").join(lines_output) + sep if lines_output else ""))
1252
1289
 
1253
- for tok_beg, tok_end in (('glpat-', '@gitlab.com'), ('ghp_', '@github.com')):
1254
- while tok_beg in log_lines: # hide the gitlab private token, e.g. from git-push-urls with authentication
1255
- start = log_lines.index(tok_beg)
1256
- end = log_lines.index(tok_end, start)
1257
- log_lines = log_lines[:start] + "private-token-" + log_lines[end - 3:]
1290
+ log_lines = mask_token(log_lines)
1258
1291
 
1259
1292
  for log_path in log_file_paths or sh_logs(log_name_prefix=log_name_prefix):
1260
1293
  write_file(log_path, log_lines, extra_mode='a')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ae_shell
3
- Version: 0.3.5
3
+ Version: 0.3.7
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
@@ -63,15 +63,15 @@ Dynamic: provides-extra
63
63
  Dynamic: requires-python
64
64
  Dynamic: summary
65
65
 
66
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.100 -->
67
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.21 -->
68
- # shell 0.3.5
66
+ <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.101 -->
67
+ <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.22 -->
68
+ # shell 0.3.7
69
69
 
70
70
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/develop?logo=python)](
71
71
  https://gitlab.com/ae-group/ae_shell)
72
72
  [![LatestPyPIrelease](
73
- https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.5?logo=python)](
74
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.5)
73
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.7?logo=python)](
74
+ https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.7)
75
75
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](
76
76
  https://pypi.org/project/ae-shell/#history)
77
77
 
@@ -23,15 +23,15 @@ setup_kwargs = {
23
23
  'install_requires': [],
24
24
  'keywords': ['configuration', 'development', 'environment', 'productivity'],
25
25
  'license': 'GPL-3.0-or-later',
26
- 'long_description': ('<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.100 -->\n'
27
- '<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.21 -->\n'
28
- '# shell 0.3.5\n'
26
+ 'long_description': ('<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.101 -->\n'
27
+ '<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.22 -->\n'
28
+ '# shell 0.3.7\n'
29
29
  '\n'
30
30
  '[![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/develop?logo=python)](\n'
31
31
  ' https://gitlab.com/ae-group/ae_shell)\n'
32
32
  '[![LatestPyPIrelease](\n'
33
- ' https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.5?logo=python)](\n'
34
- ' https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.5)\n'
33
+ ' https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.7?logo=python)](\n'
34
+ ' https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.7)\n'
35
35
  '[![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](\n'
36
36
  ' https://pypi.org/project/ae-shell/#history)\n'
37
37
  '\n'
@@ -108,7 +108,7 @@ setup_kwargs = {
108
108
  'Source': 'https://ae.readthedocs.io/en/latest/_modules/ae/shell.html'},
109
109
  'python_requires': '>=3.9',
110
110
  'url': 'https://gitlab.com/ae-group/ae_shell',
111
- 'version': '0.3.5',
111
+ 'version': '0.3.7',
112
112
  'zip_safe': True,
113
113
  }
114
114
 
@@ -39,7 +39,7 @@ from ae.shell import (
39
39
  git_current_branch, git_diff, git_fetch, git_init_if_needed, git_merge, git_push,
40
40
  git_remote_domain_group, git_remotes, git_renew_remotes, git_status,
41
41
  git_tag_add, git_ref_in_branch, git_tag_list, git_tag_remotes, git_uncommitted,
42
- hint, in_os_env, in_prj_dir_venv, in_venv, owner_project_from_url, project_name_version,
42
+ hint, in_os_env, in_prj_dir_venv, in_venv, mask_token, owner_project_from_url, project_name_version,
43
43
  sh_exec, sh_exit_if_exec_err, sh_exit_if_git_err, sh_log, sh_logs,
44
44
  temp_context_cleanup, temp_context_folders, temp_context_get_or_create, _temp_folders, venv_bin_path,
45
45
  MockedMainApp)
@@ -1452,6 +1452,23 @@ class TestHelpers:
1452
1452
  assert not hint("hint command", _hint_tst_callable, "extra message")
1453
1453
  assert not hint("hint command", _hint_tst_callable.__name__, "extra message")
1454
1454
 
1455
+ def test_mask_token(self):
1456
+ token = "glpat-gitlab token format ending at the @/ampersand directly followed by the gitlab.com domain"
1457
+ text = "a text block containing a gitlab URL with a token: https://UsaNäm:" + token + "@gitlab.com"
1458
+
1459
+ assert token not in mask_token(text)
1460
+ assert token not in mask_token([text])[0]
1461
+
1462
+ token = "ghp_-github token format ending at the @/ampersand directly followed by the github.com domain"
1463
+ text = "a text block containing a github URL with a token: https://YouSaNem:" + token + "@github.com"
1464
+
1465
+ assert token not in mask_token(text)
1466
+ assert token not in mask_token([text])[0]
1467
+
1468
+ text = "skip masking of text blocks with a start token like ghp_ or glpat- but missing end token"
1469
+
1470
+ assert mask_token(text) == text # neither throws str.index()-ValueError nor stuck in endless-loop
1471
+
1455
1472
  def test_owner_project_from_url(self):
1456
1473
  assert owner_project_from_url("owner/project") == "owner/project"
1457
1474
  assert owner_project_from_url("owner/project.git") == "owner/project"
File without changes
File without changes
File without changes