ae-base 0.3.36__tar.gz → 0.3.37__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.36 → ae_base-0.3.37}/LICENSE.md +1 -1
- {ae_base-0.3.36/ae_base.egg-info → ae_base-0.3.37}/PKG-INFO +7 -7
- {ae_base-0.3.36 → ae_base-0.3.37}/README.md +6 -6
- {ae_base-0.3.36 → ae_base-0.3.37}/ae/base.py +124 -11
- {ae_base-0.3.36 → ae_base-0.3.37/ae_base.egg-info}/PKG-INFO +7 -7
- {ae_base-0.3.36 → ae_base-0.3.37}/setup.py +1 -1
- {ae_base-0.3.36 → ae_base-0.3.37}/tests/test_base.py +203 -7
- {ae_base-0.3.36 → ae_base-0.3.37}/ae_base.egg-info/SOURCES.txt +0 -0
- {ae_base-0.3.36 → ae_base-0.3.37}/ae_base.egg-info/dependency_links.txt +0 -0
- {ae_base-0.3.36 → ae_base-0.3.37}/ae_base.egg-info/requires.txt +0 -0
- {ae_base-0.3.36 → ae_base-0.3.37}/ae_base.egg-info/top_level.txt +0 -0
- {ae_base-0.3.36 → ae_base-0.3.37}/ae_base.egg-info/zip-safe +0 -0
- {ae_base-0.3.36 → ae_base-0.3.37}/setup.cfg +0 -0
|
@@ -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
|
-
>ae_base module 0.3.
|
|
66
|
+
>ae_base module 0.3.37.
|
|
67
67
|
|
|
68
68
|
[](
|
|
69
69
|
https://ae-group.gitlab.io/ae_base/coverage/index.html)
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.
|
|
2
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.
|
|
3
|
-
# base 0.3.
|
|
1
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.94 -->
|
|
2
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
|
|
3
|
+
# base 0.3.37
|
|
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.36)
|
|
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.37.
|
|
14
14
|
|
|
15
15
|
[](
|
|
16
16
|
https://ae-group.gitlab.io/ae_base/coverage/index.html)
|
|
@@ -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,6 +686,45 @@ 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
729
|
""" determine the main module file path of a project package, containing the project __version__ module variable.
|
|
617
730
|
|
|
@@ -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
|
-
>ae_base module 0.3.
|
|
66
|
+
>ae_base module 0.3.37.
|
|
67
67
|
|
|
68
68
|
[](
|
|
69
69
|
https://ae-group.gitlab.io/ae_base/coverage/index.html)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
""" ae.base unit tests """
|
|
2
2
|
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
|
|
3
5
|
import pytest
|
|
4
6
|
import shutil
|
|
5
7
|
import sys
|
|
@@ -12,12 +14,38 @@ from typing import cast
|
|
|
12
14
|
|
|
13
15
|
# noinspection PyProtectedMember
|
|
14
16
|
from ae.base import (
|
|
15
|
-
BUILD_CONFIG_FILE, PY_EXT, PY_INIT, PY_MAIN, TESTS_FOLDER, UNSET,
|
|
17
|
+
BUILD_CONFIG_FILE, DOTENV_FILE_NAME, PY_EXT, PY_INIT, PY_MAIN, TESTS_FOLDER, UNSET,
|
|
16
18
|
app_name_guess, build_config_variable_values, camel_to_snake, deep_dict_update, dummy_function, duplicates, env_str,
|
|
17
|
-
force_encoding, full_stack_trace, import_module, instantiate_config_parser, in_wd,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
force_encoding, full_stack_trace, import_module, instantiate_config_parser, in_wd,
|
|
20
|
+
load_env_var_defaults, load_dotenvs, main_file_paths_parts, module_attr, module_file_path, module_name,
|
|
21
|
+
norm_line_sep, norm_name, norm_path, now_str, os_host_name, os_local_ip, _os_platform, os_user_name,
|
|
22
|
+
parse_dotenv, project_main_file, read_file, round_traditional, snake_to_camel, stack_frames, stack_var, stack_vars,
|
|
23
|
+
sys_env_dict, sys_env_text, to_ascii, write_file)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
env_var_name = 'env_var_nam1'
|
|
27
|
+
env_var_val = 'value of env var'
|
|
28
|
+
folder_name = 'fdr'
|
|
29
|
+
full_folders = (0, 1, 3)
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def os_env_test_env():
|
|
33
|
+
""" create .env files to test and backup os.environ. """
|
|
34
|
+
with tempfile.TemporaryDirectory() as tmp_path:
|
|
35
|
+
for deep in range(6):
|
|
36
|
+
file_path = os.path.join(tmp_path, *((folder_name,) * deep))
|
|
37
|
+
os.makedirs(file_path, exist_ok=True)
|
|
38
|
+
if deep in full_folders:
|
|
39
|
+
content = (os.linesep +
|
|
40
|
+
env_var_name + "='" + env_var_val + str(deep) + "'")
|
|
41
|
+
write_file(os.path.join(file_path, DOTENV_FILE_NAME), content)
|
|
42
|
+
|
|
43
|
+
old_env = os.environ
|
|
44
|
+
os.environ = old_env.copy()
|
|
45
|
+
|
|
46
|
+
yield tmp_path
|
|
47
|
+
|
|
48
|
+
os.environ = old_env
|
|
21
49
|
|
|
22
50
|
|
|
23
51
|
module_test_var = 'module_test_var_val' # used for stack_var()/try_exec() tests
|
|
@@ -281,6 +309,51 @@ class TestBaseHelpers:
|
|
|
281
309
|
assert os.getcwd() == tst_dir
|
|
282
310
|
assert os.getcwd() == old_dir
|
|
283
311
|
|
|
312
|
+
def test_load_dotenvs(self, os_env_test_env):
|
|
313
|
+
assert env_var_name not in os.environ
|
|
314
|
+
load_dotenvs()
|
|
315
|
+
assert env_var_name not in os.environ
|
|
316
|
+
|
|
317
|
+
def test_load_env_var_defaults_not_loaded(self, os_env_test_env):
|
|
318
|
+
assert env_var_name not in os.environ
|
|
319
|
+
|
|
320
|
+
load_env_var_defaults('/')
|
|
321
|
+
assert env_var_name not in os.environ
|
|
322
|
+
|
|
323
|
+
load_env_var_defaults('.')
|
|
324
|
+
assert env_var_name not in os.environ
|
|
325
|
+
|
|
326
|
+
load_env_var_defaults(os.path.join(os_env_test_env, *((folder_name, ) * 5)))
|
|
327
|
+
assert env_var_name not in os.environ
|
|
328
|
+
|
|
329
|
+
load_env_var_defaults(os.path.join(os_env_test_env, *((folder_name, ) * 6))) # invalid/too-deep path
|
|
330
|
+
assert env_var_name not in os.environ
|
|
331
|
+
|
|
332
|
+
def test_load_env_var_defaults_load_start_parent_first_no_chain(self, os_env_test_env):
|
|
333
|
+
load_env_var_defaults(os.path.join(os_env_test_env, *((folder_name, ) * 4)))
|
|
334
|
+
assert env_var_name in os.environ
|
|
335
|
+
assert os.environ[env_var_name] == env_var_val + '3'
|
|
336
|
+
|
|
337
|
+
def test_load_env_var_defaults_load_start_first_no_chain(self, os_env_test_env):
|
|
338
|
+
load_env_var_defaults(os.path.join(os_env_test_env, *((folder_name, ) * 3)))
|
|
339
|
+
assert env_var_name in os.environ
|
|
340
|
+
assert os.environ[env_var_name] == env_var_val + '3'
|
|
341
|
+
|
|
342
|
+
def test_load_env_var_defaults_load_start_parent_first_in_chain(self, os_env_test_env):
|
|
343
|
+
load_env_var_defaults(os.path.join(os_env_test_env, *((folder_name, ) * 2)))
|
|
344
|
+
assert env_var_name in os.environ
|
|
345
|
+
assert os.environ[env_var_name] == env_var_val + '1'
|
|
346
|
+
|
|
347
|
+
def test_load_env_var_defaults_load_start_no_parent_first_in_chain(self, os_env_test_env):
|
|
348
|
+
load_env_var_defaults(os.path.join(os_env_test_env, *((folder_name, ) * 1)))
|
|
349
|
+
assert env_var_name in os.environ
|
|
350
|
+
assert os.environ[env_var_name] == env_var_val + '1'
|
|
351
|
+
|
|
352
|
+
def test_load_env_var_defaults_load_start_on_second_within_chain(self, os_env_test_env):
|
|
353
|
+
load_env_var_defaults(os.path.join(os_env_test_env, *((folder_name, ) * 0)))
|
|
354
|
+
assert env_var_name in os.environ
|
|
355
|
+
assert os.environ[env_var_name] == env_var_val + '0'
|
|
356
|
+
|
|
284
357
|
def test_main_file_paths_parts(self):
|
|
285
358
|
assert isinstance(main_file_paths_parts(""), tuple)
|
|
286
359
|
assert len(main_file_paths_parts(""))
|
|
@@ -401,6 +474,129 @@ class TestBaseHelpers:
|
|
|
401
474
|
print(os_user_name())
|
|
402
475
|
assert os_user_name()
|
|
403
476
|
|
|
477
|
+
def test_parse_dotenv_error_space_prefixed_var_name(self):
|
|
478
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
479
|
+
fp.write(' var_nam="var val"')
|
|
480
|
+
fp.seek(0)
|
|
481
|
+
loaded = parse_dotenv(fp.name)
|
|
482
|
+
assert 'var_nam' not in loaded # added warning
|
|
483
|
+
|
|
484
|
+
def test_parse_dotenv_double_quoted_value(self):
|
|
485
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
486
|
+
fp.write('var_nam="var val"')
|
|
487
|
+
fp.seek(0)
|
|
488
|
+
loaded = parse_dotenv(fp.name)
|
|
489
|
+
assert 'var_nam' in loaded
|
|
490
|
+
assert loaded['var_nam'] == "var val"
|
|
491
|
+
|
|
492
|
+
def test_parse_dotenv_single_value(self):
|
|
493
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
494
|
+
fp.write("var_nam='var val'")
|
|
495
|
+
fp.seek(0)
|
|
496
|
+
loaded = parse_dotenv(fp.name)
|
|
497
|
+
assert 'var_nam' in loaded
|
|
498
|
+
assert loaded['var_nam'] == "var val"
|
|
499
|
+
|
|
500
|
+
def test_parse_dotenv_start_parent_first_in_chain(self, os_env_test_env):
|
|
501
|
+
assert env_var_name not in os.environ
|
|
502
|
+
file_path = os.path.join(os_env_test_env, folder_name, DOTENV_FILE_NAME)
|
|
503
|
+
loaded = parse_dotenv(file_path)
|
|
504
|
+
assert env_var_name in loaded
|
|
505
|
+
assert loaded[env_var_name] == env_var_val + '1'
|
|
506
|
+
|
|
507
|
+
def test_parse_dotenv_space_surrounded_value(self):
|
|
508
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
509
|
+
fp.write("var_nam = var val ")
|
|
510
|
+
fp.seek(0)
|
|
511
|
+
loaded = parse_dotenv(fp.name)
|
|
512
|
+
assert 'var_nam' in loaded
|
|
513
|
+
assert loaded['var_nam'] == "var val"
|
|
514
|
+
|
|
515
|
+
def test_parse_dotenv_unquoted_value(self):
|
|
516
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
517
|
+
fp.write("var_nam=var val")
|
|
518
|
+
fp.seek(0)
|
|
519
|
+
loaded = parse_dotenv(fp.name)
|
|
520
|
+
assert 'var_nam' in loaded
|
|
521
|
+
assert loaded['var_nam'] == "var val"
|
|
522
|
+
|
|
523
|
+
def test_parse_dotenv_var_escaped_double_quote(self):
|
|
524
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
525
|
+
fp.write('var_nam="escaped\\"val"')
|
|
526
|
+
fp.seek(0)
|
|
527
|
+
loaded = parse_dotenv(fp.name)
|
|
528
|
+
assert 'var_nam' in loaded
|
|
529
|
+
assert loaded['var_nam'] == 'escaped"val'
|
|
530
|
+
|
|
531
|
+
def test_parse_dotenv_var_empty_value(self):
|
|
532
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
533
|
+
fp.write("var_nam=")
|
|
534
|
+
fp.seek(0)
|
|
535
|
+
loaded = parse_dotenv(fp.name)
|
|
536
|
+
assert 'var_nam' in loaded
|
|
537
|
+
assert loaded['var_nam'] == ""
|
|
538
|
+
|
|
539
|
+
def test_parse_dotenv_var_expands_variables_found_in_values(self):
|
|
540
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
541
|
+
fp.write("env_var=var val\nvar_nam=$env_var")
|
|
542
|
+
fp.seek(0)
|
|
543
|
+
loaded = parse_dotenv(fp.name)
|
|
544
|
+
assert 'var_nam' in loaded
|
|
545
|
+
assert loaded['var_nam'] == "var val"
|
|
546
|
+
assert 'env_var' in loaded
|
|
547
|
+
assert loaded['env_var'] == "var val"
|
|
548
|
+
|
|
549
|
+
def test_parse_dotenv_var_expands_variable_wrapped_in_brackets(self):
|
|
550
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
551
|
+
fp.write("env_var=var val\n\n\nvar_nam=${env_var} tst")
|
|
552
|
+
fp.seek(0)
|
|
553
|
+
loaded = parse_dotenv(fp.name)
|
|
554
|
+
assert 'var_nam' in loaded
|
|
555
|
+
assert loaded['var_nam'] == "var val tst"
|
|
556
|
+
assert 'env_var' in loaded
|
|
557
|
+
assert loaded['env_var'] == "var val"
|
|
558
|
+
|
|
559
|
+
def test_parse_dotenv_var_expands_undefined_variable_to_empty_string(self):
|
|
560
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
561
|
+
fp.write("var_nam=$env_var")
|
|
562
|
+
fp.seek(0)
|
|
563
|
+
loaded = parse_dotenv(fp.name)
|
|
564
|
+
assert 'env_var' not in loaded
|
|
565
|
+
assert 'var_nam' in loaded
|
|
566
|
+
assert loaded['var_nam'] == ""
|
|
567
|
+
|
|
568
|
+
def test_parse_dotenv_var_expands_in_double_quoted_values(self):
|
|
569
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
570
|
+
fp.write("env_var=tst\nvar_nam=\"var val $env_var\"")
|
|
571
|
+
fp.seek(0)
|
|
572
|
+
loaded = parse_dotenv(fp.name)
|
|
573
|
+
assert 'var_nam' in loaded
|
|
574
|
+
assert loaded['var_nam'] == "var val tst"
|
|
575
|
+
|
|
576
|
+
def test_parse_dotenv_var_not_expands_in_single_quoted_values(self):
|
|
577
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
578
|
+
fp.write("var_nam='var val $env_var'")
|
|
579
|
+
fp.seek(0)
|
|
580
|
+
loaded = parse_dotenv(fp.name)
|
|
581
|
+
assert 'var_nam' in loaded
|
|
582
|
+
assert loaded['var_nam'] == "var val $env_var"
|
|
583
|
+
|
|
584
|
+
def test_parse_dotenv_var_not_expands_escaped_variables(self):
|
|
585
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
586
|
+
fp.write("var_nam=var val \\$env_var \${env_var}")
|
|
587
|
+
fp.seek(0)
|
|
588
|
+
loaded = parse_dotenv(fp.name)
|
|
589
|
+
assert 'var_nam' in loaded
|
|
590
|
+
assert loaded['var_nam'] == "var val $env_var ${env_var}"
|
|
591
|
+
|
|
592
|
+
def test_parse_dotenv_var_export_keyword(self):
|
|
593
|
+
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
|
594
|
+
fp.write("export var_nam=var val")
|
|
595
|
+
fp.seek(0)
|
|
596
|
+
loaded = parse_dotenv(fp.name)
|
|
597
|
+
assert 'var_nam' in loaded
|
|
598
|
+
assert loaded['var_nam'] == "var val"
|
|
599
|
+
|
|
404
600
|
def test_project_main_file(self):
|
|
405
601
|
assert project_main_file("not_existing_xy.tst") == ""
|
|
406
602
|
|
|
@@ -619,8 +815,8 @@ class TestModuleHelpers:
|
|
|
619
815
|
assert module_attr(mod_name, attr_name=att_name) is None
|
|
620
816
|
|
|
621
817
|
def test_module_file_path(self):
|
|
622
|
-
assert module_file_path()
|
|
623
|
-
assert module_file_path(lambda: 0)
|
|
818
|
+
assert module_file_path() == __file__
|
|
819
|
+
assert module_file_path(lambda: 0) == __file__
|
|
624
820
|
|
|
625
821
|
def test_module_name(self):
|
|
626
822
|
assert module_name() == 'test_base'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|