Source code for hvl_ccb.utils.validation

#  Copyright (c) ETH Zurich, SIS ID and HVL D-ITET
#
"""

"""
import logging
import socket
from ipaddress import IPv4Address, IPv6Address, ip_address
from logging import Logger
from typing import Optional, Sequence, Union

import numpy as np

from hvl_ccb.utils.typing import Number


[docs] def validate_number( x_name: str, x: object, limits: Optional[tuple] = (None, None), number_type: Union[type[Number], tuple[type[Number], ...]] = (int, float), logger: Optional[Logger] = None, ) -> None: """ Validate if given input `x` is a number of given `number_type` type, with value between given `limits[0]` and `limits[1]` (inclusive), if not `None`. For array-like objects (npt.NDArray, list, tuple, dict) it is checked if all elements are within the limits and have the correct type. :param x_name: string name of the validate input, use for the error message :param x: an input object to validate as number of given type within given range :param logger: logger of the calling submodule :param limits: [lower, upper] limit, with `None` denoting no limit: [-inf, +inf] :param number_type: expected type or tuple of types of a number, by default `(int, float)` :raises TypeError: when the validated input does not have expected type :raises ValueError: when the validated input has correct number type but is not within given range or has wrong input limits """ if logger is None: logger = logging.getLogger(__name__) if limits is None: limits = (-np.inf, np.inf) if limits[0] is None: limits = (-np.inf, limits[1]) if limits[1] is None: limits = (limits[0], np.inf) if limits[0] >= limits[1]: msg = ( f"Upper limit {limits[1]} should be greater than " f"the lower limit {limits[0]}" ) logger.error(msg) raise ValueError(msg) data_type = type(x) if not isinstance(number_type, Sequence): number_type = (number_type,) if isinstance(x, (float, int)): if not isinstance(x, number_type): msg = ( f"{x_name} = {x} has to be of type " f"{' or '.join(nt.__name__ for nt in number_type)}" ) logger.error(msg) raise TypeError(msg) elif isinstance(x, (list, tuple, dict, np.ndarray)): if isinstance(x, dict): x = np.asarray(list(x.values())) x = np.asarray(x) if x.dtype not in number_type: msg = ( f"{x_name} = {x} needs to include only numbers type " f"{' or '.join(nt.__name__ for nt in number_type)}" ) logger.error(msg) raise TypeError(msg) else: msg = ( f"{x_name} = {x} must be an Integer, a Float, a Tuple, a List, " f"a Dictionary or a Numpy array, but the received type is {data_type}." ) logger.error(msg) raise TypeError(msg) if np.any(x < limits[0]) or np.any(x > limits[1]): if np.isinf(limits[0]): suffix = f"less or equal than {limits[1]}" elif np.isinf(limits[1]): suffix = f"greater or equal than {limits[0]}" else: suffix = f"between {limits[0]} and {limits[1]} inclusive" msg = f"{x_name} = {x} has to be {suffix}" logger.error(msg) raise ValueError(msg)
[docs] def validate_bool(x_name: str, x: object, logger: Optional[Logger] = None) -> None: """ Validate if given input `x` is a `bool`. :param x_name: string name of the validate input, use for the error message :param x: an input object to validate as boolean :param logger: logger of the calling submodule :raises TypeError: when the validated input does not have boolean type """ if logger is None: logger = logging.getLogger(__name__) if not isinstance(x, bool): msg = f"{x_name} = {x} has to of type bool" logger.error(msg) raise TypeError(msg)
[docs] def validate_and_resolve_host( host: Union[str, IPv4Address, IPv6Address], logger: Optional[Logger] = None ) -> str: if logger is None: logger = logging.getLogger(__name__) if host is None: msg = "A host has to be provided." logger.error(msg) raise AttributeError(msg) if isinstance(host, (IPv4Address, IPv6Address)): host = str(host) else: try: host = str(ip_address(host)) except ValueError: try: host = socket.gethostbyname(host) except (socket.gaierror, UnicodeError): # UnicodeError: "192..168.01" # socket.gaierror: "192.168.0.1000" # socket.gaierror: "itet-hvl-01" msg = ( "The value of host is neither an IPv4 nor an IPv6 address nor a " f"hostname that can be resolved. The received value is: '{host}'." ) logger.error(msg) raise ValueError(msg) return str(host)
[docs] def validate_tcp_port(port: int, logger: Optional[Logger] = None): if logger is None: logger = logging.getLogger(__name__) if port is None: msg = "A port has to be provided." logger.error(msg) raise AttributeError(msg) # TCP ports are in the range between and including 0 and (2**16)-1, but 0 is # reserved and shall not be used validate_number("port", port, (1, 2**16 - 1), int, logger)