ae-base 0.3.41__py3-none-any.whl → 0.3.43__py3-none-any.whl

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.
ae/base.py CHANGED
@@ -46,8 +46,9 @@ the function :func:`duplicates` returns the duplicates of an iterable type.
46
46
  to normalize a file path, in order to remove `.`, `..` placeholders, to resolve symbolic links or to make it relative or
47
47
  absolute, call the function :func:`norm_path`.
48
48
 
49
- :func:`uri2filename` converts special characters of a URI/URL resulting in a string that can be used as a file name.
50
- use the function :func:`filename2uri` to convert this string back to the corresponding URL/URI.
49
+ :func:`defuse` converts special characters of a URI/URL or a file path string, resulting in a string that can be used
50
+ either as a URL slug or as a file name. use the function :func:`dedefuse` to convert this string back to the
51
+ corresponding URL/URI or file path.
51
52
 
52
53
  :func:`camel_to_snake` and :func:`snake_to_camel` providing name conversions of class and method names.
53
54
 
@@ -143,6 +144,13 @@ to determine e.g. variable values of the callers of a function/method.
143
144
  :attr:`title <AppBase.app_title>` of an application, if these values are not specified in the instance initializer.
144
145
 
145
146
  another useful helper function provided by this portion to inspect and debug your code is :func:`full_stack_trace`.
147
+
148
+
149
+ os.path shortcuts
150
+ -----------------
151
+
152
+ the following data items are pointers to shortcut the lookup to their related functions in the
153
+ Python module :mod:`os.path`:
146
154
  """
147
155
  import datetime
148
156
  import getpass
@@ -155,7 +163,6 @@ import shutil
155
163
  import socket
156
164
  import sys
157
165
  import unicodedata
158
- import urllib.parse
159
166
  import warnings
160
167
 
161
168
  from configparser import ConfigParser, ExtendedInterpolation
@@ -166,7 +173,21 @@ from types import ModuleType
166
173
  from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast
167
174
 
168
175
 
169
- __version__ = '0.3.41'
176
+ __version__ = '0.3.43'
177
+
178
+
179
+ os_path_abspath = os.path.abspath
180
+ os_path_basename = os.path.basename
181
+ os_path_dirname = os.path.dirname
182
+ os_path_expanduser = os.path.expanduser
183
+ os_path_isdir = os.path.isdir
184
+ os_path_isfile = os.path.isfile
185
+ os_path_join = os.path.join
186
+ os_path_normpath = os.path.normpath
187
+ os_path_realpath = os.path.realpath
188
+ os_path_relpath = os.path.relpath
189
+ os_path_sep = os.path.sep
190
+ os_path_splitext = os.path.splitext
170
191
 
171
192
 
172
193
  DOCS_FOLDER = 'docs' #: project documentation root folder name
@@ -256,10 +277,10 @@ def app_name_guess() -> str:
256
277
  if not app_name:
257
278
  unspecified_app_names = ('ae_base', 'app', '_jb_pytest_runner', 'main', '__main__', 'pydevconsole', 'src')
258
279
  path = sys.argv[0]
259
- app_name = os.path.splitext(os.path.basename(path))[0]
280
+ app_name = os_path_splitext(os_path_basename(path))[0]
260
281
  if app_name.lower() in unspecified_app_names:
261
282
  path = os.getcwd()
262
- app_name = os.path.basename(path)
283
+ app_name = os_path_basename(path)
263
284
  if app_name.lower() in unspecified_app_names:
264
285
  app_name = "unguessable"
265
286
  return app_name
@@ -273,7 +294,7 @@ def build_config_variable_values(*names_defaults: Tuple[str, Any], section: str
273
294
  :return: tuple of build config variable values (using the passed default value if not specified
274
295
  in the :data:`BUILD_CONFIG_FILE` spec file or if the spec file does not exist in cwd).
275
296
  """
276
- if not os.path.exists(BUILD_CONFIG_FILE):
297
+ if not os_path_isfile(BUILD_CONFIG_FILE):
277
298
  return tuple(def_val for name, def_val in names_defaults)
278
299
 
279
300
  config = instantiate_config_parser()
@@ -315,6 +336,96 @@ def deep_dict_update(data: dict, update: dict):
315
336
  data[upd_key] = upd_val
316
337
 
317
338
 
