Source code for masterpiece_influx.influx

from typing import Any, Optional
from typing_extensions import override
from influxdb_client_3 import InfluxDBClient3, Point
from masterpiece.timeseries import TimeSeries, Measurement
from masterpiece_influx.influx_measurement import InfluxMeasurement


[docs] class Influx(TimeSeries): """InfluxDB V3 implementation of a time series database for MasterPiece. This class provides an interface for interacting with an InfluxDB V3 time series database. The `host`, `token`, `org`, and `database` attributes are inherited from the `TimeSeries` superclass and can be initialized through the configuration system or overridden via constructor parameters. Attributes: host (str): Hostname of the InfluxDB server. token (str): Authentication token for InfluxDB. org (str): Organization name for InfluxDB. database (str): Database name for InfluxDB. influx_client (InfluxDBClient3): The client instance used for database operations. Example: Creating a custom instance with specific parameters: .. code-block:: python db = Influx( host="example.com", token="my-token", org="my-org", database="my-db" ) """ def __init__( self, name: str = "influx", host: Optional[str] = None, token: Optional[str] = None, org: Optional[str] = None, database: Optional[str] = None, ) -> None: """Construct InfluxDB v3 client for writing and reading time series. If the constructor parameters are not given, then the default class attribute values defined by the `TimeSeries` super class are used. Args: name (str, optional): Name of the object to be created. Defaults to "influx". host (Optional[str], optional): Hostname of the InfluxDB server. Defaults to class attribute. token (Optional[str], optional): Authentication token for InfluxDB. Defaults to class attribute. org (Optional[str], optional): Organization name for InfluxDB. Defaults to class attribute. database (Optional[str], optional): Database name for InfluxDB. Defaults to class attribute. """ super().__init__(name) # Use constructor parameters if provided, otherwise fall back to class attributes self.host = host if host is not None else self.host self.token = token if token is not None else self.token self.org = org if org is not None else self.org self.database = database if database is not None else self.database # Initialize the InfluxDB client self.influx_client = InfluxDBClient3( host=self.host, token=self.token, org=self.org, database=self.database, )
[docs] @override def write(self, point: Point) -> None: self.influx_client.write(record=point)
[docs] @override def write_dict( self, name: str, tags: dict[str, Any], fields: dict[str, Any], ts: str ) -> None: point: dict[str, Any] = { "measurement": name, "tags": tags, "fields": fields, "time": ts, } self.influx_client.write(record=point)
[docs] @override def read_dict( self, measurement: str, start_time: str, end_time: Optional[str] = None, tags: Optional[dict[str, Any]] = None, fields: Optional[list[str]] = None, ) -> list[dict[str, Any]]: try: # Select specific fields or all fields fields_query = ", ".join(fields) if fields else "*" # Construct the base SQL query query = ( f"SELECT {fields_query} FROM {measurement} WHERE time >= '{start_time}'" ) if end_time: query += f" AND time <= '{end_time}'" # Add tag filters if tags: tag_conditions = " AND ".join( f"{key} = '{value}'" for key, value in tags.items() ) query += f" AND {tag_conditions}" # Order by time query += " ORDER BY time" # Execute the query result = self.influx_client.query(query) # Convert the result to a list of dictionaries records = [] for row in result: records.append(dict(row)) return records except Exception as e: raise Exception(f"Failed to read data: {e}")
[docs] @override def read_last_value( self, measurement: str, tags: Optional[dict[str, Any]] = None, fields: Optional[list[str]] = None, ) -> dict[str, Any]: try: # Select specific fields or all fields fields_query = ", ".join(fields) if fields else "*" # Construct the base SQL query query = f"SELECT {fields_query} FROM {measurement}" # Add tag filters if tags: tag_conditions = " AND ".join( f"{key} = '{value}'" for key, value in tags.items() ) query += f" WHERE {tag_conditions}" # Order by time in descending order and limit to 1 query += " ORDER BY time DESC LIMIT 1" # Execute the query result = self.influx_client.query(query) # Parse and return the single record, or an empty dict if no result return dict(result[0]) if result else {} except Exception as e: raise Exception(f"Failed to read last value: {e}")
[docs] @override def read_point( self, measurement: str, start_time: str, end_time: Optional[str] = None, tags: Optional[dict[str, Any]] = None, fields: Optional[list[str]] = None, ) -> list[Any]: try: # Select specific fields or all fields fields_query = ", ".join(fields) if fields else "*" # Construct the base SQL query query = ( f"SELECT {fields_query} FROM {measurement} WHERE time >= '{start_time}'" ) if end_time: query += f" AND time <= '{end_time}'" # Add tag filters if tags: tag_conditions = " AND ".join( f"{key} = '{value}'" for key, value in tags.items() ) query += f" AND {tag_conditions}" # Order by time query += " ORDER BY time" # Execute the query result = self.influx_client.query(query) # Convert the result to a list of Point objects points = [] for row in result: point = Point(measurement) for key, value in row.items(): if key == "time": point.time(value) elif isinstance(value, (int, float)): point.field(key, value) else: point.tag(key, value) points.append(point) return points except Exception as e: raise Exception(f"Failed to read data as Points: {e}")
[docs] @override def measurement(self, measurement: str) -> Measurement: return InfluxMeasurement(measurement)