ae-base 0.3.53__tar.gz → 0.3.55__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.53
3
+ Version: 0.3.55
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.53
72
+ # base 0.3.55
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.52?logo=python)](
78
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.52)
77
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.54?logo=python)](
78
+ https://gitlab.com/ae-group/ae_base/-/tree/release0.3.54)
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.53.
82
+ >ae_base module 0.3.55.
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.53
3
+ # base 0.3.55
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.52?logo=python)](
9
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.52)
8
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.54?logo=python)](
9
+ https://gitlab.com/ae-group/ae_base/-/tree/release0.3.54)
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.53.
13
+ >ae_base module 0.3.55.
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)
@@ -7,7 +7,7 @@ functions, useful classes and context managers.
7
7
 
8
8
  .. note::
9
9
  on import of this module, while running on Android OS, it will monkey patch the :mod:`shutil` module
10
- to allow to use them on Android devices. therefore the import of this module should be one of the first ones
10
+ to allow using them on Android devices. therefore, the import of this module should be one of the first ones
11
11
  in your app's main module.
12
12
 
13
13
 
@@ -18,7 +18,7 @@ ISO format strings for ``date`` and ``datetime`` values are provided by the cons
18
18
  :data:`DATE_TIME_ISO`.
19
19
 
20
20
  the :data:`UNSET` constant is useful in cases where ``None`` is a valid data value and another special value is needed
21
- to specify that e.g. an argument or attribute has no (valid) value or did not get specified/passed.
21
+ to specify that e.g., an argument or attribute has no (valid) value or did not get specified/passed.
22
22
 
23
23
  default values to compile file and folder names for a package or an app project are provided by the constants:
24
24
  :data:`DOCS_FOLDER`, :data:`TESTS_FOLDER`, :data:`TEMPLATES_FOLDER`, :data:`BUILD_CONFIG_FILE`,
@@ -36,7 +36,7 @@ in order to convert and transfer Unicode character outside the 7-bit ASCII range
36
36
  like http, use the helper functions :func:`ascii_str` and :func:`str_ascii`.
37
37
 
38
38
  :func:`now_str` creates a timestamp string with the actual UTC date and time. the :func:`utc_datetime` provides the
39
- actual UTC date and time as datetime object.
39
+ actual UTC date and time as a datetime object.
40
40
 
41
41
  to write more compact and readable code for the most common file I/O operations, the helper functions :func:`read_file`
42
42
  and :func:`write_file` are wrapping Python's built-in :func:`open` function and its context manager.
@@ -44,11 +44,12 @@ and :func:`write_file` are wrapping Python's built-in :func:`open` function and
44
44
  the function :func:`duplicates` returns the duplicates of an iterable type.
45
45
 
46
46
  in order to hide/mask secrets like credit card numbers, passwords or tokens in deeply nested data structures,
47
- before they get dumped e.g. to an app log file, the function :func:`mask_secrets` can be used.
47
+ before they get dumped e.g., to an app log file, the function :func:`mask_secrets` can be used.
48
48
 
49
49
  :func:`norm_line_sep` is converting any combination of line separators of a string to a single new-line character.
50
50
 
51
- :func:`norm_name` converts any string into a name that can be used e.g. as file name or as method/attribute name.
51
+ the function :func:`norm_name` converts any string into a name that can be used e.g., as a file name
52
+ or as a method/attribute name.
52
53
 
53
54
  to normalize a file path, in order to remove `.`, `..` placeholders, to resolve symbolic links or to make it relative or
54
55
  absolute, call the function :func:`norm_path`.
@@ -57,16 +58,16 @@ absolute, call the function :func:`norm_path`.
57
58
  either as a URL slug or as a file name. use the function :func:`dedefuse` to convert this string back to the
58
59
  corresponding URL/URI or file path.
59
60
 
60
- :func:`camel_to_snake` and :func:`snake_to_camel` providing name conversions of class and method names.
61
+ the functions :func:`camel_to_snake` and :func:`snake_to_camel` providing name conversions of class and method names.
61
62
 
62
- to encode Unicode strings to other codecs the functions :func:`force_encoding` and :func:`to_ascii` can be used.
63
+ to encode Unicode strings to other codecs, the functions :func:`force_encoding` and :func:`to_ascii` can be used.
63
64
 
64
- the :func:`round_traditional` function get provided by this module for traditional rounding of float values. the
65
- function signature is fully compatible to Python's :func:`round` function.
65
+ the :func:`round_traditional` function gets provided by this module for traditional rounding of float values. the
66
+ function signature is fully compatible with Python's :func:`round` function.
66
67
 
67
68
  the function :func:`instantiate_config_parser` ensures that the :class:`~configparser.ConfigParser` instance is
68
- correctly configured, e.g. to support case-sensitive config variable names and to use :class:`ExtendedInterpolation` for
69
- the interpolation argument.
69
+ correctly configured, e.g., to support case-sensitive config variable names and to use :class:`ExtendedInterpolation`
70
+ as the interpolation argument.
70
71
 
71
72
  :func:`app_name_guess` guesses the name of o running Python application from the application environment, with the help
72
73
  of :func:`build_config_variable_values`, which determines config-variable-values from the build spec file of an app
@@ -79,29 +80,29 @@ operating system constants and helpers
79
80
  the string :data:`os_platform` provides the OS where your app is running, extending Python's :func:`sys.platform`
80
81
  for mobile platforms like Android and iOS.
81
82
 
