ae-base 0.3.37__tar.gz → 0.3.39__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.37/ae_base.egg-info → ae_base-0.3.39}/PKG-INFO +5 -5
- {ae_base-0.3.37 → ae_base-0.3.39}/README.md +4 -4
- {ae_base-0.3.37 → ae_base-0.3.39}/ae/base.py +69 -5
- {ae_base-0.3.37 → ae_base-0.3.39/ae_base.egg-info}/PKG-INFO +5 -5
- {ae_base-0.3.37 → ae_base-0.3.39}/tests/test_base.py +40 -1
- {ae_base-0.3.37 → ae_base-0.3.39}/LICENSE.md +0 -0
- {ae_base-0.3.37 → ae_base-0.3.39}/ae_base.egg-info/SOURCES.txt +0 -0
- {ae_base-0.3.37 → ae_base-0.3.39}/ae_base.egg-info/dependency_links.txt +0 -0
- {ae_base-0.3.37 → ae_base-0.3.39}/ae_base.egg-info/requires.txt +0 -0
- {ae_base-0.3.37 → ae_base-0.3.39}/ae_base.egg-info/top_level.txt +0 -0
- {ae_base-0.3.37 → ae_base-0.3.39}/ae_base.egg-info/zip-safe +0 -0
- {ae_base-0.3.37 → ae_base-0.3.39}/setup.cfg +0 -0
- {ae_base-0.3.37 → ae_base-0.3.39}/setup.py +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.39
|
|
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
|
|
@@ -53,17 +53,17 @@ Requires-Dist: twine; extra == "tests"
|
|
|
53
53
|
|
|
54
54
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.94 -->
|
|
55
55
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
|
|
56
|
-
# base 0.3.
|
|
56
|
+
# base 0.3.39
|
|
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.38)
|
|
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.39.
|
|
67
67
|
|
|
68
68
|
[](
|
|
69
69
|
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.
|
|
3
|
+
# base 0.3.39
|
|
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.38)
|
|
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.39.
|
|
14
14
|
|
|
15
15
|
[](
|
|
16
16
|
https://ae-group.gitlab.io/ae_base/coverage/index.html)
|
|
@@ -42,7 +42,7 @@ absolute, call the function :func:`norm_path`.
|
|
|
42
42
|
|
|
43
43
|
:func:`camel_to_snake` and :func:`snake_to_camel` providing name conversions of class and method names.
|
|
44
44
|
|
|
45
|
-
to encode
|
|
45
|
+
to encode Unicode strings to other codecs the functions :func:`force_encoding` and :func:`to_ascii` can be used.
|
|
46
46
|
|
|
47
47
|
the :func:`round_traditional` function get provided by this module for traditional rounding of float values. the
|
|
48
48
|
function signature is fully compatible to Python's :func:`round` function.
|
|
@@ -137,6 +137,7 @@ import shutil
|
|
|
137
137
|
import socket
|
|
138
138
|
import sys
|
|
139
139
|
import unicodedata
|
|
140
|
+
import urllib.parse
|
|
140
141
|
import warnings
|
|
141
142
|
|
|
142
143
|
from configparser import ConfigParser, ExtendedInterpolation
|
|
@@ -146,7 +147,8 @@ from inspect import getinnerframes, getouterframes, getsourcefile
|
|
|
146
147
|
from types import ModuleType
|
|
147
148
|
from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast
|
|
148
149
|
|
|
149
|
-
|
|
150
|
+
|
|
151
|
+
__version__ = '0.3.39'
|
|
150
152
|
|
|
151
153
|
|
|
152
154
|
DOCS_FOLDER = 'docs' #: project documentation root folder name
|
|
@@ -435,7 +437,8 @@ def load_dotenvs():
|
|
|
435
437
|
.. hint:: call from main module of project/app in order to also load ``.env`` files in/above the project folder.
|
|
436
438
|
"""
|
|
437
439
|
load_env_var_defaults(os.getcwd())
|
|
438
|
-
|
|
440
|
+
if file_name := stack_var('__file__'):
|
|
441
|
+
load_env_var_defaults(os.path.dirname(os.path.abspath(file_name)))
|
|
439
442
|
|
|
440
443
|
|
|
441
444
|
def load_env_var_defaults(start_dir: str):
|
|
@@ -920,7 +923,7 @@ def sys_env_text(ind_ch: str = " ", ind_len: int = 12, key_ch: str = "=", key_le
|
|
|
920
923
|
extra_sys_env_dict: Optional[Dict[str, str]] = None) -> str:
|
|
921
924
|
""" compile formatted text block with system environment info.
|
|
922
925
|
|
|
923
|
-
:param ind_ch: indent character (
|
|
926
|
+
:param ind_ch: indent character (defaults to " ").
|
|
924
927
|
:param ind_len: indent depths (default=12 characters).
|
|
925
928
|
:param key_ch: key-value separator character (default="=").
|
|
926
929
|
:param key_len: key-name minimum length (default=15 characters).
|
|
@@ -939,7 +942,7 @@ def sys_env_text(ind_ch: str = " ", ind_len: int = 12, key_ch: str = "=", key_le
|
|
|
939
942
|
|
|
940
943
|
|
|
941
944
|
def to_ascii(unicode_str: str) -> str:
|
|
942
|
-
""" converts
|
|
945
|
+
""" converts Unicode string into ascii representation.
|
|
943
946
|
|
|
944
947
|
useful for fuzzy string compare; inspired by MiniQuark's answer
|
|
945
948
|
in: https://stackoverflow.com/questions/517923/what-is-the-best-way-to-remove-accents-in-a-python-unicode-string
|
|
@@ -951,6 +954,36 @@ def to_ascii(unicode_str: str) -> str:
|
|
|
951
954
|
return "".join([c for c in nfkd_form if not unicodedata.combining(c)]).replace('ß', "ss").replace('€', "Euro")
|
|
952
955
|
|
|
953
956
|
|
|
957
|
+
def uri2filename(uri: str) -> str:
|
|
958
|
+
""" convert a URI to be usable as name of a file or folder
|
|
959
|
+
|
|
960
|
+
:param uri: URI to convert to a corresponding file name, that will be revertible back to this URI.
|
|
961
|
+
:return: name of a file/folder representing the specified URI.
|
|
962
|
+
|
|
963
|
+
in *nix only / and \0 are not allowed characters in file names.
|
|
964
|
+
in MS Windows are not allowed: ASCII 0...31): / \\ : * ? ” < > | (). some blogs recommend to also not allow
|
|
965
|
+
(convert) the characters # and '.
|
|
966
|
+
only old POSIX seems to be even more restricted (only allowing alphanumeric characters plus . - and _).
|
|
967
|
+
|
|
968
|
+
file name length is not restricted/shortened by this function, although the maximum is 255 characters on most OSs.
|
|
969
|
+
|
|
970
|
+
more on allowed characters in file names in the answers of RedGrittyBrick on https://superuser.com/questions/358855
|
|
971
|
+
and of Christopher Oezbek on https://stackoverflow.com/questions/1976007.
|
|
972
|
+
"""
|
|
973
|
+
# using urllib.parse.quote(uri, safe="") instead would convert also any non-ascii (e.g. umlaut) characters into hex
|
|
974
|
+
# added [] to str.join() argument because List comprehensions are faster than generator expressions
|
|
975
|
+
return "".join([f"%{hex(ord(_))[2:].upper()}" if _ in '/|\\:*?"<>%' else _ for _ in uri])
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def filename2uri(file_name: str) -> str:
|
|
979
|
+
""" convert a file name converted by :func:`uri2filename` back to its representation as a URI
|
|
980
|
+
|
|
981
|
+
:param file_name: name of the file/folder to convert back to its URI representation.
|
|
982
|
+
:return: URI string.
|
|
983
|
+
"""
|
|
984
|
+
return urllib.parse.unquote(file_name)
|
|
985
|
+
|
|
986
|
+
|
|
954
987
|
def write_file(file_path: str, content: Union[str, bytes], extra_mode: str = "", encoding: Optional[str] = None):
|
|
955
988
|
""" (over)write the file specified by :paramref:`~write_file.file_path` with text or binary/bytes content.
|
|
956
989
|
|
|
@@ -965,11 +998,42 @@ def write_file(file_path: str, content: Union[str, bytes], extra_mode: str = "",
|
|
|
965
998
|
:raises OSError: if :paramref:`~read_file.file_path` is misspelled or contains invalid characters.
|
|
966
999
|
:raises PermissionError: if current OS user account lacks permissions to read the file content.
|
|
967
1000
|
:raises ValueError: on decoding errors.
|
|
1001
|
+
|
|
1002
|
+
to extend this function for Android 14+ see https://github.com/beeware/toga/pull/1158#issuecomment-2254564657
|
|
1003
|
+
and https://gist.github.com/neonankiti/05922cf0a44108a2e2732671ed9ef386
|
|
1004
|
+
Yes, to use ACTION_CREATE_DOCUMENT, you don't supply a URI in the intent. You wait for the intent result, and that
|
|
1005
|
+
will contain a URI which you can write to.
|
|
1006
|
+
See #1158 (comment - https://github.com/beeware/toga/pull/1158#issuecomment-2254564657) for a link to a Java
|
|
1007
|
+
example, and #1158 (comment - https://github.com/beeware/toga/pull/1158#issuecomment-1446196973) for how to wait
|
|
1008
|
+
for an intent result.
|
|
1009
|
+
Related german docs: https://developer.android.com/training/data-storage/shared/media?hl=de
|
|
968
1010
|
"""
|
|
969
1011
|
with open(file_path, ('' if extra_mode.startswith('a') else 'w') + extra_mode, encoding=encoding) as file_handle:
|
|
970
1012
|
file_handle.write(content)
|
|
971
1013
|
|
|
972
1014
|
|
|
1015
|
+
class ErrorMsgMixin:
|
|
1016
|
+
""" mixin class providing error message """
|
|
1017
|
+
_err_msg: str = ""
|
|
1018
|
+
|
|
1019
|
+
@property
|
|
1020
|
+
def error_message(self) -> str:
|
|
1021
|
+
""" error message string if an error occurred or an empty string if not.
|
|
1022
|
+
|
|
1023
|
+
:getter: return the accumulated error message of the recently occurred error(s).
|
|
1024
|
+
:setter: any assigned error message will be accumulated to recent error messages.
|
|
1025
|
+
pass an empty string to reset the error message.
|
|
1026
|
+
"""
|
|
1027
|
+
return self._err_msg
|
|
1028
|
+
|
|
1029
|
+
@error_message.setter
|
|
1030
|
+
def error_message(self, next_err_msg: str):
|
|
1031
|
+
if next_err_msg:
|
|
1032
|
+
self._err_msg += ("\n\n" if self._err_msg else "") + next_err_msg
|
|
1033
|
+
else:
|
|
1034
|
+
self._err_msg = ""
|
|
1035
|
+
|
|
1036
|
+
|
|
973
1037
|
PACKAGE_NAME = stack_var('__name__') or 'unspecified_package'
|
|
974
1038
|
PACKAGE_DOMAIN = 'org.test'
|
|
975
1039
|
PERMISSIONS = "INTERNET, VIBRATE, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ae_base
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.39
|
|
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
|
|
@@ -53,17 +53,17 @@ Requires-Dist: twine; extra == "tests"
|
|
|
53
53
|
|
|
54
54
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.94 -->
|
|
55
55
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
|
|
56
|
-
# base 0.3.
|
|
56
|
+
# base 0.3.39
|
|
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.38)
|
|
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.39.
|
|
67
67
|
|
|
68
68
|
[](
|
|
69
69
|
https://ae-group.gitlab.io/ae_base/coverage/index.html)
|
|
@@ -20,9 +20,12 @@ from ae.base import (
|
|
|
20
20
|
load_env_var_defaults, load_dotenvs, main_file_paths_parts, module_attr, module_file_path, module_name,
|
|
21
21
|
norm_line_sep, norm_name, norm_path, now_str, os_host_name, os_local_ip, _os_platform, os_user_name,
|
|
22
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)
|
|
23
|
+
sys_env_dict, sys_env_text, to_ascii, filename2uri, uri2filename, write_file, ErrorMsgMixin)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
tst_uri1 = "schema://user:pwd@domain/path_root/path_sub\\path+file% Üml?ä|ït.path_ext*\"<>"
|
|
27
|
+
tst_fna1 = "schema%3A%2F%2Fuser%3Apwd@domain%2Fpath_root%2Fpath_sub%5Cpath+file%25 Üml%3Fä%7Cït.path_ext%2A%22%3C%3E"
|
|
28
|
+
|
|
26
29
|
env_var_name = 'env_var_nam1'
|
|
27
30
|
env_var_val = 'value of env var'
|
|
28
31
|
folder_name = 'fdr'
|
|
@@ -60,6 +63,27 @@ def test_unset_null_length():
|
|
|
60
63
|
assert len(UNSET) == 0
|
|
61
64
|
|
|
62
65
|
|
|
66
|
+
class TestErrorMsgMixin:
|
|
67
|
+
def test_instantiation(self):
|
|
68
|
+
assert ErrorMsgMixin()
|
|
69
|
+
|
|
70
|
+
def test_error_message_property(self):
|
|
71
|
+
ins = ErrorMsgMixin()
|
|
72
|
+
assert ins.error_message == ""
|
|
73
|
+
|
|
74
|
+
err_msg = "set new error message"
|
|
75
|
+
ins.error_message = err_msg
|
|
76
|
+
assert ins.error_message == err_msg
|
|
77
|
+
|
|
78
|
+
err_msg2 = "added error message"
|
|
79
|
+
ins.error_message = err_msg2
|
|
80
|
+
assert err_msg in ins.error_message
|
|
81
|
+
assert err_msg2 in ins.error_message
|
|
82
|
+
|
|
83
|
+
ins.error_message = ""
|
|
84
|
+
assert ins.error_message == ""
|
|
85
|
+
|
|
86
|
+
|
|
63
87
|
class TestBaseHelpers:
|
|
64
88
|
def test_app_name_guess(self):
|
|
65
89
|
assert app_name_guess() # app.exe name in pytest returning '_jb_pytest_runner'(PyCharm)/'__main__'(console)
|
|
@@ -678,6 +702,21 @@ class TestBaseHelpers:
|
|
|
678
702
|
assert to_ascii('ß') == 'ss'
|
|
679
703
|
assert to_ascii('€') == 'Euro'
|
|
680
704
|
|
|
705
|
+
def test_filename2uri(self):
|
|
706
|
+
assert filename2uri(tst_fna1) == tst_uri1
|
|
707
|
+
assert uri2filename(filename2uri(tst_fna1)) == tst_fna1
|
|
708
|
+
|
|
709
|
+
def test_uri2filename(self):
|
|
710
|
+
assert uri2filename(tst_uri1) == tst_fna1
|
|
711
|
+
assert filename2uri(uri2filename(tst_uri1)) == tst_uri1
|
|
712
|
+
|
|
713
|
+
def test_uri_file_name(self):
|
|
714
|
+
try:
|
|
715
|
+
write_file(tst_fna1, "tst uri file content")
|
|
716
|
+
finally:
|
|
717
|
+
if os.path.exists(tst_fna1):
|
|
718
|
+
os.remove(tst_fna1)
|
|
719
|
+
|
|
681
720
|
def test_write_file_as_text(self):
|
|
682
721
|
test_file = os.path.join(TESTS_FOLDER, 'tst_file_written.ext')
|
|
683
722
|
content = "any content"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|