thingsboard-common 0.1.1__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.
- thingsboard/__init__.py +7 -0
- thingsboard/common/__init__.py +15 -0
- thingsboard/common/core/__init__.py +10 -0
- thingsboard/common/core/thread_monitor.py +55 -0
- thingsboard/common/core/threaded.py +160 -0
- thingsboard/common/utils/__init__.py +7 -0
- thingsboard/common/utils/attribute_helpers.py +48 -0
- thingsboard/common/utils/datetime_helpers.py +30 -0
- thingsboard/common/utils/path_helpers.py +48 -0
- thingsboard/common/utils/resource_loader.py +78 -0
- thingsboard/common/utils/string_helpers.py +31 -0
- thingsboard/common/utils/timestamp_helpers.py +17 -0
- thingsboard/common/version.py +15 -0
- thingsboard_common-0.1.1.dist-info/METADATA +49 -0
- thingsboard_common-0.1.1.dist-info/RECORD +18 -0
- thingsboard_common-0.1.1.dist-info/WHEEL +5 -0
- thingsboard_common-0.1.1.dist-info/licenses/LICENSE +21 -0
- thingsboard_common-0.1.1.dist-info/top_level.txt +1 -0
thingsboard/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Tell python that there are more sub-packages present, physically located elsewhere.
|
|
4
|
+
# See: https://stackoverflow.com/questions/8936884/python-import-path-packages-with-the-same-name-in-different-folders
|
|
5
|
+
import pkgutil
|
|
6
|
+
|
|
7
|
+
__path__ = pkgutil.extend_path(__path__, __name__)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Tell python that there are more sub-packages present, physically located elsewhere.
|
|
4
|
+
# See: https://stackoverflow.com/questions/8936884/python-import-path-packages-with-the-same-name-in-different-folders
|
|
5
|
+
import pkgutil
|
|
6
|
+
|
|
7
|
+
__path__ = pkgutil.extend_path(__path__, __name__)
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from .version import __version__ as version
|
|
11
|
+
|
|
12
|
+
# Do not output logs if logging module is not configured
|
|
13
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
14
|
+
|
|
15
|
+
logging.getLogger(__name__).info(f'thingsboard-common version: {version}')
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Tell python that there are more sub-packages present, physically located elsewhere.
|
|
4
|
+
# See: https://stackoverflow.com/questions/8936884/python-import-path-packages-with-the-same-name-in-different-folders
|
|
5
|
+
import pkgutil
|
|
6
|
+
|
|
7
|
+
__path__ = pkgutil.extend_path(__path__, __name__)
|
|
8
|
+
|
|
9
|
+
from .threaded import Threaded
|
|
10
|
+
from .thread_monitor import ThreadMonitor
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from thingsboard.common.core.threaded import Threaded
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ThreadMonitor(object):
|
|
7
|
+
"""Monitors registered threads.
|
|
8
|
+
|
|
9
|
+
It is a passive class. To check if threads are still alive, call method check_threads() regularly.
|
|
10
|
+
"""
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
threadList = []
|
|
13
|
+
|
|
14
|
+
# References single instance of this class.
|
|
15
|
+
_instance = None # type: ThreadMonitor or None
|
|
16
|
+
|
|
17
|
+
def __init__(self, log_name_prefix:str=''):
|
|
18
|
+
super(ThreadMonitor, self).__init__()
|
|
19
|
+
if self._instance:
|
|
20
|
+
assert False, 'Only one instance of this class is allowed'
|
|
21
|
+
else:
|
|
22
|
+
self._set_instance(self)
|
|
23
|
+
if log_name_prefix:
|
|
24
|
+
type(self).log = logging.getLogger(log_name_prefix + '.' + __name__)
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def instance(cls):
|
|
28
|
+
"""Returns the single instance of this class.
|
|
29
|
+
"""
|
|
30
|
+
assert cls._instance, 'Create an instance of this class first'
|
|
31
|
+
return cls._instance
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def _set_instance(cls, instance):
|
|
35
|
+
assert cls._instance is None, 'Only one instance of this class allowed'
|
|
36
|
+
cls._instance = instance
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def register(cls, thread: Threaded):
|
|
40
|
+
cls.threadList.append(thread)
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def deregister(cls, thread: Threaded):
|
|
45
|
+
if thread in cls.threadList:
|
|
46
|
+
cls.threadList.remove(thread)
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def check_threads(cls):
|
|
51
|
+
for thread in cls.threadList:
|
|
52
|
+
if not thread.is_alive():
|
|
53
|
+
cls.log.error(f'Thread \'{thread.name}\' no more alive!')
|
|
54
|
+
return False
|
|
55
|
+
return True
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import time
|
|
5
|
+
from threading import Condition
|
|
6
|
+
from threading import Thread
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Threaded(object):
|
|
10
|
+
"""Provides a _thread which executes the _run() method at regular intervals.
|
|
11
|
+
|
|
12
|
+
Features:
|
|
13
|
+
- Thread supervision by ThreadMonitor
|
|
14
|
+
- Correct logging when exception occurs
|
|
15
|
+
- Thread reacts immediately on exit loop request (_thread_should_run)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
log = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
def __init__(self, control_interval_in_seconds=4.0, thread_monitor=None, **kwargs):
|
|
21
|
+
super(Threaded, self).__init__(**kwargs)
|
|
22
|
+
self._thread_monitor = thread_monitor # type: ThreadMonitor
|
|
23
|
+
self._thread = None # type: Thread or None # Thread executing the needed behavior
|
|
24
|
+
self._thread_should_run = True # Controls the loop of the _thread
|
|
25
|
+
self._thread_left_run_loop = False # Set to true when _thread is leaving run loop
|
|
26
|
+
self._control_interval_in_seconds = control_interval_in_seconds # Time to wait until next processing
|
|
27
|
+
self._sleep_condition = Condition()
|
|
28
|
+
|
|
29
|
+
def setup_thread(self, name=None, thread_monitor=None):
|
|
30
|
+
|
|
31
|
+
if thread_monitor:
|
|
32
|
+
assert not self._thread_monitor, 'Thread monitor should be initialized only once!'
|
|
33
|
+
self._thread_monitor = thread_monitor
|
|
34
|
+
|
|
35
|
+
if not name:
|
|
36
|
+
name = self.__class__.__name__
|
|
37
|
+
|
|
38
|
+
# Create a _thread that regularly polls the actual parameters of the real battery
|
|
39
|
+
self._thread = Thread(target=self._run_with_exception_logging, name=name,
|
|
40
|
+
daemon=True # Close _thread as soon as main _thread exits
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if self._thread_monitor:
|
|
44
|
+
# Register _thread for later monitor of itself. Thread monitor allows to take action
|
|
45
|
+
# in case the _thread crashes.
|
|
46
|
+
self._thread_monitor.register(self._thread)
|
|
47
|
+
|
|
48
|
+
def start_thread(self):
|
|
49
|
+
if self._thread is None:
|
|
50
|
+
self.log.warning('Thread not created. Calling setThread() for you!')
|
|
51
|
+
self.setup_thread()
|
|
52
|
+
|
|
53
|
+
# Reset run attributes
|
|
54
|
+
self._thread_should_run = True
|
|
55
|
+
self._thread_left_run_loop = False
|
|
56
|
+
|
|
57
|
+
self._thread.start()
|
|
58
|
+
|
|
59
|
+
def stop_thread(self):
|
|
60
|
+
# Remove _thread from monitor
|
|
61
|
+
if self._thread_monitor:
|
|
62
|
+
self._thread_monitor.deregister(self._thread)
|
|
63
|
+
|
|
64
|
+
# Tell _thread it should leave
|
|
65
|
+
self._thread_should_run = False
|
|
66
|
+
# Wait until it is gone
|
|
67
|
+
if self._thread.is_alive():
|
|
68
|
+
self.wait_on_thread_to_leave()
|
|
69
|
+
# Delete instance
|
|
70
|
+
del self._thread
|
|
71
|
+
self._thread = None
|
|
72
|
+
|
|
73
|
+
def wakeup_thread(self):
|
|
74
|
+
"""Wakes up _thread in case it is sleeping.
|
|
75
|
+
"""
|
|
76
|
+
# Release _thread waiting on condition
|
|
77
|
+
with self._sleep_condition:
|
|
78
|
+
self._sleep_condition.notify()
|
|
79
|
+
|
|
80
|
+
def join(self):
|
|
81
|
+
"""Wait for the internal _thread until it leaves.
|
|
82
|
+
|
|
83
|
+
Call stop_thread() to properly and quickly stop the internal _thread.
|
|
84
|
+
"""
|
|
85
|
+
if self._thread:
|
|
86
|
+
self._thread.join()
|
|
87
|
+
|
|
88
|
+
def _run_with_exception_logging(self):
|
|
89
|
+
"""Same as _run but logs exceptions to the console or log file.
|
|
90
|
+
|
|
91
|
+
This is necessary when running in testing/production environment.
|
|
92
|
+
In case of an exception thrown, the stack trace can be seen in the
|
|
93
|
+
log file. Otherwise, there is no info why the _thread did stop.
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
self._run()
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logging.error(e, exc_info=True)
|
|
99
|
+
finally:
|
|
100
|
+
# Wait here for a while. If leaving the method directly, the _thread
|
|
101
|
+
# gets deleted and the is_alive() method won't work anymore!
|
|
102
|
+
time.sleep(5)
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
def _thread_sleep_interval(self, sleep_interval_in_seconds=None):
|
|
106
|
+
"""Tells the executing _thread how long to sleep while being still reactive on _thread_should_run attribute.
|
|
107
|
+
"""
|
|
108
|
+
if sleep_interval_in_seconds is not None:
|
|
109
|
+
wait_time = sleep_interval_in_seconds
|
|
110
|
+
else:
|
|
111
|
+
wait_time = self._control_interval_in_seconds
|
|
112
|
+
|
|
113
|
+
if self._sleep_condition.acquire(blocking=False):
|
|
114
|
+
# Sleep the time given. Thread can be wakened up with self._sleep_condition.notify()
|
|
115
|
+
# see wakeup_thread()
|
|
116
|
+
try:
|
|
117
|
+
self._sleep_condition.wait(timeout=wait_time)
|
|
118
|
+
except RuntimeError as e: # pragma: no cover
|
|
119
|
+
self.log.exception(e)
|
|
120
|
+
finally:
|
|
121
|
+
self._sleep_condition.release()
|
|
122
|
+
return True
|
|
123
|
+
else:
|
|
124
|
+
self.log.error('Could not acquire sleep condition!') # pragma: no cover
|
|
125
|
+
return False # pragma: no cover
|
|
126
|
+
|
|
127
|
+
def _run(self):
|
|
128
|
+
assert False, 'Method needs to be implemented in derived class!'
|
|
129
|
+
|
|
130
|
+
""" Example loop:
|
|
131
|
+
|
|
132
|
+
while self._thread_should_run:
|
|
133
|
+
|
|
134
|
+
# Add your stuff here
|
|
135
|
+
print('Executes in a regular manner')
|
|
136
|
+
|
|
137
|
+
# Wait until next interval begins
|
|
138
|
+
if self._thread_should_run:
|
|
139
|
+
self._thread_sleep_interval()
|
|
140
|
+
|
|
141
|
+
self._thread_left_run_loop = True
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def wait_on_thread_to_leave(self, timeout=3):
|
|
145
|
+
"""Can be called to wait for the _thread until it left the run loop.
|
|
146
|
+
|
|
147
|
+
Replacement for self._thread.join() self._thread.join() is
|
|
148
|
+
reacting slowly! Replaced it with this method.
|
|
149
|
+
"""
|
|
150
|
+
wait_time = timeout
|
|
151
|
+
decr_value = 0.2
|
|
152
|
+
|
|
153
|
+
if self._thread_left_run_loop:
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
while wait_time > 0:
|
|
157
|
+
time.sleep(decr_value)
|
|
158
|
+
wait_time -= decr_value
|
|
159
|
+
if self._thread_left_run_loop:
|
|
160
|
+
break
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Tell python that there are more sub-packages present, physically located elsewhere.
|
|
4
|
+
# See: https://stackoverflow.com/questions/8936884/python-import-path-packages-with-the-same-name-in-different-folders
|
|
5
|
+
import pkgutil
|
|
6
|
+
|
|
7
|
+
__path__ = pkgutil.extend_path(__path__, __name__)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from thingsboard.common.utils import string_helpers
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def generate_attribute_names_by_name(name: str) -> tuple[str, str]:
|
|
7
|
+
"""Generates attribute names according to our coding standards.
|
|
8
|
+
"""
|
|
9
|
+
if len(name) == 0:
|
|
10
|
+
return tuple()
|
|
11
|
+
|
|
12
|
+
if name[0] == '_':
|
|
13
|
+
name = name[1:]
|
|
14
|
+
|
|
15
|
+
if '_' in name:
|
|
16
|
+
words = list(filter(None, name.split('_')))
|
|
17
|
+
elif '-' in name:
|
|
18
|
+
words = list(filter(None, name.split('-')))
|
|
19
|
+
else:
|
|
20
|
+
words = string_helpers.camel_case_split(name, lower_camel_case=True)
|
|
21
|
+
|
|
22
|
+
public_attribute_name = '_'.join((map(lambda x: x.lower(), words)))
|
|
23
|
+
private_attribute_name = '_' + public_attribute_name
|
|
24
|
+
|
|
25
|
+
return private_attribute_name, public_attribute_name
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def generate_setters_from_attribute_name(attribute_name: str) -> tuple[str, str]:
|
|
29
|
+
"""Generates method setter names according to our coding standards.
|
|
30
|
+
"""
|
|
31
|
+
if len(attribute_name) == 0:
|
|
32
|
+
return tuple()
|
|
33
|
+
|
|
34
|
+
if attribute_name[0] == '_':
|
|
35
|
+
attribute_name = attribute_name[1:]
|
|
36
|
+
|
|
37
|
+
if '_' in attribute_name:
|
|
38
|
+
words = list(filter(None, attribute_name.split('_')))
|
|
39
|
+
else:
|
|
40
|
+
words = string_helpers.camel_case_split(attribute_name, lower_camel_case=True)
|
|
41
|
+
|
|
42
|
+
setter_name = '_'.join((map(lambda x: x.lower(), words)))
|
|
43
|
+
setter01 = 'set_' + setter_name
|
|
44
|
+
|
|
45
|
+
setter_name = ''.join((map(lambda x: x.capitalize(), words)))
|
|
46
|
+
setter02 = 'set' + setter_name
|
|
47
|
+
|
|
48
|
+
return setter01, setter02
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
|
|
5
|
+
EPOCH = datetime.datetime.fromtimestamp(0, datetime.UTC)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_current_timestamp() -> int:
|
|
9
|
+
"""Creates a timestamp using the current date and time.
|
|
10
|
+
|
|
11
|
+
The timestamp returned is in milliseconds.
|
|
12
|
+
"""
|
|
13
|
+
current_date_time = datetime.datetime.now(datetime.UTC)
|
|
14
|
+
return _unix_time_millis(current_date_time)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_timestamp(dt: datetime.datetime) -> int:
|
|
18
|
+
"""Creates a timestamp using parameter dt.
|
|
19
|
+
:param dt The datetime object to convert to a timestamp
|
|
20
|
+
"""
|
|
21
|
+
return _unix_time_millis(dt)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _unix_time_millis(dt: datetime.datetime) -> int:
|
|
25
|
+
"""Converts a datetime object into unix time including milliseconds.
|
|
26
|
+
:param dt The datetime object to convert.
|
|
27
|
+
"""
|
|
28
|
+
ut = int((dt - EPOCH).total_seconds()) * 1000
|
|
29
|
+
ut += int(dt.microsecond / 1000)
|
|
30
|
+
return ut
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def prettify(path_name: str) -> str:
|
|
7
|
+
"""Makes all necessary stuff to get a usable path (specially under Windows)
|
|
8
|
+
|
|
9
|
+
:param path_name: Path to make pretty
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
if path_name:
|
|
13
|
+
# Remove backslashes from path
|
|
14
|
+
path_name = path_name.replace('\\', '/')
|
|
15
|
+
|
|
16
|
+
if path_name[0] == '~':
|
|
17
|
+
base_path = os.path.expanduser(path_name[0])
|
|
18
|
+
remaining_path = path_name[2:] if path_name[1] == '/' else path_name[1:]
|
|
19
|
+
path_name = os.path.join(base_path, remaining_path)
|
|
20
|
+
|
|
21
|
+
# and again
|
|
22
|
+
path_name = path_name.replace('\\', '/')
|
|
23
|
+
|
|
24
|
+
return path_name
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def home_directory():
|
|
28
|
+
home_dir = os.path.expanduser('~')
|
|
29
|
+
return home_dir
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def path_from_file(path_file_name: str) -> str:
|
|
33
|
+
"""Returns the path of the given file location string.
|
|
34
|
+
"""
|
|
35
|
+
path = os.path.abspath(os.path.dirname(path_file_name))
|
|
36
|
+
return path
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_directory_structure(path: str):
|
|
40
|
+
"""Creates one or more directories.
|
|
41
|
+
"""
|
|
42
|
+
os.makedirs(path, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def remove_directory(path):
|
|
46
|
+
from shutil import rmtree
|
|
47
|
+
|
|
48
|
+
rmtree(path, ignore_errors=True)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import urllib.parse as urlparse
|
|
5
|
+
import urllib3
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
from configobj import ConfigObj
|
|
9
|
+
from .path_helpers import prettify
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ResourceLoader(object):
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def load_from_locations(cls, filename: str, locations: Union[str, list[str]]) -> ConfigObj:
|
|
16
|
+
"""Searches for a resource file in the given locations and loads them.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
if isinstance(locations, str):
|
|
20
|
+
locations = [locations, ]
|
|
21
|
+
|
|
22
|
+
for location in locations:
|
|
23
|
+
url = urlparse.urlparse(location)
|
|
24
|
+
|
|
25
|
+
if url.scheme == 'home':
|
|
26
|
+
base_path = os.path.expanduser('~') + url.path
|
|
27
|
+
file_path = os.path.join(base_path, filename)
|
|
28
|
+
file_path = prettify(file_path)
|
|
29
|
+
|
|
30
|
+
if os.path.isfile(file_path):
|
|
31
|
+
properties = ConfigObj(file_path)
|
|
32
|
+
return properties
|
|
33
|
+
elif url.scheme in ('file', 'path'):
|
|
34
|
+
file_path = os.path.join(url.path, filename)
|
|
35
|
+
if os.path.isfile(file_path):
|
|
36
|
+
properties = ConfigObj(file_path)
|
|
37
|
+
return properties
|
|
38
|
+
elif url.scheme == 'http' or \
|
|
39
|
+
url.scheme == 'https':
|
|
40
|
+
if not location.endswith('/'):
|
|
41
|
+
location += '/'
|
|
42
|
+
file_path = location + filename
|
|
43
|
+
try:
|
|
44
|
+
http = urllib3.PoolManager()
|
|
45
|
+
r = http.request('GET', file_path)
|
|
46
|
+
file_content = r.data
|
|
47
|
+
properties = ConfigObj(file_content)
|
|
48
|
+
return properties
|
|
49
|
+
except NameError:
|
|
50
|
+
pass
|
|
51
|
+
return ConfigObj()
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def find_first_occurrence(cls, filename: str, locations: Union[str, list[str]]) -> tuple[str | None, int]:
|
|
55
|
+
""" Searches for a file in given locations.
|
|
56
|
+
|
|
57
|
+
Returns the first location where the file was found by returning the path and the location
|
|
58
|
+
index where the file was found.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
if isinstance(locations, str):
|
|
62
|
+
locations = [locations, ]
|
|
63
|
+
|
|
64
|
+
for index, location in enumerate(locations):
|
|
65
|
+
url = urlparse.urlparse(location)
|
|
66
|
+
|
|
67
|
+
if url.scheme == 'home':
|
|
68
|
+
base_path = os.path.expanduser('~') + url.path
|
|
69
|
+
file_path = os.path.join(base_path, filename)
|
|
70
|
+
file_path = prettify(file_path)
|
|
71
|
+
|
|
72
|
+
if os.path.isfile(file_path):
|
|
73
|
+
return prettify(base_path), index
|
|
74
|
+
elif url.scheme in ('file', 'path'):
|
|
75
|
+
file_path = os.path.join(url.path, filename)
|
|
76
|
+
if os.path.isfile(file_path):
|
|
77
|
+
return prettify(url.path), index
|
|
78
|
+
return None, -1
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def camel_case_split(string: str, lower_camel_case: bool = False) -> list:
|
|
7
|
+
"""Splits a camel-case formatted string into its words.
|
|
8
|
+
|
|
9
|
+
Example: 'ANiceDay' gets converted to ['A', 'Nice', 'Day'].
|
|
10
|
+
|
|
11
|
+
If lower_camel_case is specified, also the first expression is put into the list.
|
|
12
|
+
|
|
13
|
+
Credits to:
|
|
14
|
+
- https://www.geeksforgeeks.org/python-split-camelcase-string-to-individual-strings/
|
|
15
|
+
- https://stackoverflow.com/questions/29916065/how-to-do-camelcase-split-in-python
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
if not lower_camel_case:
|
|
19
|
+
return re.findall(r'[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))', string)
|
|
20
|
+
else:
|
|
21
|
+
idx = list(map(str.isupper, string))
|
|
22
|
+
# mark change of case
|
|
23
|
+
lst = [0]
|
|
24
|
+
for (i, (x, y)) in enumerate(zip(idx, idx[1:])):
|
|
25
|
+
if x and not y: # "Ul"
|
|
26
|
+
lst.append(i)
|
|
27
|
+
elif not x and y: # "lU"
|
|
28
|
+
lst.append(i + 1)
|
|
29
|
+
lst.append(len(string))
|
|
30
|
+
# for "lUl", index of "U" will pop twice, have to filter it
|
|
31
|
+
return [string[x:y] for x, y in zip(lst, lst[1:]) if x < y]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
|
|
5
|
+
EPOCH = datetime.datetime.fromtimestamp(0, datetime.UTC)
|
|
6
|
+
|
|
7
|
+
def get_time_in_milliseconds(dt: datetime.datetime = None) -> int:
|
|
8
|
+
"""Creates a timestamp using the current date and time.
|
|
9
|
+
|
|
10
|
+
If no parameters is given the actual UTC time is taken.
|
|
11
|
+
"""
|
|
12
|
+
if not dt:
|
|
13
|
+
dt = datetime.datetime.now(datetime.UTC)
|
|
14
|
+
|
|
15
|
+
ut = int((dt - EPOCH).total_seconds()) * 1000
|
|
16
|
+
ut += int(dt.microsecond / 1000)
|
|
17
|
+
return ut
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: thingsboard-common
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: ThingsBoard common package
|
|
5
|
+
Home-page: https://gitlab.com/things-board
|
|
6
|
+
Author: HES-SO Valais, School of Engineering, Sion
|
|
7
|
+
Author-email: thomas.sterren@hes-so.ch
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Project-URL: Bug Tracker, https://gitlab.com/things-board/thingsboard-common/-/issues
|
|
10
|
+
Project-URL: Source Code, https://gitlab.com/things-board/thingsboard-common
|
|
11
|
+
Project-URL: Documentation, https://gitlab.com/things-board/thingsboard-common#thingsboard-common
|
|
12
|
+
Keywords: ThingsBoard common library IoT cloud connector core utils
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Intended Audience :: Information Technology
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Operating System :: OS Independent
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: configobj
|
|
28
|
+
Requires-Dist: urllib3
|
|
29
|
+
Requires-Dist: tb-mqtt-client
|
|
30
|
+
Dynamic: author
|
|
31
|
+
Dynamic: author-email
|
|
32
|
+
Dynamic: classifier
|
|
33
|
+
Dynamic: home-page
|
|
34
|
+
Dynamic: keywords
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
Dynamic: project-url
|
|
37
|
+
Dynamic: requires-dist
|
|
38
|
+
Dynamic: summary
|
|
39
|
+
|
|
40
|
+
# ThingsBoard Common
|
|
41
|
+
|
|
42
|
+
> Note:
|
|
43
|
+
>
|
|
44
|
+
> This is not an official project of the [
|
|
45
|
+
ThingsBoard](https://thingsboard.io/) authors! Please refer to [ThingsBoard - Open-source IoT Platform](https://github.com/thingsboard) for official ThingsBoard projects!
|
|
46
|
+
|
|
47
|
+
This project serves as a toolkit allowing other projects to use the same packages/modules as codebase.
|
|
48
|
+
|
|
49
|
+
These projects are using the [ThingsBoard](https://thingsboard.io/) platform and extend it with additional functionality.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
thingsboard/__init__.py,sha256=76n4Ir0tUx-uCkvXK3u-jsWTx_SyyP-OAmv3Nf9OXp4,304
|
|
2
|
+
thingsboard/common/__init__.py,sha256=1nj8FJl6c4Wlq2zIWgjDZXtltHfHbj1vGCI0NZk1x3s,568
|
|
3
|
+
thingsboard/common/version.py,sha256=yzUT3AV_2JhkKU_bBGi7othAGr5HdYN-BBBkuh3l0GA,214
|
|
4
|
+
thingsboard/common/core/__init__.py,sha256=_O72SOav2A-q2gx9XuHDZM6ZOcLzu_5g7BGEuYjZztc,381
|
|
5
|
+
thingsboard/common/core/thread_monitor.py,sha256=G4EQ-TvzO2LowNaq93LKfz9yhQCDGHstO8iaMXakFGk,1732
|
|
6
|
+
thingsboard/common/core/threaded.py,sha256=PG5hItcADwjkyhcLvcsUsbcH6F4r-wWxxWK9FGHQ3SU,5859
|
|
7
|
+
thingsboard/common/utils/__init__.py,sha256=76n4Ir0tUx-uCkvXK3u-jsWTx_SyyP-OAmv3Nf9OXp4,304
|
|
8
|
+
thingsboard/common/utils/attribute_helpers.py,sha256=hdor2Fh2YEdLWuE2UkDIcwLalOSwsvHjvRBbTsJN4h0,1507
|
|
9
|
+
thingsboard/common/utils/datetime_helpers.py,sha256=4ZqMVFa_ZBJifT6jFkQzugigoWuS5A7N44MroEfmVgc,873
|
|
10
|
+
thingsboard/common/utils/path_helpers.py,sha256=BEbswIZyNdXMNm4lXwIUszbg9T7Fr3fAdxrgvh7TllA,1195
|
|
11
|
+
thingsboard/common/utils/resource_loader.py,sha256=M8HLQWndTjrk06P5i2wY1p-EinuozL-BiMzB_UYB9-g,2869
|
|
12
|
+
thingsboard/common/utils/string_helpers.py,sha256=tVRjZuijRhhd2G86Gbs6GtPNiVxO4FVZwPOPe0wSsbk,1124
|
|
13
|
+
thingsboard/common/utils/timestamp_helpers.py,sha256=zjZ3-xZvF-x2yjQm_DVljx8gLToTqyRzn3h0VNgzEtg,479
|
|
14
|
+
thingsboard_common-0.1.1.dist-info/licenses/LICENSE,sha256=kzvDVNE4q9rpOY-d0Fn61rXyTBtkgeRmZnRak2Nrvl4,1091
|
|
15
|
+
thingsboard_common-0.1.1.dist-info/METADATA,sha256=nGjuApSzV3jmmvm7h_4EJX7amcejBMLHhpVj6qsN3ys,2010
|
|
16
|
+
thingsboard_common-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
17
|
+
thingsboard_common-0.1.1.dist-info/top_level.txt,sha256=hIeFbajkoBwsAEzoI1q3cZpwD-MtNvckaJqYsKNggNA,12
|
|
18
|
+
thingsboard_common-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 HES-SO Valais
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
thingsboard
|