123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- """ Various coding utils (e.g. around function decoration) """
- from __future__ import annotations
- import functools
- import time
- import inspect
- import importlib
- import pkgutil
- from flask import current_app
- def delete_key_recursive(value, key):
- """Delete key in a multilevel dictionary"""
- if isinstance(value, dict):
- if key in value:
- del value[key]
- for k, v in value.items():
- value[k] = delete_key_recursive(v, key)
- # value.pop(key, None)
- elif isinstance(value, list):
- for i, v in enumerate(value):
- value[i] = delete_key_recursive(v, key)
- return value
- def optional_arg_decorator(fn):
- """
- A decorator which _optionally_ accepts arguments.
- So a decorator like this:
- @optional_arg_decorator
- def register_something(fn, optional_arg = 'Default Value'):
- ...
- return fn
- will work in both of these usage scenarios:
- @register_something('Custom Name')
- def custom_name():
- pass
- @register_something
- def default_name():
- pass
- Thanks to https://stackoverflow.com/questions/3888158/making-decorators-with-optional-arguments#comment65959042_24617244
- """
- def wrapped_decorator(*args):
- if len(args) == 1 and callable(args[0]):
- return fn(args[0])
- else:
- def real_decorator(decoratee):
- return fn(decoratee, *args)
- return real_decorator
- return wrapped_decorator
- def sort_dict(unsorted_dict: dict) -> dict:
- sorted_dict = dict(sorted(unsorted_dict.items(), key=lambda item: item[0]))
- return sorted_dict
- # This function is used for sensors_to_show in follow-up PR it will be moved and renamed to flatten_sensors_to_show
- def flatten_unique(nested_list_of_objects: list) -> list:
- """
- Get unique sensor IDs from a list of `sensors_to_show`.
- Handles:
- - Lists of sensor IDs
- - Dictionaries with a `sensors` key
- - Nested lists (one level)
- Example:
- Input:
- [1, [2, 20, 6], 10, [6, 2], {"title":None,"sensors": [10, 15]}, 15]
- Output:
- [1, 2, 20, 6, 10, 15]
- """
- all_objects = []
- for s in nested_list_of_objects:
- if isinstance(s, list):
- all_objects.extend(s)
- elif isinstance(s, dict):
- all_objects.extend(s["sensors"])
- else:
- all_objects.append(s)
- return list(dict.fromkeys(all_objects).keys())
- def timeit(func):
- """Decorator for printing the time it took to execute the decorated function."""
- @functools.wraps(func)
- def new_func(*args, **kwargs):
- start_time = time.time()
- result = func(*args, **kwargs)
- elapsed_time = time.time() - start_time
- print(f"{func.__name__} finished in {int(elapsed_time * 1_000)} ms")
- return result
- return new_func
- def deprecated(alternative, version: str | None = None):
- """Decorator for printing a warning error.
- alternative: importable object to use as an alternative to the function/method decorated
- version: version in which the function will be sunset
- """
- def decorator(func):
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- current_app.logger.warning(
- f"The method or function {func.__name__} is deprecated and it is expected to be sunset in version {version}. Please, switch to using {inspect.getmodule(alternative).__name__}:{alternative.__name__} to suppress this warning."
- )
- return func(*args, **kwargs)
- return wrapper
- return decorator
- def find_classes_module(module, superclass):
- classes = []
- module_object = importlib.import_module(f"{module}")
- module_classes = inspect.getmembers(module_object, inspect.isclass)
- classes.extend(
- [
- (class_name, klass)
- for class_name, klass in module_classes
- if issubclass(klass, superclass) and klass != superclass
- ]
- )
- return classes
- def find_classes_modules(module, superclass, skiptest=True):
- classes = []
- base_module = importlib.import_module(module)
- # root (__init__.py) of the base module
- classes += find_classes_module(module, superclass)
- for submodule in pkgutil.iter_modules(base_module.__path__):
- if skiptest and ("test" in f"{module}.{submodule.name}"):
- continue
- if submodule.ispkg:
- classes.extend(
- find_classes_modules(
- f"{module}.{submodule.name}", superclass, skiptest=skiptest
- )
- )
- else:
- classes += find_classes_module(f"{module}.{submodule.name}", superclass)
- return classes
- def get_classes_module(module, superclass, skiptest=True) -> dict:
- return dict(find_classes_modules(module, superclass, skiptest=skiptest))
|