82
- :func:`os_host_name`, :func:`os_local_ip` and :func:`os_user_name` are determining machine and user information from
83
- the OS.
83
+ the functions :func:`os_host_name`, :func:`os_local_ip` and :func:`os_user_name` are determining machine and
84
+ user information from the OS.
84
85
 
85
86
  use :func:`env_str` to determine the value of an OS environment variable with automatic variable name conversion. other
86
87
  helper functions provided by this namespace portion to determine the values of the most important system environment
87
88
  variables for your application are :func:`sys_env_dict` and :func:`sys_env_text`.
88
89
 
89
90
  to integrate system environment variables from ``.env`` files into :data:`os.environ` the helper functions
90
- :func:parse_dotenv`, :func:`load_env_var_defaults` and :func:`load_dotenvs` are provided.
91
+ :func:`parse_dotenv`, :func:`load_env_var_defaults` and :func:`load_dotenvs` are provided.
91
92
 
92
93
  the :mod:`ae.core` portion is providing more OS-specific constants and helper functions, like e.g.
93
94
  :func:`start_app_service` and :func:`request_app_permissions`.
94
95
 
95
96
  .. note::
96
- on import of this module, while running on Android OS, it will monkey patch the :mod:`shutil` module to allow to
97
- use them on Android devices, and on first app start request the permissions of your app. therefore to prevent
98
- permission errors, the import of this module should be the first statement in the main module of your app.
97
+ on import of this module, while running on Android OS, it will monkey patch the :mod:`shutil` module to allow
98
+ using them on Android devices, and on the first app start requesting the permissions of your app. therefore, to
99
+ prevent permission errors, the import of this module should be the first statement in the main module of your app.
99
100
 
100
101
 
101
102
  types, classes and mixins
102
103
  -------------------------
103
104
 
104
- the :class:`UnsetType` class can be used e.g. for the declaration of optional function and method parameters,
105
+ the :class:`UnsetType` class can be used e.g., for the declaration of optional function and method parameters,
105
106
  allowing also ``None`` is an accepted argument value.
106
107
 
107
108
  to extend any class with an intelligent error message handling, add the mixin :class:`ErrorMsgMixin` to it.
@@ -113,7 +114,7 @@ enclosed in curly brackets. the function :func:`format_given` is using them to f
113
114
  generic context manager
114
115
  -----------------------
115
116
 
116
- the context manager :func:`in_wd` allows to switch the current working directory temporarily. the following
117
+ the context manager :func:`in_wd` allows switching the current working directory temporarily. the following
117
118
  example demonstrates a typical usage, together with a temporary path, created with the help of Pythons
118
119
  :class:`~tempfile.TemporaryDirectory` class::
119
120
 
@@ -129,7 +130,7 @@ call stack inspection
129
130
  :func:`module_attr` dynamically determines a reference to an attribute (variable, function, class, ...) in a module.
130
131
 
131
132
  :func:`module_name`, :func:`stack_frames`, :func:`stack_var` and :func:`stack_vars` are inspecting the call stack frames
132
- to determine e.g. variable values of the callers of a function/method.
133
+ to determine e.g., variable values of the callers of a function/method.
133
134
 
134
135
  .. hint::
135
136
  the :class:`AppBase` class uses these helper functions to determine the :attr:`version <AppBase.app_version>` and
@@ -167,7 +168,7 @@ from types import ModuleType
167
168
  from typing import Any, Callable, Generator, Iterable, Optional, Union, cast
168
169
 
169
170
 
170
- __version__ = '0.3.53'
171
+ __version__ = '0.3.55'
171
172
 
172
173
 
173
174
  os_path_abspath = os.path.abspath
@@ -190,7 +191,7 @@ TEMPLATES_FOLDER = 'templates'
190
191
  """ template folder name, used in template and namespace root projects to maintain and provide common file templates """
191
192
 
192
193
  BUILD_CONFIG_FILE = 'buildozer.spec' #: gui app build config file
193
- PACKAGE_INCLUDE_FILES_PREFIX = 'ae_' #: file/folder names prefix included into setup package_data/ae_updater
194
+ PACKAGE_INCLUDE_FILES_PREFIX = 'ae_' #: file/folder names prefix included in setup package_data/ae_updater
194
195
 
195
196
  PY_CACHE_FOLDER = '__pycache__' #: python cache folder name
196
197
  PY_EXT = '.py' #: file extension for modules and hooks
@@ -239,14 +240,15 @@ NAME_PARTS_SEP = '_' #: name parts separator char
239
240
 
240
241
  NOW_STR_FORMAT = "{sep}%Y%m%d{sep}%H%M%S{sep}%f" #: timestamp format of :func:`now_str`
241
242
 
242
- SKIPPED_MODULES = ('ae.base', 'ae.paths', 'ae.dynamicod', 'ae.core', 'ae.console', 'ae.gui_app', 'ae.gui_help',
243
+ SKIPPED_MODULES = ('ae.base', 'ae.paths', 'ae.dynamicod', 'ae.core', 'ae.console',
244
+ 'ae.gui', 'ae.gui.app', 'ae.gui.tours', 'ae.gui.utils',
243
245
  'ae.kivy', 'ae.kivy.apps', 'ae.kivy.behaviors', 'ae.kivy.i18n', 'ae.kivy.tours', 'ae.kivy.widgets',
244
246
  'ae.enaml_app', 'ae.beeware_app', 'ae.pyglet_app', 'ae.pygobject_app', 'ae.dabo_app',
245
247
  'ae.qpython_app', 'ae.appjar_app', 'importlib._bootstrap', 'importlib._bootstrap_external')
246
248
  """ skipped modules used as default by :func:`module_name`, :func:`stack_var` and :func:`stack_vars` """
247
249
 
248
250
 
249
- # using only object() does not provide proper representation string
251
+ # using only object() does not provide a proper representation string
250
252
  class UnsetType:
251
253
  """ (singleton) UNSET (type) object class. """
252
254
  def __bool__(self):
@@ -280,7 +282,7 @@ def app_name_guess() -> str:
280
282
 
281
283
 
282
284
  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.
285
+ """ convert non-ASCII chars to a revertible 7-bit/ASCII representation, e.g., to put in an http header.
284
286
 
285
287
  :param unicode_str: string to encode/convert.
286
288
  :return: revertible representation of the specified string, using only ASCII characters.
@@ -315,7 +317,7 @@ def build_config_variable_values(*names_defaults: tuple[str, Any], section: str
315
317
 
316
318
 
317
319
  def camel_to_snake(name: str) -> str:
318
- """ convert name from CamelCase to snake_case.
320
+ """ convert a name from CamelCase to snake_case.
319
321
 
320
322
  :param name: name string in CamelCaseFormat.
321
323
  :return: name in snake_case_format.
@@ -330,9 +332,9 @@ def camel_to_snake(name: str) -> str:
330
332
 
331
333
 
332
334
  def deep_dict_update(data: dict, update: dict):
333
- """ update the optionally nested data dict in-place with the items and sub-items from the update dict.
335
+ """ update the optionally nested data dict in-place with the items and subitems from the update dict.
334
336
 
335
- :param data: dict to be updated/extended. non-existing keys of dict-sub-items will be added.
337
+ :param data: dict to be updated/extended. non-existing keys of dict-subitems will be added.
336
338
  :param update: dict with the [sub-]items to update in the :paramref:`~deep_dict_update.data` dict.
337
339
 
338
340
  .. hint:: the module/portion :mod:`ae.deep` is providing more deep update helper functions.
@@ -349,8 +351,8 @@ def deep_dict_update(data: dict, update: dict):
349
351
 
350
352
  URI_SEP_CHAR = '⫻' # U+2AFB: TRIPLE SOLIDUS BINARY RELATION
351
353
  ASCII_UNICODE = (
352
- ('/', '⁄'), # U+2044: Fraction Slash; '∕' U+2215: Division Slash; '⧸' U+29F8: Big Solidus
353
- # ; '╱' U+FF0F: Fullwidth Solidus; '╱' U+2571: Box Drawings Light Diagonal Upper Right to Lower Left
354
+ ('/', '⁄'), # U+2044: Fraction Slash; '∕' U+2215: Division Slash; '⧸' U+29F8: Big Solidus;
355
+ # '╱' U+FF0F: Fullwidth Solidus; '╱' U+2571: Box Drawings Light Diagonal Upper Right to Lower Left
354
356
  ('|', '।'), # U+0964: Devanagari Danda
355
357
  ('\\', '﹨'), # U+FE68: SMALL REVERSE SOLIDUS; '⑊' U+244A OCR DOUBLE BACKSLASH; '⧵' U+29F5 REV. SOLIDUS OPERATOR
356
358
  (':', '﹕'), # U+FE55: Small Colon
@@ -364,6 +366,8 @@ ASCII_UNICODE = (
364
366
  (')', '⟯'), # U+27EF: MATHEMATICAL RIGHT FLATTENED PARENTHESIS
365
367
  ('[', '⟦'), # U+27E6: MATHEMATICAL LEFT WHITE SQUARE BRACKET
366
368
  (']', '⟧'), # U+27E7: MATHEMATICAL RIGHT WHITE SQUARE BRACKET
369
+ ('{', '﹛'), # U+FE5B: Small Left Curly Bracket
370
+ ('}', '﹜'), # U+FE5C: Small Right Curly Bracket
367
371
  ('#', '﹟'), # U+FE5F: Small Number Sign
368
372
  (';', '﹔'), # U+FE54: Small Semicolon
369
373
  ('@', '﹫'), # U+FE6B: Small Commercial At
@@ -382,7 +386,7 @@ ASCII_UNICODE = (
382
386
  # ' ' U+202F: Narrow No-Break Space (NNBSP); ' ' U+205F Medium Mathematical Space;
383
387
  # '␠' U+2420 symbol for space; '␣' U+2423 Open Box; ' ' U+3000: Ideographic Space
384
388
  (chr(127), '␡'), # U+2421: DELETE SYMBOL
385
- # ('_', '𛲖'), # U+1BC96: Duployan Affix Low Line; '_' U+FF3F Fullwidth Low Line
389
+ # ('_', '𛲖'), # U+1BC96: Duployan Affix Low Line; '_' U+FF3F Fullwidth Low Line
386
390
  )
387
391
  """ transformation table of special ASCII to Unicode alternative character,
388
392
  see https://www.compart.com/en/unicode/category/Po and https://xahlee.info/comp/unicode_naming_slash.html (http!) """
@@ -418,7 +422,7 @@ def defuse(value: str) -> str:
418
422
 
419
423
  in most unix variants only the slash and the ASCII 0 characters are not allowed in file names.
420
424
 
421
- in MS Windows are not allowed: ASCII 0..31 / | \\ : * ? ” % < > ( ). some blogs recommend to also not allow
425
+ in MS Windows are not allowed: ASCII 0..31 / | \\ : * ? ” % < > ( ). some blogs recommend also not allowing
422
426
  (convert) the characters # and '.
423
427
 
424
428
  only old POSIX seems to be even more restricted (only allowing alphanumeric characters plus . - and _).
@@ -504,7 +508,7 @@ class UnformattedValue: # pylint: disable=too-few-public-met
504
508
  self.key = key
505
509
 
506
510
  def __format__(self, format_spec: str):
507
- """ overriding Python object class method to return placeholder unchanged including the curly brackets. """
511
+ """ overriding Python object class method to return placeholder unchanged, including the curly brackets. """
508
512
  # pylint: disable=consider-using-f-string
509
513
  return "{{{}{}}}".format(self.key, ":" + format_spec if format_spec else "")
510
514
 
@@ -523,14 +527,12 @@ def format_given(text: str, placeholder_map: dict[str, Any], strict: bool = Fals
523
527
  """ replacement for Python's str.format_map(), keeping intact placeholders that are not in the specified mapping.
524
528
 
525
529
  :param text: text/template in which the given/specified placeholders will get replaced. in contrary
526
- to str.format_map() no KeyError will be raised for placeholders not specified in
530
+ to :func:`str.format_map`, no KeyError will be raised for placeholders not specified in
527
531
  :paramref:`~format_given.placeholder_map`.
528
532
  :param placeholder_map: dict with placeholder keys to be replaced in :paramref:`~format_given.text` argument.
529
- :param strict: pass True to raise error for text templates containing unpaired curly brackets.
533
+ :param strict: pass True to raise an error for text templates containing unpaired curly brackets.
530
534
  :return: the specified :paramref:`~format_given.text` with only the placeholders specified in
531
535
  :paramref:`~format_given.placeholder_map` replaced with their respective map value.
532
- additionally any ValueError that would be thrown by str.format_map(), e.g. if the
533
-
534
536
 
535
537
  inspired by the answer of CodeManX in `https://stackoverflow.com/questions/3536303`__
