ae-base 0.3.54__tar.gz → 0.3.56__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.
- {ae_base-0.3.54/ae_base.egg-info → ae_base-0.3.56}/PKG-INFO +5 -5
- {ae_base-0.3.54 → ae_base-0.3.56}/README.md +4 -4
- {ae_base-0.3.54 → ae_base-0.3.56}/ae/base.py +110 -95
- {ae_base-0.3.54 → ae_base-0.3.56/ae_base.egg-info}/PKG-INFO +5 -5
- {ae_base-0.3.54 → ae_base-0.3.56}/tests/test_base.py +45 -19
- {ae_base-0.3.54 → ae_base-0.3.56}/LICENSE.md +0 -0
- {ae_base-0.3.54 → ae_base-0.3.56}/ae_base.egg-info/SOURCES.txt +0 -0
- {ae_base-0.3.54 → ae_base-0.3.56}/ae_base.egg-info/dependency_links.txt +0 -0
- {ae_base-0.3.54 → ae_base-0.3.56}/ae_base.egg-info/requires.txt +0 -0
- {ae_base-0.3.54 → ae_base-0.3.56}/ae_base.egg-info/top_level.txt +0 -0
- {ae_base-0.3.54 → ae_base-0.3.56}/ae_base.egg-info/zip-safe +0 -0
- {ae_base-0.3.54 → ae_base-0.3.56}/setup.cfg +0 -0
- {ae_base-0.3.54 → ae_base-0.3.56}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ae_base
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.56
|
|
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.
|
|
72
|
+
# base 0.3.56
|
|
73
73
|
|
|
74
74
|
[](
|
|
75
75
|
https://gitlab.com/ae-group/ae_base)
|
|
76
76
|
[](
|
|
78
|
+
https://gitlab.com/ae-group/ae_base/-/tree/release0.3.55)
|
|
79
79
|
[](
|
|
80
80
|
https://pypi.org/project/ae-base/#history)
|
|
81
81
|
|
|
82
|
-
>ae_base module 0.3.
|
|
82
|
+
>ae_base module 0.3.56.
|
|
83
83
|
|
|
84
84
|
[](
|
|
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.
|
|
3
|
+
# base 0.3.56
|
|
4
4
|
|
|
5
5
|
[](
|
|
6
6
|
https://gitlab.com/ae-group/ae_base)
|
|
7
7
|
[](
|
|
9
|
+
https://gitlab.com/ae-group/ae_base/-/tree/release0.3.55)
|
|
10
10
|
[](
|
|
11
11
|
https://pypi.org/project/ae-base/#history)
|
|
12
12
|
|
|
13
|
-
>ae_base module 0.3.
|
|
13
|
+
>ae_base module 0.3.56.
|
|
14
14
|
|
|
15
15
|
[](
|
|
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
|
|
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
|
|
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`,
|
|
@@ -32,11 +32,14 @@ sortable and compact string from a timestamp.
|
|
|
32
32
|
base helper functions
|
|
33
33
|
---------------------
|
|
34
34
|
|
|
35
|
+
most programming languages providing a function to determine the sign of a number. the :func:`sign` functino,
|
|
36
|
+
provided by this module/portion is filling this gap in Python.
|
|
37
|
+
|
|
35
38
|
in order to convert and transfer Unicode character outside the 7-bit ASCII range via internet transport protocols,
|
|
36
39
|
like http, use the helper functions :func:`ascii_str` and :func:`str_ascii`.
|
|
37
40
|
|
|
38
41
|
: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.
|
|
42
|
+
actual UTC date and time as a datetime object.
|
|
40
43
|
|
|
41
44
|
to write more compact and readable code for the most common file I/O operations, the helper functions :func:`read_file`
|
|
42
45
|
and :func:`write_file` are wrapping Python's built-in :func:`open` function and its context manager.
|
|
@@ -44,11 +47,12 @@ and :func:`write_file` are wrapping Python's built-in :func:`open` function and
|
|
|
44
47
|
the function :func:`duplicates` returns the duplicates of an iterable type.
|
|
45
48
|
|
|
46
49
|
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
|
|
50
|
+
before they get dumped e.g., to an app log file, the function :func:`mask_secrets` can be used.
|
|
48
51
|
|
|
49
52
|
:func:`norm_line_sep` is converting any combination of line separators of a string to a single new-line character.
|
|
50
53
|
|
|
51
|
-
:func:`norm_name` converts any string into a name that can be used e.g
|
|
54
|
+
the function :func:`norm_name` converts any string into a name that can be used e.g., as a file name
|
|
55
|
+
or as a method/attribute name.
|
|
52
56
|
|
|
53
57
|
to normalize a file path, in order to remove `.`, `..` placeholders, to resolve symbolic links or to make it relative or
|
|
54
58
|
absolute, call the function :func:`norm_path`.
|
|
@@ -57,16 +61,16 @@ absolute, call the function :func:`norm_path`.
|
|
|
57
61
|
either as a URL slug or as a file name. use the function :func:`dedefuse` to convert this string back to the
|
|
58
62
|
corresponding URL/URI or file path.
|
|
59
63
|
|
|
60
|
-
:func:`camel_to_snake` and :func:`snake_to_camel` providing name conversions of class and method names.
|
|
64
|
+
the functions :func:`camel_to_snake` and :func:`snake_to_camel` providing name conversions of class and method names.
|
|
61
65
|
|
|
62
|
-
to encode Unicode strings to other codecs the functions :func:`force_encoding` and :func:`to_ascii` can be used.
|
|
66
|
+
to encode Unicode strings to other codecs, the functions :func:`force_encoding` and :func:`to_ascii` can be used.
|
|
63
67
|
|
|
64
|
-
the :func:`round_traditional` function
|
|
65
|
-
function signature is fully compatible
|
|
68
|
+
the :func:`round_traditional` function gets provided by this module for traditional rounding of float values. the
|
|
69
|
+
function signature is fully compatible with Python's :func:`round` function.
|
|
66
70
|
|
|
67
71
|
the function :func:`instantiate_config_parser` ensures that the :class:`~configparser.ConfigParser` instance is
|
|
68
|
-
correctly configured, e.g
|
|
69
|
-
the interpolation argument.
|
|
72
|
+
correctly configured, e.g., to support case-sensitive config variable names and to use :class:`ExtendedInterpolation`
|
|
73
|
+
as the interpolation argument.
|
|
70
74
|
|
|
71
75
|
:func:`app_name_guess` guesses the name of o running Python application from the application environment, with the help
|
|
72
76
|
of :func:`build_config_variable_values`, which determines config-variable-values from the build spec file of an app
|
|
@@ -79,29 +83,29 @@ operating system constants and helpers
|
|
|
79
83
|
the string :data:`os_platform` provides the OS where your app is running, extending Python's :func:`sys.platform`
|
|
80
84
|
for mobile platforms like Android and iOS.
|
|
81
85
|
|
|
82
|
-
:func:`os_host_name`, :func:`os_local_ip` and :func:`os_user_name` are determining machine and
|
|
83
|
-
the OS.
|
|
86
|
+
the functions :func:`os_host_name`, :func:`os_local_ip` and :func:`os_user_name` are determining machine and
|
|
87
|
+
user information from the OS.
|
|
84
88
|
|
|
85
89
|
use :func:`env_str` to determine the value of an OS environment variable with automatic variable name conversion. other
|
|
86
90
|
helper functions provided by this namespace portion to determine the values of the most important system environment
|
|
87
91
|
variables for your application are :func:`sys_env_dict` and :func:`sys_env_text`.
|
|
88
92
|
|
|
89
93
|
to integrate system environment variables from ``.env`` files into :data:`os.environ` the helper functions
|
|
90
|
-
:func
|
|
94
|
+
:func:`parse_dotenv`, :func:`load_env_var_defaults` and :func:`load_dotenvs` are provided.
|
|
91
95
|
|
|
92
96
|
the :mod:`ae.core` portion is providing more OS-specific constants and helper functions, like e.g.
|
|
93
97
|
:func:`start_app_service` and :func:`request_app_permissions`.
|
|
94
98
|
|
|
95
99
|
.. note::
|
|
96
|
-
on import of this module, while running on Android OS, it will monkey patch the :mod:`shutil` module to allow
|
|
97
|
-
|
|
98
|
-
permission errors, the import of this module should be the first statement in the main module of your app.
|
|
100
|
+
on import of this module, while running on Android OS, it will monkey patch the :mod:`shutil` module to allow
|
|
101
|
+
using them on Android devices, and on the first app start requesting the permissions of your app. therefore, to
|
|
102
|
+
prevent permission errors, the import of this module should be the first statement in the main module of your app.
|
|
99
103
|
|
|
100
104
|
|
|
101
105
|
types, classes and mixins
|
|
102
106
|
-------------------------
|
|
103
107
|
|
|
104
|
-
the :class:`UnsetType` class can be used e.g
|
|
108
|
+
the :class:`UnsetType` class can be used e.g., for the declaration of optional function and method parameters,
|
|
105
109
|
allowing also ``None`` is an accepted argument value.
|
|
106
110
|
|
|
107
111
|
to extend any class with an intelligent error message handling, add the mixin :class:`ErrorMsgMixin` to it.
|
|
@@ -113,7 +117,7 @@ enclosed in curly brackets. the function :func:`format_given` is using them to f
|
|
|
113
117
|
generic context manager
|
|
114
118
|
-----------------------
|
|
115
119
|
|
|
116
|
-
the context manager :func:`in_wd` allows
|
|
120
|
+
the context manager :func:`in_wd` allows switching the current working directory temporarily. the following
|
|
117
121
|
example demonstrates a typical usage, together with a temporary path, created with the help of Pythons
|
|
118
122
|
:class:`~tempfile.TemporaryDirectory` class::
|
|
119
123
|
|
|
@@ -129,7 +133,7 @@ call stack inspection
|
|
|
129
133
|
:func:`module_attr` dynamically determines a reference to an attribute (variable, function, class, ...) in a module.
|
|
130
134
|
|
|
131
135
|
:func:`module_name`, :func:`stack_frames`, :func:`stack_var` and :func:`stack_vars` are inspecting the call stack frames
|
|
132
|
-
to determine e.g
|
|
136
|
+
to determine e.g., variable values of the callers of a function/method.
|
|
133
137
|
|
|
134
138
|
.. hint::
|
|
135
139
|
the :class:`AppBase` class uses these helper functions to determine the :attr:`version <AppBase.app_version>` and
|
|
@@ -167,7 +171,7 @@ from types import ModuleType
|
|
|
167
171
|
from typing import Any, Callable, Generator, Iterable, Optional, Union, cast
|
|
168
172
|
|
|
169
173
|
|
|
170
|
-
__version__ = '0.3.
|
|
174
|
+
__version__ = '0.3.56'
|
|
171
175
|
|
|
172
176
|
|
|
173
177
|
os_path_abspath = os.path.abspath
|
|
@@ -190,7 +194,7 @@ TEMPLATES_FOLDER = 'templates'
|
|
|
190
194
|
""" template folder name, used in template and namespace root projects to maintain and provide common file templates """
|
|
191
195
|
|
|
192
196
|
BUILD_CONFIG_FILE = 'buildozer.spec' #: gui app build config file
|
|
193
|
-
PACKAGE_INCLUDE_FILES_PREFIX = 'ae_' #: file/folder names prefix included
|
|
197
|
+
PACKAGE_INCLUDE_FILES_PREFIX = 'ae_' #: file/folder names prefix included in setup package_data/ae_updater
|
|
194
198
|
|
|
195
199
|
PY_CACHE_FOLDER = '__pycache__' #: python cache folder name
|
|
196
200
|
PY_EXT = '.py' #: file extension for modules and hooks
|
|
@@ -239,14 +243,15 @@ NAME_PARTS_SEP = '_' #: name parts separator char
|
|
|
239
243
|
|
|
240
244
|
NOW_STR_FORMAT = "{sep}%Y%m%d{sep}%H%M%S{sep}%f" #: timestamp format of :func:`now_str`
|
|
241
245
|
|
|
242
|
-
SKIPPED_MODULES = ('ae.base', 'ae.paths', 'ae.dynamicod', 'ae.core', 'ae.console',
|
|
246
|
+
SKIPPED_MODULES = ('ae.base', 'ae.paths', 'ae.dynamicod', 'ae.core', 'ae.console',
|
|
247
|
+
'ae.gui', 'ae.gui.app', 'ae.gui.tours', 'ae.gui.utils',
|
|
243
248
|
'ae.kivy', 'ae.kivy.apps', 'ae.kivy.behaviors', 'ae.kivy.i18n', 'ae.kivy.tours', 'ae.kivy.widgets',
|
|
244
249
|
'ae.enaml_app', 'ae.beeware_app', 'ae.pyglet_app', 'ae.pygobject_app', 'ae.dabo_app',
|
|
245
250
|
'ae.qpython_app', 'ae.appjar_app', 'importlib._bootstrap', 'importlib._bootstrap_external')
|
|
246
251
|
""" skipped modules used as default by :func:`module_name`, :func:`stack_var` and :func:`stack_vars` """
|
|
247
252
|
|
|
248
253
|
|
|
249
|
-
# using only object() does not provide proper representation string
|
|
254
|
+
# using only object() does not provide a proper representation string
|
|
250
255
|
class UnsetType:
|
|
251
256
|
""" (singleton) UNSET (type) object class. """
|
|
252
257
|
def __bool__(self):
|
|
@@ -280,7 +285,7 @@ def app_name_guess() -> str:
|
|
|
280
285
|
|
|
281
286
|
|
|
282
287
|
def ascii_str(unicode_str: str) -> str:
|
|
283
|
-
""" convert non-ASCII chars
|
|
288
|
+
""" convert non-ASCII chars to a revertible 7-bit/ASCII representation, e.g., to put in an http header.
|
|
284
289
|
|
|
285
290
|
:param unicode_str: string to encode/convert.
|
|
286
291
|
:return: revertible representation of the specified string, using only ASCII characters.
|
|
@@ -315,7 +320,7 @@ def build_config_variable_values(*names_defaults: tuple[str, Any], section: str
|
|
|
315
320
|
|
|
316
321
|
|
|
317
322
|
def camel_to_snake(name: str) -> str:
|
|
318
|
-
""" convert name from CamelCase to snake_case.
|
|
323
|
+
""" convert a name from CamelCase to snake_case.
|
|
319
324
|
|
|
320
325
|
:param name: name string in CamelCaseFormat.
|
|
321
326
|
:return: name in snake_case_format.
|
|
@@ -330,9 +335,9 @@ def camel_to_snake(name: str) -> str:
|
|
|
330
335
|
|
|
331
336
|
|
|
332
337
|
def deep_dict_update(data: dict, update: dict):
|
|
333
|
-
""" update the optionally nested data dict in-place with the items and
|
|
338
|
+
""" update the optionally nested data dict in-place with the items and subitems from the update dict.
|
|
334
339
|
|
|
335
|
-
:param data: dict to be updated/extended. non-existing keys of dict-
|
|
340
|
+
:param data: dict to be updated/extended. non-existing keys of dict-subitems will be added.
|
|
336
341
|
:param update: dict with the [sub-]items to update in the :paramref:`~deep_dict_update.data` dict.
|
|
337
342
|
|
|
338
343
|
.. hint:: the module/portion :mod:`ae.deep` is providing more deep update helper functions.
|
|
@@ -349,8 +354,8 @@ def deep_dict_update(data: dict, update: dict):
|
|
|
349
354
|
|
|
350
355
|
URI_SEP_CHAR = '⫻' # U+2AFB: TRIPLE SOLIDUS BINARY RELATION
|
|
351
356
|
ASCII_UNICODE = (
|
|
352
|
-
('/', '⁄'), # U+2044: Fraction Slash; '∕' U+2215: Division Slash; '⧸' U+29F8: Big Solidus
|
|
353
|
-
#
|
|
357
|
+
('/', '⁄'), # U+2044: Fraction Slash; '∕' U+2215: Division Slash; '⧸' U+29F8: Big Solidus;
|
|
358
|
+
# '╱' U+FF0F: Fullwidth Solidus; '╱' U+2571: Box Drawings Light Diagonal Upper Right to Lower Left
|
|
354
359
|
('|', '।'), # U+0964: Devanagari Danda
|
|
355
360
|
('\\', '﹨'), # U+FE68: SMALL REVERSE SOLIDUS; '⑊' U+244A OCR DOUBLE BACKSLASH; '⧵' U+29F5 REV. SOLIDUS OPERATOR
|
|
356
361
|
(':', '﹕'), # U+FE55: Small Colon
|
|
@@ -384,7 +389,7 @@ ASCII_UNICODE = (
|
|
|
384
389
|
# ' ' U+202F: Narrow No-Break Space (NNBSP); ' ' U+205F Medium Mathematical Space;
|
|
385
390
|
# '␠' U+2420 symbol for space; '␣' U+2423 Open Box; ' ' U+3000: Ideographic Space
|
|
386
391
|
(chr(127), '␡'), # U+2421: DELETE SYMBOL
|
|
387
|
-
# ('_', '𛲖'),
|
|
392
|
+
# ('_', '𛲖'), # U+1BC96: Duployan Affix Low Line; '_' U+FF3F Fullwidth Low Line
|
|
388
393
|
)
|
|
389
394
|
""" transformation table of special ASCII to Unicode alternative character,
|
|
390
395
|
see https://www.compart.com/en/unicode/category/Po and https://xahlee.info/comp/unicode_naming_slash.html (http!) """
|
|
@@ -420,7 +425,7 @@ def defuse(value: str) -> str:
|
|
|
420
425
|
|
|
421
426
|
in most unix variants only the slash and the ASCII 0 characters are not allowed in file names.
|
|
422
427
|
|
|
423
|
-
in MS Windows are not allowed: ASCII 0..31 / | \\ : * ? ” % < > ( ). some blogs recommend
|
|
428
|
+
in MS Windows are not allowed: ASCII 0..31 / | \\ : * ? ” % < > ( ). some blogs recommend also not allowing
|
|
424
429
|
(convert) the characters # and '.
|
|
425
430
|
|
|
426
431
|
only old POSIX seems to be even more restricted (only allowing alphanumeric characters plus . - and _).
|
|
@@ -506,7 +511,7 @@ class UnformattedValue: # pylint: disable=too-few-public-met
|
|
|
506
511
|
self.key = key
|
|
507
512
|
|
|
508
513
|
def __format__(self, format_spec: str):
|
|
509
|
-
""" overriding Python object class method to return placeholder unchanged including the curly brackets. """
|
|
514
|
+
""" overriding Python object class method to return placeholder unchanged, including the curly brackets. """
|
|
510
515
|
# pylint: disable=consider-using-f-string
|
|
511
516
|
return "{{{}{}}}".format(self.key, ":" + format_spec if format_spec else "")
|
|
512
517
|
|
|
@@ -525,14 +530,12 @@ def format_given(text: str, placeholder_map: dict[str, Any], strict: bool = Fals
|
|
|
525
530
|
""" replacement for Python's str.format_map(), keeping intact placeholders that are not in the specified mapping.
|
|
526
531
|
|
|
527
532
|
:param text: text/template in which the given/specified placeholders will get replaced. in contrary
|
|
528
|
-
to str.format_map
|
|
533
|
+
to :func:`str.format_map`, no KeyError will be raised for placeholders not specified in
|
|
529
534
|
:paramref:`~format_given.placeholder_map`.
|
|
530
535
|
:param placeholder_map: dict with placeholder keys to be replaced in :paramref:`~format_given.text` argument.
|
|
531
|
-
:param strict: pass True to raise error for text templates containing unpaired curly brackets.
|
|
536
|
+
:param strict: pass True to raise an error for text templates containing unpaired curly brackets.
|
|
532
537
|
:return: the specified :paramref:`~format_given.text` with only the placeholders specified in
|
|
533
538
|
:paramref:`~format_given.placeholder_map` replaced with their respective map value.
|
|
534
|
-
additionally any ValueError that would be thrown by str.format_map(), e.g. if the
|
|
535
|
-
|
|
536
539
|
|
|
537
540
|
inspired by the answer of CodeManX in `https://stackoverflow.com/questions/3536303`__
|
|
538
541
|
"""
|
|
@@ -546,7 +549,7 @@ def format_given(text: str, placeholder_map: dict[str, Any], strict: bool = Fals
|
|
|
546
549
|
|
|
547
550
|
|
|
548
551
|
def full_stack_trace(ex: Exception) -> str:
|
|
549
|
-
""" get full stack trace from an exception.
|
|
552
|
+
""" get a full stack trace from an exception.
|
|
550
553
|
|
|
551
554
|
:param ex: exception instance.
|
|
552
555
|
:return: str with stack trace info.
|
|
@@ -577,7 +580,7 @@ def import_module(import_name: str, path: Optional[Union[str, UnsetType]] = UNSE
|
|
|
577
580
|
:param path: optional file path of the module to import. if this arg is not specified or has the
|
|
578
581
|
default value (:data:`UNSET`), then the path will be determined from the import name.
|
|
579
582
|
specify ``None`` to prevent the module search.
|
|
580
|
-
:return: a reference to the loaded module or ``None`` if module could not be imported.
|
|
583
|
+
:return: a reference to the loaded module or ``None`` if the module could not be imported.
|
|
581
584
|
"""
|
|
582
585
|
if path is UNSET:
|
|
583
586
|
path = import_name.replace('.', os_path_sep)
|
|
@@ -602,23 +605,23 @@ def instantiate_config_parser() -> ConfigParser:
|
|
|
602
605
|
cfg_parser = ConfigParser(allow_no_value=True, interpolation=ExtendedInterpolation())
|
|
603
606
|
# set optionxform to have case-sensitive var names (or use 'lambda option: option')
|
|
604
607
|
# mypy V 0.740 bug - see mypy issue #5062: adding pragma "type: ignore" breaks PyCharm (showing
|
|
605
|
-
#
|
|
606
|
-
#
|
|
607
|
-
#
|
|
608
|
+
# inspection warning "Non-self attribute could not be type-hinted"), but
|
|
609
|
+
# also cast(Callable[[Arg(str, 'option')], str], str) and # type: ... is not working
|
|
610
|
+
# (because Arg is not available in plain mypy, only in the extra mypy_extensions package)
|
|
608
611
|
setattr(cfg_parser, 'optionxform', str)
|
|
609
612
|
return cfg_parser
|
|
610
613
|
|
|
611
614
|
|
|
612
615
|
@contextmanager
|
|
613
616
|
def in_wd(new_cwd: str) -> Generator[None, None, None]:
|
|
614
|
-
""" context manager to
|
|
617
|
+
""" context manager to temporarily switch the current working directory / cwd.
|
|
615
618
|
|
|
616
619
|
:param new_cwd: path to the directory to switch to (within the context/with block).
|
|
617
620
|
an empty string gets interpreted as the current working directory.
|
|
618
621
|
"""
|
|
619
622
|
cur_dir = os.getcwd()
|
|
620
623
|
try:
|
|
621
|
-
if new_cwd:
|
|
624
|
+
if new_cwd: # empty new_cwd results in the current working folder (no dir change needed/prevent error)
|
|
622
625
|
os.chdir(new_cwd)
|
|
623
626
|
yield
|
|
624
627
|
finally:
|
|
@@ -628,7 +631,7 @@ def in_wd(new_cwd: str) -> Generator[None, None, None]:
|
|
|
628
631
|
def load_dotenvs():
|
|
629
632
|
""" detect and load multiple ``.env`` files in/above the current working directory and the calling module folder.
|
|
630
633
|
|
|
631
|
-
.. hint:: call from main module of project/app in order to also load ``.env`` files in/above the project folder.
|
|
634
|
+
.. hint:: call from the main module of project/app in order to also load ``.env`` files in/above the project folder.
|
|
632
635
|
"""
|
|
633
636
|
load_env_var_defaults(os.getcwd())
|
|
634
637
|
if file_name := stack_var('__file__'):
|
|
@@ -636,17 +639,18 @@ def load_dotenvs():
|
|
|
636
639
|
|
|
637
640
|
|
|
638
641
|
def load_env_var_defaults(start_dir: str):
|
|
639
|
-
""" detect and load chain of ``.env`` files starting in the specified folder or one above.
|
|
642
|
+
""" detect and load a chain of ``.env`` files starting in the specified folder or one above.
|
|
640
643
|
|
|
641
|
-
:param start_dir: folder to start search of an ``.env`` file, if not found then checks the parent folder.
|
|
642
|
-
if
|
|
643
|
-
into Python's :data:`os.environ`. after loading the first one,
|
|
644
|
+
:param start_dir: folder to start search of an ``.env`` file, if not found, then checks the parent folder.
|
|
645
|
+
if the first ``.env `` file got found, then load their shell environment variables
|
|
646
|
+
into Python's :data:`os.environ`. after loading the first one, it repeats to check for
|
|
644
647
|
further ``.env`` files in the parent folder to load them too, until either detecting
|
|
645
648
|
a folder without an ``.env`` file or until an ``.env`` got loaded from the root folder.
|
|
646
649
|
|
|
647
650
|
.. note::
|
|
648
651
|
only variables that are not declared in :data:`os.environ` will be added (with the
|
|
649
|
-
value specified in the ``.env`` file to be loaded).
|
|
652
|
+
value specified in the ``.env`` file to be loaded). the variable values declared in the subfolders
|
|
653
|
+
are having preference over the values declared in the parent folders.
|
|
650
654
|
"""
|
|
651
655
|
file_path = os_path_abspath(os_path_join(start_dir, DOTENV_FILE_NAME))
|
|
652
656
|
if not os_path_isfile(file_path):
|
|
@@ -684,7 +688,7 @@ def mask_secrets(data: Union[dict, Iterable], fragments: Iterable[str] = ('passw
|
|
|
684
688
|
:param data: iterable deep data structure wherein its item values get masked if their related dict
|
|
685
689
|
item key contains one of the fragments specified in :paramref:`~mask_secrets.fragments`.
|
|
686
690
|
:param fragments: dict key string fragments of which the related value will be masked. each fragment has
|
|
687
|
-
to be specified
|
|
691
|
+
to be specified with lower case chars! defaults to ('password', 'pwd') if not passed.
|
|
688
692
|
:return: specified data structure with the secrets masked (¡in-place!).
|
|
689
693
|
"""
|
|
690
694
|
is_dict = isinstance(data, dict)
|
|
@@ -708,8 +712,8 @@ def module_attr(import_name: str, attr_name: str = "") -> Optional[Any]:
|
|
|
708
712
|
:param attr_name: name of the attribute declared within the module. do not specify or pass an empty
|
|
709
713
|
string to get/return a reference to the imported module instance.
|
|
710
714
|
:return: module instance or module attribute value
|
|
711
|
-
or None if module not found
|
|
712
|
-
or UNSET if module attribute doesn't exist.
|
|
715
|
+
or None if the module got not found
|
|
716
|
+
or UNSET if the module attribute doesn't exist.
|
|
713
717
|
|
|
714
718
|
.. note:: a previously not imported module will *not* be added to `sys.modules` by this function.
|
|
715
719
|
|
|
@@ -722,10 +726,10 @@ def module_file_path(local_object: Optional[Callable] = None) -> str:
|
|
|
722
726
|
""" determine the absolute path of the module from which this function got called.
|
|
723
727
|
|
|
724
728
|
:param local_object: optional local module, class, method, function, traceback, frame, or code object of the
|
|
725
|
-
calling module (passing `lambda: 0` also works). omit
|
|
726
|
-
module variable (which will not work if the module is frozen by
|
|
727
|
-
``PyInstaller``).
|
|
728
|
-
:return: module path (inclusive module file name) or empty string if
|
|
729
|
+
calling module (passing `lambda: 0` also works). omit this argument in order to use
|
|
730
|
+
the `__file__` module variable (which will not work if the module is frozen by
|
|
731
|
+
``py2exe`` or ``PyInstaller``).
|
|
732
|
+
:return: module path (inclusive module file name) or empty string if not found/determinable.
|
|
729
733
|
"""
|
|
730
734
|
if local_object:
|
|
731
735
|
file_path = getsourcefile(local_object)
|
|
@@ -746,11 +750,11 @@ def module_name(*skip_modules: str, depth: int = 0) -> Optional[str]:
|
|
|
746
750
|
""" find the first module in the call stack that is *not* in :paramref:`~module_name.skip_modules`.
|
|
747
751
|
|
|
748
752
|
:param skip_modules: module names to skip (def=this and other core modules, see :data:`SKIPPED_MODULES`).
|
|
749
|
-
:param depth: the calling level from which on to search. the default value 0 refers the frame and
|
|
753
|
+
:param depth: the calling level from which on to search. the default value 0 refers to the frame and
|
|
750
754
|
the module of the caller of this function.
|
|
751
755
|
pass 1 or an even higher value if you want to get the module name of a function/method
|
|
752
756
|
in a deeper level in the call stack.
|
|
753
|
-
:return: the module name of the call stack level specified by :paramref:`~module_name.depth`.
|
|
757
|
+
:return: the module name of the call stack level, specified by :paramref:`~module_name.depth`.
|
|
754
758
|
"""
|
|
755
759
|
if not skip_modules:
|
|
756
760
|
skip_modules = SKIPPED_MODULES
|
|
@@ -771,7 +775,7 @@ def norm_name(name: str, allow_num_prefix: bool = False) -> str:
|
|
|
771
775
|
|
|
772
776
|
:param name: any string to be converted into a valid variable/method/file/... name.
|
|
773
777
|
:param allow_num_prefix: pass True to allow leading digits in the returned normalized name.
|
|
774
|
-
:return: cleaned/normalized/converted name string (e.g
|
|
778
|
+
:return: cleaned/normalized/converted name string (e.g., for a variable-/method-/file-name).
|
|
775
779
|
"""
|
|
776
780
|
str_parts: list[str] = []
|
|
777
781
|
for char in name:
|
|
@@ -784,10 +788,10 @@ def norm_name(name: str, allow_num_prefix: bool = False) -> str:
|
|
|
784
788
|
|
|
785
789
|
def norm_path(path: str, make_absolute: bool = True, remove_base_path: str = "", remove_dots: bool = True,
|
|
786
790
|
resolve_sym_links: bool = True) -> str:
|
|
787
|
-
""" normalize path, replacing `..`/`.` parts or the tilde character (
|
|
791
|
+
""" normalize a path, replacing `..`/`.` parts or the tilde character (home folder) and transform to relative/abs.
|
|
788
792
|
|
|
789
793
|
:param path: path string to normalize/transform.
|
|
790
|
-
:param make_absolute: pass False to not convert path to an absolute path.
|
|
794
|
+
:param make_absolute: pass False to not convert the returned path to an absolute path.
|
|
791
795
|
:param remove_base_path: pass a valid base path to return a relative path, even if the argument values of
|
|
792
796
|
:paramref:`~norm_path.make_absolute` or :paramref:`~norm_path.resolve_sym_links` are
|
|
793
797
|
`True`.
|
|
@@ -797,8 +801,8 @@ def norm_path(path: str, make_absolute: bool = True, remove_base_path: str = "",
|
|
|
797
801
|
:return: normalized path string: absolute if :paramref:`~norm_path.remove_base_path` is empty and
|
|
798
802
|
either :paramref:`~norm_path.make_absolute` or :paramref:`~norm_path.resolve_sym_links`
|
|
799
803
|
is `True`; relative if :paramref:`~norm_path.remove_base_path` is a base path of
|
|
800
|
-
:paramref:`~norm_path.path` or if :paramref:`~norm_path.path` got specified as
|
|
801
|
-
path and neither :paramref:`~norm_path.make_absolute` nor
|
|
804
|
+
:paramref:`~norm_path.path` or if :paramref:`~norm_path.path` got specified as a
|
|
805
|
+
relative path and neither :paramref:`~norm_path.make_absolute` nor
|
|
802
806
|
:paramref:`~norm_path.resolve_sym_links` is `True`.
|
|
803
807
|
|
|
804
808
|
.. hint:: the :func:`~ae.paths.normalize` function additionally replaces :data:`~ae.paths.PATH_PLACEHOLDERS`.
|
|
@@ -852,6 +856,7 @@ def os_local_ip() -> str:
|
|
|
852
856
|
or empty string if this machine is not connected to any network.
|
|
853
857
|
"""
|
|
854
858
|
socket1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
859
|
+
ip_address = ""
|
|
855
860
|
try:
|
|
856
861
|
socket1.connect(('10.255.255.255', 1)) # doesn't even have to be reachable
|
|
857
862
|
ip_address = socket1.getsockname()[0]
|
|
@@ -863,7 +868,7 @@ def os_local_ip() -> str:
|
|
|
863
868
|
socket2.connect(('<broadcast>', 0))
|
|
864
869
|
ip_address = socket2.getsockname()[0]
|
|
865
870
|
except (OSError, IOError, Exception): # pylint: disable=broad-except
|
|
866
|
-
|
|
871
|
+
pass
|
|
867
872
|
finally:
|
|
868
873
|
socket2.close()
|
|
869
874
|
finally:
|
|
@@ -946,13 +951,13 @@ def parse_dotenv(file_path: str) -> dict[str, str]:
|
|
|
946
951
|
|
|
947
952
|
|
|
948
953
|
def project_main_file(import_name: str, project_path: str = "") -> str:
|
|
949
|
-
""" determine the main module file path of a project package
|
|
954
|
+
""" determine the main module file path of a project package containing the project __version__ module variable.
|
|
950
955
|
|
|
951
|
-
:param import_name:
|
|
956
|
+
:param import_name: name of the module/package (including namespace prefixes, separated with dots).
|
|
952
957
|
:param project_path: optional path where the project of the package/module is situated. not needed if the
|
|
953
958
|
current working directory is the root folder of either the import_name project or of a
|
|
954
959
|
sister project (under the same project parent folder).
|
|
955
|
-
:return: absolute file path
|
|
960
|
+
:return: absolute file path of the main module or empty string if no main/version file is found.
|
|
956
961
|
"""
|
|
957
962
|
*namespace_dirs, portion_name = import_name.split('.')
|
|
958
963
|
project_name = ('_'.join(namespace_dirs) + '_' if namespace_dirs else "") + portion_name
|
|
@@ -970,6 +975,7 @@ def project_main_file(import_name: str, project_path: str = "") -> str:
|
|
|
970
975
|
for path_parts in paths_parts:
|
|
971
976
|
main_file = os_path_join(module_path, *path_parts)
|
|
972
977
|
if os_path_isfile(main_file):
|
|
978
|
+
# noinspection PyTypeChecker
|
|
973
979
|
return main_file
|
|
974
980
|
return ""
|
|
975
981
|
|
|
@@ -978,22 +984,22 @@ def read_file(file_path: str, extra_mode: str = "", encoding: Optional[str] = No
|
|
|
978
984
|
) -> Union[str, bytes]:
|
|
979
985
|
""" returning content of the text/binary file specified by file_path argument as string.
|
|
980
986
|
|
|
981
|
-
:param file_path:
|
|
982
|
-
:param extra_mode: extra open mode flag characters appended to "r" onto open
|
|
983
|
-
read the content of a binary file returned
|
|
984
|
-
passed in :paramref:`~read_file.error_handling` will be ignored.
|
|
987
|
+
:param file_path: path/name of the file to load the content from.
|
|
988
|
+
:param extra_mode: extra open mode flag characters appended to "r" onto the :func:`open` mode argument.
|
|
989
|
+
pass "b" to read the content of a binary file returned of the type `bytes`. in binary
|
|
990
|
+
mode the argument passed in :paramref:`~read_file.error_handling` will be ignored.
|
|
985
991
|
:param encoding: encoding used to load and convert/interpret the file content.
|
|
986
|
-
:param error_handling: for files opened in text mode pass `'strict'` or ``None`` to return ``None`` (instead
|
|
987
|
-
an empty string) for the cases where either a decoding `ValueError` exception or any
|
|
992
|
+
:param error_handling: for files opened in text mode, pass `'strict'` or ``None`` to return ``None`` (instead
|
|
993
|
+
of an empty string) for the cases where either a decoding `ValueError` exception or any
|
|
988
994
|
`OSError`, `FileNotFoundError` or `PermissionError` exception got raised.
|
|
989
995
|
the default value `'ignore'` will ignore any decoding errors (missing some characters)
|
|
990
996
|
and will return an empty string on any file/os exception. this parameter will be ignored
|
|
991
997
|
if the :paramref:`~read_file.extra_mode` argument contains the 'b' character (to read
|
|
992
998
|
the file content as binary/bytes-array).
|
|
993
999
|
:return: file content string or bytes array.
|
|
994
|
-
:raises FileNotFoundError: if file does not exist.
|
|
1000
|
+
:raises FileNotFoundError: if the file to read from does not exist.
|
|
995
1001
|
:raises OSError: if :paramref:`~read_file.file_path` is misspelled or contains invalid characters.
|
|
996
|
-
:raises PermissionError: if current OS user account lacks permissions to read the file content.
|
|
1002
|
+
:raises PermissionError: if the current OS user account lacks permissions to read the file content.
|
|
997
1003
|
:raises ValueError: on decoding errors.
|
|
998
1004
|
"""
|
|
999
1005
|
extra_kwargs = {} if "b" in extra_mode else {'errors': error_handling}
|
|
@@ -1004,7 +1010,7 @@ def read_file(file_path: str, extra_mode: str = "", encoding: Optional[str] = No
|
|
|
1004
1010
|
def round_traditional(num_value: float, num_digits: int = 0) -> float:
|
|
1005
1011
|
""" round numeric value traditional.
|
|
1006
1012
|
|
|
1007
|
-
needed because python round() is working differently, e.g
|
|
1013
|
+
needed because python round() is working differently, e.g., round(0.075, 2) == 0.07 instead of 0.08
|
|
1008
1014
|
inspired by https://stackoverflow.com/questions/31818050/python-2-7-round-number-to-nearest-integer.
|
|
1009
1015
|
|
|
1010
1016
|
:param num_value: float value to be round.
|
|
@@ -1015,6 +1021,15 @@ def round_traditional(num_value: float, num_digits: int = 0) -> float:
|
|
|
1015
1021
|
return round(num_value + 10 ** (-len(str(num_value)) - 1), num_digits)
|
|
1016
1022
|
|
|
1017
1023
|
|
|
1024
|
+
def sign(number: float) -> int:
|
|
1025
|
+
""" return ths sign (-1, 0, 1) of a number.
|
|
1026
|
+
|
|
1027
|
+
:param number: any number of type float or int.
|
|
1028
|
+
:return: -1 if the number is negative, 0 if it is zero, or 1 if it is positive.
|
|
1029
|
+
"""
|
|
1030
|
+
return (number > 0) - (number < 0)
|
|
1031
|
+
|
|
1032
|
+
|
|
1018
1033
|
def snake_to_camel(name: str, back_convertible: bool = False) -> str:
|
|
1019
1034
|
""" convert name from snake_case to CamelCase.
|
|
1020
1035
|
|
|
@@ -1035,7 +1050,7 @@ def stack_frames(depth: int = 1) -> Generator: # Generator[frame, None, None]
|
|
|
1035
1050
|
""" generator returning the call stack frame from the level given in :paramref:`~stack_frames.depth`.
|
|
1036
1051
|
|
|
1037
1052
|
:param depth: the stack level to start; the first returned frame by this generator. the default value
|
|
1038
|
-
(1) refers the next deeper stack frame, respectively the one of the caller of this
|
|
1053
|
+
(1) refers to the next deeper stack frame, respectively the one of the caller of this
|
|
1039
1054
|
function. pass 2 or a higher value if you want to start with an even deeper frame/level.
|
|
1040
1055
|
:return: generated frames of the call stack.
|
|
1041
1056
|
"""
|
|
@@ -1056,7 +1071,7 @@ def stack_var(name: str, *skip_modules: str, scope: str = '', depth: int = 1) ->
|
|
|
1056
1071
|
:param scope: pass 'locals' to only check for local variables (ignoring globals) or
|
|
1057
1072
|
'globals' to only check for global variables (ignoring locals). the default value (an
|
|
1058
1073
|
empty string) will not restrict the scope, returning either a local or global value.
|
|
1059
|
-
:param depth: the calling level from which on to search. the default value (1) refers the next
|
|
1074
|
+
:param depth: the calling level from which on to search. the default value (1) refers to the next
|
|
1060
1075
|
deeper stack frame, which is the caller of the function. pass 2 or an even higher
|
|
1061
1076
|
value if you want to start the variable search from a deeper level in the call stack.
|
|
1062
1077
|
:return: the variable value of a deeper level within the call stack or UNSET if the variable was
|
|
@@ -1073,19 +1088,19 @@ def stack_vars(*skip_modules: str,
|
|
|
1073
1088
|
""" determine all global and local variables in a calling stack/frames.
|
|
1074
1089
|
|
|
1075
1090
|
:param skip_modules: module names to skip (def=see :data:`SKIPPED_MODULES` module constant).
|
|
1076
|
-
:param find_name: if passed then the returned stack frame must contain a variable with the passed name.
|
|
1091
|
+
:param find_name: if passed, then the returned stack frame must contain a variable with the passed name.
|
|
1077
1092
|
:param scope: scope to search the variable name passed via :paramref:`~stack_vars.find_name`. pass
|
|
1078
1093
|
'locals' to only search for local variables (ignoring globals) or 'globals' to only
|
|
1079
1094
|
check for global variables (ignoring locals). passing an empty string will find the
|
|
1080
|
-
variable within either locals
|
|
1095
|
+
variable within either locals or globals.
|
|
1081
1096
|
:param min_depth: the call stack level from which on to search. the default value (1) refers the next
|
|
1082
|
-
deeper stack frame, respectively to the caller of this function. pass 2 or a higher
|
|
1097
|
+
deeper stack frame, respectively, to the caller of this function. pass 2 or a higher
|
|
1083
1098
|
value if you want to get the variables from a deeper level in the call stack.
|
|
1084
1099
|
:param max_depth: the maximum depth in the call stack from which to return the variables. if the specified
|
|
1085
|
-
argument is not zero and no :paramref:`~stack_vars.skip_modules` are specified then the
|
|
1100
|
+
argument is not zero and no :paramref:`~stack_vars.skip_modules` are specified, then the
|
|
1086
1101
|
first deeper stack frame that is not within the default :data:`SKIPPED_MODULES` will be
|
|
1087
|
-
returned. if this argument and :paramref:`~stack_vars.find_name` get not passed
|
|
1088
|
-
variables of the top stack frame will be returned.
|
|
1102
|
+
returned. if this argument and :paramref:`~stack_vars.find_name` get not passed,
|
|
1103
|
+
then the variables of the top stack frame will be returned.
|
|
1089
1104
|
:return: tuple of the global and local variable dicts and the depth in the call stack.
|
|
1090
1105
|
"""
|
|
1091
1106
|
if not skip_modules:
|
|
@@ -1102,8 +1117,8 @@ def stack_vars(*skip_modules: str,
|
|
|
1102
1117
|
break
|
|
1103
1118
|
if max_depth and depth > max_depth:
|
|
1104
1119
|
break
|
|
1105
|
-
# experienced strange overwrites of locals (e.g
|
|
1106
|
-
# check if f_locals is a dict (because enaml is using their DynamicScope object which is missing a copy method)
|
|
1120
|
+
# experienced strange overwrites of locals (e.g., self) when returning f_locals directly (adding .copy() fixed it)
|
|
1121
|
+
# check if f_locals is a dict (because enaml is using their DynamicScope object, which is missing a copy method)
|
|
1107
1122
|
if isinstance(loc, dict):
|
|
1108
1123
|
loc = loc.copy()
|
|
1109
1124
|
return glo.copy(), loc, depth - 1
|
|
@@ -1139,7 +1154,7 @@ def sys_env_dict() -> dict[str, Any]:
|
|
|
1139
1154
|
|
|
1140
1155
|
def sys_env_text(ind_ch: str = " ", ind_len: int = 12, key_ch: str = "=", key_len: int = 15,
|
|
1141
1156
|
extra_sys_env_dict: Optional[dict[str, str]] = None) -> str:
|
|
1142
|
-
""" compile formatted text block with system environment info.
|
|
1157
|
+
""" compile a formatted text block with system environment info.
|
|
1143
1158
|
|
|
1144
1159
|
:param ind_ch: indent character (defaults to " ").
|
|
1145
1160
|
:param ind_len: indent depths (default=12 characters).
|
|
@@ -1185,24 +1200,24 @@ def write_file(file_path: str, content: Union[str, bytes],
|
|
|
1185
1200
|
""" (over)write the file specified by :paramref:`~write_file.file_path` with text or binary/bytes content.
|
|
1186
1201
|
|
|
1187
1202
|
:param file_path: file path/name to write the passed content into (overwriting any previous content!).
|
|
1188
|
-
:param content: new file content passed either as string or bytes
|
|
1203
|
+
:param content: new file content passed either as string or as `bytes`. if a byte array gets passed,
|
|
1189
1204
|
then this method will automatically write the content as binary.
|
|
1190
1205
|
:param extra_mode: additional open mode flag characters. passed to the `mode` argument of :func:`open` if
|
|
1191
1206
|
this argument starts with 'a' or 'w', else this argument value will be appended to 'w'
|
|
1192
|
-
before it
|
|
1193
|
-
if the :paramref:`~write_file.content` is
|
|
1207
|
+
before it gets passed to the `mode` argument of :func:`open`.
|
|
1208
|
+
if the :paramref:`~write_file.content` is of the `bytes` type, then a 'b' character will
|
|
1194
1209
|
be automatically added to the `mode` argument of :func:`open` (if not already specified
|
|
1195
1210
|
in this argument).
|
|
1196
1211
|
:param encoding: encoding used to write/convert/interpret the file content to write.
|
|
1197
1212
|
:param make_dirs: pass True to automatically create not existing folders specified in
|
|
1198
1213
|
:paramref:`~write_file.file_path`.
|
|
1199
|
-
:raises FileExistsError: if file exists already and is write-protected.
|
|
1214
|
+
:raises FileExistsError: if the file to write to exists already and is write-protected.
|
|
1200
1215
|
:raises FileNotFoundError: if parts of the file path do not exist.
|
|
1201
1216
|
:raises OSError: if :paramref:`~write_file.file_path` is misspelled or contains invalid characters.
|
|
1202
|
-
:raises PermissionError: if current OS user account lacks permissions to read the file content.
|
|
1217
|
+
:raises PermissionError: if the current OS user account lacks permissions to read the file content.
|
|
1203
1218
|
:raises ValueError: on decoding errors.
|
|
1204
1219
|
|
|
1205
|
-
to extend this function for Android 14
|
|
1220
|
+
to extend this function for Android 14+, see `<https://github.com/beeware/toga/pull/1158#issuecomment-2254564657>`__
|
|
1206
1221
|
and `<https://gist.github.com/neonankiti/05922cf0a44108a2e2732671ed9ef386>`__
|
|
1207
1222
|
Yes, to use ACTION_CREATE_DOCUMENT, you don't supply a URI in the intent. You wait for the intent result, and that
|
|
1208
1223
|
will contain a URI which you can write to.
|
|
@@ -1307,7 +1322,7 @@ if os_platform == 'android': # pragma: no
|
|
|
1307
1322
|
except Exception: # pylint: disable=broad-except
|
|
1308
1323
|
pass
|
|
1309
1324
|
|
|
1310
|
-
# monkey
|
|
1325
|
+
# monkey patches the :func:`shutil.copystat` and :func:`shutil.copymode` helper functions, which are crashing on
|
|
1311
1326
|
# 'android' (see # `<https://bugs.python.org/issue28141>`__ and `<https://bugs.python.org/issue32073>`__). these
|
|
1312
1327
|
# functions are used by shutil.copy2/copy/copytree/move to copy OS-specific file attributes.
|
|
1313
1328
|
# 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.
|
|
3
|
+
Version: 0.3.56
|
|
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.
|
|
72
|
+
# base 0.3.56
|
|
73
73
|
|
|
74
74
|
[](
|
|
75
75
|
https://gitlab.com/ae-group/ae_base)
|
|
76
76
|
[](
|
|
78
|
+
https://gitlab.com/ae-group/ae_base/-/tree/release0.3.55)
|
|
79
79
|
[](
|
|
80
80
|
https://pypi.org/project/ae-base/#history)
|
|
81
81
|
|
|
82
|
-
>ae_base module 0.3.
|
|
82
|
+
>ae_base module 0.3.56.
|
|
83
83
|
|
|
84
84
|
[](
|
|
85
85
|
https://ae-group.gitlab.io/ae_base/coverage/index.html)
|
|
@@ -18,15 +18,15 @@ from typing import cast
|
|
|
18
18
|
# noinspection PyProtectedMember
|
|
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
|
-
UNSET,
|
|
22
|
-
|
|
21
|
+
UNSET, URI_SEP_CHAR,
|
|
22
|
+
app_name_guess, ascii_str, build_config_variable_values, camel_to_snake,
|
|
23
23
|
dedefuse, deep_dict_update, defuse, dummy_function, duplicates, env_str,
|
|
24
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
|
-
parse_dotenv, project_main_file, read_file, round_traditional,
|
|
29
|
-
str_ascii, sys_env_dict, sys_env_text, to_ascii, utc_datetime, write_file,
|
|
28
|
+
parse_dotenv, project_main_file, read_file, round_traditional, sign, snake_to_camel,
|
|
29
|
+
stack_frames, stack_var, stack_vars, str_ascii, sys_env_dict, sys_env_text, to_ascii, utc_datetime, write_file,
|
|
30
30
|
ErrorMsgMixin)
|
|
31
31
|
|
|
32
32
|
|
|
@@ -40,6 +40,7 @@ env_var_val = 'value of env var'
|
|
|
40
40
|
folder_name = 'fdr'
|
|
41
41
|
full_folders = (0, 1, 3)
|
|
42
42
|
|
|
43
|
+
|
|
43
44
|
@pytest.fixture
|
|
44
45
|
def os_env_test_env():
|
|
45
46
|
""" create .env files to test and backup os.environ. """
|
|
@@ -79,7 +80,7 @@ class TestErrorMsgMixin:
|
|
|
79
80
|
assert ins.cae is None # in test env is no console/gui app available
|
|
80
81
|
assert ins.po is ins.dpo is ins.vpo is print
|
|
81
82
|
|
|
82
|
-
with patch('ae.core.main_app_instance', lambda
|
|
83
|
+
with patch('ae.core.main_app_instance', lambda: None):
|
|
83
84
|
ins = ErrorMsgMixin()
|
|
84
85
|
assert ins
|
|
85
86
|
assert ins.cae is None
|
|
@@ -105,7 +106,7 @@ class TestErrorMsgMixin:
|
|
|
105
106
|
|
|
106
107
|
app_ins = _AppMock()
|
|
107
108
|
|
|
108
|
-
with patch('ae.core.main_app_instance', lambda
|
|
109
|
+
with patch('ae.core.main_app_instance', lambda: app_ins):
|
|
109
110
|
ins = ErrorMsgMixin()
|
|
110
111
|
assert ins.cae is app_ins
|
|
111
112
|
assert ins.po is not print
|
|
@@ -559,17 +560,15 @@ class TestBaseHelpers:
|
|
|
559
560
|
assert mask_secrets({'_token': "secret"}, fragments=('TOKEN', 'secret')) == {'_token': "secret"}
|
|
560
561
|
|
|
561
562
|
untouched = 'untouched_Pw_d_p_a_s_s_word'
|
|
562
|
-
dat = {'key1':
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
'passWord___': "secRet",
|
|
569
|
-
},
|
|
563
|
+
dat = {'key1': {'subKey1': (
|
|
564
|
+
{'host_Pwd': "secret"},
|
|
565
|
+
untouched,
|
|
566
|
+
),
|
|
567
|
+
'passWord___': "secRet",
|
|
568
|
+
},
|
|
570
569
|
'any_PASSWORD_to_hide': "Se",
|
|
571
570
|
untouched: untouched,
|
|
572
|
-
|
|
571
|
+
}
|
|
573
572
|
assert mask_secrets(dat) is dat
|
|
574
573
|
assert dat['key1']['subKey1'][0]['host_Pwd'] == "sec*********"
|
|
575
574
|
assert dat['key1']['passWord___'] == "sec*********"
|
|
@@ -634,6 +633,7 @@ class TestBaseHelpers:
|
|
|
634
633
|
finally:
|
|
635
634
|
os.environ.pop('ANDROID_ARGUMENT', None)
|
|
636
635
|
|
|
636
|
+
# noinspection PyUnreachableCode
|
|
637
637
|
try:
|
|
638
638
|
os.environ['KIVY_BUILD'] = 'android'
|
|
639
639
|
assert _os_platform() == 'android'
|
|
@@ -802,7 +802,7 @@ class TestBaseHelpers:
|
|
|
802
802
|
|
|
803
803
|
def test_parse_dotenv_var_not_expands_escaped_variables(self):
|
|
804
804
|
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
805
|
-
fp.write("var_nam=var val \\$env_var
|
|
805
|
+
fp.write("var_nam=var val \\$env_var \\${env_var}")
|
|
806
806
|
fp.seek(0)
|
|
807
807
|
loaded = parse_dotenv(fp.name)
|
|
808
808
|
assert 'var_nam' in loaded
|
|
@@ -853,6 +853,31 @@ class TestBaseHelpers:
|
|
|
853
853
|
assert round_traditional(0.075, 2) == 0.08
|
|
854
854
|
assert round(0.075, 2) == 0.07
|
|
855
855
|
|
|
856
|
+
def test_sign_with_float_arg(self):
|
|
857
|
+
assert sign(1.11) == 1
|
|
858
|
+
assert sign(-0.000003) == -1
|
|
859
|
+
assert sign(0.0000000) == 0
|
|
860
|
+
assert sign(-0.0) == 0
|
|
861
|
+
|
|
862
|
+
def test_sign_with_int_arg(self):
|
|
863
|
+
assert sign(3) == 1
|
|
864
|
+
assert sign(-6) == -1
|
|
865
|
+
assert sign(0) == 0
|
|
866
|
+
assert sign(-0) == 0
|
|
867
|
+
|
|
868
|
+
def test_sign_with_invalid_arg(self):
|
|
869
|
+
with pytest.raises(TypeError):
|
|
870
|
+
# noinspection PyArgumentList
|
|
871
|
+
sign()
|
|
872
|
+
|
|
873
|
+
with pytest.raises(TypeError):
|
|
874
|
+
# noinspection PyArgumentList,PyTypeChecker
|
|
875
|
+
sign(None)
|
|
876
|
+
|
|
877
|
+
with pytest.raises(TypeError):
|
|
878
|
+
# noinspection PyArgumentList,PyTypeChecker
|
|
879
|
+
sign(UNSET)
|
|
880
|
+
|
|
856
881
|
def test_snake_to_camel(self):
|
|
857
882
|
assert snake_to_camel("_Any_Camel_Case_Name") == "AnyCamelCaseName"
|
|
858
883
|
assert snake_to_camel("any_Camel_Case_Name") == "AnyCamelCaseName"
|
|
@@ -981,6 +1006,7 @@ class TestModuleHelpers:
|
|
|
981
1006
|
os.remove(module_file)
|
|
982
1007
|
|
|
983
1008
|
# test already imported module
|
|
1009
|
+
# noinspection PyUnreachableCode
|
|
984
1010
|
callee = module_attr('textwrap', attr_name='indent')
|
|
985
1011
|
assert callable(callee)
|
|
986
1012
|
assert callee is textwrap.indent
|
|
@@ -1033,7 +1059,7 @@ class TestModuleHelpers:
|
|
|
1033
1059
|
os.remove(module_file)
|
|
1034
1060
|
|
|
1035
1061
|
def test_module_attr_not_exists_attr(self):
|
|
1036
|
-
""" first test with non-existing module, second test with non-existing function. """
|
|
1062
|
+
""" first test with a non-existing module, second test with a non-existing function. """
|
|
1037
1063
|
namespace = TESTS_FOLDER
|
|
1038
1064
|
mod_name = 'test_module_name'
|
|
1039
1065
|
att_name = 'test_module_func'
|
|
@@ -1060,7 +1086,7 @@ class TestModuleHelpers:
|
|
|
1060
1086
|
os.remove(module_file)
|
|
1061
1087
|
|
|
1062
1088
|
def test_module_attr_not_exists_module(self):
|
|
1063
|
-
""" first test with non-existing module, second test with non-existing function. """
|
|
1089
|
+
""" first test with a non-existing module, second test with a non-existing function. """
|
|
1064
1090
|
mod_name = 'non_existing_test_module_name'
|
|
1065
1091
|
att_name = 'non_existing_test_module_func'
|
|
1066
1092
|
assert module_attr(mod_name, attr_name=att_name) is None
|
|
@@ -1102,7 +1128,7 @@ class TestStackHelpers:
|
|
|
1102
1128
|
for frame in stack_frames():
|
|
1103
1129
|
assert frame
|
|
1104
1130
|
assert getattr(frame, 'f_globals')
|
|
1105
|
-
# if pytest runs from terminal then f_locals is missing in the highest frame:
|
|
1131
|
+
# if pytest runs from terminal, then f_locals is missing in the highest frame:
|
|
1106
1132
|
# assert getattr(frame, 'f_locals')
|
|
1107
1133
|
|
|
1108
1134
|
def test_stack_var_module(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|