ae-base 0.3.35__py3-none-any.whl → 0.3.37__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 +128 -15
- {ae_base-0.3.35.dist-info → ae_base-0.3.37.dist-info}/LICENSE.md +1 -1
- {ae_base-0.3.35.dist-info → ae_base-0.3.37.dist-info}/METADATA +7 -7
- ae_base-0.3.37.dist-info/RECORD +7 -0
- {ae_base-0.3.35.dist-info → ae_base-0.3.37.dist-info}/WHEEL +1 -1
- ae_base-0.3.35.dist-info/RECORD +0 -7
- {ae_base-0.3.35.dist-info → ae_base-0.3.37.dist-info}/top_level.txt +0 -0
- {ae_base-0.3.35.dist-info → ae_base-0.3.37.dist-info}/zip-safe +0 -0
ae/base.py
CHANGED
|
@@ -69,6 +69,9 @@ use :func:`env_str` to determine the value of an OS environment variable with au
|
|
|
69
69
|
helper functions provided by this namespace portion to determine the values of the most important system environment
|
|
70
70
|
variables for your application are :func:`sys_env_dict` and :func:`sys_env_text`.
|
|
71
71
|
|
|
72
|
+
to integrate system environment variables from ``.env`` files into :data:`os.environ` the helper functions
|
|
73
|
+
:func:parse_dotenv`, :func:`load_env_var_defaults` and :func:`load_dotenvs` are provided.
|
|
74
|
+
|
|
72
75
|
|
|
73
76
|
android-specific constants and helper functions
|
|
74
77
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
@@ -129,20 +132,21 @@ import importlib.abc
|
|
|
129
132
|
import importlib.util
|
|
130
133
|
import os
|
|
131
134
|
import platform
|
|
135
|
+
import re
|
|
132
136
|
import shutil
|
|
133
137
|
import socket
|
|
134
138
|
import sys
|
|
135
139
|
import unicodedata
|
|
140
|
+
import warnings
|
|
136
141
|
|
|
137
142
|
from configparser import ConfigParser, ExtendedInterpolation
|
|
138
143
|
from contextlib import contextmanager
|
|
139
144
|
from importlib.machinery import ModuleSpec
|
|
140
145
|
from inspect import getinnerframes, getouterframes, getsourcefile
|
|
141
146
|
from types import ModuleType
|
|
142
|
-
from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union
|
|
143
|
-
|
|
147
|
+
from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast
|
|
144
148
|
|
|
145
|
-
__version__ = '0.3.
|
|
149
|
+
__version__ = '0.3.37'
|
|
146
150
|
|
|
147
151
|
|
|
148
152
|
DOCS_FOLDER = 'docs' #: project documentation root folder name
|
|
@@ -169,6 +173,33 @@ DEF_ENCODING = 'ascii'
|
|
|
169
173
|
""" encoding for :func:`force_encoding` that will always work independent from destination (console, file sys, ...).
|
|
170
174
|
"""
|
|
171
175
|
|
|
176
|
+
DOTENV_FILE_NAME = '.env' #: name of the file containing console/shell environment variables
|
|
177
|
+
_env_line = re.compile(r"""
|
|
178
|
+
^
|
|
179
|
+
(?:export\s+)? # optional export
|
|
180
|
+
([\w.]+) # key
|
|
181
|
+
(?:\s*=\s*|:\s+?) # separator
|
|
182
|
+
( # optional value begin
|
|
183
|
+
'(?:\'|[^'])*' # single quoted value
|
|
184
|
+
| # or
|
|
185
|
+
"(?:\"|[^"])*" # double quoted value
|
|
186
|
+
| # or
|
|
187
|
+
[^#\n]+ # unquoted value
|
|
188
|
+
)? # value end
|
|
189
|
+
(?:\s*\#.*)? # optional comment
|
|
190
|
+
$
|
|
191
|
+
""", re.VERBOSE)
|
|
192
|
+
_env_variable = re.compile(r"""
|
|
193
|
+
(\\)? # is it escaped with a backslash?
|
|
194
|
+
(\$) # literal $
|
|
195
|
+
( # collect braces with var for sub
|
|
196
|
+
\{? # allow brace wrapping
|
|
197
|
+
([A-Z0-9_]+) # match the variable
|
|
198
|
+
}? # closing brace
|
|
199
|
+
) # braces end
|
|
200
|
+
""", re.IGNORECASE | re.VERBOSE)
|
|
201
|
+
|
|
202
|
+
|
|
172
203
|
NAME_PARTS_SEP = '_' #: name parts separator character, e.g. for :func:`norm_name`
|
|
173
204
|
|
|
174
205
|
SKIPPED_MODULES = ('ae.base', 'ae.paths', 'ae.dynamicod', 'ae.core', 'ae.console', 'ae.gui_app', 'ae.gui_help',
|
|
@@ -248,7 +279,7 @@ def deep_dict_update(data: dict, update: dict):
|
|
|
248
279
|
""" update the optionally nested data dict in-place with the items and sub-items from the update dict.
|
|
249
280
|
|
|
250
281
|
:param data: dict to be updated/extended. non-existing keys of dict-sub-items will be added.
|
|
251
|
-
:param update: dict with the [sub-]items to update in the :paramref
|
|
282
|
+
:param update: dict with the [sub-]items to update in the :paramref:`~deep_dict_update.data` dict.
|
|
252
283
|
|
|
253
284
|
.. hint:: the module/portion :mod:`ae.deep` is providing more deep update helper functions.
|
|
254
285
|
|
|
@@ -272,7 +303,7 @@ def dummy_function(*_args, **_kwargs):
|
|
|
272
303
|
|
|
273
304
|
|
|
274
305
|
def duplicates(values: Iterable) -> list:
|
|
275
|
-
""" determine all duplicates in the iterable specified in the :paramref
|
|
306
|
+
""" determine all duplicates in the iterable specified in the :paramref:`~duplicates.values` argument.
|
|
276
307
|
|
|
277
308
|
inspired by Ritesh Kumars answer to https://stackoverflow.com/questions/9835762.
|
|
278
309
|
|
|
@@ -398,6 +429,42 @@ def in_wd(new_cwd: str) -> Generator[None, None, None]:
|
|
|
398
429
|
os.chdir(cur_dir)
|
|
399
430
|
|
|
400
431
|
|
|
432
|
+
def load_dotenvs():
|
|
433
|
+
""" detect and load multiple ``.env`` files in/above the current working directory and the calling module folder.
|
|
434
|
+
|
|
435
|
+
.. hint:: call from main module of project/app in order to also load ``.env`` files in/above the project folder.
|
|
436
|
+
"""
|
|
437
|
+
load_env_var_defaults(os.getcwd())
|
|
438
|
+
load_env_var_defaults(os.path.dirname(stack_var('__file__', depth=2)))
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def load_env_var_defaults(start_dir: str):
|
|
442
|
+
""" detect and load chain of ``.env`` files starting in the specified folder or one above.
|
|
443
|
+
|
|
444
|
+
:param start_dir: folder to start search of an ``.env`` file, if not found then checks the parent folder.
|
|
445
|
+
if a first ``.env`` file got found, then load their console/shell environment variables
|
|
446
|
+
into Python's :data:`os.environ`. after loading the first one, repeat to check for
|
|
447
|
+
further ``.env`` files in the parent folder to load them too, until either detecting
|
|
448
|
+
a folder without an ``.env`` file or until an ``.env`` got loaded from the root folder.
|
|
449
|
+
|
|
450
|
+
.. note::
|
|
451
|
+
only variables that are not declared in :data:`os.environ` will be added (with the
|
|
452
|
+
value specified in the ``.env`` file to be loaded).
|
|
453
|
+
"""
|
|
454
|
+
file_path = os.path.abspath(os.path.join(start_dir, DOTENV_FILE_NAME))
|
|
455
|
+
if not os.path.isfile(file_path):
|
|
456
|
+
file_path = os.path.join(os.path.dirname(start_dir), DOTENV_FILE_NAME)
|
|
457
|
+
|
|
458
|
+
while os.path.isfile(file_path):
|
|
459
|
+
for var_nam, var_val in parse_dotenv(file_path).items():
|
|
460
|
+
if var_nam not in os.environ:
|
|
461
|
+
os.environ[var_nam] = var_val
|
|
462
|
+
|
|
463
|
+
if os.sep not in file_path:
|
|
464
|
+
break # pragma: no cover # prevent endless-loop for ``.env`` file in root dir (os.sep == '/')
|
|
465
|
+
file_path = os.path.join(os.path.dirname(os.path.dirname(file_path)), DOTENV_FILE_NAME)
|
|
466
|
+
|
|
467
|
+
|
|
401
468
|
def main_file_paths_parts(portion_name: str) -> Tuple[Tuple[str, ...], ...]:
|
|
402
469
|
""" determine tuple of supported main/version file name path part tuples.
|
|
403
470
|
|
|
@@ -445,7 +512,14 @@ def module_file_path(local_object: Optional[Callable] = None) -> str:
|
|
|
445
512
|
if file_path:
|
|
446
513
|
return norm_path(file_path)
|
|
447
514
|
|
|
448
|
-
|
|
515
|
+
file_path = stack_var('__file__')
|
|
516
|
+
if not file_path: # pragma: no cover
|
|
517
|
+
try:
|
|
518
|
+
# noinspection PyProtectedMember,PyUnresolvedReferences
|
|
519
|
+
file_path = sys._getframe().f_back.f_code.co_filename # type: ignore # pylint: disable=protected-access
|
|
520
|
+
except (AttributeError, Exception): # pylint: disable=broad-except # pragma: no cover
|
|
521
|
+
file_path = ""
|
|
522
|
+
return file_path
|
|
449
523
|
|
|
450
524
|
|
|
451
525
|
def module_name(*skip_modules: str, depth: int = 0) -> Optional[str]:
|
|
@@ -531,13 +605,13 @@ def norm_path(path: str, make_absolute: bool = True, remove_base_path: str = "",
|
|
|
531
605
|
|
|
532
606
|
|
|
533
607
|
def now_str(sep: str = "") -> str:
|
|
534
|
-
""" return the current timestamp as string (to use as suffix for file and variable/attribute names).
|
|
608
|
+
""" return the current UTC timestamp as string (to use as suffix for file and variable/attribute names).
|
|
535
609
|
|
|
536
610
|
:param sep: optional prefix and separator character (separating date from time and in time part
|
|
537
611
|
the seconds from the microseconds).
|
|
538
|
-
:return: timestamp as string (length=20 + 3 * len(sep)).
|
|
612
|
+
:return: UTC timestamp as string (length=20 + 3 * len(sep)).
|
|
539
613
|
"""
|
|
540
|
-
return datetime.datetime.
|
|
614
|
+
return datetime.datetime.utcnow().strftime("{sep}%Y%m%d{sep}%H%M%S{sep}%f".format(sep=sep))
|
|
541
615
|
|
|
542
616
|
|
|
543
617
|
def os_host_name() -> str:
|
|
@@ -612,8 +686,47 @@ def os_user_name() -> str:
|
|
|
612
686
|
return getpass.getuser()
|
|
613
687
|
|
|
614
688
|
|
|
689
|
+
def parse_dotenv(file_path: str) -> Dict[str, str]:
|
|
690
|
+
""" parse ``.env`` file content and return environment variable names as dict keys and values as dict values.
|
|
691
|
+
|
|
692
|
+
:param file_path: string with the name/path of an existing ``.env``/:data:`DOTENV_FILE_NAME` file.
|
|
693
|
+
:return: dict with environment variable names and values
|
|
694
|
+
"""
|
|
695
|
+
env_vars: Dict[str, str] = {}
|
|
696
|
+
for line in cast(str, read_file(file_path)).splitlines():
|
|
697
|
+
match = _env_line.search(line)
|
|
698
|
+
if not match:
|
|
699
|
+
if not re.search(r'^\s*(?:#.*)?$', line): # not comment or blank
|
|
700
|
+
warnings.warn(f"'{line!r}' in '{file_path}' doesn't match {DOTENV_FILE_NAME} format", SyntaxWarning)
|
|
701
|
+
continue
|
|
702
|
+
|
|
703
|
+
var_nam, var_val = match.groups()
|
|
704
|
+
var_val = "" if var_val is None else var_val.strip()
|
|
705
|
+
|
|
706
|
+
# remove surrounding quotes, unescape all chars except $ so variables can be escaped properly
|
|
707
|
+
match = re.match(r'^([\'"])(.*)\1$', var_val)
|
|
708
|
+
if match:
|
|
709
|
+
delimiter, var_val = match.groups()
|
|
710
|
+
if delimiter == '"':
|
|
711
|
+
var_val = re.sub(r'\\([^$])', r'\1', var_val)
|
|
712
|
+
else:
|
|
713
|
+
delimiter = None
|
|
714
|
+
if delimiter != "'":
|
|
715
|
+
for parts in _env_variable.findall(var_val):
|
|
716
|
+
if parts[0] == '\\':
|
|
717
|
+
replace = "".join(parts[1:-1]) # don't replace escaped variables
|
|
718
|
+
else:
|
|
719
|
+
# substitute variables in a value, replace it with the value from the environment
|
|
720
|
+
replace = env_vars.get(parts[-1], os.environ.get(parts[-1], ""))
|
|
721
|
+
var_val = var_val.replace("".join(parts[0:-1]), replace)
|
|
722
|
+
|
|
723
|
+
env_vars[var_nam] = var_val
|
|
724
|
+
|
|
725
|
+
return env_vars
|
|
726
|
+
|
|
727
|
+
|
|
615
728
|
def project_main_file(import_name: str, project_path: str = "") -> str:
|
|
616
|
-
""" determine the main module file path of a package
|
|
729
|
+
""" determine the main module file path of a project package, containing the project __version__ module variable.
|
|
617
730
|
|
|
618
731
|
:param import_name: import name of the module/package (including namespace prefixes for namespace packages).
|
|
619
732
|
:param project_path: optional path where the project of the package/module is situated. not needed if the
|
|
@@ -623,13 +736,13 @@ def project_main_file(import_name: str, project_path: str = "") -> str:
|
|
|
623
736
|
"""
|
|
624
737
|
join = os.path.join
|
|
625
738
|
*namespace_dirs, portion_name = import_name.split('.')
|
|
626
|
-
|
|
739
|
+
project_name = ('_'.join(namespace_dirs) + '_' if namespace_dirs else "") + portion_name
|
|
627
740
|
paths_parts = main_file_paths_parts(portion_name)
|
|
628
741
|
|
|
629
742
|
project_path = norm_path(project_path)
|
|
630
743
|
module_paths = []
|
|
631
|
-
if os.path.basename(project_path) !=
|
|
632
|
-
module_paths.append(join(os.path.dirname(project_path),
|
|
744
|
+
if os.path.basename(project_path) != project_name:
|
|
745
|
+
module_paths.append(join(os.path.dirname(project_path), project_name, *namespace_dirs))
|
|
633
746
|
if namespace_dirs:
|
|
634
747
|
module_paths.append(join(project_path, *namespace_dirs))
|
|
635
748
|
module_paths.append(project_path)
|
|
@@ -878,12 +991,12 @@ if os_platform == 'android': # pragma: no cov
|
|
|
878
991
|
# 'android' (see # https://bugs.python.org/issue28141 and https://bugs.python.org/issue32073). these functions are
|
|
879
992
|
# used by shutil.copy2/copy/copytree/move to copy OS-specific file attributes.
|
|
880
993
|
# although shutil.copytree() and shutil.move() are copying/moving the files correctly when the copy_function
|
|
881
|
-
# arg is set to :func:`shutil.copyfile`, they will finally also crash
|
|
994
|
+
# arg is set to :func:`shutil.copyfile`, they will finally also crash afterward when they try to set the attributes
|
|
882
995
|
# on the destination root directory.
|
|
883
996
|
shutil.copymode = dummy_function
|
|
884
997
|
shutil.copystat = dummy_function
|
|
885
998
|
|
|
886
|
-
# import permissions module from python-for-android (
|
|
999
|
+
# import permissions module from python-for-android (recipes/android/src/android/permissions.py)
|
|
887
1000
|
# noinspection PyUnresolvedReferences
|
|
888
1001
|
from android.permissions import request_permissions, Permission # type: ignore # pylint: disable=import-error
|
|
889
1002
|
from jnius import autoclass # type: ignore
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ae_base
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.37
|
|
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
|
|
@@ -51,19 +51,19 @@ Requires-Dist: types-setuptools ; extra == 'tests'
|
|
|
51
51
|
Requires-Dist: wheel ; extra == 'tests'
|
|
52
52
|
Requires-Dist: twine ; extra == 'tests'
|
|
53
53
|
|
|
54
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.
|
|
55
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.
|
|
56
|
-
# base 0.3.
|
|
54
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.94 -->
|
|
55
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
|
|
56
|
+
# base 0.3.37
|
|
57
57
|
|
|
58
58
|
[](
|
|
59
59
|
https://gitlab.com/ae-group/ae_base)
|
|
60
60
|
[](
|
|
62
|
+
https://gitlab.com/ae-group/ae_base/-/tree/release0.3.36)
|
|
63
63
|
[](
|
|
64
64
|
https://pypi.org/project/ae-base/#history)
|
|
65
65
|
|
|
66
|
-
>
|
|
66
|
+
>ae_base module 0.3.37.
|
|
67
67
|
|
|
68
68
|
[](
|
|
69
69
|
https://ae-group.gitlab.io/ae_base/coverage/index.html)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ae/base.py,sha256=Jb4C0-uyu40_vK0dQZnHHR2hTwDILzaGpS7ecp-0BQs,52008
|
|
2
|
+
ae_base-0.3.37.dist-info/LICENSE.md,sha256=3X7IwvwQFt4PqRHb7mV8qoJjQ1E-HmcGioyT4Y6-6c8,35002
|
|
3
|
+
ae_base-0.3.37.dist-info/METADATA,sha256=2wTlS7GMWnV1vuGdsuMb1z_KWr-y2KwUsLcesuWYVMw,5245
|
|
4
|
+
ae_base-0.3.37.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
5
|
+
ae_base-0.3.37.dist-info/top_level.txt,sha256=vUdgAslSmhZLXWU48fm8AG2BjVnkOWLco8rzuW-5zY0,3
|
|
6
|
+
ae_base-0.3.37.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
|
+
ae_base-0.3.37.dist-info/RECORD,,
|
ae_base-0.3.35.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
ae/base.py,sha256=Yh-2Bx0QuticTeEWgrdeYDxuEKnMGS_3wuBJW5iRnes,46681
|
|
2
|
-
ae_base-0.3.35.dist-info/LICENSE.md,sha256=5jt4IAGrDpKDQaqK_A46-FUHAOYejE5kPpxoKuOOd6o,35002
|
|
3
|
-
ae_base-0.3.35.dist-info/METADATA,sha256=Kt65tjx2jDeDCrRU7kIeSUgvXe9-8i8u0J0A9SNz7Is,5311
|
|
4
|
-
ae_base-0.3.35.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
5
|
-
ae_base-0.3.35.dist-info/top_level.txt,sha256=vUdgAslSmhZLXWU48fm8AG2BjVnkOWLco8rzuW-5zY0,3
|
|
6
|
-
ae_base-0.3.35.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
|
-
ae_base-0.3.35.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|