-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: jparisu <[email protected]>
- Loading branch information
jparisu
committed
Jun 26, 2023
1 parent
4631f5a
commit fc1a5dc
Showing
15 changed files
with
801 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima). | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
""" | ||
This file contains utils to get information regarding the stack of the execution. | ||
It allows to get the module, class or function name where the execution currently is. | ||
""" | ||
|
||
import inspect | ||
|
||
|
||
def get_calling_frame(depth: int = 1): | ||
# Get the calling frame | ||
calling_frame = inspect.currentframe() | ||
|
||
# Move up the stack frames based on depth | ||
for _ in range(depth): | ||
calling_frame = calling_frame.f_back | ||
|
||
return calling_frame | ||
|
||
|
||
def get_function_name(depth: int = 1, extend: bool = False) -> str: | ||
|
||
if not extend: | ||
# Get the calling frame | ||
calling_frame = get_calling_frame(depth=depth + 1) | ||
|
||
# Retrieve the function name | ||
return inspect.getframeinfo(calling_frame).function | ||
|
||
else: | ||
return get_context(depth + 1) | ||
|
||
|
||
def is_inside_class(depth: int = 1) -> bool: | ||
# Get the calling frame | ||
calling_frame = get_calling_frame(depth=depth + 1) | ||
|
||
# Check if the frame is there is a self or a cls variable | ||
return "self" in calling_frame.f_locals or "cls" in calling_frame.f_locals | ||
|
||
|
||
def get_class_name(depth: int = 1, extend: bool = False) -> str: | ||
# Get the calling frame | ||
calling_frame = get_calling_frame(depth=depth + 1) | ||
|
||
result = None | ||
|
||
# Retrieve the class name if there is a self variable | ||
self_var = calling_frame.f_locals.get("self") | ||
cls_var = calling_frame.f_locals.get("cls") | ||
if self_var is not None: | ||
result = self_var.__class__.__name__ | ||
|
||
# Retrieve the class name if there is a cls variable | ||
elif cls_var is not None: | ||
result = cls_var.__name__ | ||
|
||
if result is None or not extend: | ||
return result | ||
|
||
else: | ||
return get_module_name(depth=depth + 1) + result | ||
|
||
|
||
def get_module_name(depth: int = 1) -> str: | ||
# Get the calling frame | ||
calling_frame = get_calling_frame(depth=depth + 1) | ||
|
||
# Retrieve the module name | ||
module_name = inspect.getmodule(calling_frame).__name__ | ||
return module_name | ||
|
||
|
||
def get_context(depth: int = 1) -> str: | ||
# Retrieve the module name | ||
ctx_name = get_module_name(depth + 1) | ||
|
||
# If it is in a class, return its class name | ||
class_name = get_class_name(depth + 1) | ||
if class_name is not None: | ||
ctx_name += '.' + class_name | ||
|
||
ctx_name += '.' + get_function_name(depth + 1) | ||
|
||
return ctx_name | ||
|
||
|
||
def get_variable_module(v) -> str: | ||
return inspect.getmodule(v).__name__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima). | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
""" | ||
This file contains utils to debug python code. | ||
""" | ||
|
||
import sys | ||
|
||
from py_utils.decorator.oop import pure_virtual | ||
from py_utils.logging.log_utils import logger, logging | ||
|
||
|
||
class IntrospectionInformation(): | ||
|
||
@pure_virtual | ||
def __init__(self, obj, recursive: int, privates: bool): | ||
pass | ||
|
||
@pure_virtual | ||
def type(self): | ||
pass | ||
|
||
@pure_virtual | ||
def __str__(self): | ||
pass | ||
|
||
@pure_virtual | ||
def pretty_print(self, indent: int): | ||
pass | ||
|
||
|
||
class ModuleIntrospectionInformation(IntrospectionInformation): | ||
|
||
def __init__(self, module): | ||
|
||
if not isinstance(module, type(sys)): | ||
raise TypeError(f'Object {module} not of Module type.') | ||
|
||
self.name_ = module.__name__ | ||
self.importables_ = [] | ||
for name in dir(module): | ||
self.importables_.append((name, type(getattr(module, name)))) | ||
|
||
def __str__(self): | ||
|
||
|
||
|
||
def get_module_introspection(obj) -> dict: | ||
result = {} | ||
|
||
if not isinstance(obj, type(sys)): | ||
return result | ||
|
||
result['name'] = obj.__name__ | ||
result['importables'] = [] | ||
for name in dir(obj): | ||
importables.append((name, type(getattr(obj, name)))) | ||
|
||
logger.log(debug_level, f'{{MODULE: <{module_name}>; Importables: <{importables}>}}') | ||
|
||
|
||
def debug_module_introspection(obj, debug_level: int = logging.DEBUG): | ||
if not isinstance(obj, type(sys)): | ||
return | ||
|
||
module_name = obj.__name__ | ||
importables = [] | ||
for name in dir(obj): | ||
importables.append((name, type(getattr(obj, name)))) | ||
|
||
logger.log(debug_level, f'{{MODULE: <{module_name}>; Importables: <{importables}>}}') | ||
|
||
|
||
def debug_class_introspection(obj, debug_level: int = logging.DEBUG): | ||
"""Log in debug introspection information regarding an object.""" | ||
if not isinstance(obj, type): | ||
return | ||
|
||
# If module | ||
class_name = obj.__name__ | ||
methods = [method_name for method_name in dir(obj) if callable(getattr(obj, method_name))] | ||
attributes = [method_name for method_name in dir(obj) if not callable(getattr(obj, method_name))] | ||
parent_classes = obj.__bases__ | ||
logger.log(debug_level, f'{{CLASS: <{class_name}>; Methods: <{methods}>; Attributes: <{attributes}>}}; Bases: <{parent_classes}>') | ||
|
||
|
||
def debug_object_introspection(obj, debug_level: int = logging.DEBUG): | ||
"""Log in debug introspection information regarding an object.""" | ||
# If module | ||
class_name = type(obj).__name__ | ||
methods = [method_name for method_name in dir(obj) if callable(getattr(obj, method_name))] | ||
attributes = [method_name for method_name in dir(obj) if not callable(getattr(obj, method_name))] | ||
to_str = obj.__str__() | ||
logger.log(debug_level, f'{{OBJECT: <{to_str}>; Class: <{class_name}>; Methods: <{methods}>; Attributes: <{attributes}>}}') | ||
|
||
|
||
def debug_variable_introspection(obj, debug_level: int = logging.DEBUG): | ||
"""Log in debug introspection information regarding an object.""" | ||
# If module | ||
if isinstance(obj, type(sys)): | ||
debug_module_introspection(obj, debug_level) | ||
elif isinstance(obj, type): | ||
debug_class_introspection(obj, debug_level) | ||
else: | ||
debug_object_introspection(obj, debug_level) | ||
|
||
|
||
def debug_function_decorator( | ||
debug_level: int = logging.DEBUG): | ||
"""Decorator to debug information regarding start, arguments and finish of a function.""" | ||
def decorator(func): | ||
def wrapper(*args, **kwargs): | ||
logger.log(debug_level, f'Function <{func.__name__}> called with arguments: <{args}>, <{kwargs}>') | ||
result = func(*args, **kwargs) | ||
logger.log(debug_level, f'Function <{func.__name__}> finished with return: <{result}>') | ||
return result | ||
return wrapper | ||
|
||
if callable(debug_level): | ||
func = debug_level | ||
debug_level = logging.DEBUG | ||
return decorator(func) | ||
|
||
else: | ||
return decorator | ||
|
||
|
||
def debug_separator( | ||
debug_level: int = logging.DEBUG): | ||
print() | ||
logger.log(debug_level, '####################################################################\n') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima). | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from functools import wraps | ||
|
||
|
||
class CustomFunctionDecorator(): | ||
|
||
def __init__(self, *args, **kwargs): | ||
self.func_ = None | ||
self.ctor_args_ = args | ||
self.ctor_kwargs_ = kwargs | ||
|
||
# If the decorator has no arguments, use the func argument passed | ||
if (len(args) == 1 and callable(args[0]) and len(kwargs) == 0): | ||
self.func_ = args[0] | ||
self.ctor_args_ = () # No arguments passed, so remove | ||
|
||
# If the decorator has arguments, check if any kwargs sets any of the internal attributes | ||
else: | ||
for attr in dir(self): | ||
if attr in kwargs.keys(): | ||
setattr(self, attr, kwargs[attr]) | ||
|
||
def __call__(self, *args, **kwargs): | ||
# If decorator created without arguments, function already available | ||
# Also it means this is calling the actual function already | ||
if self.func_ is not None: | ||
return self.wrapper(self.func_, *args, **kwargs) | ||
|
||
# If created with args, this is only calling to set the function | ||
else: | ||
self.func_ = args[0] | ||
return self.__call__ | ||
|
||
def wrapper(self, func, *args, **kwargs): | ||
# Call the function with the provided arguments | ||
return func(*args, **kwargs) | ||
|
||
def ctor_had_arguments(self): | ||
# Call the function with the provided arguments | ||
return len(self.ctor_args_) > 0 or len(self.ctor_kwargs_) > 0 |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima). | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
""" | ||
This file contains decorators for debug. | ||
""" | ||
|
||
from functools import wraps | ||
|
||
from py_utils.logging.log_utils import logger, logging | ||
from py_utils.decorators.CustomFunctionDecorator import CustomFunctionDecorator | ||
from py_utils.debugging.inspection import get_variable_module | ||
|
||
|
||
def debug_function_decorator( | ||
debug_level: int = logging.DEBUG): | ||
"""Decorator to debug information regarding start, arguments and finish of a function.""" | ||
@wraps(debug_level) | ||
def decorator(func): | ||
|
||
@wraps(func) | ||
def wrapper(*args, **kwargs): | ||
logger.log( | ||
debug_level, | ||
f'Function <{func.__name__}> called with arguments: <{args}>, <{kwargs}>') | ||
result = func(*args, **kwargs) | ||
logger.log( | ||
debug_level, | ||
f'Function <{func.__name__}> finished with return: <{result}>') | ||
return result | ||
return wrapper | ||
|
||
if callable(debug_level): | ||
func = debug_level | ||
debug_level = logging.DEBUG | ||
return decorator(func) | ||
|
||
else: | ||
return decorator | ||
|
||
|
||
class DebugFunctionDecorator(CustomFunctionDecorator): | ||
|
||
def __init__(self, *args, **kwargs): | ||
|
||
# Default values (may be changed by kwargs) | ||
self.debug_level = logging.DEBUG | ||
self.add_module = True | ||
|
||
super().__init__(*args, **kwargs) | ||
|
||
def wrapper(self, func, *args, **kwargs): | ||
|
||
if self.add_module: | ||
fun_str = get_variable_module(func) + '.' + func.__name__ | ||
else: | ||
fun_str = func.__name__ | ||
|
||
logger.log( | ||
self.debug_level, | ||
f'Function <{fun_str}> called with arguments: <{args}>, <{kwargs}>') | ||
result = func(*args, **kwargs) | ||
logger.log( | ||
self.debug_level, | ||
f'Function <{fun_str}> finished with return: <{result}>') | ||
return result |
Oops, something went wrong.