339
+ URI_SEP_CHAR = '⫻' # U+2AFB: TRIPLE SOLIDUS BINARY RELATION
340
+ ASCII_UNICODE = (
341
+ ('/', '⁄'), # U+2044: Fraction Slash; '∕' U+2215: Division Slash; '⧸' U+29F8: Big Solidus
342
+ # ; '╱' U+FF0F: Fullwidth Solidus; '╱' U+2571: Box Drawings Light Diagonal Upper Right to Lower Left
343
+ ('|', '।'), # U+0964: Devanagari Danda
344
+ ('\\', '﹨'), # U+FE68: SMALL REVERSE SOLIDUS; '⑊' U+244A OCR DOUBLE BACKSLASH; '⧵' U+29F5 REV. SOLIDUS OPERATOR
345
+ (':', '﹕'), # U+FE55: Small Colon
346
+ ('*', '﹡'), # U+FE61: Small Asterisk
347
+ ('?', '﹖'), # U+FE56: Small Question Mark
348
+ ('"', '"'), # U+FF02: Fullwidth Quotation Mark
349
+ ("'", '‘'), # U+2018: Left Single; '’' U+2019: Right Single; '‛' U+201B: Single High-Reversed-9 Quotation Mark
350
+ ('<', '⟨'), # U+27E8: LEFT ANGLE BRACKET; '‹' U+2039: Single Left-Pointing Angle Quotation Mark
351
+ ('>', '⟩'), # U+27E9: RIGHT ANGLE BRACKET; '›' U+203A: Single Right-Pointing Angle Quotation Mark
352
+ ('(', '⟮'), # U+27EE: MATHEMATICAL LEFT FLATTENED PARENTHESIS
353
+ (')', '⟯'), # U+27EF: MATHEMATICAL RIGHT FLATTENED PARENTHESIS
354
+ ('[', '⟦'), # U+27E6: MATHEMATICAL LEFT WHITE SQUARE BRACKET
355
+ (']', '⟧'), # U+27E7: MATHEMATICAL RIGHT WHITE SQUARE BRACKET
356
+ ('_', '𛲖'), # U+1BC96: Duployan Affix Low Line; '_' U+FF3F Fullwidth Low Line
357
+ ('#', '﹟'), # U+FE5F: Small Number Sign
358
+ (';', '﹔'), # U+FE54: Small Semicolon
359
+ ('@', '﹫'), # U+FE6B: Small Commercial At
360
+ ('&', '﹠'), # U+FE60: Small Ampersand
361
+ ('=', '﹦'), # U+FE66: Small Equals Sign
362
+ ('+', '﹢'), # U+FE62: Small Plus Sign
363
+ ('$', '﹩'), # U+FE69: Small Dollar Sign
364
+ ('%', '﹪'), # U+FE6A: Small Percent Sign
365
+ ('^', '^'), # U+FF3E: Fullwidth Circumflex Accent
366
+ (',', '﹐'), # U+FE50: Small Comma
367
+ (' ', ' '), # U+3000: Ideographic Space; ' ' U+200A Hair Space; ' ' U+2007 Figure Space;
368
+ # ' ' U+2009 Thin; ' ' U+2003 Em Space; ' ' U+2002 En Space; ' ' U+2008 Punctuation Space
369
+ # ' ' U+00A0: No-Break Space (NBSP); ' ' U+202F: Narrow No-Break Space (NNBSP)
370
+ (chr(127), '␡'), # U+2421: DELETE SYMBOL
371
+ )
372
+ """ transformation table of special ASCII to Unicode alternative character,
373
+ see https://www.compart.com/en/unicode/category/Po and https://xahlee.info/comp/unicode_naming_slash.html (http!) """
374
+
375
+ ASCII_TO_UNICODE = dict(ASCII_UNICODE) #: map to convert ASCII to an alternative defused Unicode character
376
+ UNICODE_TO_ASCII = {unicode_char: ascii_char for ascii_char, unicode_char in ASCII_UNICODE} #: Unicode to ASCII map
377
+
378
+
379
+ def dedefuse(value: str) -> str:
380
+ """ convert a string that got defused with :func:`defuse` back to its original form.
381
+
382
+ :param value: string defused with the function :func:`defuse`.
383
+ :return: re-activated form of the string (with all ASCII special characters recovered).
384
+ """
385
+ original = ""
386
+ for char in value:
387
+ if char in UNICODE_TO_ASCII:
388
+ char = UNICODE_TO_ASCII[char]
389
+ elif 0x2400 <= (code := ord(char)) <= 0x241F:
390
+ char = chr(code - 0x2400)
391
+ original += char
392
+
393
+ return original.replace(URI_SEP_CHAR, '://')
394
+
395
+
396
+ def defuse(value: str) -> str:
397
+ """ convert a file path or a URI into a defused/presentational form to be usable as URL slug or file/folder name.
398
+
399
+ :param value: any string to defuse (replace special chars with Unicode alternatives).
400
+ :return: string with its special characters replaced by its pure presentational alternatives.
401
+
402
+ the ASCII character range 0..31 gets converted to the Unicode range U+2400 + ord(char): 0==U+2400 ... 31==U+241F.
403
+
404
+ in *nix only / and \0 are not allowed characters in file names.
405
+
406
+ in MS Windows are not allowed: ASCII 0...31): / | \\ : * ? ” % < > ( ). some blogs recommend to also not allow
407
+ (convert) the characters # and '.
408
+ only old POSIX seems to be even more restricted (only allowing alphanumeric characters plus . - and _).
409
+
410
+ more on allowed characters in file names in the answers of RedGrittyBrick on https://superuser.com/questions/358855
411
+ and of Christopher Oezbek on https://stackoverflow.com/questions/1976007.
412
+
413
+ file name length is not restricted/shortened by this function, although the maximum is 255 characters on most OSs.
414
+
415
+ .. hint:: use :func:`dedefuse` to convert the defused string back to the corresponding URI/file-path.
416
+
417
+ """
418
+ defused = ""
419
+ value = value.replace('://', URI_SEP_CHAR) # make URIs shorter
420
+ for char in value:
421
+ if char in ASCII_TO_UNICODE:
422
+ char = ASCII_TO_UNICODE[char]
423
+ elif (code := ord(char)) <= 31:
424
+ char = chr(0x2400 + code)
425
+ defused += char
426
+ return defused
427
+
428
+
318
429
  def dummy_function(*_args, **_kwargs):
