juham_core.juham_ts — juham-shelly documentation

# Source code for juham_core.juham_ts

    import traceback
    from typing import Any, Dict, Optional, cast, Union
    from typing_extensions import override
    from masterpiece.timeseries import TimeSeries, Measurement
    from .juham import Juham

    class JuhamTs(Juham):
        """Base class for automation objects with timeseries database
        support.

        To configure the class to use a specific timeseries database implementation set
        the `database_class_id` class attribute. When instantiated the object will instantiate
        the given MQTT object with it.
        """

        database_class_id: str = ""
        write_attempts: int = 3

        def __init__(self, name: str = "") -> None:
            super().__init__(name)
            self.database_client: Optional[Union[TimeSeries, None]] = None

        @override
        def to_dict(self) -> Dict[str, Any]:
            data: Dict[str, Any] = super().to_dict()
            if self.database_client is not None:
                data["_database"] = {"db_client": self.database_client.to_dict()}
            return data

        @override
        def from_dict(self, data: Dict[str, Any]) -> None:
            super().from_dict(data)
            if "_database" in data:
                value = data["_database"]["db_client"]
                self.database_client = cast(
                    Optional[TimeSeries], Juham.instantiate(value["_class"], self.name)
                )
                if self.database_client is not None and "_object" in value:
                    self.database_client.from_dict(value)

        def initialize(self) -> None:
            self.init_database(self.name)
            super().initialize()

        def measurement(self, name: str) -> Measurement:
            timeseries: TimeSeries = cast(TimeSeries, self.database_client)
            return timeseries.measurement(name)

        def init_database(self, name: str) -> None:
            if (
                JuhamTs.database_class_id != None
                and Juham.find_class(JuhamTs.database_class_id) != None
            ):
                self.database_client = cast(
                    Optional[TimeSeries], Juham.instantiate(JuhamTs.database_class_id)
                )
            else:
                self.warning("Suspicious configuration: no database_class_id set")

        def write(self, point: Measurement) -> None:
            if not self.database_client:
                raise ValueError("Database client is not initialized.")

            first_exception: Optional[BaseException] = None
            for i in range(self.write_attempts):
                try:
                    self.database_client.write(point)
                    return
                except Exception as e:
                    if first_exception is None:
                        first_exception = e
                    self.warning(f"Writing ts failed, attempt {i+1}: {repr(e)}")

            self.log_message(
                "Error",
                f"Writing failed after {self.write_attempts} attempts, giving up",
                "".join(
                    traceback.format_exception_only(type(first_exception), first_exception)
                    if first_exception
                    else "No exception"
                ),
            )

        def write_point(
            self, name: str, tags: dict[str, Any], fields: dict[str, Any], ts: str
        ) -> None:
            if not self.database_client:
                raise ValueError("Database client is not initialized.")

            first_exception: Optional[BaseException] = None
            for i in range(self.write_attempts):
                try:
                    self.database_client.write_dict(name, tags, fields, ts)
                    return
                except Exception as e:
                    if first_exception is None:
                        first_exception is None
                    self.warning(f"Writing ts failed, attempt {i+1}: {repr(e)}")

            self.log_message(
                "Error",
                f"Writing failed after {self.write_attempts} attempts, giving up",
                "".join(
                    traceback.format_exception_only(type(first_exception), first_exception)
                ),
            )

        def read_last_value(
            self,
            measurement: str,
            tags: Optional[dict[str, Any]] = None,
            fields: Optional[list[str]] = None,
        ) -> dict[str, Any]:
            if not self.database_client:
                raise ValueError("Database client is not initialized.")

            first_exception: Optional[BaseException] = None
            for i in range(self.write_attempts):
                try:
                    return self.database_client.read_last_value(measurement, tags, fields)
                except Exception as e:
                    if first_exception is None:
                        first_exception = e
                    self.warning(f"Reading ts failed, attempt {i+1}: {repr(e)}")

            self.log_message(
                "Error",
                f"Reading failed after {self.write_attempts} attempts, giving up",
                "".join(
                    traceback.format_exception_only(type(first_exception), first_exception)
                ),
            )
            return {}

        def read(self, point: Measurement) -> None:
            pass