536
538
  """
@@ -544,7 +546,7 @@ def format_given(text: str, placeholder_map: dict[str, Any], strict: bool = Fals
544
546
 
545
547
 
546
548
  def full_stack_trace(ex: Exception) -> str:
547
- """ get full stack trace from an exception.
549
+ """ get a full stack trace from an exception.
548
550
 
549
551
  :param ex: exception instance.
550
552
  :return: str with stack trace info.
@@ -575,7 +577,7 @@ def import_module(import_name: str, path: Optional[Union[str, UnsetType]] = UNSE
575
577
  :param path: optional file path of the module to import. if this arg is not specified or has the
576
578
  default value (:data:`UNSET`), then the path will be determined from the import name.
577
579
  specify ``None`` to prevent the module search.
578
- :return: a reference to the loaded module or ``None`` if module could not be imported.
580
+ :return: a reference to the loaded module or ``None`` if the module could not be imported.
579
581
  """
580
582
  if path is UNSET:
581
583
  path = import_name.replace('.', os_path_sep)
@@ -600,23 +602,23 @@ def instantiate_config_parser() -> ConfigParser:
600
602
  cfg_parser = ConfigParser(allow_no_value=True, interpolation=ExtendedInterpolation())
601
603
  # set optionxform to have case-sensitive var names (or use 'lambda option: option')
