Source code for aas_core3_2.common

"""Provide common functions shared among the modules."""

# This code has been automatically generated by aas-core-codegen.
# Do NOT edit or append.


import collections.abc
import re
from typing import Any, NoReturn, Optional, Sequence


[docs]def assert_never(value: NoReturn) -> NoReturn: """ Signal to mypy to perform an exhaustive matching. Please see the following page for more details: https://hakibenita.com/python-mypy-exhaustive-checking """ raise AssertionError(f"Unhandled value: {value} ({type(value).__name__})")
[docs]def try_to_cast_to_array_like(value: Any) -> Optional[Sequence[Any]]: """ Try to cast :paramref:`value` to something like a JSON array. A mapping and a set are refused, as neither is a JSON array, and so are a ``str``, a ``bytes`` and a ``bytearray``, which Python counts among the sequences but JSON does not. Only a sequence is admitted, and never an arbitrary iterable. A one-shot iterable such as a generator is spent by the first walk over it, so a verification followed by a serialization would silently give an empty array -- this function is the single place where all the modules agree on what an array is, so the refusal has to hold for all of them. >>> assert try_to_cast_to_array_like(True) is None >>> assert try_to_cast_to_array_like(0) is None >>> assert try_to_cast_to_array_like(2.2) is None >>> assert try_to_cast_to_array_like("hello") is None >>> assert try_to_cast_to_array_like(b"hello") is None >>> try_to_cast_to_array_like([1, 2]) [1, 2] >>> assert try_to_cast_to_array_like({"a": 3}) is None >>> assert try_to_cast_to_array_like(collections.OrderedDict()) is None >>> try_to_cast_to_array_like(range(1, 2)) range(1, 2) >>> try_to_cast_to_array_like((1, 2)) (1, 2) >>> assert try_to_cast_to_array_like({1, 2, 3}) is None >>> assert try_to_cast_to_array_like(iter([1, 2])) is None """ # NOTE (mristin): # A ``list`` is what :py:mod:`json` gives us, and ``isinstance`` against # a concrete class costs a fraction of ``isinstance`` against the abstract # :py:class:`collections.abc.Sequence`, so we shortcut it here. if isinstance(value, list): return value if isinstance(value, (str, bytes, bytearray)): return None if isinstance(value, collections.abc.Sequence): return value return None
#: Match an optional sign followed by the ASCII digits. #: #: Mind the explicit ``[0-9]``: ``\d`` would match a digit of any script. _SAFE_INT_RE = re.compile(r"[-+]?[0-9]+")
[docs]def parse_safe_int(text: str) -> int: """ Parse :paramref:`text` as a safe integer. The meta-model calls ``int`` on strings, and this is its transpilation. We are stricter than the built-in :py:func:`int` so that all the SDKs behave the same. We accept only an optional sign followed by the ASCII digits, and only the safe integers, *i.e.*, the integers within ``-(2 ** 53 - 1)`` and ``2 ** 53 - 1``, which a double-precision floating-point number represents exactly. :param text: to be parsed :return: parsed integer :raise: :py:class:`ValueError` if :paramref:`text` is not a safe integer >>> parse_safe_int("-0042") -42 """ # NOTE (mristin): # Mind that it is checked with ``fullmatch`` and not with ``match``: ``$`` # would also match just before a trailing newline. if _SAFE_INT_RE.fullmatch(text) is None: raise ValueError( f"Expected an optional sign followed by the ASCII digits, " f"but got: {text!r}" ) # NOTE (mristin): # We count the significant digits before we call ``int``, as the conversion # is quadratic in the length of the text, and Python refuses a text of more # than 4300 digits, leading zeros included. digits = text.lstrip("+-").lstrip("0") if len(digits) > 16: raise ValueError( f"Expected a safe integer, but got a text out of its range: {text!r}" ) value = int(digits) if len(digits) > 0 else 0 # NOTE (mristin): # This is 2 ** 53 - 1. if value > 9007199254740991: raise ValueError( f"Expected a safe integer, but got a text out of its range: {text!r}" ) return -value if text[0] == "-" else value
[docs]def try_to_convert_int_to_float(value: int) -> Optional[float]: """ Try to convert :paramref:`value` to the ``float`` which JSON prescribes. JSON knows a single numeric type, so an ``int`` has to become a ``float`` on its way into and out of a JSON-able value. The conversion has to be exact: a ``float`` which does not convert back to :paramref:`value` would quietly say a different number than the one which was given, so such a value is refused instead of rounded. Mind that a ``bool`` is an ``int`` in Python. The callers all sort out the booleans before they come here. >>> try_to_convert_int_to_float(1) 1.0 >>> try_to_convert_int_to_float(-3) -3.0 >>> assert try_to_convert_int_to_float(2 ** 53 + 1) is None >>> assert try_to_convert_int_to_float(10 ** 400) is None """ try: result = float(value) except OverflowError: # NOTE (mristin): # A Python ``int`` is unbounded, so one can be far too large for # a ``float`` to hold at all. return None if int(result) != value: return None return result
# This code has been automatically generated by aas-core-codegen. # Do NOT edit or append.