319
430
  """ null function accepting any arguments and returning None.
320
431
 
@@ -358,17 +469,6 @@ def env_str(name: str, convert_name: bool = False) -> Optional[str]:
358
469
  return os.environ.get(name)
359
470
 
360
471
 
361
- def filename2uri(file_name: str) -> str:
362
- """ convert a file name converted by :func:`uri2filename` back to its representation as a URI
363
-
364
- :param file_name: name of the file/folder to convert back to its URI representation.
365
- :return: URI string.
366
-
367
- .. hint:: to ensure proper conversion the specified file name has to be created by :func:`uri2filename`.
368
- """
369
- return urllib.parse.unquote(file_name)
370
-
371
-
372
472
  def force_encoding(text: Union[str, bytes], encoding: str = DEF_ENCODING, errors: str = DEF_ENCODE_ERRORS) -> str:
373
473
  """ force/ensure the encoding of text (str or bytes) without any UnicodeDecodeError/UnicodeEncodeError.
374
474
 
@@ -417,8 +517,8 @@ def import_module(import_name: str, path: Optional[Union[str, UnsetType]] = UNSE
417
517
  :return: a reference to the loaded module or ``None`` if module could not be imported.
418
518
  """
419
519
  if path is UNSET:
420
- path = import_name.replace('.', os.path.sep)
421
- path += PY_EXT if os.path.isfile(path + PY_EXT) else os.path.sep + PY_INIT
520
+ path = import_name.replace('.', os_path_sep)
521
+ path += PY_EXT if os_path_isfile(path + PY_EXT) else os_path_sep + PY_INIT
422
522
  mod_ref = None
423
523
 
424
524
  spec = importlib.util.spec_from_file_location(import_name, path) # type: ignore # silly mypy
@@ -469,7 +569,7 @@ def load_dotenvs():
469
569
  """
470
570
  load_env_var_defaults(os.getcwd())
471
571
  if file_name := stack_var('__file__'):
472
- load_env_var_defaults(os.path.dirname(os.path.abspath(file_name)))
572
+ load_env_var_defaults(os_path_dirname(os_path_abspath(file_name)))
473
573
 
474
574
 
475
575
  def load_env_var_defaults(start_dir: str):
@@ -485,18 +585,18 @@ def load_env_var_defaults(start_dir: str):
485
585
  only variables that are not declared in :data:`os.environ` will be added (with the
486
586
  value specified in the ``.env`` file to be loaded).
487
587
  """
488
- file_path = os.path.abspath(os.path.join(start_dir, DOTENV_FILE_NAME))
489
- if not os.path.isfile(file_path):
490
- file_path = os.path.join(os.path.dirname(start_dir), DOTENV_FILE_NAME)
588
+ file_path = os_path_abspath(os_path_join(start_dir, DOTENV_FILE_NAME))
589
+ if not os_path_isfile(file_path):
590
+ file_path = os_path_join(os_path_dirname(start_dir), DOTENV_FILE_NAME)
491
591
 
492
- while os.path.isfile(file_path):
592
+ while os_path_isfile(file_path):
493
593
  for var_nam, var_val in parse_dotenv(file_path).items():
494
594
  if var_nam not in os.environ:
495
595
  os.environ[var_nam] = var_val
496
596
 
497
597
  if os.sep not in file_path:
498
598
  break # pragma: no cover # prevent endless-loop for ``.env`` file in root dir (os.sep == '/')
499
- file_path = os.path.join(os.path.dirname(os.path.dirname(file_path)), DOTENV_FILE_NAME)
599
+ file_path = os_path_join(os_path_dirname(os_path_dirname(file_path)), DOTENV_FILE_NAME)
500
600
 
501
601
 
502
602
  def main_file_paths_parts(portion_name: str) -> Tuple[Tuple[str, ...], ...]:
@@ -620,20 +720,20 @@ def norm_path(path: str, make_absolute: bool = True, remove_base_path: str = "",
620
720
  """
621
721
  path = path or "."
622
722
  if path[0] == "~":
623
- path = os.path.expanduser(path)
723
+ path = os_path_expanduser(path)
624
724
 
625
725
  if remove_dots:
626
- path = os.path.normpath(path)
726
+ path = os_path_normpath(path)
627
727
 
628
728
  if resolve_sym_links:
629
- path = os.path.realpath(path)
729
+ path = os_path_realpath(path)
630
730
  elif make_absolute:
631
- path = os.path.abspath(path)
731
+ path = os_path_abspath(path)
632
732
 
633
733
  if remove_base_path:
634
734
  if remove_base_path[0] == "~":
635
- remove_base_path = os.path.expanduser(remove_base_path)
636
- path = os.path.relpath(path, remove_base_path)
735
+ remove_base_path = os_path_expanduser(remove_base_path)
736
+ path = os_path_relpath(path, remove_base_path)
637
737
 
638
738
  return path
639
739
 
@@ -768,23 +868,22 @@ def project_main_file(import_name: str, project_path: str = "") -> str:
768
868
  sister project (under the same project parent folder).
769
869
  :return: absolute file path/name of main module or empty string if no main/version file found.
770
870
  """
771
- join = os.path.join
772
871
  *namespace_dirs, portion_name = import_name.split('.')
773
872
  project_name = ('_'.join(namespace_dirs) + '_' if namespace_dirs else "") + portion_name
774
873
  paths_parts = main_file_paths_parts(portion_name)
775
874
 
776
875
  project_path = norm_path(project_path)
777
876
  module_paths = []
778
- if os.path.basename(project_path) != project_name:
779
- module_paths.append(join(os.path.dirname(project_path), project_name, *namespace_dirs))
877
+ if os_path_basename(project_path) != project_name:
878
+ module_paths.append(os_path_join(os_path_dirname(project_path), project_name, *namespace_dirs))
780
879
  if namespace_dirs:
781
- module_paths.append(join(project_path, *namespace_dirs))
880
+ module_paths.append(os_path_join(project_path, *namespace_dirs))
782
881
  module_paths.append(project_path)
783
882
 
784
883
  for module_path in module_paths:
785
884
  for path_parts in paths_parts:
786
- main_file = join(module_path, *path_parts)
787
- if os.path.isfile(main_file):
885
+ main_file = os_path_join(module_path, *path_parts)
886
+ if os_path_isfile(main_file):
788
887
  return main_file
789
888
  return ""
790
889
 
@@ -899,7 +998,7 @@ def stack_vars(*skip_modules: str,
899
998
  :param max_depth: the maximum depth in the call stack from which to return the variables. if the specified
900
999
  argument is not zero and no :paramref:`~stack_vars.skip_modules` are specified then the
901
1000
  first deeper stack frame that is not within the default :data:`SKIPPED_MODULES` will be
902
- returned. if this argument and :paramref:`~stack_var.find_name` get not passed then the
1001
+ returned. if this argument and :paramref:`~stack_vars.find_name` get not passed then the
903
1002
  variables of the top stack frame will be returned.
904
1003
  :return: tuple of the global and local variable dicts and the depth in the call stack.
905
1004
  """
@@ -985,29 +1084,6 @@ def to_ascii(unicode_str: str) -> str:
985
1084
  return "".join([c for c in nfkd_form if not unicodedata.combining(c)]).replace('ß', "ss").replace('€', "Euro")
986
1085
 
987
1086
 
988
- def uri2filename(uri: str) -> str:
989
- """ convert a URI to be usable as name of a file or folder
990
-
991
- :param uri: URI to convert to a corresponding file name, that will be revertible back to this URI.
992
- :return: name of a file/folder representing the specified URI.
993
-
994
- in *nix only / and \0 are not allowed characters in file names.
995
- in MS Windows are not allowed: ASCII 0...31): / \\ : * ? ” < > | (). some blogs recommend to also not allow
996
- (convert) the characters # and '.
997
- only old POSIX seems to be even more restricted (only allowing alphanumeric characters plus . - and _).
998
-
999
- file name length is not restricted/shortened by this function, although the maximum is 255 characters on most OSs.
1000
-
1001
- more on allowed characters in file names in the answers of RedGrittyBrick on https://superuser.com/questions/358855
1002
- and of Christopher Oezbek on https://stackoverflow.com/questions/1976007.
1003
-
1004
- .. hint:: use :func:`filename2uri` to convert the resulting file name back to the corresponding URO
1005
- """
1006
- # using urllib.parse.quote(uri, safe="") instead would convert also any non-ascii (e.g. umlaut) characters into hex
1007
- # added [] to str.join() argument because List comprehensions are faster than generator expressions
1008
- return "".join([f"%{hex(ord(_))[2:].upper()}" if _ in '/|\\:*?"<>%' else _ for _ in uri])
1009
-
1010
-
1011
1087
  def utc_datetime() -> datetime.datetime:
1012
1088
  """ return the current UTC timestamp as string (to use as suffix for file and variable/attribute names).
1013
1089
 
@@ -1020,14 +1096,18 @@ def write_file(file_path: str, content: Union[str, bytes], extra_mode: str = "",
1020
1096
  """ (over)write the file specified by :paramref:`~write_file.file_path` with text or binary/bytes content.
1021
1097
 
1022
1098
  :param file_path: file path/name to write the passed content into (overwriting any previous content!).
1023
- :param content: new file content either passed as string or list of line strings (will be
1024
- concatenated with the line separator of the current OS: os.linesep).
1025
- :param extra_mode: open mode flag characters. passed unchanged to the `mode` argument of :func:`open` if
1026
- this argument starts with 'a', else this argument value will be appended to 'w'.
1099
+ :param content: new file content passed either as string or bytes array. if a bytes array get passed
1100
+ then this method will automatically write the content as binary.
1101
+ :param extra_mode: additional open mode flag characters. passed to the `mode` argument of :func:`open` if
1102
+ this argument starts with 'a' or 'w', else this argument value will be appended to 'w'
1103
+ before it get passed to the `mode` argument of :func:`open`.
1104
+ if the :paramref:`~write_file.content` is a bytes array, then a 'b' character will
1105
+ be automatically added to the `mode` argument of :func:`open` (if not already specified
1106
+ in this argument).
1027
1107
  :param encoding: encoding used to write/convert/interpret the file content to write.
1028
1108
  :raises FileExistsError: if file exists already and is write-protected.
1029
1109
  :raises FileNotFoundError: if parts of the file path do not exist.
1030
- :raises OSError: if :paramref:`~read_file.file_path` is misspelled or contains invalid characters.
1110
+ :raises OSError: if :paramref:`~write_file.file_path` is misspelled or contains invalid characters.
1031
1111
  :raises PermissionError: if current OS user account lacks permissions to read the file content.
1032
1112
  :raises ValueError: on decoding errors.
1033
1113
 
@@ -1040,7 +1120,13 @@ def write_file(file_path: str, content: Union[str, bytes], extra_mode: str = "",
1040
1120
  for an intent result.
1041
1121
  Related german docs: https://developer.android.com/training/data-storage/shared/media?hl=de
1042
1122
  """
1043
- with open(file_path, ('' if extra_mode.startswith('a') else 'w') + extra_mode, encoding=encoding) as file_handle:
1123
+ if isinstance(content, bytes) and 'b' not in extra_mode:
1124
+ extra_mode += 'b'
1125
+
1126
+ if extra_mode == '' or extra_mode[0] not in ('a', 'w'):
1127
+ extra_mode = 'w' + extra_mode
1128
+
1129
+ with open(file_path, mode=extra_mode, encoding=encoding) as file_handle:
1044
1130
  file_handle.write(content)
1045
1131
 
1046
1132
 
@@ -1069,16 +1155,16 @@ class ErrorMsgMixin:
1069
1155
  PACKAGE_NAME = stack_var('__name__') or 'unspecified_package'
1070
1156
  PACKAGE_DOMAIN = 'org.test'
1071
1157
  PERMISSIONS = "INTERNET, VIBRATE, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE"
1072
- if os.path.exists(BUILD_CONFIG_FILE): # pragma: no cover
1158
+ if os_path_isfile(BUILD_CONFIG_FILE): # pragma: no cover
1073
1159
  PACKAGE_NAME, PACKAGE_DOMAIN, PERMISSIONS = build_config_variable_values(
1074
1160
  ('package.name', PACKAGE_NAME),
1075
1161
  ('package.domain', PACKAGE_DOMAIN),
1076
1162
  ('android.permissions', PERMISSIONS))
1077
1163
  elif os_platform == 'android': # pragma: no cover
1078
1164
  _importing_package = norm_path(stack_var('__file__') or 'empty_package' + PY_EXT)
1079
- if os.path.basename(_importing_package) in (PY_INIT, PY_MAIN):
1080
- _importing_package = os.path.dirname(_importing_package)
1081
- _importing_package = os.path.splitext(os.path.basename(_importing_package))[0]
1165
+ if os_path_basename(_importing_package) in (PY_INIT, PY_MAIN):
1166
+ _importing_package = os_path_dirname(_importing_package)
1167
+ _importing_package = os_path_splitext(os_path_basename(_importing_package))[0]
1082
1168
  write_file(f'{_importing_package}_debug.log', f"{BUILD_CONFIG_FILE} not bundled - using defaults\n", extra_mode='a')
1083
1169
 
1084
1170
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ae_base
3
- Version: 0.3.41
3
+ Version: 0.3.43
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
@@ -55,17 +55,17 @@ Requires-Dist: twine; extra == "tests"
55
55
 
56
56
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.94 -->
57
57
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
58
- # base 0.3.41
58
+ # base 0.3.43
59
59
 
60
60
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_base/develop?logo=python)](
61
61
  https://gitlab.com/ae-group/ae_base)
62
62
  [![LatestPyPIrelease](
63
- https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.40?logo=python)](
64
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.40)
63
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.42?logo=python)](
64
+ https://gitlab.com/ae-group/ae_base/-/tree/release0.3.42)
65
65
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_base)](
66
66
  https://pypi.org/project/ae-base/#history)
67
67
 
68
- >ae_base module 0.3.41.
68
+ >ae_base module 0.3.43.
69
69
 
70
70
  [![Coverage](https://ae-group.gitlab.io/ae_base/coverage.svg)](
71
71
  https://ae-group.gitlab.io/ae_base/coverage/index.html)
@@ -0,0 +1,7 @@
1
+ ae/base.py,sha256=SomP4LSOql8hbXH2rzcrutx_hIBAmeV0kmEEarF4k0g,60888
2
+ ae_base-0.3.43.dist-info/LICENSE.md,sha256=eHVr16LWo8bsm2NbdFOBcQp-F_3MUXo9I922sABpsMQ,35002
3
+ ae_base-0.3.43.dist-info/METADATA,sha256=ha7RhCCUS7BHy9yAqBPWzCGSXbZ2c4toQDW2z0RPRNU,5311
4
+ ae_base-0.3.43.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
5
+ ae_base-0.3.43.dist-info/top_level.txt,sha256=vUdgAslSmhZLXWU48fm8AG2BjVnkOWLco8rzuW-5zY0,3
6
+ ae_base-0.3.43.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
7
+ ae_base-0.3.43.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,7 +0,0 @@
1
- ae/base.py,sha256=9qixFzNhWVcGztz9Y7zv7vsQL485LEXH-IsfF61yo_M,56696
2
- ae_base-0.3.41.dist-info/LICENSE.md,sha256=eHVr16LWo8bsm2NbdFOBcQp-F_3MUXo9I922sABpsMQ,35002
3
- ae_base-0.3.41.dist-info/METADATA,sha256=pr0WsX0BOV067Vj5cxyfuWPNtr0rTHURIlRoacPaZe0,5311
4
- ae_base-0.3.41.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
5
- ae_base-0.3.41.dist-info/top_level.txt,sha256=vUdgAslSmhZLXWU48fm8AG2BjVnkOWLco8rzuW-5zY0,3
6
- ae_base-0.3.41.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
7
- ae_base-0.3.41.dist-info/RECORD,,