1# SPDX-License-Identifier: AGPL-3.0-or-later 2# Copyright (C) 2025 Dionisis Toulatos 3 4importre 5fromenumimportEnum 6fromtypingimportAnnotated 7 8frompydanticimportAfterValidator,BaseModelasPydanticBaseModel,ConfigDict 91011# Why in gods green earth does pydantic not allow to have an optional, non-nullable field12# that is not defined by default? Hence, this monstrosity of a custom singleton type hint below.13class_Unset(Enum):14TOKEN=None151617Unset=_Unset.TOKEN18"""A custom singleton type used by to indicate a value is unset."""1920typeOptional[T]=T|_Unset2122typeRequired=None# Required fields should always be defined like `Annotated[<actual type>, Required]`232425def_validate_regex_string(value:str)->str:26try:27re.compile(value)28returnvalue29exceptre.error:30raiseValueError(f"Invalid regex string: {value}")313233typeRegex=Annotated[str,AfterValidator(_validate_regex_string)]34"""A custom type alias used by :py:mod:`nirahmq` to validate a string is a valid regex pattern."""3536
[docs]37defsanitize_string(string:str)->str:38"""Sanitizes a string for use as an MQTT topic.3940 .. note:: The sanitization is not an MQTT restriction but a Home Assistant one.4142 :param str string: The string to be sanitized43 :return: The sanitized string44 :rtype: str45 """46# Replace whitespace character between non-whitespace ones with single underscore47string=re.sub(r"(\S)\s+(\S)",r"\1_\2",string)48# Replace non-valid characters with nothing (remove them)49# This handles any whitespaces that are at the beginning or at the end50string=re.sub(r"[^a-zA-Z0-9_-]+",'',string)51returnstring
5253
[docs]54defclamp[T](val:T,min_:T,max_:T)->T:55"""Clamp a value in the range :math:`[min\\_, max\\_]`.5657 :param T val: The value to be clamped58 :param T min\\_: The minimum allowed value (inclusive)59 :param T max\\_: The maximum allowed value (inclusive)60 :return: The clamped value61 :rtype: T62 """63returnmin(max(val,min_),max_)
6465
[docs]66classBaseModel(PydanticBaseModel):67"""A customized :py:class:`pydantic.BaseModel` class for use by the rest of the library."""6869model_config=ConfigDict(70validate_by_alias=True,71validate_by_name=True,72# use_enum_values=True,73validate_default=True,74strict=True,75extra='forbid'76)