ae-base 0.3.47__py3-none-any.whl → 0.3.49__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 +63 -88
- {ae_base-0.3.47.dist-info → ae_base-0.3.49.dist-info}/METADATA +18 -6
- ae_base-0.3.49.dist-info/RECORD +7 -0
- {ae_base-0.3.47.dist-info → ae_base-0.3.49.dist-info}/WHEEL +1 -1
- ae_base-0.3.47.dist-info/RECORD +0 -7
- {ae_base-0.3.47.dist-info → ae_base-0.3.49.dist-info}/LICENSE.md +0 -0
- {ae_base-0.3.47.dist-info → ae_base-0.3.49.dist-info}/top_level.txt +0 -0
- {ae_base-0.3.47.dist-info → ae_base-0.3.49.dist-info}/zip-safe +0 -0
ae/base.py
CHANGED
|
@@ -5,6 +5,11 @@ basic constants, helper functions and context manager
|
|
|
5
5
|
this module is pure python, has no external dependencies, and is providing base constants, common helper
|
|
6
6
|
functions, useful classes and context managers.
|
|
7
7
|
|
|
8
|
+
.. note::
|
|
9
|
+
on import of this module, while running on Android OS, it will monkey patch the :mod:`shutil` module
|
|
10
|
+
to allow to use them on Android devices. therefore the import of this module should be one of the first ones
|
|
11
|
+
in your app's main module.
|
|
12
|
+
|
|
8
13
|
|
|
9
14
|
base constants
|
|
10
15
|
--------------
|
|
@@ -20,10 +25,6 @@ default values to compile file and folder names for a package or an app project
|
|
|
20
25
|
:data:`PACKAGE_INCLUDE_FILES_PREFIX`, :data:`PY_EXT`, :data:`PY_INIT`, :data:`PY_MAIN`, :data:`CFG_EXT`
|
|
21
26
|
and :data:`INI_EXT`.
|
|
22
27
|
|
|
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
|
-
:data:`build config file <BUILD_CONFIG_FILE>` - if it exists in the current working directory.
|
|
26
|
-
|
|
27
28
|
with the help of the format string constant :data:`NOW_STR_FORMAT` and the function :func:`now_str` you can create a
|
|
28
29
|
sortable and compact string from a timestamp.
|
|
29
30
|
|
|
@@ -82,31 +83,13 @@ variables for your application are :func:`sys_env_dict` and :func:`sys_env_text`
|
|
|
82
83
|
to integrate system environment variables from ``.env`` files into :data:`os.environ` the helper functions
|
|
83
84
|
:func:parse_dotenv`, :func:`load_env_var_defaults` and :func:`load_dotenvs` are provided.
|
|
84
85
|
|
|
86
|
+
the :mod:`ae.core` portion is providing more OS-specific constants and helper functions, like e.g.
|
|
87
|
+
:func:`start_app_service` and :func:`request_app_permissions`.
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
the helper function :func:`start_app_service` is starting a service in its own, separate thread.
|
|
92
|
-
with the function :func:`request_app_permissions` you can ensure that all your Android permissions will be
|
|
93
|
-
requested. the module :mod:`ae.kivy.apps` does this automatically on app startup. on other platforms than
|
|
94
|
-
Android it will have no effect to call these functions.
|
|
95
|
-
|
|
96
|
-
.. note:: importing this module on an Android OS, it is monkey patching the :mod:`shutil` module to prevent crashes.
|
|
97
|
-
|
|
98
|
-
links to other android code and service examples and documentation:
|
|
99
|
-
|
|
100
|
-
* `https://python-for-android.readthedocs.io/en/latest/`__
|
|
101
|
-
* `https://github.com/kivy/python-for-android/tree/develop/pythonforandroid/recipes/android/src/android`__
|
|
102
|
-
* `https://github.com/tshirtman/kivy_service_osc/blob/master/src/main.py`__
|
|
103
|
-
* `https://blog.kivy.org/2014/01/building-a-background-application-on-android-with-kivy/`__
|
|
104
|
-
* `https://github.com/Android-for-Python/Android-for-Python-Users`__
|
|
105
|
-
* `https://github.com/Android-for-Python/INDEX-of-Examples`__
|
|
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
|
|
109
|
-
osc example.
|
|
89
|
+
.. note::
|
|
90
|
+
on import of this module, while running on Android OS, it will monkey patch the :mod:`shutil` module to allow to
|
|
91
|
+
use them on Android devices, and on first app start request the permissions of your app. therefore to prevent
|
|
92
|
+
permission errors, the import of this module should be the first statement in the main module of your app.
|
|
110
93
|
|
|
111
94
|
|
|
112
95
|
types, classes and mixins
|
|
@@ -117,6 +100,9 @@ allowing also ``None`` is an accepted argument value.
|
|
|
117
100
|
|
|
118
101
|
to extend any class with an intelligent error message handling, add the mixin :class:`ErrorMsgMixin` to it.
|
|
119
102
|
|
|
103
|
+
the classes :class:`UnformattedValue` and :class:`GivenFormatter` can be used to format strings with placeholders
|
|
104
|
+
enclosed in curly brackets. the function :func:`format_given` is using them to format templates with placeholders.
|
|
105
|
+
|
|
120
106
|
|
|
121
107
|
generic context manager
|
|
122
108
|
-----------------------
|
|
@@ -149,7 +135,7 @@ another useful helper function provided by this portion to inspect and debug you
|
|
|
149
135
|
os.path shortcuts
|
|
150
136
|
-----------------
|
|
151
137
|
|
|
152
|
-
the following data items are pointers to shortcut the lookup to their related functions in the
|
|
138
|
+
the following data items are pointers to shortcut at runtime the lookup to their related functions in the
|
|
153
139
|
Python module :mod:`os.path`:
|
|
154
140
|
"""
|
|
155
141
|
import datetime
|
|
@@ -161,6 +147,7 @@ import platform
|
|
|
161
147
|
import re
|
|
162
148
|
import shutil
|
|
163
149
|
import socket
|
|
150
|
+
import string
|
|
164
151
|
import sys
|
|
165
152
|
import unicodedata
|
|
166
153
|
import warnings
|
|
@@ -173,7 +160,7 @@ from types import ModuleType
|
|
|
173
160
|
from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast
|
|
174
161
|
|
|
175
162
|
|
|
176
|
-
__version__ = '0.3.
|
|
163
|
+
__version__ = '0.3.49'
|
|
177
164
|
|
|
178
165
|
|
|
179
166
|
os_path_abspath = os.path.abspath
|
|
@@ -248,8 +235,7 @@ NOW_STR_FORMAT = "{sep}%Y%m%d{sep}%H%M%S{sep}%f" #: timestamp format of :func
|
|
|
248
235
|
SKIPPED_MODULES = ('ae.base', 'ae.paths', 'ae.dynamicod', 'ae.core', 'ae.console', 'ae.gui_app', 'ae.gui_help',
|
|
249
236
|
'ae.kivy', 'ae.kivy.apps', 'ae.kivy.behaviors', 'ae.kivy.i18n', 'ae.kivy.tours', 'ae.kivy.widgets',
|
|
250
237
|
'ae.enaml_app', 'ae.beeware_app', 'ae.pyglet_app', 'ae.pygobject_app', 'ae.dabo_app',
|
|
251
|
-
'ae.qpython_app', 'ae.appjar_app',
|
|
252
|
-
'importlib._bootstrap', 'importlib._bootstrap_external')
|
|
238
|
+
'ae.qpython_app', 'ae.appjar_app', 'importlib._bootstrap', 'importlib._bootstrap_external')
|
|
253
239
|
""" skipped modules used as default by :func:`module_name`, :func:`stack_var` and :func:`stack_vars` """
|
|
254
240
|
|
|
255
241
|
|
|
@@ -487,6 +473,50 @@ def force_encoding(text: Union[str, bytes], encoding: str = DEF_ENCODING, errors
|
|
|
487
473
|
return enc_str.decode(encoding=encoding)
|
|
488
474
|
|
|
489
475
|
|
|
476
|
+
class UnformattedValue:
|
|
477
|
+
""" helper class for :func:`~ae.base.format_given` to keep placeholder with format unchanged if not found. """
|
|
478
|
+
def __init__(self, key: str):
|
|
479
|
+
self.key = key
|
|
480
|
+
|
|
481
|
+
def __format__(self, format_spec: str):
|
|
482
|
+
""" overriding Python object class method to return placeholder unchanged including the curly brackets. """
|
|
483
|
+
return "{{{}{}}}".format(self.key, ":" + format_spec if format_spec else "")
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class GivenFormatter(string.Formatter):
|
|
487
|
+
""" helper class for :func:`~ae.base.format_given` to keep placeholder with format unchanged if not found. """
|
|
488
|
+
def get_value(self, key, args, kwargs):
|
|
489
|
+
""" overriding to keep placeholder unchanged if not found """
|
|
490
|
+
try:
|
|
491
|
+
return super().get_value(key, args, kwargs)
|
|
492
|
+
except KeyError:
|
|
493
|
+
return UnformattedValue(key)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def format_given(text: str, placeholder_map: dict[str, Any], strict: bool = False):
|
|
497
|
+
""" replacement for Python's str.format_map(), keeping intact placeholders that are not in the specified mapping.
|
|
498
|
+
|
|
499
|
+
:param text: text/template in which the given/specified placeholders will get replaced. in contrary
|
|
500
|
+
to str.format_map() no KeyError will be raised for placeholders not specified in
|
|
501
|
+
:paramref:`~format_given.placeholder_map`.
|
|
502
|
+
:param placeholder_map: dict with placeholder keys to be replaced in :paramref:`~format_given.text` argument.
|
|
503
|
+
:param strict: pass True to raise error for text templates containing unpaired curly brackets.
|
|
504
|
+
:return: the specified :paramref:`~format_given.text` with only the placeholders specified in
|
|
505
|
+
:paramref:`~format_given.placeholder_map` replaced with their respective map value.
|
|
506
|
+
additionally any ValueError that would be thrown by str.format_map(), e.g. if the
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
inspired by the answer of CodeManX in `https://stackoverflow.com/questions/3536303`__
|
|
510
|
+
"""
|
|
511
|
+
formatter = GivenFormatter()
|
|
512
|
+
try:
|
|
513
|
+
return formatter.vformat(text, (), placeholder_map)
|
|
514
|
+
except (ValueError, Exception) as ex:
|
|
515
|
+
if strict:
|
|
516
|
+
raise ex
|
|
517
|
+
return text
|
|
518
|
+
|
|
519
|
+
|
|
490
520
|
def full_stack_trace(ex: Exception) -> str:
|
|
491
521
|
""" get full stack trace from an exception.
|
|
492
522
|
|
|
@@ -758,7 +788,7 @@ def os_host_name() -> str:
|
|
|
758
788
|
|
|
759
789
|
:return: machine name string.
|
|
760
790
|
"""
|
|
761
|
-
return defuse(platform.node()) or "
|
|
791
|
+
return defuse(platform.node()) or "indeterminableHostName"
|
|
762
792
|
|
|
763
793
|
|
|
764
794
|
def os_local_ip() -> str:
|
|
@@ -1190,23 +1220,6 @@ class ErrorMsgMixin:
|
|
|
1190
1220
|
self._err_msg = ""
|
|
1191
1221
|
|
|
1192
1222
|
|
|
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
|
|
1197
|
-
if os_path_isfile(BUILD_CONFIG_FILE): # pragma: no cover
|
|
1198
|
-
PACKAGE_NAME, PACKAGE_DOMAIN, PERMISSIONS = build_config_variable_values(
|
|
1199
|
-
('package.name', PACKAGE_NAME),
|
|
1200
|
-
('package.domain', PACKAGE_DOMAIN),
|
|
1201
|
-
('android.permissions', PERMISSIONS))
|
|
1202
|
-
elif os_platform == 'android': # pragma: no cover
|
|
1203
|
-
_importing_package = norm_path(stack_var('__file__') or 'empty_package' + PY_EXT)
|
|
1204
|
-
if os_path_basename(_importing_package) in (PY_INIT, PY_MAIN):
|
|
1205
|
-
_importing_package = os_path_dirname(_importing_package)
|
|
1206
|
-
_importing_package = os_path_splitext(os_path_basename(_importing_package))[0]
|
|
1207
|
-
write_file(f'{_importing_package}_debug.log', f"{BUILD_CONFIG_FILE} not bundled - using defaults\n", extra_mode='a')
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
1223
|
if os_platform == 'android': # pragma: no cover
|
|
1211
1224
|
# monkey patch the :func:`shutil.copystat` and :func:`shutil.copymode` helper functions, which are crashing on
|
|
1212
1225
|
# 'android' (see # `<https://bugs.python.org/issue28141>`__ and `<https://bugs.python.org/issue32073>`__). these
|
|
@@ -1216,41 +1229,3 @@ if os_platform == 'android': # pragma: no cov
|
|
|
1216
1229
|
# on the destination root directory.
|
|
1217
1230
|
shutil.copymode = dummy_function
|
|
1218
1231
|
shutil.copystat = dummy_function
|
|
1219
|
-
|
|
1220
|
-
# import permissions module from python-for-android (recipes/android/src/android/permissions.py)
|
|
1221
|
-
# noinspection PyUnresolvedReferences
|
|
1222
|
-
from android.permissions import request_permissions, Permission # type: ignore # pylint: disable=import-error
|
|
1223
|
-
from jnius import autoclass # type: ignore
|
|
1224
|
-
|
|
1225
|
-
def request_app_permissions(callback: Optional[Callable[[List[Permission], List[bool]], None]] = None):
|
|
1226
|
-
""" request app/service permissions on Android OS.
|
|
1227
|
-
|
|
1228
|
-
:param callback: optional callback receiving two list arguments with identical length,
|
|
1229
|
-
the 1st with the requested permissions and
|
|
1230
|
-
the 2nd with booleans stating if the permission got granted (True) or rejected (False).
|
|
1231
|
-
"""
|
|
1232
|
-
permissions = []
|
|
1233
|
-
for permission_str in PERMISSIONS.split(','):
|
|
1234
|
-
permission = getattr(Permission, permission_str.strip(), None)
|
|
1235
|
-
if permission:
|
|
1236
|
-
permissions.append(permission)
|
|
1237
|
-
request_permissions(permissions, callback=callback)
|
|
1238
|
-
|
|
1239
|
-
def start_app_service(service_arg: str = "") -> Any:
|
|
1240
|
-
""" start service.
|
|
1241
|
-
|
|
1242
|
-
:param service_arg: string value to be assigned to environment variable PYTHON_SERVICE_ARGUMENT on start.
|
|
1243
|
-
:return: service instance.
|
|
1244
|
-
|
|
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>`__
|
|
1247
|
-
"""
|
|
1248
|
-
service_instance = autoclass(f"{PACKAGE_DOMAIN}.{PACKAGE_NAME}.Service{PACKAGE_NAME.capitalize()}")
|
|
1249
|
-
activity = autoclass('org.kivy.android.PythonActivity').mActivity
|
|
1250
|
-
service_instance.start(activity, service_arg) # service_arg will be in env var PYTHON_SERVICE_ARGUMENT
|
|
1251
|
-
|
|
1252
|
-
return service_instance
|
|
1253
|
-
|
|
1254
|
-
else:
|
|
1255
|
-
request_app_permissions = dummy_function
|
|
1256
|
-
start_app_service = dummy_function
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: ae_base
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.49
|
|
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,20 +53,32 @@ Requires-Dist: typing; extra == "tests"
|
|
|
53
53
|
Requires-Dist: types-setuptools; extra == "tests"
|
|
54
54
|
Requires-Dist: wheel; extra == "tests"
|
|
55
55
|
Requires-Dist: twine; extra == "tests"
|
|
56
|
+
Dynamic: author
|
|
57
|
+
Dynamic: author-email
|
|
58
|
+
Dynamic: classifier
|
|
59
|
+
Dynamic: description
|
|
60
|
+
Dynamic: description-content-type
|
|
61
|
+
Dynamic: home-page
|
|
62
|
+
Dynamic: keywords
|
|
63
|
+
Dynamic: license
|
|
64
|
+
Dynamic: project-url
|
|
65
|
+
Dynamic: provides-extra
|
|
66
|
+
Dynamic: requires-python
|
|
67
|
+
Dynamic: summary
|
|
56
68
|
|
|
57
69
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae V0.3.95 -->
|
|
58
70
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.tpl_namespace_root V0.3.14 -->
|
|
59
|
-
# base 0.3.
|
|
71
|
+
# base 0.3.49
|
|
60
72
|
|
|
61
73
|
[](
|
|
62
74
|
https://gitlab.com/ae-group/ae_base)
|
|
63
75
|
[](
|
|
77
|
+
https://gitlab.com/ae-group/ae_base/-/tree/release0.3.48)
|
|
66
78
|
[](
|
|
67
79
|
https://pypi.org/project/ae-base/#history)
|
|
68
80
|
|
|
69
|
-
>ae_base module 0.3.
|
|
81
|
+
>ae_base module 0.3.49.
|
|
70
82
|
|
|
71
83
|
[](
|
|
72
84
|
https://ae-group.gitlab.io/ae_base/coverage/index.html)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ae/base.py,sha256=V0wU87UunINFILcnar0HlBvF6gB8nLnj1tfQ-uXoCiY,61257
|
|
2
|
+
ae_base-0.3.49.dist-info/LICENSE.md,sha256=uoIIfORuk4V8ZeNh6SN0EUhiU79RC-indIMFqePKVhY,35002
|
|
3
|
+
ae_base-0.3.49.dist-info/METADATA,sha256=Ezl-iNQcsr9r0ssjPJAVgVTvZcQZDbHIqno7of0zTos,5636
|
|
4
|
+
ae_base-0.3.49.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
5
|
+
ae_base-0.3.49.dist-info/top_level.txt,sha256=vUdgAslSmhZLXWU48fm8AG2BjVnkOWLco8rzuW-5zY0,3
|
|
6
|
+
ae_base-0.3.49.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
|
+
ae_base-0.3.49.dist-info/RECORD,,
|
ae_base-0.3.47.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
ae/base.py,sha256=MYA5vcALiaubDxxDLDUywjXwMldKpijjN6LPoixWIto,63016
|
|
2
|
-
ae_base-0.3.47.dist-info/LICENSE.md,sha256=uoIIfORuk4V8ZeNh6SN0EUhiU79RC-indIMFqePKVhY,35002
|
|
3
|
-
ae_base-0.3.47.dist-info/METADATA,sha256=TF_qiiV2cvZ1dHeZrCSV1M_QKYgvTv-aKzlL9nxttYQ,5382
|
|
4
|
-
ae_base-0.3.47.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
5
|
-
ae_base-0.3.47.dist-info/top_level.txt,sha256=vUdgAslSmhZLXWU48fm8AG2BjVnkOWLco8rzuW-5zY0,3
|
|
6
|
-
ae_base-0.3.47.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
|
-
ae_base-0.3.47.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|