Skip to content

Commit fb7d0f4

Browse files
authored
Add Python utils with Timer and WaitHandler classes (#63)
* Add Python utils with Timer and WaitHandler classes Signed-off-by: jparisu <javierparis@eprosima.com> * Add BooleanWaitHandler Signed-off-by: jparisu <javierparis@eprosima.com> * Add features to py utils Signed-off-by: jparisu <javierparis@eprosima.com> * apply suggestions Signed-off-by: jparisu <javierparis@eprosima.com> * Add IntWaitHandler tests Signed-off-by: jparisu <javierparis@eprosima.com> * Document code Signed-off-by: jparisu <javierparis@eprosima.com> * document new feature Signed-off-by: jparisu <javierparis@eprosima.com> * fix py linter Signed-off-by: jparisu <javierparis@eprosima.com> --------- Signed-off-by: jparisu <javierparis@eprosima.com>
1 parent 47d01a5 commit fb7d0f4

21 files changed

Lines changed: 825 additions & 0 deletions

py_utils/package.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0"?>
2+
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3+
<package format="3">
4+
<name>py_utils</name>
5+
<version>0.3.0</version>
6+
<description>
7+
*eprosima* Dev Utils Module is a Python implementation for some generic utils methods and classes.
8+
</description>
9+
<maintainer email="RaulSanchezMateos@eprosima.com">Raul Sánchez-Mateos</maintainer>
10+
<maintainer email="javierparis@eprosima.com">Javier París</maintainer>
11+
<maintainer email="juanlopez@eprosima.com">Juan López</maintainer>
12+
<license file="LICENSE">Apache 2.0</license>
13+
14+
<url type="website">https://www.eprosima.com/</url>
15+
<url type="bugtracker">https://github.com/eProsima/dev-utils/issues</url>
16+
<url type="repository">https://github.com/eProsima/dev-utils</url>
17+
18+
<test_depend>python3-pytest</test_depend>
19+
20+
<export>
21+
<build_type>ament_python</build_type>
22+
</export>
23+
</package>

py_utils/py_utils/__init__.py

Whitespace-only changes.

py_utils/py_utils/debugging/__init__.py

Whitespace-only changes.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
This file contains utils to debug python code.
17+
"""
18+
19+
from py_utils.logging.log_utils import logger
20+
21+
22+
def debug_object_introspection(obj):
23+
"""Log in debug introspection information regarding an object."""
24+
class_name = type(obj).__name__
25+
methods = [method_name for method_name in dir(obj) if callable(getattr(obj, method_name))]
26+
logger.debug(f'{{Class: <{class_name}; Methods: {methods}}}')
27+
28+
29+
def debug_function_decorator(
30+
func):
31+
"""Decorator to debug information regarding start, arguments and finish of a function."""
32+
def wrapper(*args, **kwargs):
33+
logger.debug(f'Function <{func.__name__}> called with arguments: <{args}>, <{kwargs}>')
34+
result = func(*args, **kwargs)
35+
logger.debug(f'Function <{func.__name__}> finished')
36+
return result
37+
38+
return wrapper

py_utils/py_utils/logging/__init__.py

Whitespace-only changes.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
This file contains utils to use native python Log.
17+
18+
Implement a Custom Logger class and creates a global object of such class.
19+
In order to use this log handler, import logger object from this module and use it
20+
as any other Logger:
21+
- logger.info(...)
22+
- logger.debug(...)
23+
- logger.log(logging.DEBUG, ...)
24+
25+
In order to set a different log level, use set_log_level or activate_debug logger methods.
26+
"""
27+
28+
import logging
29+
30+
31+
class CustomLogger(logging.Logger):
32+
"""
33+
Class to create a global Logger shared by the whole process.
34+
It inherits from native logging.Logger and implement auxiliary methods.
35+
"""
36+
37+
def __init__(
38+
self,
39+
log_level=logging.INFO,
40+
logger_name: str = 'GLOBAL_LOG'):
41+
"""Creates an object of Logger class and set log level and default format."""
42+
super().__init__(logger_name)
43+
self.set_format()
44+
self.set_log_level(log_level)
45+
46+
def set_format(
47+
self,
48+
format: str = '[%(asctime)s][%(name)s][%(levelname)s] %(message)s'):
49+
"""Modifies format for logger."""
50+
l_format = logging.Formatter(format)
51+
l_handler = logging.StreamHandler()
52+
l_handler.setFormatter(l_format)
53+
self.addHandler(l_handler)
54+
55+
def set_log_level(
56+
self,
57+
log_level):
58+
"""Modifies logger level."""
59+
self.setLevel(log_level)
60+
61+
def activate_debug(self):
62+
"""Modifies logger level so it accepts everything equalt or greater than DEBUG."""
63+
self.set_log_level(logging.DEBUG)
64+
65+
66+
"""Global instance of logger using CustomLogger class."""
67+
logger = CustomLogger()

py_utils/py_utils/time/Timer.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
Timer class implementation.
17+
"""
18+
19+
import time
20+
21+
22+
class Timer:
23+
"""
24+
A simple timer class for measuring elapsed time.
25+
26+
Attributes:
27+
_start_time (float): The start time of the timer in seconds since the epoch.
28+
29+
NOTE: Chat GPT has written all this (including tests (⊙_⊙) )
30+
"""
31+
32+
def __init__(self):
33+
"""
34+
Initializes a new Timer instance and sets the start time to the current time.
35+
"""
36+
self._start_time = time.time()
37+
38+
def reset(self):
39+
"""
40+
Resets the timer by setting the start time to the current time.
41+
"""
42+
self._start_time = time.time()
43+
44+
def elapsed(self):
45+
"""
46+
Calculates the elapsed time since the start time.
47+
48+
Returns:
49+
float: The elapsed time in seconds.
50+
"""
51+
return time.time() - self._start_time

py_utils/py_utils/time/__init__.py

Whitespace-only changes.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
BooleanWaitHandler class implementation.
17+
"""
18+
19+
from py_utils.wait.WaitHandler import WaitHandler
20+
21+
22+
class BooleanWaitHandler(WaitHandler):
23+
"""
24+
BooleanWaitHandler is a WaitHandler that waits on a boolean value.
25+
Whenever this value is True, threads awake. This is also called open.
26+
Whenever it is False, threads wait. This is also called close.
27+
"""
28+
29+
def __init__(
30+
self,
31+
enabled: bool,
32+
init_value: bool):
33+
"""
34+
BooleanWaitHandler ctor.
35+
36+
Arguments:
37+
enabled (bool): whether the handler must start enabled or disabled.
38+
init_value (bool): whether the handler starts opened or closed.
39+
"""
40+
super().__init__(enabled, init_value)
41+
42+
def open(self):
43+
"""Set internal value to True: awake all waiting threads."""
44+
self.set_value(True)
45+
46+
def close(self):
47+
"""Set internal value to False: new threads will wait."""
48+
self.set_value(False)
49+
50+
def wait(
51+
self,
52+
timeout: float = None):
53+
"""
54+
Wait till any thread open this object.
55+
56+
Specialization of parent method with a specific predicate.
57+
58+
Arguments:
59+
timeout: if thread awaken because timeout reached.
60+
"""
61+
return super().wait(predicate=(lambda v: v), timeout=timeout)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
IntWaitHandler class implementation.
17+
"""
18+
19+
from py_utils.wait.WaitHandler import WaitHandler
20+
21+
22+
class IntWaitHandler(WaitHandler):
23+
"""
24+
IntWaitHandler is a WaitHandler that waits on a int value.
25+
It implements functions to wait for a specific value, or wait for the value to
26+
be greater or greater equal.
27+
It also implements functions to increase or decrease the internal value.
28+
29+
This is useful, for instance, to wait for an event to happen N or more times.
30+
31+
For more specific set or wait methods, implement a child class.
32+
"""
33+
34+
def __init__(
35+
self,
36+
enabled: bool,
37+
init_value: int = 0):
38+
"""
39+
IntWaitHandler ctor.
40+
41+
Arguments:
42+
enabled (bool): whether the handler must start enabled or disabled.
43+
init_value (int): initial value of the internal integer.
44+
"""
45+
super().__init__(enabled, init_value)
46+
47+
def increase(self, value: int = 1):
48+
"""Increment the internal integer in value."""
49+
with self._lock:
50+
self._value += value
51+
self._condition.notify_all()
52+
53+
def decrease(self, value: int = 1):
54+
"""Decrease the internal integer in value."""
55+
with self._lock:
56+
self._value -= value
57+
self._condition.notify_all()
58+
59+
def wait_equal(self, value, timeout=None):
60+
"""
61+
Wait till internal object is equal the given value.
62+
63+
Specialization of parent method with a specific predicate.
64+
65+
Arguments:
66+
value: value to wait to be equal the internal integer.
67+
timeout: if thread awaken because timeout reached.
68+
"""
69+
return super().wait(predicate=(lambda v: value == v), timeout=timeout)
70+
71+
def wait_greater_equal(self, value, timeout=None):
72+
"""Wait for the internal integer to be greater or equal the value given."""
73+
return super().wait(predicate=(lambda v: v >= value), timeout=timeout)
74+
75+
def wait_greater(self, value, timeout=None):
76+
"""Wait for the internal integer to be greater than value given."""
77+
return super().wait(predicate=(lambda v: v > value), timeout=timeout)

0 commit comments

Comments
 (0)