602
604
  # mypy V 0.740 bug - see mypy issue #5062: adding pragma "type: ignore" breaks PyCharm (showing
603
- # .. inspection warning "Non-self attribute could not be type-hinted"), but
604
- # .. also cast(Callable[[Arg(str, 'option')], str], str) and # type: ... is not working
605
- # .. (because Arg is not available in plain mypy, only in the extra mypy_extensions package)
605
+ # inspection warning "Non-self attribute could not be type-hinted"), but
606
+ # also cast(Callable[[Arg(str, 'option')], str], str) and # type: ... is not working
607
+ # (because Arg is not available in plain mypy, only in the extra mypy_extensions package)
606
608
  setattr(cfg_parser, 'optionxform', str)
607
609
  return cfg_parser
608
610
 
609
611
 
610
612
  @contextmanager
611
613
  def in_wd(new_cwd: str) -> Generator[None, None, None]:
612
- """ context manager to temporary switch the current working directory / cwd.
614
+ """ context manager to temporarily switch the current working directory / cwd.
613
615
 
614
616
  :param new_cwd: path to the directory to switch to (within the context/with block).
615
617
  an empty string gets interpreted as the current working directory.
616
618
  """
617
619
  cur_dir = os.getcwd()
618
620
  try:
619
- if new_cwd: # empty new_cwd results in current working folder (no dir change needed/prevent error)
621
+ if new_cwd: # empty new_cwd results in the current working folder (no dir change needed/prevent error)
620
622
  os.chdir(new_cwd)
621
623
  yield
622
624
  finally:
@@ -626,7 +628,7 @@ def in_wd(new_cwd: str) -> Generator[None, None, None]:
626
628
  def load_dotenvs():
627
629
  """ detect and load multiple ``.env`` files in/above the current working directory and the calling module folder.
628
630
 
