ae-base 0.3.65__py3-none-any.whl → 0.3.67__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ae/base.py +268 -122
- {ae_base-0.3.65.dist-info → ae_base-0.3.67.dist-info}/METADATA +7 -11
- ae_base-0.3.67.dist-info/RECORD +7 -0
- {ae_base-0.3.65.dist-info → ae_base-0.3.67.dist-info}/licenses/LICENSE.md +1 -1
- ae_base-0.3.65.dist-info/RECORD +0 -7
- {ae_base-0.3.65.dist-info → ae_base-0.3.67.dist-info}/WHEEL +0 -0
- {ae_base-0.3.65.dist-info → ae_base-0.3.67.dist-info}/top_level.txt +0 -0
- {ae_base-0.3.65.dist-info → ae_base-0.3.67.dist-info}/zip-safe +0 -0
ae/base.py
CHANGED
|
@@ -1,156 +1,221 @@
|
|
|
1
1
|
"""
|
|
2
|
-
basic constants, helper functions and context
|
|
3
|
-
|
|
2
|
+
basic constants, helper functions and context managers
|
|
3
|
+
======================================================
|
|
4
4
|
|
|
5
|
-
this module is pure python, has no external dependencies, and
|
|
6
|
-
functions, useful classes and context managers.
|
|
5
|
+
this module is pure python, has no external dependencies, and provides a comprehensive toolkit of base constants,
|
|
6
|
+
common helper functions, useful classes, and context managers for a wide variety of programming tasks.
|
|
7
7
|
|
|
8
8
|
.. note::
|
|
9
|
-
on import
|
|
10
|
-
|
|
11
|
-
in your app's main module.
|
|
9
|
+
on import, this module checks if it is running on the Android OS. if so, it will monkey patch the
|
|
10
|
+
:mod:`shutil` module to ensure functions like ``copy`` and ``move`` work correctly. to prevent
|
|
11
|
+
permission-related errors, this module should be one of the first imports in your Android app's main module.
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
ISO format strings for ``date`` and ``datetime`` values are provided by the constants :data:`DATE_ISO` and
|
|
18
|
-
:data:`DATE_TIME_ISO`.
|
|
14
|
+
string manipulation
|
|
15
|
+
-------------------
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
to specify that e.g., an argument or attribute has no (valid) value or did not get specified/passed.
|
|
17
|
+
functions for converting, cleaning, normalizing, and formatting strings.
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
* :func:`camel_to_snake`: converts a string from CamelCase to snake_case.
|
|
20
|
+
* :func:`snake_to_camel`: converts a string from snake_case to CamelCase.
|
|
21
|
+
* :func:`norm_name`: normalizes a string to be a valid identifier (e.g., for variable-, method-, or file-names).
|
|
22
|
+
* :func:`norm_line_sep`: converts all line separator combinations (CRLF, CR) in a string to a single newline (LF).
|
|
23
|
+
* :func:`defuse`: converts special characters in string to Unicode alternatives, making it safe for use as
|
|
24
|
+
a URL slug, path or filename.
|
|
25
|
+
* :func:`dedefuse`: reverses the operation of :func:`defuse`, restoring the original string.
|
|
26
|
+
* :func:`force_encoding`: ensures text is in a specific encoding without raising errors, replacing characters as needed.
|
|
27
|
+
* :func:`to_ascii`: converts a Unicode string into its closest ASCII representation by removing accents and diacritics.
|
|
28
|
+
* :func:`ascii_str`: encodes a Unicode string into a reversible 7-bit ASCII representation, useful for transport
|
|
29
|
+
protocols like HTTP headers.
|
|
30
|
+
* :func:`str_ascii`: decodes a string created by :func:`ascii_str` back to its original Unicode form.
|
|
31
|
+
* :func:`format_given`: a replacement for `str.format_map` that formats a string but leaves placeholders intact if they
|
|
32
|
+
are not found in the provided mapping.
|
|
27
33
|
|
|
28
|
-
with the help of the format string constant :data:`NOW_STR_FORMAT` and the function :func:`now_str` you can create a
|
|
29
|
-
sortable and compact string from a timestamp.
|
|
30
34
|
|
|
35
|
+
system & environment
|
|
36
|
+
--------------------
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
---------------------
|
|
38
|
+
inspect the operating system and manage environment variables.
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
.. hint::
|
|
41
|
+
the :mod:`ae.core` portion is providing more OS-specific constants and helper functions, like e.g.
|
|
42
|
+
:func:`~ae.core.start_app_service` and :func:`~ae.core.request_app_permissions`.
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
OS information
|
|
45
|
+
~~~~~~~~~~~~~~
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
* :data:`os_platform`: a string identifying the operating system (e.g., 'linux', 'win32', 'android', 'ios').
|
|
48
|
+
* :data:`os_device_id`: a string with the ID/name of the device.
|
|
49
|
+
* :func:`os_host_name`: determines the operating system's host/machine name.
|
|
50
|
+
* :func:`os_local_ip`: determines the local IP address of the machine.
|
|
51
|
+
* :func:`os_user_name`: determines the current logged-in user's name.
|
|
52
|
+
* :func:`sys_env_dict`: returns a dictionary containing key Python runtime environment values.
|
|
53
|
+
* :func:`sys_env_text`: compiles a formatted text block with system environment information, useful for logging.
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
environment variables & `.env` files
|
|
56
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
* :func:`env_str`: retrieves the string value of an OS environment variable, with an option to automatically convert the
|
|
59
|
+
variable name to the conventional format.
|
|
60
|
+
* :func:`parse_dotenv`: parses a `.env` file and returns its key-value pairs as a dictionary.
|
|
61
|
+
* :func:`load_env_var_defaults`: recursively searches parent directories for `.env` files and loads any undeclared
|
|
62
|
+
variables.
|
|
63
|
+
* :func:`load_dotenvs`: detects and loads all relevant `.env` files from the current working directory and optional
|
|
64
|
+
also from the main module's path.
|
|
50
65
|
|
|
51
|
-
the function :func:`duplicates` returns the duplicates of an iterable type.
|
|
52
66
|
|
|
53
|
-
|
|
54
|
-
|
|
67
|
+
data structure utilities
|
|
68
|
+
------------------------
|
|
55
69
|
|
|
56
|
-
|
|
70
|
+
helpers for working with lists, dictionaries, and other data structures.
|
|
57
71
|
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
* :func:`evaluate_literal`: replacement for :func:`ast.literal_eval` that also interprets/recognizes unquoted strings
|
|
73
|
+
as `str` type.
|
|
74
|
+
* :func:`duplicates`: returns a list of all duplicate items found in any type of iterable.
|
|
75
|
+
* :func:`deep_dict_update`: recursively updates a dictionary in-place with values from another dictionary.
|
|
76
|
+
* :func:`mask_secrets`: hides sensitive string values (e.g., passwords, API keys) in deeply nested data structures,
|
|
77
|
+
useful for logging.
|
|
60
78
|
|
|
61
|
-
to normalize a file path, in order to remove `.`, `..` placeholders, to resolve symbolic links or to make it relative or
|
|
62
|
-
absolute, call the function :func:`norm_path`.
|
|
63
79
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
corresponding URL/URI or file path.
|
|
80
|
+
application & project helpers
|
|
81
|
+
-----------------------------
|
|
67
82
|
|
|
68
|
-
|
|
83
|
+
functions to aid in application setup, configuration, and build introspection.
|
|
69
84
|
|
|
70
|
-
|
|
85
|
+
* :func:`app_name_guess`: attempts to determine the name of the currently running application from its environment.
|
|
86
|
+
* :func:`build_config_variable_values`: reads variable values from a `buildozer.spec` file.
|
|
87
|
+
* :func:`instantiate_config_parser`: returns a `ConfigParser` instance pre-configured for case-sensitive keys and
|
|
88
|
+
extended interpolation.
|
|
89
|
+
* :func:`project_main_file`: determines the absolute path to the main module file of a project package (where the
|
|
90
|
+
`__version__` of the app|package is defined).
|
|
91
|
+
* :func:`main_file_paths_parts`: returns a tuple of possible main/version file path names combinations of any project.
|
|
71
92
|
|
|
72
|
-
the :func:`round_traditional` function gets provided by this module for traditional rounding of float values. the
|
|
73
|
-
function signature is fully compatible with Python's :func:`round` function.
|
|
74
93
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
as the interpolation argument.
|
|
94
|
+
modules and call stack inspection
|
|
95
|
+
---------------------------------
|
|
78
96
|
|
|
79
|
-
|
|
80
|
-
of :func:`build_config_variable_values`, which determines config-variable-values from the build spec file of an app
|
|
81
|
-
project.
|
|
97
|
+
dynamically inspect modules, execution frames, and variables on the call stack.
|
|
82
98
|
|
|
99
|
+
* :func:`import_module`: dynamically imports a Python module from a path without adding it to `sys.modules`.
|
|
100
|
+
* :func:`module_attr`: dynamically gets a reference to a module or any attribute (variable, function, class) within it.
|
|
101
|
+
* :func:`module_file_path`: determines the absolute file path of the module from which it is called.
|
|
102
|
+
* :func:`module_name`: finds the name of the first module in the call stack that is not in a predefined skip list.
|
|
103
|
+
* :func:`stack_frames`: a generator that yields frames from the call stack, starting at a specified depth.
|
|
104
|
+
* :func:`stack_var`: finds the value of a specific variable by searching up the call stack.
|
|
105
|
+
* :func:`stack_vars`: returns the global and local variables from a specific frame in the call stack.
|
|
106
|
+
* :func:`full_stack_trace`: generates a complete, detailed string representation of an exception's stack trace.
|
|
83
107
|
|
|
84
|
-
|
|
85
|
-
|
|
108
|
+
.. hint::
|
|
109
|
+
the :class:`~ae.core.AppBase` class uses these helper functions to determine the
|
|
110
|
+
:attr:`version <ae.core.AppBase.app_version>` and :attr:`title <ae.core.AppBase.app_title>` of an application,
|
|
111
|
+
if these values are not specified in the instance initializer.
|
|
86
112
|
|
|
87
|
-
the string :data:`os_platform` provides the OS where your app is running, extending Python's :func:`sys.platform`
|
|
88
|
-
for mobile platforms like Android and iOS.
|
|
89
113
|
|
|
90
|
-
|
|
91
|
-
|
|
114
|
+
networking utilities
|
|
115
|
+
--------------------
|
|
92
116
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
variables for your application are :func:`sys_env_dict` and :func:`sys_env_text`.
|
|
117
|
+
* :func:`url_failure`: determines if and why a HTTP|FTP target is unavailable.
|
|
118
|
+
* :func:`mask_url`: hides or replaces the password/token portion of a URL for safe logging.
|
|
96
119
|
|
|
97
|
-
to integrate system environment variables from ``.env`` files into :data:`os.environ` the helper functions
|
|
98
|
-
:func:`parse_dotenv`, :func:`load_env_var_defaults` and :func:`load_dotenvs` are provided.
|
|
99
120
|
|
|
100
|
-
|
|
101
|
-
|
|
121
|
+
general utilities & helpers
|
|
122
|
+
---------------------------
|
|
102
123
|
|
|
103
|
-
|
|
104
|
-
on import of this module, while running on Android OS, it will monkey patch the :mod:`shutil` module to allow
|
|
105
|
-
using them on Android devices, and on the first app start requesting the permissions of your app. therefore, to
|
|
106
|
-
prevent permission errors, the import of this module should be the first statement in the main module of your app.
|
|
124
|
+
a collection of miscellaneous mathematical, date/time, and other standalone helper functions.
|
|
107
125
|
|
|
126
|
+
mathematical
|
|
127
|
+
~~~~~~~~~~~~
|
|
108
128
|
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
* :func:`sign`: returns the sign of a number (-1 for negative, 0 for zero, 1 for positive).
|
|
130
|
+
* :func:`round_traditional`: rounds a float value using traditional rounding rules (e.g., `0.5` rounds up).
|
|
111
131
|
|
|
112
|
-
|
|
113
|
-
|
|
132
|
+
date & time
|
|
133
|
+
~~~~~~~~~~~
|
|
134
|
+
* :func:`utc_datetime`: Returns the current date and time as a timezone-naive `datetime` object in UTC.
|
|
135
|
+
* :func:`now_str`: creates a compact, sortable timestamp string from the current UTC time.
|
|
114
136
|
|
|
115
|
-
|
|
137
|
+
miscellaneous
|
|
138
|
+
~~~~~~~~~~~~~
|
|
139
|
+
* :func:`dummy_function`: a null function that accepts any arguments and returns `None`.
|
|
116
140
|
|
|
117
|
-
the classes :class:`UnformattedValue` and :class:`GivenFormatter` can be used to format strings with placeholders
|
|
118
|
-
enclosed in curly brackets. the function :func:`format_given` is using them to format templates with placeholders.
|
|
119
141
|
|
|
120
|
-
|
|
121
|
-
generic context manager
|
|
142
|
+
types, classes & mixins
|
|
122
143
|
-----------------------
|
|
123
144
|
|
|
124
|
-
the
|
|
125
|
-
|
|
126
|
-
:class
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
call stack inspection
|
|
135
|
-
---------------------
|
|
136
|
-
|
|
137
|
-
:func:`module_attr` dynamically determines a reference to an attribute (variable, function, class, ...) in a module.
|
|
145
|
+
* :class:`UnsetType`: the class for the :data:`UNSET` singleton object, useful as a sentinel value when `None` is a
|
|
146
|
+
valid input.
|
|
147
|
+
* :class:`ErrorMsgMixin`: a mixin class that provides any class with a sophisticated error message handling and
|
|
148
|
+
logging property.
|
|
149
|
+
* :class:`UnformattedValue`: a helper class for :func:`format_given` to represent a placeholder that was not found in
|
|
150
|
+
the formatting map.
|
|
151
|
+
* :class:`GivenFormatter`: a helper class for :func:`format_given` that overrides default formatting behavior to keep
|
|
152
|
+
missing placeholders.
|
|
138
153
|
|
|
139
|
-
:func:`module_name`, :func:`stack_frames`, :func:`stack_var` and :func:`stack_vars` are inspecting the call stack frames
|
|
140
|
-
to determine e.g., variable values of the callers of a function/method.
|
|
141
154
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
:attr:`title <AppBase.app_title>` of an application, if these values are not specified in the instance initializer.
|
|
145
|
-
|
|
146
|
-
another useful helper function provided by this portion to inspect and debug your code is :func:`full_stack_trace`.
|
|
155
|
+
base constants
|
|
156
|
+
--------------
|
|
147
157
|
|
|
158
|
+
predefined constants for project structure, file conventions, and default settings.
|
|
159
|
+
|
|
160
|
+
project & file structure
|
|
161
|
+
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
162
|
+
|
|
163
|
+
* :data:`DOCS_FOLDER`: default name for a project's documentation folder ('docs').
|
|
164
|
+
* :data:`TESTS_FOLDER`: default name for a project's tests folder ('tests').
|
|
165
|
+
* :data:`TEMPLATES_FOLDER`: default name for a folder containing file templates ('templates').
|
|
166
|
+
* :data:`BUILD_CONFIG_FILE`: default name for a build configuration file ('buildozer.spec').
|
|
167
|
+
* :data:`DEF_PROJECT_PARENT_FOLDER`: default directory name for grouping source code projects ('src').
|
|
168
|
+
* :data:`PY_CACHE_FOLDER`: default name for Python's cache folder ('__pycache__').
|
|
169
|
+
* :data:`PY_EXT`: file extension for Python modules ('.py').
|
|
170
|
+
* :data:`PY_INIT`: the filename for a Python package initializer ('__init__.py').
|
|
171
|
+
* :data:`PY_MAIN`: the filename for a Python executable's main module ('__main__.py').
|
|
172
|
+
* :data:`CFG_EXT`: file extension for CFG configuration files ('.cfg').
|
|
173
|
+
* :data:`INI_EXT`: file extension for INI configuration files ('.ini').
|
|
174
|
+
* :data:`DOTENV_FILE_NAME`: default name for environment variable files ('.env').
|
|
175
|
+
* :data:`PACKAGE_INCLUDE_FILES_PREFIX`: prefix for files/folders to be included in setup package data (used by
|
|
176
|
+
:mod:`ae.updater` and :mod:`aedev.project_manager`)
|
|
177
|
+
|
|
178
|
+
formats & default settings
|
|
179
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
180
|
+
|
|
181
|
+
* :data:`DATE_ISO`: ISO format string for dates ("%Y-%m-%d").
|
|
182
|
+
* :data:`DATE_TIME_ISO`: ISO format string for :mod:`datetime.datetime` dates ("%Y-%m-%d %H:%M:%S.%f").
|
|
183
|
+
* :data:`NOW_STR_FORMAT`: the datetime format string, used e.g. by :func:`now_str` for creating timestamps.
|
|
184
|
+
* :data:`NAME_PARTS_SEP`: the character used as a separator in name conversions ('_').
|
|
185
|
+
* :data:`DEF_ENCODING`: the default encoding used for string operations ('ascii').
|
|
186
|
+
* :data:`DEF_ENCODE_ERRORS`: the default error handling strategy for encoding ('backslashreplace').
|
|
187
|
+
* :data:`SKIPPED_MODULES`: a tuple of module names to be ignored by stack inspection functions.
|
|
188
|
+
* :data:`UNSET`: a singleton instance of :class:`UnsetType`, used where `None` is a valid data value.
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
file, path & I/O operations
|
|
192
|
+
---------------------------
|
|
193
|
+
|
|
194
|
+
simplify file system interactions with wrappers and context managers.
|
|
195
|
+
|
|
196
|
+
* :func:`read_file`: reads the entire content of a text or binary file into a string or bytes object.
|
|
197
|
+
* :func:`write_file`: writes a string or bytes object to a file, overwriting existing content.
|
|
198
|
+
* :func:`norm_path`: normalizes a path by expanding user home directories (`~`), resolving `.`, `..`, symbolic links,
|
|
199
|
+
and converting between absolute and relative paths.
|
|
200
|
+
* :func:`in_wd`: a context manager that temporarily switches the current working directory.
|
|
148
201
|
|
|
149
202
|
os.path shortcuts
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
the following
|
|
153
|
-
|
|
203
|
+
~~~~~~~~~~~~~~~~~
|
|
204
|
+
|
|
205
|
+
the following are direct references to functions in the :mod:`os.path` module for convenient and quicker access:
|
|
206
|
+
|
|
207
|
+
* :data:`os_path_abspath`: :func:`os.path.abspath`
|
|
208
|
+
* :data:`os_path_basename`: :func:`os.path.basename`
|
|
209
|
+
* :data:`os_path_dirname`: :func:`os.path.dirname`
|
|
210
|
+
* :data:`os_path_expanduser`: :func:`os.path.expanduser`
|
|
211
|
+
* :data:`os_path_isdir`: :func:`os.path.isdir`
|
|
212
|
+
* :data:`os_path_isfile`: :func:`os.path.isfile`
|
|
213
|
+
* :data:`os_path_join`: :func:`os.path.join`
|
|
214
|
+
* :data:`os_path_normpath`: :func:`os.path.normpath`
|
|
215
|
+
* :data:`os_path_realpath`: :func:`os.path.realpath`
|
|
216
|
+
* :data:`os_path_relpath`: :func:`os.path.relpath`
|
|
217
|
+
* :data:`os_path_sep`: :data:`os.path.sep`
|
|
218
|
+
* :data:`os_path_splitext`: :func:`os.path.splitext`
|
|
154
219
|
"""
|
|
155
220
|
# pylint: disable=too-many-lines
|
|
156
221
|
import datetime
|
|
@@ -162,6 +227,7 @@ import platform
|
|
|
162
227
|
import re
|
|
163
228
|
import shutil
|
|
164
229
|
import socket
|
|
230
|
+
import ssl
|
|
165
231
|
import string
|
|
166
232
|
import sys
|
|
167
233
|
import unicodedata
|
|
@@ -172,11 +238,14 @@ from configparser import ConfigParser, ExtendedInterpolation
|
|
|
172
238
|
from contextlib import contextmanager
|
|
173
239
|
from importlib.machinery import ModuleSpec
|
|
174
240
|
from inspect import getinnerframes, getouterframes, getsourcefile
|
|
241
|
+
from urllib.error import HTTPError, URLError
|
|
242
|
+
from urllib.parse import urlparse, urlunparse
|
|
243
|
+
from urllib.request import urlopen
|
|
175
244
|
from types import ModuleType
|
|
176
245
|
from typing import Any, Callable, Generator, Iterable, MutableMapping, Optional, Union, cast
|
|
177
246
|
|
|
178
247
|
|
|
179
|
-
__version__ = '0.3.
|
|
248
|
+
__version__ = '0.3.67'
|
|
180
249
|
|
|
181
250
|
|
|
182
251
|
os_path_abspath = os.path.abspath
|
|
@@ -360,6 +429,7 @@ def deep_dict_update(data: dict, update: dict, overwrite: bool = True):
|
|
|
360
429
|
|
|
361
430
|
|
|
362
431
|
URI_SEP_CHAR = '⫻' # U+2AFB: TRIPLE SOLIDUS BINARY RELATION
|
|
432
|
+
# noinspection GrazieInspection
|
|
363
433
|
ASCII_UNICODE = (
|
|
364
434
|
('/', '⁄'), # U+2044: Fraction Slash; '∕' U+2215: Division Slash; '⧸' U+29F8: Big Solidus;
|
|
365
435
|
# '╱' U+FF0F: Fullwidth Solidus; '╱' U+2571: Box Drawings Light Diagonal Upper Right to Lower Left
|
|
@@ -433,7 +503,7 @@ def defuse(value: str) -> str:
|
|
|
433
503
|
in most unix variants only the slash and the ASCII 0 characters are not allowed in file names.
|
|
434
504
|
|
|
435
505
|
in MS Windows are not allowed: ASCII 0..31 / | \\ : * ? ” % < > ( ). some blogs recommend also not allowing
|
|
436
|
-
(convert) the characters
|
|
506
|
+
(convert) the characters `#` and `'`.
|
|
437
507
|
|
|
438
508
|
only old POSIX seems to be even more restricted (only allowing alphanumeric characters plus . - and _).
|
|
439
509
|
|
|
@@ -499,13 +569,14 @@ def env_str(name: str, convert_name: bool = False) -> Optional[str]:
|
|
|
499
569
|
return os.environ.get(name)
|
|
500
570
|
|
|
501
571
|
|
|
502
|
-
def evaluate_literal(literal_string: str
|
|
572
|
+
def evaluate_literal(literal_string: str
|
|
573
|
+
) -> Optional[Union[bool, bytes, dict, complex, float, int, list, set, str, tuple]]:
|
|
503
574
|
""" evaluates a Python expression while accepting unquoted strings as str type.
|
|
504
575
|
|
|
505
576
|
:param literal_string: any literal of the base types (like dict, list, set, tuple) which are recognized
|
|
506
577
|
by :func:`ast.literal_eval`.
|
|
507
|
-
:return: an instance of the data type or the specified string, even if it is not quoted with
|
|
508
|
-
|
|
578
|
+
:return: an instance of the data type or the specified string, even if it is not quoted with high
|
|
579
|
+
comma characters. `None` will be returned if the specified literal is the string "None".
|
|
509
580
|
"""
|
|
510
581
|
try:
|
|
511
582
|
return literal_eval(literal_string)
|
|
@@ -622,7 +693,11 @@ def import_module(import_name: str, path: Optional[Union[str, UnsetType]] = UNSE
|
|
|
622
693
|
|
|
623
694
|
|
|
624
695
|
def instantiate_config_parser() -> ConfigParser:
|
|
625
|
-
""" instantiate and prepare config file parser.
|
|
696
|
+
""" instantiate and prepare config file parser.
|
|
697
|
+
|
|
698
|
+
ensures that the :class:`~configparser.ConfigParser` instance is correctly configured, e.g., to support
|
|
699
|
+
case-sensitive config variable names and to use :class:`ExtendedInterpolation` as the interpolation argument.
|
|
700
|
+
"""
|
|
626
701
|
cfg_parser = ConfigParser(allow_no_value=True, interpolation=ExtendedInterpolation())
|
|
627
702
|
# set optionxform to have case-sensitive var names (or use 'lambda option: option')
|
|
628
703
|
# mypy V 0.740 bug - see mypy issue #5062: adding pragma "type: ignore" breaks PyCharm (showing
|
|
@@ -639,6 +714,15 @@ def in_wd(new_cwd: str) -> Generator[None, None, None]:
|
|
|
639
714
|
|
|
640
715
|
:param new_cwd: path to the directory to switch to (within the context/with block).
|
|
641
716
|
an empty string gets interpreted as the current working directory.
|
|
717
|
+
|
|
718
|
+
the following example demonstrates a typical usage, together with a temporary path, created with the help of Pythons
|
|
719
|
+
:class:`~tempfile.TemporaryDirectory` class::
|
|
720
|
+
|
|
721
|
+
with tempfile.TemporaryDirectory() as tmp_dir, in_wd(tmp_dir):
|
|
722
|
+
# within the context the tmp_dir is set as the current working directory
|
|
723
|
+
assert os.getcwd() == tmp_dir
|
|
724
|
+
# here the current working directory got set back to the original path and the temporary directory got removed
|
|
725
|
+
|
|
642
726
|
"""
|
|
643
727
|
cur_dir = os.getcwd()
|
|
644
728
|
try:
|
|
@@ -649,14 +733,26 @@ def in_wd(new_cwd: str) -> Generator[None, None, None]:
|
|
|
649
733
|
os.chdir(cur_dir)
|
|
650
734
|
|
|
651
735
|
|
|
652
|
-
def load_dotenvs():
|
|
653
|
-
""" detect and load
|
|
736
|
+
def load_dotenvs(from_module_path: bool = False):
|
|
737
|
+
""" detect and load not defined OS environment variables from ``.env`` files.
|
|
654
738
|
|
|
655
|
-
|
|
739
|
+
:param from_module_path: pass True to load OS environment variables (that are not already loaded from ``.env``
|
|
740
|
+
files situated in or above the current working directory) also from/above the folder of
|
|
741
|
+
the first module in the call stack that gets not excluded/skipped by :func:`stack_var`.
|
|
742
|
+
|
|
743
|
+
in order to also load ``.env`` files in/above the project folder.
|
|
744
|
+
call this function from the main module of project/app.
|
|
745
|
+
|
|
746
|
+
.. note::
|
|
747
|
+
only variables that are not already defined in the OS environment variables mapping :data:`os.environ` will be
|
|
748
|
+
loaded/added. variables will be loaded first from the first ``.env`` file found in or above the current working
|
|
749
|
+
directory, while the variable values in the deeper situated files are overwriting the values defined in the
|
|
750
|
+
``.env`` files situated in the above folders.
|
|
656
751
|
"""
|
|
657
752
|
env_vars = os.environ
|
|
658
753
|
load_env_var_defaults(os.getcwd(), env_vars)
|
|
659
|
-
|
|
754
|
+
|
|
755
|
+
if from_module_path and (file_name := stack_var('__file__')):
|
|
660
756
|
load_env_var_defaults(os_path_dirname(os_path_abspath(file_name)), env_vars)
|
|
661
757
|
|
|
662
758
|
|
|
@@ -731,6 +827,22 @@ def mask_secrets(data: Union[dict, Iterable], fragments: Iterable[str] = ('passw
|
|
|
731
827
|
return data
|
|
732
828
|
|
|
733
829
|
|
|
830
|
+
def mask_url(url: str, replacement: str = "¿¿¿") -> str:
|
|
831
|
+
""" hide|replace the password/token in a URL.
|
|
832
|
+
|
|
833
|
+
:param url: URL in which an optional password|token will be searched and replaced.
|
|
834
|
+
:param replacement: optional replacement string, if not specified then the default value will be used.
|
|
835
|
+
:return: URL with the credentials masked/replaced.
|
|
836
|
+
"""
|
|
837
|
+
parts = urlparse(url)
|
|
838
|
+
if parts.password is None:
|
|
839
|
+
return url
|
|
840
|
+
# manually split out the netloc, because using parts.hostname/,port would have to be checked for None&hostname.lower
|
|
841
|
+
parts = parts._replace(netloc=f"{parts.username}:{replacement}@{parts.netloc.rpartition('@')[-1]}")
|
|
842
|
+
# noinspection PyTypeChecker
|
|
843
|
+
return urlunparse(parts)
|
|
844
|
+
|
|
845
|
+
|
|
734
846
|
def module_attr(import_name: str, attr_name: str = "") -> Optional[Any]:
|
|
735
847
|
""" determine dynamically a reference to a module or to any attribute (variable/func/class) declared in the module.
|
|
736
848
|
|
|
@@ -788,11 +900,12 @@ def module_name(*skip_modules: str, depth: int = 0) -> Optional[str]:
|
|
|
788
900
|
|
|
789
901
|
|
|
790
902
|
def norm_line_sep(text: str) -> str:
|
|
903
|
+
# noinspection GrazieInspection
|
|
791
904
|
""" convert any combination of line separators in the :paramref:`~norm_line_sep.text` arg to new-line characters.
|
|
792
905
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
906
|
+
:param text: string containing any combination of line separators ('\\\\r\\\\n' or '\\\\r').
|
|
907
|
+
:return: normalized/converted string with only new-line ('\\\\n') line separator characters.
|
|
908
|
+
"""
|
|
796
909
|
return text.replace('\r\n', '\n').replace('\r', '\n')
|
|
797
910
|
|
|
798
911
|
|
|
@@ -906,7 +1019,8 @@ def os_local_ip() -> str:
|
|
|
906
1019
|
def _os_platform() -> str:
|
|
907
1020
|
""" determine the operating system where this code is running (used to initialize the :data:`os_platform` variable).
|
|
908
1021
|
|
|
909
|
-
:return: operating system (extension) as string:
|
|
1022
|
+
:return: operating system (extension) as string. extending Python's :func:`sys.platform`
|
|
1023
|
+
for mobile platforms like Android and iOS:
|
|
910
1024
|
|
|
911
1025
|
* `'android'` for all Android systems.
|
|
912
1026
|
* `'cygwin'` for MS Windows with an installed Cygwin extension.
|
|
@@ -926,7 +1040,7 @@ os_platform = _os_platform()
|
|
|
926
1040
|
""" operating system / platform string (see :func:`_os_platform`).
|
|
927
1041
|
|
|
928
1042
|
this string value gets determined for most of the operating systems with the help of Python's :func:`sys.platform`
|
|
929
|
-
function and additionally detects the operating systems iOS and Android (not supported by Python).
|
|
1043
|
+
function and additionally detects the operating systems iOS and Android (currently not fully supported by Python).
|
|
930
1044
|
"""
|
|
931
1045
|
|
|
932
1046
|
|
|
@@ -1213,6 +1327,38 @@ def to_ascii(unicode_str: str) -> str:
|
|
|
1213
1327
|
return "".join([c for c in nfkd_form if not unicodedata.combining(c)]).replace('ß', "ss").replace('€', "Euro")
|
|
1214
1328
|
|
|
1215
1329
|
|
|
1330
|
+
def url_failure(url: str, timeout: Optional[float] = None) -> str: # pylint: disable=too-many-return-statements
|
|
1331
|
+
""" determine if and why an FTP or HTTP[S] target is not available via a GET request.
|
|
1332
|
+
|
|
1333
|
+
:param url: URL of an target|page|file to check (not downloaded, fetching only the header).
|
|
1334
|
+
:param timeout: connection timeout in seconds (see :func:`urllib.request.urlopen`).
|
|
1335
|
+
:return: empty string if target header is available, else an error description. if an
|
|
1336
|
+
FTP|HTTP response error occurred then the error/status code
|
|
1337
|
+
will be returned in the first 3 characters.
|
|
1338
|
+
"""
|
|
1339
|
+
# noinspection PyBroadException
|
|
1340
|
+
try:
|
|
1341
|
+
with urlopen(url, timeout=timeout) as response: # open connection and read header
|
|
1342
|
+
status = response.getcode() # no need to call response.read()
|
|
1343
|
+
return "" if 200 <= status < 300 else f"{status} {mask_url(url)} {response.reason=}"
|
|
1344
|
+
|
|
1345
|
+
except HTTPError as exception:
|
|
1346
|
+
return f"{exception.code} {mask_url(url)} raised HTTPError {exception.reason=}"
|
|
1347
|
+
|
|
1348
|
+
except URLError as exception:
|
|
1349
|
+
err_prefix = f"996 {mask_url(url)} raised {exception.errno=} {exception.reason=};"
|
|
1350
|
+
if isinstance(exception.reason, socket.gaierror):
|
|
1351
|
+
return f"{err_prefix} could not resolve hostname"
|
|
1352
|
+
if isinstance(exception.reason, socket.timeout):
|
|
1353
|
+
return f"{err_prefix} connection timed out after {timeout} seconds"
|
|
1354
|
+
if isinstance(exception.reason, ssl.SSLCertVerificationError):
|
|
1355
|
+
return f"{err_prefix} SSL certificate verification failed"
|
|
1356
|
+
return f"{err_prefix} could not reach the server"
|
|
1357
|
+
|
|
1358
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
1359
|
+
return f"999 {mask_url(url)} raised unexpected exception" # NOT put str(_exception) because contains password
|
|
1360
|
+
|
|
1361
|
+
|
|
1216
1362
|
def utc_datetime() -> datetime.datetime:
|
|
1217
1363
|
""" return the current UTC timestamp as string (to use as suffix for file and variable/attribute names).
|
|
1218
1364
|
|
|
@@ -1314,14 +1460,14 @@ class ErrorMsgMixin: # pylint: di
|
|
|
1314
1460
|
os_device_id = os_host_name()
|
|
1315
1461
|
""" user-definable id/name of the device, defaults to os_host_name() on most platforms, alternatives are:
|
|
1316
1462
|
|
|
1317
|
-
on all platforms:
|
|
1318
|
-
- socket.gethostname()
|
|
1319
1463
|
on Android (check with adb shell 'settings get global device_name' and adb shell 'settings list global'):
|
|
1320
1464
|
- Settings.Global.DEVICE_NAME (Settings.Global.getString(context.getContentResolver(), "device_name"))
|
|
1321
1465
|
- android.os.Build.DEVICE/.MANUFACTURER/.BRAND/.HOST
|
|
1322
1466
|
- DeviceName.getDeviceName()
|
|
1323
1467
|
on MS Windows:
|
|
1324
1468
|
- os.environ['COMPUTERNAME']
|
|
1469
|
+
on all other platforms:
|
|
1470
|
+
- socket.gethostname()
|
|
1325
1471
|
"""
|
|
1326
1472
|
if os_platform == 'android': # pragma: no cover
|
|
1327
1473
|
# determine Android device id because os_host_name() returns mostly 'localhost' and not the user-definable device id
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ae_base
|
|
3
|
-
Version: 0.3.
|
|
4
|
-
Summary: ae namespace module portion base: basic constants, helper functions and context
|
|
3
|
+
Version: 0.3.67
|
|
4
|
+
Summary: ae namespace module portion base: basic constants, helper functions and context managers
|
|
5
5
|
Home-page: https://gitlab.com/ae-group/ae_base
|
|
6
6
|
Author: AndiEcker
|
|
7
7
|
Author-email: aecker2@gmail.com
|
|
@@ -36,8 +36,6 @@ Requires-Dist: pytest-cov; extra == "dev"
|
|
|
36
36
|
Requires-Dist: pytest-django; extra == "dev"
|
|
37
37
|
Requires-Dist: typing; extra == "dev"
|
|
38
38
|
Requires-Dist: types-setuptools; extra == "dev"
|
|
39
|
-
Requires-Dist: wheel; extra == "dev"
|
|
40
|
-
Requires-Dist: twine; extra == "dev"
|
|
41
39
|
Provides-Extra: docs
|
|
42
40
|
Provides-Extra: tests
|
|
43
41
|
Requires-Dist: anybadge; extra == "tests"
|
|
@@ -51,8 +49,6 @@ Requires-Dist: pytest-cov; extra == "tests"
|
|
|
51
49
|
Requires-Dist: pytest-django; extra == "tests"
|
|
52
50
|
Requires-Dist: typing; extra == "tests"
|
|
53
51
|
Requires-Dist: types-setuptools; extra == "tests"
|
|
54
|
-
Requires-Dist: wheel; extra == "tests"
|
|
55
|
-
Requires-Dist: twine; extra == "tests"
|
|
56
52
|
Dynamic: author
|
|
57
53
|
Dynamic: author-email
|
|
58
54
|
Dynamic: classifier
|
|
@@ -67,19 +63,19 @@ Dynamic: provides-extra
|
|
|
67
63
|
Dynamic: requires-python
|
|
68
64
|
Dynamic: summary
|
|
69
65
|
|
|
70
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae
|
|
66
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.96 -->
|
|
71
67
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
|
|
72
|
-
# base 0.3.
|
|
68
|
+
# base 0.3.67
|
|
73
69
|
|
|
74
70
|
[](
|
|
75
71
|
https://gitlab.com/ae-group/ae_base)
|
|
76
72
|
[](
|
|
74
|
+
https://gitlab.com/ae-group/ae_base/-/tree/release0.3.66)
|
|
79
75
|
[](
|
|
80
76
|
https://pypi.org/project/ae-base/#history)
|
|
81
77
|
|
|
82
|
-
>ae namespace module portion base: basic constants, helper functions and context
|
|
78
|
+
>ae namespace module portion base: basic constants, helper functions and context managers.
|
|
83
79
|
|
|
84
80
|
[](
|
|
85
81
|
https://ae-group.gitlab.io/ae_base/coverage/index.html)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ae/base.py,sha256=aEJAZNKzsY0XCiFga5CDqcnNoL5X9uaaRv5WGN8rWTM,77020
|
|
2
|
+
ae_base-0.3.67.dist-info/licenses/LICENSE.md,sha256=hlo7PHR_Bh9Fv5DMlZv7Nbq-i_-KmNDpZ_wOSLFsLEs,35003
|
|
3
|
+
ae_base-0.3.67.dist-info/METADATA,sha256=-YtEa0YEng9v25SWrjzmNuJhm0nNyCiSqdz-_Bafwfw,5469
|
|
4
|
+
ae_base-0.3.67.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
+
ae_base-0.3.67.dist-info/top_level.txt,sha256=vUdgAslSmhZLXWU48fm8AG2BjVnkOWLco8rzuW-5zY0,3
|
|
6
|
+
ae_base-0.3.67.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
|
+
ae_base-0.3.67.dist-info/RECORD,,
|
ae_base-0.3.65.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
ae/base.py,sha256=dMNP9veLav-IvUULLmNPs1PXpHQ2xAn5WMva9qIVRKk,68719
|
|
2
|
-
ae_base-0.3.65.dist-info/licenses/LICENSE.md,sha256=-UEWsJNTHESFg25my1bZ5yx9aZzHD04wbguYc6ZfgEY,35003
|
|
3
|
-
ae_base-0.3.65.dist-info/METADATA,sha256=s82tpQCZKNjEzSNvz-9KEWY5AkXzRDHIzUJChWcg-vk,5619
|
|
4
|
-
ae_base-0.3.65.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
-
ae_base-0.3.65.dist-info/top_level.txt,sha256=vUdgAslSmhZLXWU48fm8AG2BjVnkOWLco8rzuW-5zY0,3
|
|
6
|
-
ae_base-0.3.65.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
|
-
ae_base-0.3.65.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|