Source code for bo4e.validators

"""
Contains validators for BO s and COM s classes.
"""
import re
from datetime import datetime
from typing import Any, Dict, Optional, Protocol

from bo4e.enum.aufabschlagstyp import AufAbschlagstyp

# pylint: disable=unused-argument
from bo4e.enum.waehrungseinheit import Waehrungseinheit


[docs]def einheit_only_for_abschlagstyp_absolut(cls, value: Waehrungseinheit, values: Dict[str, Any]) -> Waehrungseinheit: # type: ignore[no-untyped-def] """ Check that einheit is only there if abschlagstyp is absolut. Currently, (2021-12-15) only used in COM AufAbschlag. """ if value and (not values["auf_abschlagstyp"] or (values["auf_abschlagstyp"] != AufAbschlagstyp.ABSOLUT)): raise ValueError("Only state einheit if auf_abschlagstyp is absolute.") return value
# pylint:disable=too-few-public-methods class _VonBisType(Protocol): """ a protocol for all classes that have an inclusive start and exclusive end """ @staticmethod def _get_inclusive_start(values: Dict[str, Any]) -> Optional[datetime]: """ should return the inclusive start of the timeslice """ # def _get_exclusive_end(self) -> Optional[datetime]: # """ # should return the exclusive end of the timeslice # """
[docs]def check_bis_is_later_than_von(cls, value: datetime, values: Dict[str, Any]): # type: ignore[no-untyped-def] """ assert that 'bis' is later than 'von' """ # we want access to private methods here because these helper methods should be "hidden" start = cls._get_inclusive_start(values) # pylint: disable=protected-access end = value if start and end and not end >= start: raise ValueError(f"The end '{end}' has to be later than the start '{start}'") return value
# pylint:disable=line-too-long #: a regular expression that should match all OBIS Kennziffern OBIS_PATTERN = r"((1)-((?:[0-5]?[0-9])|(?:6[0-5])):((?:[1-8]|99))\.((?:6|8|9|29))\.([0-9]{1,2})|(7)-((?:[0-5]?[0-9])|(?:6[0-5])):(.{1,2})\.(.{1,2})\.([0-9]{1,2}))" # TODO: Maybe create custom data type for OBIS. See https://pydantic-docs.helpmanual.io/usage/types/#custom-data-types _malo_id_pattern = re.compile(r"^[1-9]\d{10}$") # pylint: disable=unused-argument
[docs]def validate_marktlokations_id(cls, marktlokations_id: str, values: Dict[str, Any]) -> str: # type: ignore[no-untyped-def] """ A validator for marktlokations IDs """ if not marktlokations_id: raise ValueError("The marktlokations_id must not be empty.") if not _malo_id_pattern.match(marktlokations_id): raise ValueError(f"The Marktlokations-ID '{marktlokations_id}' does not match {_malo_id_pattern.pattern}") expected_checksum = _get_malo_id_checksum(marktlokations_id) actual_checksum = marktlokations_id[10:11] if expected_checksum != actual_checksum: # pylint: disable=line-too-long raise ValueError( f"The Marktlokations-ID '{marktlokations_id}' has checksum '{actual_checksum}' but '{expected_checksum}' was expected." ) return marktlokations_id
def _get_malo_id_checksum(malo_id: str) -> str: """ Get the checksum of a marktlokations id. a) Quersumme aller Ziffern in ungerader Position b) Quersumme aller Ziffern auf gerader Position multipliziert mit 2 c) Summe von a) und b) d) Differenz von c) zum nächsten Vielfachen von 10 (ergibt sich hier 10, wird die Prüfziffer 0 genommen https://bdew-codes.de/Content/Files/MaLo/2017-04-28-BDEW-Anwendungshilfe-MaLo-ID_Version1.0_FINAL.PDF :param self: :return: the checksum as string """ odd_checksum: int = 0 even_checksum: int = 0 # start counting at 1 to be consistent with the above description # of "even" and "odd" but stop at tenth digit. for i in range(1, 11): digit = malo_id[i - 1 : i] if i % 2 - 1 == 0: odd_checksum += int(digit) else: even_checksum += 2 * int(digit) result: int = (10 - ((even_checksum + odd_checksum) % 10)) % 10 return str(result)