629
- .. hint:: call from main module of project/app in order to also load ``.env`` files in/above the project folder.
631
+ .. hint:: call from the main module of project/app in order to also load ``.env`` files in/above the project folder.
630
632
  """
631
633
  load_env_var_defaults(os.getcwd())
632
634
  if file_name := stack_var('__file__'):
@@ -634,17 +636,18 @@ def load_dotenvs():
634
636
 
635
637
 
636
638
  def load_env_var_defaults(start_dir: str):
637
- """ detect and load chain of ``.env`` files starting in the specified folder or one above.
639
+ """ detect and load a chain of ``.env`` files starting in the specified folder or one above.
638
640
 
639
- :param start_dir: folder to start search of an ``.env`` file, if not found then checks the parent folder.
640
- if a first ``.env`` file got found, then load their console/shell environment variables
641
- into Python's :data:`os.environ`. after loading the first one, repeat to check for
641
+ :param start_dir: folder to start search of an ``.env`` file, if not found, then checks the parent folder.
642
+ if the first ``.env `` file got found, then load their shell environment variables
643
+ into Python's :data:`os.environ`. after loading the first one, it repeats to check for
642
644
  further ``.env`` files in the parent folder to load them too, until either detecting
643
645
  a folder without an ``.env`` file or until an ``.env`` got loaded from the root folder.
644
646
 
645
647
  .. note::
646
648
  only variables that are not declared in :data:`os.environ` will be added (with the
647
- value specified in the ``.env`` file to be loaded).
649
+ value specified in the ``.env`` file to be loaded). the variable values declared in the subfolders
650
+ are having preference over the values declared in the parent folders.
648
651
  """
649
652
  file_path = os_path_abspath(os_path_join(start_dir, DOTENV_FILE_NAME))
650
653
  if not os_path_isfile(file_path):
