ae-base 0.3.43__tar.gz → 0.3.45__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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ae_base
3
- Version: 0.3.43
3
+ Version: 0.3.45
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
@@ -55,17 +55,17 @@ Requires-Dist: twine; extra == "tests"
55
55
 
56
56
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.94 -->
57
57
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
58
- # base 0.3.43
58
+ # base 0.3.45
59
59
 
60
60
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_base/develop?logo=python)](
61
61
  https://gitlab.com/ae-group/ae_base)
62
62
  [![LatestPyPIrelease](
63
- https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.42?logo=python)](
64
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.42)
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)
65
65
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_base)](
66
66
  https://pypi.org/project/ae-base/#history)
67
67
 
68
- >ae_base module 0.3.43.
68
+ >ae_base module 0.3.45.
69
69
 
70
70
  [![Coverage](https://ae-group.gitlab.io/ae_base/coverage.svg)](
71
71
  https://ae-group.gitlab.io/ae_base/coverage/index.html)
@@ -1,16 +1,16 @@
1
1
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.94 -->
2
2
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
3
- # base 0.3.43
3
+ # base 0.3.45
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.42?logo=python)](
9
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.42)
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)
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.43.
13
+ >ae_base module 0.3.45.
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)
@@ -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.43'
176
+ __version__ = '0.3.45'
177
177
 
178
178
 
179
179
  os_path_abspath = os.path.abspath
