ae-base 0.3.45__tar.gz → 0.3.46__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_project V0.3.31 -->
1
+ <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_project V0.3.32 -->
2
2
  ### GNU GENERAL PUBLIC LICENSE
3
3
 
4
4
  Version 3, 29 June 2007
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ae_base
3
- Version: 0.3.45
3
+ Version: 0.3.46
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
7
7
  Author-email: aecker2@gmail.com
8
8
  License: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
9
+ Project-URL: Bug Tracker, https://gitlab.com/ae-group/ae_base/-/issues
9
10
  Project-URL: Documentation, https://ae.readthedocs.io/en/latest/_autosummary/ae.base.html
10
11
  Project-URL: Repository, https://gitlab.com/ae-group/ae_base
11
12
  Project-URL: Source, https://ae.readthedocs.io/en/latest/_modules/ae/base.html
@@ -53,19 +54,19 @@ Requires-Dist: types-setuptools; extra == "tests"
53
54
  Requires-Dist: wheel; extra == "tests"
54
55
  Requires-Dist: twine; extra == "tests"
55
56
 
56
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.94 -->
57
+ <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.95 -->
57
58
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
58
- # base 0.3.45
59
+ # base 0.3.46
59
60
 
60
61
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_base/develop?logo=python)](
61
62
  https://gitlab.com/ae-group/ae_base)
62
63
  [![LatestPyPIrelease](
63
- https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.44?logo=python)](
64
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.44)
64
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.45?logo=python)](
65
+ https://gitlab.com/ae-group/ae_base/-/tree/release0.3.45)
65
66
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_base)](
66
67
  https://pypi.org/project/ae-base/#history)
67
68
 
68
- >ae_base module 0.3.45.
69
+ >ae_base module 0.3.46.
69
70
 
70
71
  [![Coverage](https://ae-group.gitlab.io/ae_base/coverage.svg)](
71
72
  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.94 -->
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.45
3
+ # base 0.3.46
4
4
 
5
5
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_base/develop?logo=python)](
6
6
  https://gitlab.com/ae-group/ae_base)
7
7
  [![LatestPyPIrelease](
8
- https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.44?logo=python)](
9
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.44)
8
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.45?logo=python)](
9
+ https://gitlab.com/ae-group/ae_base/-/tree/release0.3.45)
10
10
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_base)](
11
11
  https://pypi.org/project/ae-base/#history)
12
12
 
13
- >ae_base module 0.3.45.
13
+ >ae_base module 0.3.46.
14
14
 
15
15
  [![Coverage](https://ae-group.gitlab.io/ae_base/coverage.svg)](
16
16
  https://ae-group.gitlab.io/ae_base/coverage/index.html)
@@ -9,10 +9,10 @@ functions, useful classes and context managers.
9
9
  base constants
10
10
  --------------
11
11
 
12
- ISO format strings for `date` and `datetime` values are provided by the constants :data:`DATE_ISO` and
12
+ ISO format strings for ``date`` and ``datetime`` values are provided by the constants :data:`DATE_ISO` and
13
13
  :data:`DATE_TIME_ISO`.
14
14
 
15
- the :data:`UNSET` constant is useful in cases where `None` is a valid data value and another special value is needed
15
+ the :data:`UNSET` constant is useful in cases where ``None`` is a valid data value and another special value is needed
16
16
  to specify that e.g. an argument or attribute has no (valid) value or did not get specified/passed.
17
17
 
18
18
  default values to compile file and folder names for a package or an app project are provided by the constants:
@@ -20,8 +20,8 @@ default values to compile file and folder names for a package or an app project
20
20
  :data:`PACKAGE_INCLUDE_FILES_PREFIX`, :data:`PY_EXT`, :data:`PY_INIT`, :data:`PY_MAIN`, :data:`CFG_EXT`
21
21
  and :data:`INI_EXT`.
22
22
 
23
- the constants :data:`PACKAGE_NAME`, :data:`PACKAGE_DOMAIN` and :data:`PERMISSIONS` are mainly used for apps running
24
- on mobile devices. to avoid redundancies, these values get loaded from the
23
+ the constants :data:`PACKAGE_NAME`, :data:`PACKAGE_DOMAIN` and :data:`PERMISSIONS` are mainly
24
+ used for apps running on mobile devices. to avoid redundancies, these values get loaded from the
25
25
  :data:`build config file <BUILD_CONFIG_FILE>` - if it exists in the current working directory.
26
26
 
27
27
  with the help of the format string constant :data:`NOW_STR_FORMAT` and the function :func:`now_str` you can create a
@@ -104,8 +104,8 @@ links to other android code and service examples and documentation:
104
104
  * `https://github.com/Android-for-Python/Android-for-Python-Users`__
105
105
  * `https://github.com/Android-for-Python/INDEX-of-Examples`__
106
106
 
107
- big thanks to `Robert Flatt <https://github.com/RobertFlatt>`_ for his investigations, findings and documentations to
108
- code and build Kivy apps for the Android OS, and to `Gabriel Pettier <https://github.com/tshirtman>`_ for his service
107
+ big thanks to `Robert Flatt <https://github.com/RobertFlatt>`__ for his investigations, findings and documentations to
108
+ code and build Kivy apps for the Android OS, and to `Gabriel Pettier <https://github.com/tshirtman>`__ for his service
109
109
  osc example.
110
110
 
111
111
 
@@ -113,9 +113,9 @@ types, classes and mixins
113
113
  -------------------------
114
114
 
115
115
  the :class:`UnsetType` class can be used e.g. for the declaration of optional function and method parameters,
116
- allowing also `None` is an accepted argument value.
116
+ allowing also ``None`` is an accepted argument value.
117
117
 
118
- to extend any class with an intelligent error message property, add the mixin :class:`ErrorMsgMixin` to it.
118
+ to extend any class with an intelligent error message handling, add the mixin :class:`ErrorMsgMixin` to it.
119
119
 
120
120
 
121
121
  generic context manager
@@ -173,7 +173,7 @@ from types import ModuleType
173
173
  from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast
174
174
 
175
175
 
176
- __version__ = '0.3.45'
176
+ __version__ = '0.3.46'
177
177
 
178
178
 
179
179
  os_path_abspath = os.path.abspath
@@ -265,7 +265,7 @@ class UnsetType:
265
265
  return 0
266
266
 
267
267
 
268
- UNSET = UnsetType() #: pseudo value used for attributes/arguments if `None` is needed as a valid value
268
+ UNSET = UnsetType() #: pseudo value used for attributes/arguments if ``None`` is needed as a valid value
269
269
 
270
270
 
271
271
  def app_name_guess() -> str:
@@ -405,10 +405,11 @@ def defuse(value: str) -> str:
405
405
 
406
406
  the ASCII character range 0..31 gets converted to the Unicode range U+2400 + ord(char): 0==U+2400 ... 31==U+241F.
407
407
 
408
- in *nix only / and \0 are not allowed characters in file names.
408
+ in most unix variants only the slash and the ASCII 0 characters are not allowed in file names.
409
409
 
410
- in MS Windows are not allowed: ASCII 0...31): / | \\ : * ? ” % < > ( ). some blogs recommend to also not allow
410
+ in MS Windows are not allowed: ASCII 0..31 / | \\ : * ? ” % < > ( ). some blogs recommend to also not allow
411
411
  (convert) the characters # and '.
412
+
412
413
  only old POSIX seems to be even more restricted (only allowing alphanumeric characters plus . - and _).
413
414
 
414
415
  more on allowed characters in file names in the answers of RedGrittyBrick on https://superuser.com/questions/358855
@@ -416,7 +417,7 @@ def defuse(value: str) -> str:
416
417
 
417
418
  file name length is not restricted/shortened by this function, although the maximum is 255 characters on most OSs.
418
419
 
419
- .. hint:: use :func:`dedefuse` to convert the defused string back to the corresponding URI/file-path.
420
+ .. hint:: use the :func:`dedefuse` function to convert the defused string back to the corresponding URI/file-path.
420
421
 
421
422
  """
422
423
  defused = ""
@@ -663,7 +664,7 @@ def module_file_path(local_object: Optional[Callable] = None) -> str:
663
664
  def module_name(*skip_modules: str, depth: int = 0) -> Optional[str]:
664
665
  """ find the first module in the call stack that is *not* in :paramref:`~module_name.skip_modules`.
665
666
 
666
- :param skip_modules: module names to skip (def=this ae.core module).
667
+ :param skip_modules: module names to skip (def=this and other core modules, see :data:`SKIPPED_MODULES`).
667
668
  :param depth: the calling level from which on to search. the default value 0 refers the frame and
668
669
  the module of the caller of this function.
669
670
  pass 1 or an even higher value if you want to get the module name of a function/method
@@ -851,12 +852,11 @@ def parse_dotenv(file_path: str) -> Dict[str, str]:
851
852
  delimiter = None
852
853
  if delimiter != "'":
853
854
  for parts in _env_variable.findall(var_val):
854
- if parts[0] == '\\':
855
- replace = "".join(parts[1:-1]) # don't replace escaped variables
856
- else:
857
- # substitute variables in a value, replace it with the value from the environment
858
- replace = env_vars.get(parts[-1], os.environ.get(parts[-1], ""))
859
- var_val = var_val.replace("".join(parts[0:-1]), replace)
855
+ # substitute env variables in a value with its value, if declared and not escaped
856
+ if parts[0] == '\\' or (replace := env_vars.get(parts[-1], os.environ.get(parts[-1], UNSET))) is UNSET:
857
+ replace = "".join(parts[1:-1]) # don't replace escaped/undeclared vars to prevent value cut at '$'
858
+
859
+ var_val = var_val.replace("".join(parts[0:-1]), cast(str, replace))
860
860
 
861
861
  env_vars[var_nam] = var_val
862
862
 
@@ -901,8 +901,8 @@ def read_file(file_path: str, extra_mode: str = "", encoding: Optional[str] = No
901
901
  read the content of a binary file returned as bytes array. in binary mode the argument
902
902
  passed in :paramref:`~read_file.error_handling` will be ignored.
903
903
  :param encoding: encoding used to load and convert/interpret the file content.
904
- :param error_handling: for files opened in text mode pass `'strict'` or `None` to return `None` (instead of an
905
- empty string) for the cases where either a decoding `ValueError` exception or any
904
+ :param error_handling: for files opened in text mode pass `'strict'` or ``None`` to return ``None`` (instead of
905
+ an empty string) for the cases where either a decoding `ValueError` exception or any
906
906
  `OSError`, `FileNotFoundError` or `PermissionError` exception got raised.
907
907
  the default value `'ignore'` will ignore any decoding errors (missing some characters)
908
908
  and will return an empty string on any file/os exception. this parameter will be ignored
@@ -1118,14 +1118,14 @@ def write_file(file_path: str, content: Union[str, bytes],
1118
1118
  :raises PermissionError: if current OS user account lacks permissions to read the file content.
1119
1119
  :raises ValueError: on decoding errors.
1120
1120
 
1121
- to extend this function for Android 14+ see https://github.com/beeware/toga/pull/1158#issuecomment-2254564657
1122
- and https://gist.github.com/neonankiti/05922cf0a44108a2e2732671ed9ef386
1121
+ to extend this function for Android 14+ see `<https://github.com/beeware/toga/pull/1158#issuecomment-2254564657>`__
1122
+ and `<https://gist.github.com/neonankiti/05922cf0a44108a2e2732671ed9ef386>`__
1123
1123
  Yes, to use ACTION_CREATE_DOCUMENT, you don't supply a URI in the intent. You wait for the intent result, and that
1124
1124
  will contain a URI which you can write to.
1125
- See #1158 (comment - https://github.com/beeware/toga/pull/1158#issuecomment-2254564657) for a link to a Java
1126
- example, and #1158 (comment - https://github.com/beeware/toga/pull/1158#issuecomment-1446196973) for how to wait
1127
- for an intent result.
1128
- Related german docs: https://developer.android.com/training/data-storage/shared/media?hl=de
1125
+ See #1158 (comment - `<https://github.com/beeware/toga/pull/1158#issuecomment-2254564657>`__) for a link to a Java
1126
+ example, and #1158 (comment - `<https://github.com/beeware/toga/pull/1158#issuecomment-1446196973>`__) for how to
1127
+ wait for an intent result.
1128
+ Related german docs: `<https://developer.android.com/training/data-storage/shared/media?hl=de>`__
1129
1129
  """
1130
1130
  if make_dirs and (dir_path := os_path_dirname(file_path)):
1131
1131
  os.makedirs(dir_path, exist_ok=True)
@@ -1141,9 +1141,33 @@ def write_file(file_path: str, content: Union[str, bytes],
1141
1141
 
1142
1142
 
1143
1143
  class ErrorMsgMixin:
1144
- """ mixin class providing error message """
1144
+ """ mixin class providing sophisticated error message handling. """
1145
1145
  _err_msg: str = ""
1146
1146
 
1147
+ cae = None
1148
+ po = print
1149
+ dpo = print
1150
+ vpo = print
1151
+
1152
+ def __init__(self):
1153
+ try:
1154
+ from ae.core import main_app_instance # type: ignore
1155
+
1156
+ self.cae = cae = main_app_instance()
1157
+ assert cae is not None, f"{self.__class__.__name__}.__init__() called too early; main app instance not"
1158
+
1159
+ self.po = cae.po
1160
+ self.dpo = cae.dpo
1161
+ self.vpo = cae.vpo
1162
+
1163
+ except (ImportError, AssertionError, Exception) as exc:
1164
+ print(f"{self.__class__.__name__}.__init__() raised {exc}; using print() instead of main app error loggers")
1165
+
1166
+ # self.cae = None
1167
+ # self.po = print
1168
+ # self.dpo = print
1169
+ # self.vpo = print
1170
+
1147
1171
  @property
1148
1172
  def error_message(self) -> str:
1149
1173
  """ error message string if an error occurred or an empty string if not.
@@ -1157,14 +1181,19 @@ class ErrorMsgMixin:
1157
1181
  @error_message.setter
1158
1182
  def error_message(self, next_err_msg: str):
1159
1183
  if next_err_msg:
1184
+ if "WARNING" in next_err_msg.upper():
1185
+ self.vpo(f" .::. {next_err_msg}")
1186
+ else:
1187
+ self.dpo(f" .::. {next_err_msg}")
1160
1188
  self._err_msg += ("\n\n" if self._err_msg else "") + next_err_msg
1161
1189
  else:
1162
1190
  self._err_msg = ""
1163
1191
 
1164
1192
 
1165
- PACKAGE_NAME = stack_var('__name__') or 'unspecified_package'
1166
- PACKAGE_DOMAIN = 'org.test'
1167
- PERMISSIONS = "INTERNET, VIBRATE, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE"
1193
+ # package and permissions handling defaults for all other platforms and frameworks
1194
+ PACKAGE_NAME = stack_var('__name__') or 'unspecified_package' #: package name default
1195
+ PACKAGE_DOMAIN = 'org.test' #: package domain default
1196
+ PERMISSIONS = "INTERNET, VIBRATE, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE" #: permissions default
1168
1197
  if os_path_isfile(BUILD_CONFIG_FILE): # pragma: no cover
1169
1198
  PACKAGE_NAME, PACKAGE_DOMAIN, PERMISSIONS = build_config_variable_values(
1170
1199
  ('package.name', PACKAGE_NAME),
@@ -1180,8 +1209,8 @@ elif os_platform == 'android': # pragma: no cov
1180
1209
 
1181
1210
  if os_platform == 'android': # pragma: no cover
1182
1211
  # monkey patch the :func:`shutil.copystat` and :func:`shutil.copymode` helper functions, which are crashing on
1183
- # 'android' (see # https://bugs.python.org/issue28141 and https://bugs.python.org/issue32073). these functions are
1184
- # used by shutil.copy2/copy/copytree/move to copy OS-specific file attributes.
1212
+ # 'android' (see # `<https://bugs.python.org/issue28141>`__ and `<https://bugs.python.org/issue32073>`__). these
1213
+ # functions are used by shutil.copy2/copy/copytree/move to copy OS-specific file attributes.
1185
1214
  # although shutil.copytree() and shutil.move() are copying/moving the files correctly when the copy_function
1186
1215
  # arg is set to :func:`shutil.copyfile`, they will finally also crash afterward when they try to set the attributes
1187
1216
  # on the destination root directory.
@@ -1213,8 +1242,8 @@ if os_platform == 'android': # pragma: no cov
1213
1242
  :param service_arg: string value to be assigned to environment variable PYTHON_SERVICE_ARGUMENT on start.
1214
1243
  :return: service instance.
1215
1244
 
1216
- see https://github.com/tshirtman/kivy_service_osc/blob/master/src/main.py
1217
- and https://python-for-android.readthedocs.io/en/latest/services/#arbitrary-scripts-services
1245
+ see `<https://github.com/tshirtman/kivy_service_osc/blob/master/src/main.py>`__
1246
+ and `<https://python-for-android.readthedocs.io/en/latest/services/#arbitrary-scripts-services>`__
1218
1247
  """
1219
1248
  service_instance = autoclass(f"{PACKAGE_DOMAIN}.{PACKAGE_NAME}.Service{PACKAGE_NAME.capitalize()}")
1220
1249
  activity = autoclass('org.kivy.android.PythonActivity').mActivity
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ae_base
3
- Version: 0.3.45
3
+ Version: 0.3.46
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
7
7
  Author-email: aecker2@gmail.com
8
8
  License: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
9
+ Project-URL: Bug Tracker, https://gitlab.com/ae-group/ae_base/-/issues
9
10
  Project-URL: Documentation, https://ae.readthedocs.io/en/latest/_autosummary/ae.base.html
10
11
  Project-URL: Repository, https://gitlab.com/ae-group/ae_base
11
12
  Project-URL: Source, https://ae.readthedocs.io/en/latest/_modules/ae/base.html
@@ -53,19 +54,19 @@ Requires-Dist: types-setuptools; extra == "tests"
53
54
  Requires-Dist: wheel; extra == "tests"
54
55
  Requires-Dist: twine; extra == "tests"
55
56
 
56
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.94 -->
57
+ <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.95 -->
57
58
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
58
- # base 0.3.45
59
+ # base 0.3.46
59
60
 
60
61
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_base/develop?logo=python)](
61
62
  https://gitlab.com/ae-group/ae_base)
62
63
  [![LatestPyPIrelease](
63
- https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.44?logo=python)](
64
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.44)
64
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.45?logo=python)](
65
+ https://gitlab.com/ae-group/ae_base/-/tree/release0.3.45)
65
66
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_base)](
66
67
  https://pypi.org/project/ae-base/#history)
67
68
 
68
- >ae_base module 0.3.45.
69
+ >ae_base module 0.3.46.
69
70
 
70
71
  [![Coverage](https://ae-group.gitlab.io/ae_base/coverage.svg)](
71
72
  https://ae-group.gitlab.io/ae_base/coverage/index.html)
@@ -1,4 +1,4 @@
1
- # THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_project V0.3.31
1
+ # THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_project V0.3.32
2
2
  """ setup this project with setuptools and aedev.setup_project. """
3
3
  import pprint
4
4
  import sys
@@ -3,6 +3,7 @@ import datetime
3
3
  import os
4
4
  import string
5
5
  import tempfile
6
+ from unittest.mock import patch
6
7
 
7
8
  import pytest
8
9
  import shutil
@@ -71,7 +72,43 @@ def test_unset_null_length():
71
72
 
72
73
  class TestErrorMsgMixin:
73
74
  def test_instantiation(self):
74
- assert ErrorMsgMixin()
75
+ ins = ErrorMsgMixin()
76
+ assert ins
77
+ assert ins.cae is None # in test env is no console/gui app available
78
+ assert ins.po is ins.dpo is ins.vpo is print
79
+
80
+ with patch('ae.core.main_app_instance', lambda : None):
81
+ ins = ErrorMsgMixin()
82
+ assert ins
83
+ assert ins.cae is None
84
+ assert ins.po is ins.dpo is ins.vpo is print
85
+
86
+ class _AppMock(ErrorMsgMixin):
87
+ cae = None
88
+
89
+ def po(self):
90
+ """ po() mock """
91
+ return "po"
92
+
93
+ def dpo(self):
94
+ """ dpo() mock """
95
+ return "dpo"
96
+
97
+ def vpo(self):
98
+ """ vpo() mock """
99
+ return "vpo"
100
+
101
+ app_ins = _AppMock()
102
+
103
+ with patch('ae.core.main_app_instance', lambda : app_ins):
104
+ ins = ErrorMsgMixin()
105
+ assert ins.cae is app_ins
106
+ assert ins.po is not print
107
+ assert ins.po() == "po"
108
+ assert ins.dpo is not print
109
+ assert ins.dpo() == "dpo"
110
+ assert ins.vpo is not print
111
+ assert ins.vpo() == "vpo"
75
112
 
76
113
  def test_error_message_property(self):
77
114
  ins = ErrorMsgMixin()
@@ -89,6 +126,14 @@ class TestErrorMsgMixin:
89
126
  ins.error_message = ""
90
127
  assert ins.error_message == ""
91
128
 
129
+ def test_error_message_property_for_warnings(self):
130
+ ins = ErrorMsgMixin()
131
+
132
+ err_msg = "error message with the word warning"
133
+ ins.error_message = err_msg
134
+ ins.error_message = "another message"
135
+ assert err_msg in ins.error_message
136
+
92
137
 
93
138
  class TestBaseHelpers:
94
139
  def test_app_name_guess(self):
@@ -542,12 +587,15 @@ class TestBaseHelpers:
542
587
  print(os_user_name())
543
588
  assert os_user_name()
544
589
 
545
- def test_parse_dotenv_error_space_prefixed_var_name(self):
590
+ def test_parse_dotenv_dollar_char_does_not_cutoff_value(self):
546
591
  with tempfile.NamedTemporaryFile(mode="w") as fp:
547
- fp.write(' var_nam="var val"')
592
+ fp.write('declaredVar = DeclaredValue\n')
593
+ fp.write('replacedVar = beforeTheDollar$declaredVar\n')
594
+ fp.write('uncutVar = beforeTheDollar$afterTheDollar\n')
548
595
  fp.seek(0)
549
596
  loaded = parse_dotenv(fp.name)
550
- assert 'var_nam' not in loaded # added warning
597
+ assert loaded['replacedVar'] == "beforeTheDollarDeclaredValue"
598
+ assert loaded['uncutVar'] == "beforeTheDollar$afterTheDollar"
551
599
 
552
600
  def test_parse_dotenv_double_quoted_value(self):
553
601
  with tempfile.NamedTemporaryFile(mode="w") as fp:
@@ -557,6 +605,13 @@ class TestBaseHelpers:
557
605
  assert 'var_nam' in loaded
558
606
  assert loaded['var_nam'] == "var val"
559
607
 
608
+ def test_parse_dotenv_error_space_prefixed_var_name(self):
609
+ with tempfile.NamedTemporaryFile(mode="w") as fp:
610
+ fp.write(' var_nam="var val"')
611
+ fp.seek(0)
612
+ loaded = parse_dotenv(fp.name)
613
+ assert 'var_nam' not in loaded # added warning
614
+
560
615
  def test_parse_dotenv_single_value(self):
561
616
  with tempfile.NamedTemporaryFile(mode="w") as fp:
562
617
  fp.write("var_nam='var val'")
@@ -624,14 +679,14 @@ class TestBaseHelpers:
624
679
  assert 'env_var' in loaded
625
680
  assert loaded['env_var'] == "var val"
626
681
 
627
- def test_parse_dotenv_var_expands_undefined_variable_to_empty_string(self):
682
+ def test_parse_dotenv_var_expands_not_an_undefined_variable_to_empty_string(self):
628
683
  with tempfile.NamedTemporaryFile(mode="w") as fp:
629
684
  fp.write("var_nam=$env_var")
630
685
  fp.seek(0)
631
686
  loaded = parse_dotenv(fp.name)
632
687
  assert 'env_var' not in loaded
633
688
  assert 'var_nam' in loaded
634
- assert loaded['var_nam'] == ""
689
+ assert loaded['var_nam'] == "$env_var"
635
690
 
636
691
  def test_parse_dotenv_var_expands_in_double_quoted_values(self):
637
692
  with tempfile.NamedTemporaryFile(mode="w") as fp:
File without changes