@@ -682,7 +685,7 @@ def mask_secrets(data: Union[dict, Iterable], fragments: Iterable[str] = ('passw
682
685
  :param data: iterable deep data structure wherein its item values get masked if their related dict
683
686
  item key contains one of the fragments specified in :paramref:`~mask_secrets.fragments`.
684
687
  :param fragments: dict key string fragments of which the related value will be masked. each fragment has
685
- to be specified in lower case! defaults to ('password', 'pwd') if not passed.
688
+ to be specified with lower case chars! defaults to ('password', 'pwd') if not passed.
686
689
  :return: specified data structure with the secrets masked (¡in-place!).
687
690
  """
688
691
  is_dict = isinstance(data, dict)
@@ -706,8 +709,8 @@ def module_attr(import_name: str, attr_name: str = "") -> Optional[Any]:
706
709
  :param attr_name: name of the attribute declared within the module. do not specify or pass an empty
707
710
  string to get/return a reference to the imported module instance.
708
711
  :return: module instance or module attribute value
709
- or None if module not found
710
- or UNSET if module attribute doesn't exist.
712
+ or None if the module got not found
713
+ or UNSET if the module attribute doesn't exist.
711
714
 
712
715
  .. note:: a previously not imported module will *not* be added to `sys.modules` by this function.
713
716
 
@@ -720,10 +723,10 @@ def module_file_path(local_object: Optional[Callable] = None) -> str:
720
723
  """ determine the absolute path of the module from which this function got called.
721
724
 
722
725
  :param local_object: optional local module, class, method, function, traceback, frame, or code object of the
723
- calling module (passing `lambda: 0` also works). omit to use instead the `__file__`
724
- module variable (which will not work if the module is frozen by ``py2exe`` or
725
- ``PyInstaller``).
726
- :return: module path (inclusive module file name) or empty string if path not found/determinable.
726
+ calling module (passing `lambda: 0` also works). omit this argument in order to use
727
+ the `__file__` module variable (which will not work if the module is frozen by
728
+ ``py2exe`` or ``PyInstaller``).
729
+ :return: module path (inclusive module file name) or empty string if not found/determinable.
727
730
  """
728
731
  if local_object:
729
732
  file_path = getsourcefile(local_object)
@@ -744,11 +747,11 @@ def module_name(*skip_modules: str, depth: int = 0) -> Optional[str]:
744
747
  """ find the first module in the call stack that is *not* in :paramref:`~module_name.skip_modules`.
745
748
 
746
749
  :param skip_modules: module names to skip (def=this and other core modules, see :data:`SKIPPED_MODULES`).
747
- :param depth: the calling level from which on to search. the default value 0 refers the frame and
750
+ :param depth: the calling level from which on to search. the default value 0 refers to the frame and
748
751
  the module of the caller of this function.
749
752
  pass 1 or an even higher value if you want to get the module name of a function/method
750
753
  in a deeper level in the call stack.
751
- :return: the module name of the call stack level specified by :paramref:`~module_name.depth`.
754
+ :return: the module name of the call stack level, specified by :paramref:`~module_name.depth`.
752
755
  """
753
756
  if not skip_modules:
754
757
  skip_modules = SKIPPED_MODULES
@@ -769,7 +772,7 @@ def norm_name(name: str, allow_num_prefix: bool = False) -> str:
769
772
 
770
773
  :param name: any string to be converted into a valid variable/method/file/... name.
771
774
  :param allow_num_prefix: pass True to allow leading digits in the returned normalized name.
772
- :return: cleaned/normalized/converted name string (e.g. for a variable-/method-/file-name).
775
+ :return: cleaned/normalized/converted name string (e.g., for a variable-/method-/file-name).
773
776
  """
774
777
  str_parts: list[str] = []
775
778
  for char in name:
@@ -782,10 +785,10 @@ def norm_name(name: str, allow_num_prefix: bool = False) -> str:
782
785
 
783
786
  def norm_path(path: str, make_absolute: bool = True, remove_base_path: str = "", remove_dots: bool = True,
784
787
  resolve_sym_links: bool = True) -> str:
785
- """ normalize path, replacing `..`/`.` parts or the tilde character (for home folder) and transform to relative/abs.
788
+ """ normalize a path, replacing `..`/`.` parts or the tilde character (home folder) and transform to relative/abs.
786
789
 
787
790
  :param path: path string to normalize/transform.
788
- :param make_absolute: pass False to not convert path to an absolute path.
791
+ :param make_absolute: pass False to not convert the returned path to an absolute path.
789
792
  :param remove_base_path: pass a valid base path to return a relative path, even if the argument values of
790
793
  :paramref:`~norm_path.make_absolute` or :paramref:`~norm_path.resolve_sym_links` are
791
794
  `True`.
@@ -795,8 +798,8 @@ def norm_path(path: str, make_absolute: bool = True, remove_base_path: str = "",
795
798
  :return: normalized path string: absolute if :paramref:`~norm_path.remove_base_path` is empty and
796
799
  either :paramref:`~norm_path.make_absolute` or :paramref:`~norm_path.resolve_sym_links`
797
800
  is `True`; relative if :paramref:`~norm_path.remove_base_path` is a base path of
798
- :paramref:`~norm_path.path` or if :paramref:`~norm_path.path` got specified as relative
799
- path and neither :paramref:`~norm_path.make_absolute` nor
801
+ :paramref:`~norm_path.path` or if :paramref:`~norm_path.path` got specified as a
802
+ relative path and neither :paramref:`~norm_path.make_absolute` nor
800
803
  :paramref:`~norm_path.resolve_sym_links` is `True`.
801
804
 
802
805
  .. hint:: the :func:`~ae.paths.normalize` function additionally replaces :data:`~ae.paths.PATH_PLACEHOLDERS`.
@@ -850,6 +853,7 @@ def os_local_ip() -> str:
850
853
  or empty string if this machine is not connected to any network.
851
854
  """
852
855
  socket1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
856
+ ip_address = ""
853
857
  try:
854
858
  socket1.connect(('10.255.255.255', 1)) # doesn't even have to be reachable
855
859
  ip_address = socket1.getsockname()[0]
@@ -861,7 +865,7 @@ def os_local_ip() -> str:
861
865
  socket2.connect(('<broadcast>', 0))
862
866
  ip_address = socket2.getsockname()[0]
863
867
  except (OSError, IOError, Exception): # pylint: disable=broad-except
864
- ip_address = ""
868
+ pass
865
869
  finally:
866
870
  socket2.close()
867
871
  finally:
@@ -944,13 +948,13 @@ def parse_dotenv(file_path: str) -> dict[str, str]:
944
948
 
945
949
 
946
950
  def project_main_file(import_name: str, project_path: str = "") -> str:
947
- """ determine the main module file path of a project package, containing the project __version__ module variable.
951
+ """ determine the main module file path of a project package containing the project __version__ module variable.
948
952
 
949
- :param import_name: import name of the module/package (including namespace prefixes for namespace packages).
953
+ :param import_name: name of the module/package (including namespace prefixes, separated with dots).
950
954
  :param project_path: optional path where the project of the package/module is situated. not needed if the
951
955
  current working directory is the root folder of either the import_name project or of a
952
956
  sister project (under the same project parent folder).
953
- :return: absolute file path/name of main module or empty string if no main/version file found.
957
+ :return: absolute file path of the main module or empty string if no main/version file is found.
954
958
  """
955
959
  *namespace_dirs, portion_name = import_name.split('.')
956
960
  project_name = ('_'.join(namespace_dirs) + '_' if namespace_dirs else "") + portion_name
@@ -968,6 +972,7 @@ def project_main_file(import_name: str, project_path: str = "") -> str:
968
972
  for path_parts in paths_parts:
969
973
  main_file = os_path_join(module_path, *path_parts)
970
974
  if os_path_isfile(main_file):
975
+ # noinspection PyTypeChecker
971
976
  return main_file
972
977
  return ""
973
978
 
@@ -976,22 +981,22 @@ def read_file(file_path: str, extra_mode: str = "", encoding: Optional[str] = No
976
981
  ) -> Union[str, bytes]:
977
982
  """ returning content of the text/binary file specified by file_path argument as string.
978
983
 
979
- :param file_path: file path/name to load into a string or a bytes array.
980
- :param extra_mode: extra open mode flag characters appended to "r" onto open() mode argument. pass "b" to
981
- read the content of a binary file returned as bytes array. in binary mode the argument
982
- passed in :paramref:`~read_file.error_handling` will be ignored.
984
+ :param file_path: path/name of the file to load the content from.
985
+ :param extra_mode: extra open mode flag characters appended to "r" onto the :func:`open` mode argument.
986
+ pass "b" to read the content of a binary file returned of the type `bytes`. in binary
987
+ mode the argument passed in :paramref:`~read_file.error_handling` will be ignored.
983
988
  :param encoding: encoding used to load and convert/interpret the file content.
984
- :param error_handling: for files opened in text mode pass `'strict'` or ``None`` to return ``None`` (instead of
985
- an empty string) for the cases where either a decoding `ValueError` exception or any
989
+ :param error_handling: for files opened in text mode, pass `'strict'` or ``None`` to return ``None`` (instead
990
+ of an empty string) for the cases where either a decoding `ValueError` exception or any
986
991
  `OSError`, `FileNotFoundError` or `PermissionError` exception got raised.
987
992
  the default value `'ignore'` will ignore any decoding errors (missing some characters)
988
993
  and will return an empty string on any file/os exception. this parameter will be ignored
989
994
  if the :paramref:`~read_file.extra_mode` argument contains the 'b' character (to read
990
995
  the file content as binary/bytes-array).
991
996
  :return: file content string or bytes array.
992
- :raises FileNotFoundError: if file does not exist.
997
+ :raises FileNotFoundError: if the file to read from does not exist.
993
998
  :raises OSError: if :paramref:`~read_file.file_path` is misspelled or contains invalid characters.
994
- :raises PermissionError: if current OS user account lacks permissions to read the file content.
999
+ :raises PermissionError: if the current OS user account lacks permissions to read the file content.
995
1000
  :raises ValueError: on decoding errors.
996
1001
  """
997
1002
  extra_kwargs = {} if "b" in extra_mode else {'errors': error_handling}
@@ -1002,7 +1007,7 @@ def read_file(file_path: str, extra_mode: str = "", encoding: Optional[str] = No
1002
1007
  def round_traditional(num_value: float, num_digits: int = 0) -> float:
1003
1008
  """ round numeric value traditional.
1004
1009
 
1005
- needed because python round() is working differently, e.g. round(0.075, 2) == 0.07 instead of 0.08
1010
+ needed because python round() is working differently, e.g., round(0.075, 2) == 0.07 instead of 0.08
1006
1011
  inspired by https://stackoverflow.com/questions/31818050/python-2-7-round-number-to-nearest-integer.
1007
1012
 
1008
1013
  :param num_value: float value to be round.
@@ -1033,7 +1038,7 @@ def stack_frames(depth: int = 1) -> Generator: # Generator[frame, None, None]
1033
1038
  """ generator returning the call stack frame from the level given in :paramref:`~stack_frames.depth`.
1034
1039
 
1035
1040
  :param depth: the stack level to start; the first returned frame by this generator. the default value
1036
- (1) refers the next deeper stack frame, respectively the one of the caller of this
1041
+ (1) refers to the next deeper stack frame, respectively the one of the caller of this
1037
1042
  function. pass 2 or a higher value if you want to start with an even deeper frame/level.
1038
1043
  :return: generated frames of the call stack.
1039
1044
  """
@@ -1054,7 +1059,7 @@ def stack_var(name: str, *skip_modules: str, scope: str = '', depth: int = 1) ->
1054
1059
  :param scope: pass 'locals' to only check for local variables (ignoring globals) or
1055
1060
  'globals' to only check for global variables (ignoring locals). the default value (an
1056
1061
  empty string) will not restrict the scope, returning either a local or global value.
1057
- :param depth: the calling level from which on to search. the default value (1) refers the next
1062
+ :param depth: the calling level from which on to search. the default value (1) refers to the next
1058
1063
  deeper stack frame, which is the caller of the function. pass 2 or an even higher
1059
1064
  value if you want to start the variable search from a deeper level in the call stack.
1060
1065
  :return: the variable value of a deeper level within the call stack or UNSET if the variable was
@@ -1071,19 +1076,19 @@ def stack_vars(*skip_modules: str,
1071
1076
  """ determine all global and local variables in a calling stack/frames.
1072
1077
 
1073
1078
  :param skip_modules: module names to skip (def=see :data:`SKIPPED_MODULES` module constant).
1074
- :param find_name: if passed then the returned stack frame must contain a variable with the passed name.
1079
+ :param find_name: if passed, then the returned stack frame must contain a variable with the passed name.
1075
1080
  :param scope: scope to search the variable name passed via :paramref:`~stack_vars.find_name`. pass
1076
1081
  'locals' to only search for local variables (ignoring globals) or 'globals' to only
1077
1082
  check for global variables (ignoring locals). passing an empty string will find the
1078
- variable within either locals and globals.
1083
+ variable within either locals or globals.
1079
1084
  :param min_depth: the call stack level from which on to search. the default value (1) refers the next
1080
- deeper stack frame, respectively to the caller of this function. pass 2 or a higher
1085
+ deeper stack frame, respectively, to the caller of this function. pass 2 or a higher
1081
1086
  value if you want to get the variables from a deeper level in the call stack.
1082
1087
  :param max_depth: the maximum depth in the call stack from which to return the variables. if the specified
1083
- argument is not zero and no :paramref:`~stack_vars.skip_modules` are specified then the
1088
+ argument is not zero and no :paramref:`~stack_vars.skip_modules` are specified, then the
1084
1089
  first deeper stack frame that is not within the default :data:`SKIPPED_MODULES` will be
1085
- returned. if this argument and :paramref:`~stack_vars.find_name` get not passed then the
1086
- variables of the top stack frame will be returned.
1090
+ returned. if this argument and :paramref:`~stack_vars.find_name` get not passed,
1091
+ then the variables of the top stack frame will be returned.
1087
1092
  :return: tuple of the global and local variable dicts and the depth in the call stack.
1088
1093
  """
1089
1094
  if not skip_modules:
@@ -1100,8 +1105,8 @@ def stack_vars(*skip_modules: str,
1100
1105
  break
1101
1106
  if max_depth and depth > max_depth:
1102
1107
  break
1103
- # experienced strange overwrites of locals (e.g. self) when returning f_locals directly (adding .copy() fixed it)
1104
- # check if f_locals is a dict (because enaml is using their DynamicScope object which is missing a copy method)
1108
+ # experienced strange overwrites of locals (e.g., self) when returning f_locals directly (adding .copy() fixed it)
1109
+ # check if f_locals is a dict (because enaml is using their DynamicScope object, which is missing a copy method)
1105
1110
  if isinstance(loc, dict):
1106
1111
  loc = loc.copy()
1107
1112
  return glo.copy(), loc, depth - 1
@@ -1137,7 +1142,7 @@ def sys_env_dict() -> dict[str, Any]:
1137
1142
 
1138
1143
  def sys_env_text(ind_ch: str = " ", ind_len: int = 12, key_ch: str = "=", key_len: int = 15,
1139
1144
  extra_sys_env_dict: Optional[dict[str, str]] = None) -> str:
1140
- """ compile formatted text block with system environment info.
1145
+ """ compile a formatted text block with system environment info.
1141
1146
 
1142
1147
  :param ind_ch: indent character (defaults to " ").
1143
1148
  :param ind_len: indent depths (default=12 characters).
@@ -1183,24 +1188,24 @@ def write_file(file_path: str, content: Union[str, bytes],
1183
1188
  """ (over)write the file specified by :paramref:`~write_file.file_path` with text or binary/bytes content.
1184
1189
 
1185
1190
  :param file_path: file path/name to write the passed content into (overwriting any previous content!).
1186
- :param content: new file content passed either as string or bytes array. if a bytes array get passed
1191
+ :param content: new file content passed either as string or as `bytes`. if a byte array gets passed,
1187
1192
  then this method will automatically write the content as binary.
1188
1193
  :param extra_mode: additional open mode flag characters. passed to the `mode` argument of :func:`open` if
1189
1194
  this argument starts with 'a' or 'w', else this argument value will be appended to 'w'
1190
- before it get passed to the `mode` argument of :func:`open`.
1191
- if the :paramref:`~write_file.content` is a bytes array, then a 'b' character will
1195
+ before it gets passed to the `mode` argument of :func:`open`.
1196
+ if the :paramref:`~write_file.content` is of the `bytes` type, then a 'b' character will
1192
1197
  be automatically added to the `mode` argument of :func:`open` (if not already specified
1193
1198
  in this argument).
1194
1199
  :param encoding: encoding used to write/convert/interpret the file content to write.
1195
1200
  :param make_dirs: pass True to automatically create not existing folders specified in
1196
1201
  :paramref:`~write_file.file_path`.
1197
- :raises FileExistsError: if file exists already and is write-protected.
1202
+ :raises FileExistsError: if the file to write to exists already and is write-protected.
1198
1203
  :raises FileNotFoundError: if parts of the file path do not exist.
1199
1204
  :raises OSError: if :paramref:`~write_file.file_path` is misspelled or contains invalid characters.
1200
- :raises PermissionError: if current OS user account lacks permissions to read the file content.
1205
+ :raises PermissionError: if the current OS user account lacks permissions to read the file content.
1201
1206
  :raises ValueError: on decoding errors.
1202
1207
 
1203
- to extend this function for Android 14+ see `<https://github.com/beeware/toga/pull/1158#issuecomment-2254564657>`__
1208
+ to extend this function for Android 14+, see `<https://github.com/beeware/toga/pull/1158#issuecomment-2254564657>`__
1204
1209
  and `<https://gist.github.com/neonankiti/05922cf0a44108a2e2732671ed9ef386>`__
1205
1210
  Yes, to use ACTION_CREATE_DOCUMENT, you don't supply a URI in the intent. You wait for the intent result, and that
1206
1211
  will contain a URI which you can write to.
@@ -1305,7 +1310,7 @@ if os_platform == 'android': # pragma: no
1305
1310
  except Exception: # pylint: disable=broad-except
1306
1311
  pass
1307
1312
 
1308
- # monkey patch the :func:`shutil.copystat` and :func:`shutil.copymode` helper functions, which are crashing on
1313
+ # monkey patches the :func:`shutil.copystat` and :func:`shutil.copymode` helper functions, which are crashing on
1309
1314
  # 'android' (see # `<https://bugs.python.org/issue28141>`__ and `<https://bugs.python.org/issue32073>`__). these
1310
1315
  # functions are used by shutil.copy2/copy/copytree/move to copy OS-specific file attributes.
1311
1316
  # although shutil.copytree() and shutil.move() are copying/moving the files correctly when the copy_function
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ae_base
3
- Version: 0.3.53
3
+ Version: 0.3.55
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.53
72
+ # base 0.3.55
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.52?logo=python)](
78
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.52)
77
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.54?logo=python)](
78
+ https://gitlab.com/ae-group/ae_base/-/tree/release0.3.54)
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.53.
82
+ >ae_base module 0.3.55.
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)
@@ -31,7 +31,7 @@ from ae.base import (
31
31
 
32
32
 
33
33
  tst_uri1 = "schema://user:pwd@domain/path_root/path_sub\\path+file% Üml?ä|ït.path_ext*\"<>|*'()[]{}#^;&=$,~" + chr(127)
34
- 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﹡"⟨⟩।﹡‘⟮⟯⟦⟧﹛﹜﹟^﹔﹠﹦﹩﹐~␡"
35
35
  tst_uri2 = "test control chars" + "".join(chr(_) for _ in range(1, 32))
36
36
  tst_fna2 = "test␣control␣chars␁␂␃␄␅␆␇␈␉␊␋␌␍␎␏␐␑␒␓␔␕␖␗␘␙␚␛␜␝␞␟"
37
37
 
File without changes
File without changes
File without changes