@@ -353,7 +353,6 @@ ASCII_UNICODE = (
353
353
  (')', '⟯'), # U+27EF: MATHEMATICAL RIGHT FLATTENED PARENTHESIS
354
354
  ('[', '⟦'), # U+27E6: MATHEMATICAL LEFT WHITE SQUARE BRACKET
355
355
  (']', '⟧'), # U+27E7: MATHEMATICAL RIGHT WHITE SQUARE BRACKET
356
- ('_', '𛲖'), # U+1BC96: Duployan Affix Low Line; '_' U+FF3F Fullwidth Low Line
357
356
  ('#', '﹟'), # U+FE5F: Small Number Sign
358
357
  (';', '﹔'), # U+FE54: Small Semicolon
359
358
  ('@', '﹫'), # U+FE6B: Small Commercial At
@@ -364,10 +363,15 @@ ASCII_UNICODE = (
364
363
  ('%', '﹪'), # U+FE6A: Small Percent Sign
365
364
  ('^', '^'), # U+FF3E: Fullwidth Circumflex Accent
366
365
  (',', '﹐'), # U+FE50: Small Comma
367
- (' ', ' '), # U+3000: Ideographic Space; ' ' U+200A Hair Space; ' ' U+2007 Figure Space;
368
- # ' ' U+2009 Thin; ' ' U+2003 Em Space; '' U+2002 En Space; '' U+2008 Punctuation Space
369
- # ' ' U+00A0: No-Break Space (NBSP); '' U+202F: Narrow No-Break Space (NNBSP)
366
+ (' ', ''), # U+2423: Open Box; more see underneath and https://unicode-explorer.com/articles/space-characters:
367
+ # ' ' U+00A0: No-Break Space (NBSP); '?¿?' U+1680 Ogham Space Mark; ' ' U+2000 En Quad;
368
+ # ' ' U+2001 Em Quad; ' ' U+2002 En Space; '' U+2003 Em Space; ' ' U+2004 Three-Per-Em
369
+ # ' ' U+2005 Four-Per-Em; ' ' U+2006 Six-Per-Em; ' ' U+2007 Figure Space;
370
+ # ' ' U+2008 Punctuation Space; ' ' U+2009 Thin; ' ' U+200A Hair Space;
371
+ # ' ' U+202F: Narrow No-Break Space (NNBSP); ' ' U+205F Medium Mathematical Space;
372
+ # '␠' U+2420 symbol for space; '␣' U+2423 Open Box; ' ' U+3000: Ideographic Space
370
373
  (chr(127), '␡'), # U+2421: DELETE SYMBOL
374
+ # ('_', '𛲖'), # U+1BC96: Duployan Affix Low Line; '_' U+FF3F Fullwidth Low Line
371
375
  )
372
376
  """ transformation table of special ASCII to Unicode alternative character,
373
377
  see https://www.compart.com/en/unicode/category/Po and https://xahlee.info/comp/unicode_naming_slash.html (http!) """
@@ -1092,7 +1096,8 @@ def utc_datetime() -> datetime.datetime:
1092
1096
  return datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
1093
1097
 
1094
1098
 
1095
- def write_file(file_path: str, content: Union[str, bytes], extra_mode: str = "", encoding: Optional[str] = None):
1099
+ def write_file(file_path: str, content: Union[str, bytes],
1100
+ extra_mode: str = "", encoding: Optional[str] = None, make_dirs: bool = False):
1096
1101
  """ (over)write the file specified by :paramref:`~write_file.file_path` with text or binary/bytes content.
1097
1102
 
1098
1103
  :param file_path: file path/name to write the passed content into (overwriting any previous content!).
@@ -1105,6 +1110,8 @@ def write_file(file_path: str, content: Union[str, bytes], extra_mode: str = "",
1105
1110
  be automatically added to the `mode` argument of :func:`open` (if not already specified
1106
1111
  in this argument).
1107
1112
  :param encoding: encoding used to write/convert/interpret the file content to write.
1113
+ :param make_dirs: pass True to automatically create not existing folders specified in
1114
+ :paramref:`~write_file.file_path`.
1108
1115
  :raises FileExistsError: if file exists already and is write-protected.
1109
1116
  :raises FileNotFoundError: if parts of the file path do not exist.
1110
1117
  :raises OSError: if :paramref:`~write_file.file_path` is misspelled or contains invalid characters.
@@ -1120,6 +1127,9 @@ def write_file(file_path: str, content: Union[str, bytes], extra_mode: str = "",
1120
1127
  for an intent result.
1121
1128
  Related german docs: https://developer.android.com/training/data-storage/shared/media?hl=de
1122
1129
  """
1130
+ if make_dirs and (dir_path := os_path_dirname(file_path)):
1131
+ os.makedirs(dir_path, exist_ok=True)
1132
+
1123
1133
  if isinstance(content, bytes) and 'b' not in extra_mode:
1124
1134
  extra_mode += 'b'
1125
1135
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ae_base
3
- Version: 0.3.43
3
+ Version: 0.3.45
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
@@ -55,17 +55,17 @@ Requires-Dist: twine; extra == "tests"
55
55
 
56
56
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.94 -->
57
57
  <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
58
- # base 0.3.43
58
+ # base 0.3.45
59
59
 
60
60
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_base/develop?logo=python)](
61
61
  https://gitlab.com/ae-group/ae_base)
62
62
  [![LatestPyPIrelease](
63
- https://img.shields.io/gitlab/pipeline/ae-group/ae_base/release0.3.42?logo=python)](
64
- https://gitlab.com/ae-group/ae_base/-/tree/release0.3.42)
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)
65
65
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_base)](
66
66
  https://pypi.org/project/ae-base/#history)
67
67
 
68
- >ae_base module 0.3.43.
68
+ >ae_base module 0.3.45.
69
69
 
70
70
  [![Coverage](https://ae-group.gitlab.io/ae_base/coverage.svg)](
71
71
  https://ae-group.gitlab.io/ae_base/coverage/index.html)
@@ -1,6 +1,7 @@
1
1
  """ ae.base unit tests """
2
2
  import datetime
3
3
  import os
4
+ import string
4
5
  import tempfile
5
6
 
6
7
  import pytest
@@ -27,8 +28,9 @@ from ae.base import (
27
28
 
28
29
 
29
30
  tst_uri1 = "schema://user:pwd@domain/path_root/path_sub\\path+file% Üml?ä|ït.path_ext*\"<>|*'()[]{}#^;&=$,~" + chr(127)
30
- tst_fna1 = "schema⫻user﹕pwd﹫domain⁄path𛲖rootpath𛲖sub﹨path﹢file﹪ Üml﹖ä।ït.path𛲖ext﹡"⟨⟩।﹡‘⟮⟯⟦⟧{}﹟^﹔﹠﹦﹩﹐~␡"
31
- tst_fna2 = "test control chars" + "".join(chr(_) for _ in range(1, 32))
31
+ tst_fna1 = "schema⫻user﹕pwd﹫domain⁄path_rootpath_sub﹨path﹢file﹪␣Üml﹖ä।ït.path_ext﹡"⟨⟩।﹡‘⟮⟯⟦⟧{}﹟^﹔﹠﹦﹩﹐~␡"
32
+ tst_uri2 = "test control chars" + "".join(chr(_) for _ in range(1, 32))
33
+ tst_fna2 = "test␣control␣chars␁␂␃␄␅␆␇␈␉␊␋␌␍␎␏␐␑␒␓␔␕␖␗␘␙␚␛␜␝␞␟"
32
34
 
33
35
  env_var_name = 'env_var_nam1'
34
36
  env_var_val = 'value of env var'
@@ -176,13 +178,17 @@ class TestBaseHelpers:
176
178
 
177
179
  def test_dedefuse_file_name(self):
178
180
  assert dedefuse(tst_fna1) == tst_uri1
179
- assert defuse(dedefuse(tst_fna1)) == tst_fna1
181
+ assert dedefuse(tst_fna2) == tst_uri2
180
182
 
181
- assert dedefuse(defuse(tst_fna2)) == tst_fna2
183
+ assert dedefuse(defuse(tst_uri1)) == tst_uri1
184
+ assert dedefuse(defuse(tst_uri2)) == tst_uri2
182
185
 
183
186
  def test_defuse_file_name(self):
184
187
  assert defuse(tst_uri1) == tst_fna1
185
- assert dedefuse(defuse(tst_uri1)) == tst_uri1
188
+ assert defuse(tst_uri2) == tst_fna2
189
+
190
+ assert defuse(dedefuse(tst_fna1)) == tst_fna1
191
+ assert defuse(dedefuse(tst_fna2)) == tst_fna2
186
192
 
187
193
  def test_defuse_os_file_name(self):
188
194
  try:
@@ -196,10 +202,18 @@ class TestBaseHelpers:
196
202
  if os.path.exists(tst_fna2):
197
203
  os.remove(tst_fna2)
198
204
 
199
- def test_defuse_maps(self):
205
+ def test_defuse_maps_integrity(self):
200
206
  assert URI_SEP_CHAR not in UNICODE_TO_ASCII
201
207
  assert len(UNICODE_TO_ASCII) == len(ASCII_TO_UNICODE) # check for duplicates in the ASCII_UNICODE map
202
208
 
209
+ def test_defuse_maps_not_touching_chars_allowed_as_slug_and_filename(self):
210
+ assert '-' not in ASCII_TO_UNICODE
211
+ assert '_' not in ASCII_TO_UNICODE
212
+ assert '.' not in ASCII_TO_UNICODE
213
+ assert '~' not in ASCII_TO_UNICODE
214
+ for char in string.ascii_letters + string.digits:
215
+ assert char not in ASCII_TO_UNICODE
216
+
203
217
  def test_dummy_function(self):
204
218
  assert dummy_function() is None
205
219
  assert dummy_function(999, "any_args") is None
@@ -769,6 +783,27 @@ class TestBaseHelpers:
769
783
  if os.path.exists(test_file):
770
784
  os.remove(test_file)
771
785
 
786
+ def test_write_file_make_dirs(self):
787
+ root_dir = os.path.join(TESTS_FOLDER, 'root path of file')
788
+ test_dir = os.path.join(root_dir, '1st sub dir of file', 'subDir2')
789
+ test_file = os.path.join(test_dir, 'file in sub dir.ext')
790
+ content = "any content"
791
+ assert not os.path.exists(test_dir)
792
+ assert not os.path.exists(test_file)
793
+ try:
794
+ with pytest.raises(FileNotFoundError):
795
+ write_file(test_file, content)
796
+ write_file(test_file, content, make_dirs=True)
797
+ assert os.path.exists(test_dir)
798
+ assert os.path.isdir(test_dir)
799
+ assert os.path.exists(test_file)
800
+ assert os.path.isfile(test_file)
801
+ assert read_file(test_file) == content
802
+
803
+ finally:
804
+ if os.path.exists(root_dir):
805
+ shutil.rmtree(root_dir)
806
+
772
807
 
773
808
  class TestModuleHelpers:
774
809
  def test_module_attr_callable_with_args(self):
File without changes
File without changes
File without changes