ae-base 0.3.52__tar.gz → 0.3.54__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_base
3
- Version: 0.3.52
3
+ Version: 0.3.54
4
4
  Summary: ae namespace module portion base: basic constants, helper functions and context manager
5
5
  Home-page: https://gitlab.com/ae-group/ae_base
6
6
  Author: AndiEcker
@@ -69,17 +69,17 @@ Dynamic: summary
69
69
 
70
70
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.95 -->
71
71
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
72
- # base 0.3.52
72
+ # base 0.3.54
73
73
 
74
74
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_base/develop?logo=python)](
75
75
  https://gitlab.com/ae-group/ae_base)
76
76
  [![LatestPyPIrelease](
77
- https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.51?logo=python)](
78
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.51)
77
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.53?logo=python)](
78
+ https://gitlab.com/ae-group/ae_base/-/tree/release0.3.53)
79
79
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_base)](
80
80
  https://pypi.org/project/ae-base/#history)
81
81
 
82
- >ae_base module 0.3.52.
82
+ >ae_base module 0.3.54.
83
83
 
84
84
  [![Coverage](https://ae-group.gitlab.io/ae_base/coverage.svg)](
85
85
  https://ae-group.gitlab.io/ae_base/coverage/index.html)
@@ -1,16 +1,16 @@
1
1
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.95 -->
2
2
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
3
- # base 0.3.52
3
+ # base 0.3.54
4
4
 
5
5
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_base/develop?logo=python)](
6
6
  https://gitlab.com/ae-group/ae_base)
7
7
  [![LatestPyPIrelease](
8
- https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.51?logo=python)](
9
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.51)
8
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.53?logo=python)](
9
+ https://gitlab.com/ae-group/ae_base/-/tree/release0.3.53)
10
10
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_base)](
11
11
  https://pypi.org/project/ae-base/#history)
12
12
 
13
- >ae_base module 0.3.52.
13
+ >ae_base module 0.3.54.
14
14
 
15
15
  [![Coverage](https://ae-group.gitlab.io/ae_base/coverage.svg)](
16
16
  https://ae-group.gitlab.io/ae_base/coverage/index.html)
@@ -32,6 +32,9 @@ sortable and compact string from a timestamp.
32
32
  base helper functions
33
33
  ---------------------
34
34
 
35
+ in order to convert and transfer Unicode character outside the 7-bit ASCII range via internet transport protocols,
36
+ like http, use the helper functions :func:`ascii_str` and :func:`str_ascii`.
37
+
35
38
  :func:`now_str` creates a timestamp string with the actual UTC date and time. the :func:`utc_datetime` provides the
36
39
  actual UTC date and time as datetime object.
37
40
 
@@ -155,6 +158,7 @@ import sys
155
158
  import unicodedata
156
159
  import warnings
157
160
 
161
+ from ast import literal_eval
158
162
  from configparser import ConfigParser, ExtendedInterpolation
159
163
  from contextlib import contextmanager
160
164
  from importlib.machinery import ModuleSpec
@@ -163,7 +167,7 @@ from types import ModuleType
163
167
  from typing import Any, Callable, Generator, Iterable, Optional, Union, cast
164
168
 
165
169
 
166
- __version__ = '0.3.52'
170
+ __version__ = '0.3.54'
167
171
 
168
172
 
169
173
  os_path_abspath = os.path.abspath
@@ -176,7 +180,7 @@ os_path_join = os.path.join
176
180
  os_path_normpath = os.path.normpath
177
181
  os_path_realpath = os.path.realpath
178
182
  os_path_relpath = os.path.relpath
179
- os_path_sep = os.path.sep
183
+ os_path_sep = os.path.sep # pylint: disable=invalid-name
180
184
  os_path_splitext = os.path.splitext
181
185
 
182
186
 
@@ -275,6 +279,24 @@ def app_name_guess() -> str:
275
279
  return defuse(app_name)
276
280
 
277
281
 
282
+ def ascii_str(unicode_str: str) -> str:
283
+ """ convert non-ASCII chars in str object to a revertible 7-bit/ASCII representation, e.g. to put in a http header.
284
+
285
+ :param unicode_str: string to encode/convert.
286
+ :return: revertible representation of the specified string, using only ASCII characters.
287
+ """
288
+ return repr(unicode_str.encode())
289
+
290
+
291
+ def str_ascii(encoded_str: str) -> str:
292
+ """ convert non-ASCII chars in str object encoded with :func:`ascii_str` back to their corresponding Unicode chars.
293
+
294
+ :param encoded_str: string to decode (covert contained ASCII-encoded characters back Unicode chars).
295
+ :return: decoded string.
296
+ """
297
+ return literal_eval(encoded_str).decode()
298
+
299
+
278
300
  def build_config_variable_values(*names_defaults: tuple[str, Any], section: str = 'app') -> tuple[Any, ...]:
279
301
  """ determine build config variable values from the ``buildozer.spec`` file in the current directory.
280
302
 
@@ -342,6 +364,8 @@ ASCII_UNICODE = (
342
364
  (')', '⟯'), # U+27EF: MATHEMATICAL RIGHT FLATTENED PARENTHESIS
343
365
  ('[', '⟦'), # U+27E6: MATHEMATICAL LEFT WHITE SQUARE BRACKET
344
366
  (']', '⟧'), # U+27E7: MATHEMATICAL RIGHT WHITE SQUARE BRACKET
367
+ ('{', '﹛'), # U+FE5B: Small Left Curly Bracket
368
+ ('}', '﹜'), # U+FE5C: Small Right Curly Bracket
345
369
  ('#', '﹟'), # U+FE5F: Small Number Sign
346
370
  (';', '﹔'), # U+FE54: Small Semicolon
347
371
  ('@', '﹫'), # U+FE6B: Small Commercial At
@@ -476,13 +500,14 @@ def force_encoding(text: Union[str, bytes], encoding: str = DEF_ENCODING, errors
476
500
  return enc_str.decode(encoding=encoding)
477
501
 
478
502
 
479
- class UnformattedValue:
503
+ class UnformattedValue: # pylint: disable=too-few-public-methods
480
504
  """ helper class for :func:`~ae.base.format_given` to keep placeholder with format unchanged if not found. """
481
505
  def __init__(self, key: str):
482
506
  self.key = key
483
507
 
484
508
  def __format__(self, format_spec: str):
485
509
  """ overriding Python object class method to return placeholder unchanged including the curly brackets. """
510
+ # pylint: disable=consider-using-f-string
486
511
  return "{{{}{}}}".format(self.key, ":" + format_spec if format_spec else "")
487
512
 
488
513
 
@@ -514,7 +539,7 @@ def format_given(text: str, placeholder_map: dict[str, Any], strict: bool = Fals
514
539
  formatter = GivenFormatter()
515
540
  try:
516
541
  return formatter.vformat(text, (), placeholder_map)
517
- except (ValueError, Exception) as ex:
542
+ except (ValueError, Exception) as ex: # pylint: disable=broad-except
518
543
  if strict:
519
544
  raise ex
520
545
  return text
@@ -817,6 +842,7 @@ def os_host_name() -> str:
817
842
  return defuse(platform.node()) or "indeterminableHostName"
818
843
 
819
844
 
845
+ # noinspection PyTypeChecker
820
846
  def os_local_ip() -> str:
821
847
  """ determine ip address of this system/machine in the local network (LAN or WLAN).
822
848
 
@@ -827,16 +853,16 @@ def os_local_ip() -> str:
827
853
  """
828
854
  socket1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
829
855
  try:
830
- socket1.connect(('10.255.255.255', 1)) # doesn't even have to be reachable
856
+ socket1.connect(('10.255.255.255', 1)) # doesn't even have to be reachable
831
857
  ip_address = socket1.getsockname()[0]
832
- except (OSError, IOError): # pragma: no cover
858
+ except (OSError, IOError, Exception): # pylint: disable=broad-except # pragma: no cover
833
859
  # ConnectionAbortedError, ConnectionError, ConnectionRefusedError, ConnectionResetError inherit from OSError
834
860
  socket2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
835
861
  try:
836
862
  socket2.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
837
863
  socket2.connect(('<broadcast>', 0))
838
864
  ip_address = socket2.getsockname()[0]
839
- except (OSError, IOError):
865
+ except (OSError, IOError, Exception): # pylint: disable=broad-except
840
866
  ip_address = ""
841
867
  finally:
842
868
  socket2.close()
@@ -1198,7 +1224,7 @@ def write_file(file_path: str, content: Union[str, bytes],
1198
1224
  file_handle.write(content)
1199
1225
 
1200
1226
 
1201
- class ErrorMsgMixin:
1227
+ class ErrorMsgMixin: # pylint: disable=too-few-public-methods
1202
1228
  """ mixin class providing sophisticated error message handling. """
1203
1229
  _err_msg: str = ""
1204
1230
 
@@ -1209,7 +1235,7 @@ class ErrorMsgMixin:
1209
1235
 
1210
1236
  def __init__(self):
1211
1237
  try:
1212
- from ae.core import main_app_instance # type: ignore
1238
+ from ae.core import main_app_instance # type: ignore # pylint: disable=import-outside-toplevel
1213
1239
 
1214
1240
  self.cae = cae = main_app_instance()
1215
1241
  assert cae is not None, f"{self.__class__.__name__}.__init__() called too early; main app instance not"
@@ -1218,7 +1244,7 @@ class ErrorMsgMixin:
1218
1244
  self.dpo = cae.dpo
1219
1245
  self.vpo = cae.vpo
1220
1246
 
1221
- except (ImportError, AssertionError, Exception) as exc:
1247
+ except (ImportError, AssertionError, Exception) as exc: # pylint: disable=broad-except
1222
1248
  print(f"{self.__class__.__name__}.__init__() raised {exc}; using print() instead of main app error loggers")
1223
1249
 
1224
1250
  # self.cae = None
@@ -1278,7 +1304,7 @@ if os_platform == 'android': # pragma: no
1278
1304
  if _dev_id := Settings.getString(context.getContentResolver(), 'device_name'):
1279
1305
  os_device_id = defuse(_dev_id)
1280
1306
 
1281
- except Exception:
1307
+ except Exception: # pylint: disable=broad-except
1282
1308
  pass
1283
1309
 
1284
1310
  # monkey patch the :func:`shutil.copystat` and :func:`shutil.copymode` helper functions, which are crashing on
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ae_base
3
- Version: 0.3.52
3
+ Version: 0.3.54
4
4
  Summary: ae namespace module portion base: basic constants, helper functions and context manager
5
5
  Home-page: https://gitlab.com/ae-group/ae_base
6
6
  Author: AndiEcker
@@ -69,17 +69,17 @@ Dynamic: summary
69
69
 
70
70
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.95 -->
71
71
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
72
- # base 0.3.52
72
+ # base 0.3.54
73
73
 
74
74
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_base/develop?logo=python)](
75
75
  https://gitlab.com/ae-group/ae_base)
76
76
  [![LatestPyPIrelease](
77
- https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.51?logo=python)](
78
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.51)
77
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.53?logo=python)](
78
+ https://gitlab.com/ae-group/ae_base/-/tree/release0.3.53)
79
79
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_base)](
80
80
  https://pypi.org/project/ae-base/#history)
81
81
 
82
- >ae_base module 0.3.52.
82
+ >ae_base module 0.3.54.
83
83
 
84
84
  [![Coverage](https://ae-group.gitlab.io/ae_base/coverage.svg)](
85
85
  https://ae-group.gitlab.io/ae_base/coverage/index.html)
@@ -19,18 +19,19 @@ from typing import cast
19
19
  from ae.base import (
20
20
  ASCII_TO_UNICODE, BUILD_CONFIG_FILE, DOTENV_FILE_NAME, PY_EXT, PY_INIT, PY_MAIN, TESTS_FOLDER, UNICODE_TO_ASCII,
21
21
  UNSET,
22
- URI_SEP_CHAR, app_name_guess, build_config_variable_values, camel_to_snake, deep_dict_update, dummy_function,
23
- duplicates, env_str,
24
- dedefuse, force_encoding, format_given, full_stack_trace, import_module, instantiate_config_parser, in_wd,
22
+ URI_SEP_CHAR, app_name_guess, ascii_str, build_config_variable_values, camel_to_snake,
23
+ dedefuse, deep_dict_update, defuse, dummy_function, duplicates, env_str,
24
+ force_encoding, format_given, full_stack_trace, import_module, instantiate_config_parser, in_wd,
25
25
  load_env_var_defaults, load_dotenvs, main_file_paths_parts, mask_secrets, module_attr,
26
26
  module_file_path, module_name, norm_line_sep, norm_name, norm_path, now_str,
27
27
  os_host_name, os_local_ip, _os_platform, os_user_name,
28
28
  parse_dotenv, project_main_file, read_file, round_traditional, snake_to_camel, stack_frames, stack_var, stack_vars,
29
- sys_env_dict, sys_env_text, to_ascii, defuse, utc_datetime, write_file, ErrorMsgMixin)
29
+ str_ascii, sys_env_dict, sys_env_text, to_ascii, utc_datetime, write_file,
30
+ ErrorMsgMixin)
30
31
 
31
32
 
32
33
  tst_uri1 = "schema://user:pwd@domain/path_root/path_sub\\path+file% Üml?ä|ït.path_ext*\"<>|*'()[]{}#^;&=$,~" + chr(127)
33
- tst_fna1 = "schema⫻user﹕pwd﹫domain⁄path_root⁄path_sub﹨path﹢file﹪␣Üml﹖ä।ït.path_ext﹡"⟨⟩।﹡‘⟮⟯⟦⟧{}﹟^﹔﹠﹦﹩﹐~␡"
34
+ tst_fna1 = "schema⫻user﹕pwd﹫domain⁄path_root⁄path_sub﹨path﹢file﹪␣Üml﹖ä।ït.path_ext﹡"⟨⟩।﹡‘⟮⟯⟦⟧﹛﹜﹟^﹔﹠﹦﹩﹐~␡"
34
35
  tst_uri2 = "test control chars" + "".join(chr(_) for _ in range(1, 32))
35
36
  tst_fna2 = "test␣control␣chars␁␂␃␄␅␆␇␈␉␊␋␌␍␎␏␐␑␒␓␔␕␖␗␘␙␚␛␜␝␞␟"
36
37
 
@@ -145,6 +146,41 @@ class TestBaseHelpers:
145
146
  assert app_name_guess() != 'main'
146
147
  assert app_name_guess() == 'unguessable'
147
148
 
149
+ def test_ascii_str(self):
150
+ assert isinstance(ascii_str(""), str)
151
+
152
+ uni_str = "äÄßéÉíÍñÑòÒùÙ"
153
+ assert all(ord(_) >= 128 for _ in uni_str)
154
+ assert all(ord(_) < 128 for _ in ascii_str(uni_str))
155
+
156
+ uni_str = "".join(UNICODE_TO_ASCII.keys())
157
+ assert any(ord(_) >= 128 for _ in uni_str)
158
+ assert all(ord(_) < 128 for _ in ascii_str(uni_str))
159
+
160
+ asc_str = "".join(ASCII_TO_UNICODE.keys())
161
+ assert any(ord(_) < 128 for _ in asc_str)
162
+ assert all(ord(_) < 128 for _ in ascii_str(asc_str))
163
+
164
+ def test_str_ascii(self):
165
+ assert isinstance(str_ascii(ascii_str("tst")), str)
166
+
167
+ assert str_ascii(ascii_str("tst")) == "tst"
168
+ uni_str = "äÄßéÉíÍñÑòÒùÙ"
169
+ assert str_ascii(ascii_str(uni_str)) == uni_str
170
+
171
+ uni_str = "".join(UNICODE_TO_ASCII.keys())
172
+ assert str_ascii(ascii_str(uni_str)) == uni_str
173
+
174
+ asc_str = "".join(ASCII_TO_UNICODE.keys())
175
+ assert str_ascii(ascii_str(asc_str)) == asc_str
176
+
177
+ def test_str_ascii_errors(self):
178
+ with pytest.raises(SyntaxError):
179
+ str_ascii("")
180
+
181
+ with pytest.raises(SyntaxError):
182
+ str_ascii("any tst string not encoded/converted via ascii_str()")
183
+
148
184
  def test_build_config_variable_values_with_spec(self):
149
185
  try:
150
186
  with open(BUILD_CONFIG_FILE, "w") as file_handle:
@@ -344,9 +380,9 @@ class TestBaseHelpers:
344
380
 
345
381
  def test_format_given_err(self):
346
382
  with pytest.raises(ValueError):
347
- format_given("test text with {placeholder", {}, strict=True) # expected '}' before end of string
383
+ format_given("test text with {placeholder", {}, strict=True) # missing closing curly bracket
348
384
  with pytest.raises(ValueError):
349
- format_given("test text with placeholder}", {}, strict=True) # Single '}' encountered in format string
385
+ format_given("test text with placeholder}", {}, strict=True) # missing opening curly bracket
350
386
 
351
387
  def test_import_module_ae_base(self):
352
388
  mod_ref = import_module('ae.base')
File without changes
File without changes
File without changes