"""Commons function for Sync and Async client.""" from __future__ import absolute_import import base64 import configparser import logging import os from datetime import datetime, timedelta from typing import List, Generator, Any, Union, Iterable, AsyncGenerator from urllib3 import HTTPResponse from influxdb_client import Configuration, Dialect, Query, OptionStatement, VariableAssignment, Identifier, \ Expression, BooleanLiteral, IntegerLiteral, FloatLiteral, DateTimeLiteral, UnaryExpression, DurationLiteral, \ Duration, StringLiteral, ArrayExpression, ImportDeclaration, MemberExpression, MemberAssignment, File, \ WriteService, QueryService, DeleteService, DeletePredicateRequest from influxdb_client.client.flux_csv_parser import FluxResponseMetadataMode, FluxCsvParser, FluxSerializationMode from influxdb_client.client.flux_table import FluxRecord, TableList, CSVIterator from influxdb_client.client.util.date_utils import get_date_helper from influxdb_client.client.util.helpers import get_org_query_param from influxdb_client.client.warnings import MissingPivotFunction from influxdb_client.client.write.dataframe_serializer import DataframeSerializer from influxdb_client.rest import _UTF_8_encoding try: import dataclasses _HAS_DATACLASS = True except ModuleNotFoundError: _HAS_DATACLASS = False LOGGERS_NAMES = [ 'influxdb_client.client.influxdb_client', 'influxdb_client.client.influxdb_client_async', 'influxdb_client.client.write_api', 'influxdb_client.client.write_api_async', 'influxdb_client.client.write.retry', 'influxdb_client.client.write.dataframe_serializer', 'influxdb_client.client.util.multiprocessing_helper', 'influxdb_client.client.http', 'influxdb_client.client.exceptions', ] # noinspection PyMethodMayBeStatic class _BaseClient(object): def __init__(self, url, token, debug=None, timeout=10_000, enable_gzip=False, org: str = None, default_tags: dict = None, http_client_logger: str = None, **kwargs) -> None: self.url = url self.token = token self.org = org self.default_tags = default_tags self.conf = _Configuration() if not isinstance(self.url, str): raise ValueError('"url" attribute is not str instance') if self.url.endswith("/"): self.conf.host = self.url[:-1] else: self.conf.host = self.url self.conf.enable_gzip = enable_gzip self.conf.verify_ssl = kwargs.get('verify_ssl', True) self.conf.ssl_ca_cert = kwargs.get('ssl_ca_cert', None) self.conf.cert_file = kwargs.get('cert_file', None) self.conf.cert_key_file = kwargs.get('cert_key_file', None) self.conf.cert_key_password = kwargs.get('cert_key_password', None) self.conf.ssl_context = kwargs.get('ssl_context', None) self.conf.proxy = kwargs.get('proxy', None) self.conf.proxy_headers = kwargs.get('proxy_headers', None) self.conf.connection_pool_maxsize = kwargs.get('connection_pool_maxsize', self.conf.connection_pool_maxsize) self.conf.timeout = timeout # logging self.conf.loggers["http_client_logger"] = logging.getLogger(http_client_logger) for client_logger in LOGGERS_NAMES: self.conf.loggers[client_logger] = logging.getLogger(client_logger) self.conf.debug = debug self.conf.username = kwargs.get('username', None) self.conf.password = kwargs.get('password', None) # defaults self.auth_header_name = None self.auth_header_value = None # by token if self.token: self.auth_header_name = "Authorization" self.auth_header_value = "Token " + self.token # by HTTP basic auth_basic = kwargs.get('auth_basic', False) if auth_basic: self.auth_header_name = "Authorization" self.auth_header_value = "Basic " + base64.b64encode(token.encode()).decode() # by username, password if self.conf.username and self.conf.password: self.auth_header_name = None self.auth_header_value = None self.retries = kwargs.get('retries', False) self.profilers = kwargs.get('profilers', None) pass @classmethod def _from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gzip=False, **kwargs): config = configparser.ConfigParser() config_name = kwargs.get('config_name', 'influx2') is_json = False try: config.read(config_file) except configparser.ParsingError: with open(config_file) as json_file: import json config = json.load(json_file) is_json = True def _config_value(key: str): value = str(config[key]) if is_json else config[config_name][key] return value.strip('"') def _has_option(key: str): return key in config if is_json else config.has_option(config_name, key) def _has_section(key: str): return key in config if is_json else config.has_section(key) url = _config_value('url') token = _config_value('token') timeout = None if _has_option('timeout'): timeout = _config_value('timeout') org = None if _has_option('org'): org = _config_value('org') verify_ssl = True if _has_option('verify_ssl'): verify_ssl = _config_value('verify_ssl') ssl_ca_cert = None if _has_option('ssl_ca_cert'): ssl_ca_cert = _config_value('ssl_ca_cert') cert_file = None if _has_option('cert_file'): cert_file = _config_value('cert_file') cert_key_file = None if _has_option('cert_key_file'): cert_key_file = _config_value('cert_key_file') cert_key_password = None if _has_option('cert_key_password'): cert_key_password = _config_value('cert_key_password') connection_pool_maxsize = None if _has_option('connection_pool_maxsize'): connection_pool_maxsize = _config_value('connection_pool_maxsize') auth_basic = False if _has_option('auth_basic'): auth_basic = _config_value('auth_basic') default_tags = None if _has_section('tags'): if is_json: default_tags = config['tags'] else: tags = {k: v.strip('"') for k, v in config.items('tags')} default_tags = dict(tags) profilers = None if _has_option('profilers'): profilers = [x.strip() for x in _config_value('profilers').split(',')] proxy = None if _has_option('proxy'): proxy = _config_value('proxy') return cls(url, token, debug=debug, timeout=_to_int(timeout), org=org, default_tags=default_tags, enable_gzip=enable_gzip, verify_ssl=_to_bool(verify_ssl), ssl_ca_cert=ssl_ca_cert, cert_file=cert_file, cert_key_file=cert_key_file, cert_key_password=cert_key_password, connection_pool_maxsize=_to_int(connection_pool_maxsize), auth_basic=_to_bool(auth_basic), profilers=profilers, proxy=proxy, **kwargs) @classmethod def _from_env_properties(cls, debug=None, enable_gzip=False, **kwargs): url = os.getenv('INFLUXDB_V2_URL', "http://localhost:8086") token = os.getenv('INFLUXDB_V2_TOKEN', "my-token") timeout = os.getenv('INFLUXDB_V2_TIMEOUT', "10000") org = os.getenv('INFLUXDB_V2_ORG', "my-org") verify_ssl = os.getenv('INFLUXDB_V2_VERIFY_SSL', "True") ssl_ca_cert = os.getenv('INFLUXDB_V2_SSL_CA_CERT', None) cert_file = os.getenv('INFLUXDB_V2_CERT_FILE', None) cert_key_file = os.getenv('INFLUXDB_V2_CERT_KEY_FILE', None) cert_key_password = os.getenv('INFLUXDB_V2_CERT_KEY_PASSWORD', None) connection_pool_maxsize = os.getenv('INFLUXDB_V2_CONNECTION_POOL_MAXSIZE', None) auth_basic = os.getenv('INFLUXDB_V2_AUTH_BASIC', "False") prof = os.getenv("INFLUXDB_V2_PROFILERS", None) profilers = None if prof is not None: profilers = [x.strip() for x in prof.split(',')] default_tags = dict() for key, value in os.environ.items(): if key.startswith("INFLUXDB_V2_TAG_"): default_tags[key[16:].lower()] = value return cls(url, token, debug=debug, timeout=_to_int(timeout), org=org, default_tags=default_tags, enable_gzip=enable_gzip, verify_ssl=_to_bool(verify_ssl), ssl_ca_cert=ssl_ca_cert, cert_file=cert_file, cert_key_file=cert_key_file, cert_key_password=cert_key_password, connection_pool_maxsize=_to_int(connection_pool_maxsize), auth_basic=_to_bool(auth_basic), profilers=profilers, **kwargs) # noinspection PyMethodMayBeStatic class _BaseQueryApi(object): default_dialect = Dialect(header=True, delimiter=",", comment_prefix="#", annotations=["datatype", "group", "default"], date_time_format="RFC3339") def __init__(self, influxdb_client, query_options=None): from influxdb_client.client.query_api import QueryOptions self._query_options = QueryOptions() if query_options is None else query_options self._influxdb_client = influxdb_client self._query_api = QueryService(influxdb_client.api_client) """Base implementation for Queryable API.""" def _to_tables(self, response, query_options=None, response_metadata_mode: FluxResponseMetadataMode = FluxResponseMetadataMode.full) -> TableList: """ Parse HTTP response to TableList. :param response: HTTP response from an HTTP client. Expected type: `urllib3.response.HTTPResponse`. """ _parser = self._to_tables_parser(response, query_options, response_metadata_mode) list(_parser.generator()) return _parser.table_list() async def _to_tables_async(self, response, query_options=None, response_metadata_mode: FluxResponseMetadataMode = FluxResponseMetadataMode.full) -> TableList: """ Parse HTTP response to TableList. :param response: HTTP response from an HTTP client. Expected type: `aiohttp.client_reqrep.ClientResponse`. """ async with self._to_tables_parser(response, query_options, response_metadata_mode) as parser: async for _ in parser.generator_async(): pass return parser.table_list() def _to_csv(self, response: HTTPResponse) -> CSVIterator: """Parse HTTP response to CSV.""" return CSVIterator(response) def _to_flux_record_stream(self, response, query_options=None, response_metadata_mode: FluxResponseMetadataMode = FluxResponseMetadataMode.full) -> \ Generator[FluxRecord, Any, None]: """ Parse HTTP response to FluxRecord stream. :param response: HTTP response from an HTTP client. Expected type: `urllib3.response.HTTPResponse`. """ _parser = self._to_flux_record_stream_parser(query_options, response, response_metadata_mode) return _parser.generator() async def _to_flux_record_stream_async(self, response, query_options=None, response_metadata_mode: FluxResponseMetadataMode = FluxResponseMetadataMode.full) -> \ AsyncGenerator['FluxRecord', None]: """ Parse HTTP response to FluxRecord stream. :param response: HTTP response from an HTTP client. Expected type: `aiohttp.client_reqrep.ClientResponse`. """ _parser = self._to_flux_record_stream_parser(query_options, response, response_metadata_mode) return (await _parser.__aenter__()).generator_async() def _to_data_frame_stream(self, data_frame_index, response, query_options=None, response_metadata_mode: FluxResponseMetadataMode = FluxResponseMetadataMode.full, use_extension_dtypes=False): """ Parse HTTP response to DataFrame stream. :param response: HTTP response from an HTTP client. Expected type: `urllib3.response.HTTPResponse`. """ _parser = self._to_data_frame_stream_parser(data_frame_index, query_options, response, response_metadata_mode, use_extension_dtypes) return _parser.generator() async def _to_data_frame_stream_async(self, data_frame_index, response, query_options=None, response_metadata_mode: FluxResponseMetadataMode = FluxResponseMetadataMode.full, use_extension_dtypes=False): """ Parse HTTP response to DataFrame stream. :param response: HTTP response from an HTTP client. Expected type: `aiohttp.client_reqrep.ClientResponse`. """ _parser = self._to_data_frame_stream_parser(data_frame_index, query_options, response, response_metadata_mode, use_extension_dtypes) return (await _parser.__aenter__()).generator_async() def _to_tables_parser(self, response, query_options, response_metadata_mode): return FluxCsvParser(response=response, serialization_mode=FluxSerializationMode.tables, query_options=query_options, response_metadata_mode=response_metadata_mode) def _to_flux_record_stream_parser(self, query_options, response, response_metadata_mode): return FluxCsvParser(response=response, serialization_mode=FluxSerializationMode.stream, query_options=query_options, response_metadata_mode=response_metadata_mode) def _to_data_frame_stream_parser(self, data_frame_index, query_options, response, response_metadata_mode, use_extension_dtypes): return FluxCsvParser(response=response, serialization_mode=FluxSerializationMode.dataFrame, data_frame_index=data_frame_index, query_options=query_options, response_metadata_mode=response_metadata_mode, use_extension_dtypes=use_extension_dtypes) def _to_data_frames(self, _generator): """Parse stream of DataFrames into expected type.""" from ..extras import pd if isinstance(_generator, list): _dataFrames = _generator else: _dataFrames = list(_generator) if len(_dataFrames) == 0: return pd.DataFrame(columns=[], index=None) elif len(_dataFrames) == 1: return _dataFrames[0] else: return _dataFrames def _org_param(self, org): return get_org_query_param(org=org, client=self._influxdb_client) def _get_query_options(self): if self._query_options and self._query_options.profilers: return self._query_options elif self._influxdb_client.profilers: from influxdb_client.client.query_api import QueryOptions return QueryOptions(profilers=self._influxdb_client.profilers) def _create_query(self, query, dialect=default_dialect, params: dict = None, **kwargs): query_options = self._get_query_options() profilers = query_options.profilers if query_options is not None else None q = Query(query=query, dialect=dialect, extern=_BaseQueryApi._build_flux_ast(params, profilers)) if profilers: print("\n===============") print("Profiler: query") print("===============") print(query) if kwargs.get('dataframe_query', False): MissingPivotFunction.print_warning(query) return q @staticmethod def _params_to_extern_ast(params: dict) -> List['OptionStatement']: statements = [] for key, value in params.items(): expression = _BaseQueryApi._parm_to_extern_ast(value) if expression is None: continue statements.append(OptionStatement("OptionStatement", VariableAssignment("VariableAssignment", Identifier("Identifier", key), expression))) return statements @staticmethod def _parm_to_extern_ast(value) -> Union[Expression, None]: if value is None: return None if isinstance(value, bool): return BooleanLiteral("BooleanLiteral", value) elif isinstance(value, int): return IntegerLiteral("IntegerLiteral", str(value)) elif isinstance(value, float): return FloatLiteral("FloatLiteral", value) elif isinstance(value, datetime): value = get_date_helper().to_utc(value) nanoseconds = getattr(value, 'nanosecond', 0) fraction = f'{(value.microsecond * 1000 + nanoseconds):09d}' return DateTimeLiteral("DateTimeLiteral", value.strftime('%Y-%m-%dT%H:%M:%S.') + fraction + 'Z') elif isinstance(value, timedelta): _micro_delta = int(value / timedelta(microseconds=1)) if _micro_delta < 0: return UnaryExpression("UnaryExpression", argument=DurationLiteral("DurationLiteral", [ Duration(magnitude=-_micro_delta, unit="us")]), operator="-") else: return DurationLiteral("DurationLiteral", [Duration(magnitude=_micro_delta, unit="us")]) elif isinstance(value, str): return StringLiteral("StringLiteral", str(value)) elif isinstance(value, Iterable): return ArrayExpression("ArrayExpression", elements=list(map(lambda it: _BaseQueryApi._parm_to_extern_ast(it), value))) else: return value @staticmethod def _build_flux_ast(params: dict = None, profilers: List[str] = None): imports = [] body = [] if profilers is not None and len(profilers) > 0: imports.append(ImportDeclaration( "ImportDeclaration", path=StringLiteral("StringLiteral", "profiler"))) elements = [] for profiler in profilers: elements.append(StringLiteral("StringLiteral", value=profiler)) member = MemberExpression( "MemberExpression", object=Identifier("Identifier", "profiler"), _property=Identifier("Identifier", "enabledProfilers")) prof = OptionStatement( "OptionStatement", assignment=MemberAssignment( "MemberAssignment", member=member, init=ArrayExpression( "ArrayExpression", elements=elements))) body.append(prof) if params is not None: body.extend(_BaseQueryApi._params_to_extern_ast(params)) return File(package=None, name=None, type=None, imports=imports, body=body) class _BaseWriteApi(object): def __init__(self, influxdb_client, point_settings=None): self._influxdb_client = influxdb_client self._point_settings = point_settings self._write_service = WriteService(influxdb_client.api_client) if influxdb_client.default_tags: for key, value in influxdb_client.default_tags.items(): self._point_settings.add_default_tag(key, value) def _append_default_tag(self, key, val, record): from influxdb_client import Point if isinstance(record, bytes) or isinstance(record, str): pass elif isinstance(record, Point): record.tag(key, val) elif isinstance(record, dict): record.setdefault("tags", {}) record.get("tags")[key] = val elif isinstance(record, Iterable): for item in record: self._append_default_tag(key, val, item) def _append_default_tags(self, record): if self._point_settings.defaultTags and record is not None: for key, val in self._point_settings.defaultTags.items(): self._append_default_tag(key, val, record) def _serialize(self, record, write_precision, payload, **kwargs): from influxdb_client import Point if isinstance(record, bytes): payload[write_precision].append(record) elif isinstance(record, str): self._serialize(record.encode(_UTF_8_encoding), write_precision, payload, **kwargs) elif isinstance(record, Point): precision_from_point = kwargs.get('precision_from_point', True) precision = record.write_precision if precision_from_point else write_precision self._serialize(record.to_line_protocol(precision=precision), precision, payload, **kwargs) elif isinstance(record, dict): self._serialize(Point.from_dict(record, write_precision=write_precision, **kwargs), write_precision, payload, **kwargs) elif 'DataFrame' in type(record).__name__: serializer = DataframeSerializer(record, self._point_settings, write_precision, **kwargs) self._serialize(serializer.serialize(), write_precision, payload, **kwargs) elif hasattr(record, "_asdict"): # noinspection PyProtectedMember self._serialize(record._asdict(), write_precision, payload, **kwargs) elif _HAS_DATACLASS and dataclasses.is_dataclass(record): self._serialize(dataclasses.asdict(record), write_precision, payload, **kwargs) elif isinstance(record, Iterable): for item in record: self._serialize(item, write_precision, payload, **kwargs) # noinspection PyMethodMayBeStatic class _BaseDeleteApi(object): def __init__(self, influxdb_client): self._influxdb_client = influxdb_client self._service = DeleteService(influxdb_client.api_client) def _prepare_predicate_request(self, start, stop, predicate): date_helper = get_date_helper() if isinstance(start, datetime): start = date_helper.to_utc(start) if isinstance(stop, datetime): stop = date_helper.to_utc(stop) predicate_request = DeletePredicateRequest(start=start, stop=stop, predicate=predicate) return predicate_request class _Configuration(Configuration): def __init__(self): Configuration.__init__(self) self.enable_gzip = False self.username = None self.password = None def update_request_header_params(self, path: str, params: dict): super().update_request_header_params(path, params) if self.enable_gzip: # GZIP Request if path == '/api/v2/write': params["Content-Encoding"] = "gzip" params["Accept-Encoding"] = "identity" pass # GZIP Response if path == '/api/v2/query': # params["Content-Encoding"] = "gzip" params["Accept-Encoding"] = "gzip" pass pass pass def update_request_body(self, path: str, body): _body = super().update_request_body(path, body) if self.enable_gzip: # GZIP Request if path == '/api/v2/write': import gzip if isinstance(_body, bytes): return gzip.compress(data=_body) else: return gzip.compress(bytes(_body, _UTF_8_encoding)) return _body def _to_bool(bool_value): return str(bool_value).lower() in ("yes", "true") def _to_int(int_value): return int(int_value) if int_value is not None else None