text: from ..authorization.authorization_api import AuthorizationApi
  from ..common.common_funcs import CommonFuncs
  from ..types.types_api import TypesApi
  from ..exceptions import TSIQueryError, TSIStoreError
  import requests
  import json
  import logging
  import pandas as pd
  
  
  
  class QueryApi():
      def __init__(
          self,
          application_name: str,
          environment_id: str,
          authorization_api: AuthorizationApi,
          common_funcs: CommonFuncs,
          typesApi: TypesApi,
          instances: dict,
      ):
          self.authorization_api = authorization_api
          self._applicationName = application_name
          self.environmentId = environment_id
          self.common_funcs = common_funcs
          self.instances = instances
          self.types_api = typesApi
  
      def _getVariableAggregate(self, typeList=None, currType=None, aggregate=None, interpolationKind=None, interpolationSpan=None):
          """Creates the fields of the payload corresponding to the inlineVariable 
              and to the projectedVariables name
  
          Args:
              aggregate (str): The aggregation method to be used without interpolation:
                  ("min", "max", "sum", "avg", "first", "last", "median", "stdev").
              The aggregation method to be used with interpolation:
                  ("twsum", "twavg", "left", "right")
              interpolationKind (str): Type of interpolation technique: ("Linear", "Step")
              interpolationSpan (str): The time range to the left and right of 
              the search span to be used for Interpolation.
  
          Returns:
              tuple: A tuple with the inlineVar (dict) and the variableName (str).
          """
  
          if aggregate not in ["min", "max", "sum", "avg", "first", "last", "median","stdev",\
                  "twsum", "twavg", "left", "right", None]:
              raise TSIQueryError(
                  "TSIClient: Aggregation method not supported, must be \"min\", \"max\"," \
                      "\"sum\", \"avg\", \"first\", \"last\", \"median\", \"stdev\", "\
                      "\"twsum\", \"twavg\", \"left\" or \"right\"."
              )
  
          inlineVar = None
          variableName = None
  
          if aggregate != None:
              if aggregate in ["twsum", "twavg", "left", "right"]:
                  if interpolationKind==None:
                      raise TSIQueryError(
                      "TSIClient: Aggregation method not supported without interpolation."\
                          "Interpolation type must be either \"Linear\" or \"Step\"."
                      )
                  if interpolationSpan==None:
                      raise TSIQueryError(
                      "TSIClient: Aggregation method not supported without interpolation."\
                          "Need interpolation boundary."
                      )
                  variableName = interpolationKind.capitalize() + aggregate.capitalize() + 'Interpolation'
                  if typeList == None or currType == None:
                      inlineVar = {"kind":"numeric", "value": {"tsx": "$event.value"}, "filter": None,\
                          "interpolation":{"kind": "{0!s}".format(interpolationKind),\
                          "boundary":{"span": "{0!s}".format(interpolationSpan)}},\
                          "aggregation": {"tsx": "{0!s}($value)".format(aggregate)}}
                  else:
                      inlineVar = {"kind":"numeric", "value": {"tsx": typeList[currType]}, "filter": None,\
                          "interpolation":{"kind": "{0!s}".format(interpolationKind),\
                          "boundary":{"span": "{0!s}".format(interpolationSpan)}},\
                          "aggregation": {"tsx": "{0!s}($value)".format(aggregate)}}
  
  
              else: 
                  variableName = aggregate.capitalize() + 'VarAggregate'
                  if typeList == None or currType == None:
                      inlineVar = {"kind":"numeric", "value": {"tsx": "$event.value"}, "filter": None,\
                          "aggregation": {"tsx": "{0!s}($value)".format(aggregate)}}
                  else:
                      inlineVar = {"kind":"numeric", "value": {"tsx": typeList[currType]}, "filter": None,\
                          "aggregation": {"tsx": "{0!s}($value)".format(aggregate)}}
  
          return (inlineVar, variableName)
  
      def getRequestType(self, aggregate=None, requestBodyType=None):
          if aggregate == None and (requestBodyType == None or requestBodyType=="getSeries"):
              requestType = "getSeries"
          elif aggregate != None and (requestBodyType == None or requestBodyType=="aggregateSeries"):
              requestType = "aggregateSeries"
          elif requestBodyType == "getEvents":
              requestType = "getEvents"
          else:
              raise TSIQueryError(
                  "TSIClient: request body types are either getSeries, aggregateSeries or getEvents"
              )
          return requestType
  
      def getInlineVariablesAggregate(self, typeList=None, currType=None, aggregateList=None, interpolationList=None, interpolationSpanList=None):
          """Returns a tuple of lists to apply in the payload consisiting of the InlineVariables and the 
              projectedVariables.
          Args:
              aggregateList (list): List of the aggregation methods to be used without interpolation:
                  ("min", "max", "sum", "avg", "first", "last", "median", "stdev").
                  The aggregation method to be used with interpolation:
                  ("twsum", "twavg", "left", "right")
              interpolationList (list): A list of interpolation methods. Either Linear or Step.
              interpolationSpanList (str): The time interval that is used during aggregation. Must follow the ISO-8601 duration format.
                  Example: interpolation Boundary span ="P1D", for 1 day to the left and right of the search span to be used for Interpolation.
          Returns:
              A tuple of lists to apply in the payload consisiting of the InlineVariables and the 
              projectedVariables. 
          Raises:
              TSIQueryError: Raised if there was an error in the aggregation lists, 
              the list have either different length or interpolation aggregation 
              is given but there is not given specifications for the interpolation 
              kind and the interpolation boundary.
              If aggregates given do not require interpolation, the interpolation lists are not needed.
          """
          if isinstance(aggregateList, list) and isinstance(interpolationList, list) and isinstance(interpolationSpanList, list):
              if not len(aggregateList)==len(interpolationList)==len(interpolationSpanList):
                  raise TSIQueryError(
                      "TSIClient: All Aggregate lists must be of the same length"
                  )
              projectedVarNames = []
              inlineVarPayload = []
              for i in range(0,len(aggregateList)):
                  (inlineVar, variableName) = self._getVariableAggregate(typeList=typeList, currType=currType, aggregate=aggregateList[i],\
                      interpolationKind=interpolationList[i], interpolationSpan=interpolationSpanList[i])
                  projectedVarNames.append(variableName)
                  inlineVarPayload.append(inlineVar)
  
          elif isinstance(aggregateList, str) and (isinstance(interpolationList, str) or interpolationList == None)\
              and (isinstance(interpolationSpanList, str) or interpolationSpanList == None):
  
              projectedVarNames = []
              inlineVarPayload = []
              (inlineVar, variableName) = self._getVariableAggregate(typeList=typeList, currType=currType, aggregate=aggregateList,\
                  interpolationKind=interpolationList, interpolationSpan=interpolationSpanList)
              projectedVarNames.append(variableName)
              inlineVarPayload.append(inlineVar)
  
          else:
              raise TSIQueryError(
                  "TSIClient: If aggregate list contains aggregations requiring interpolation, "\
                      "both the aggregate and interpolation specifications must be lists"
              )
  
          return (inlineVarPayload, projectedVarNames)
  
  
      def getNameById(self, ids):
          """Returns the timeseries names that correspond to the given ids.
  
          Args:
              ids (list): The ids for which to get names.
  
          Returns:
              list: The timeseries names, None if timeseries id does not exist in the TSI environment.
          """
  
          if not isinstance(ids,list):
              ids = [ids]
          timeSeriesNames=[]
          idMap={}
          
          for instance in self.instances['instances']:
              if isinstance(instance,list):
                  ID = tuple(instance)
              if 'timeSeriesId' in instance:
                  idMap[instance['timeSeriesId'][0]] = instance
          for ID in ids:
              if isinstance(ID,list):
                  ID = tuple(ID)
              if ID in idMap:
                  timeSeriesNames.append(idMap[ID]['name'])
              else:
                  timeSeriesNames.append(None)
          return timeSeriesNames
  
  
      def getIdByAssets(self, asset):
          """Returns the timeseries ids that belong to a given asset.
  
          Args:
              asset (str): The asset name.
  
          Returns:
              list: The timeseries ids.
          """
  
          timeSeriesIds=[]
          nameMap={}
          for instance in self.instances['instances']:
              if 'name' in instance and asset in instance['name']:
                  nameMap[instance['name']] = instance
                  timeSeriesIds.append(instance['timeSeriesId'][0])
              else:
                  continue#timeSeriesIds.append(None)
          return timeSeriesIds
  
  
      def getIdByName(self, names):
          """Returns the timeseries ids that correspond to the given names.
  
          Args:
              names (list(str)): The names for which to get ids.
  
          Returns:
              list: The timeseries ids, None if timeseries name does not exist in the TSI environment.
          """
          if not isinstance(names,list):
              names = [names]
          timeSeriesIds=[]
          nameMap={}
          for instance in self.instances['instances']:
              if 'name' in instance:
                  nameMap[instance['name']] = instance
          for name in names:
              if name in nameMap:
                  timeSeriesIds.append((nameMap[name]['timeSeriesId']))
              else:
                  timeSeriesIds.append(None)
          return timeSeriesIds
  
  
      def getIdByDescription(self, names):
          """Returns the timeseries ids that correspond to the given descriptions.
  
          Args:
              names (list): The descriptions for which to get ids.
  
          Returns:
              list: The timeseries ids, None if timeseries description does not exist in the TSI environment.
          """
  
          if not isinstance(names,list):
              names = [names]
          timeSeriesIds=[]
          nameMap={}
          for instance in self.instances['instances']:
              if 'description' in instance:
                  nameMap[instance['description']] = instance
          for name in names:
              if name in nameMap:
                  timeSeriesIds.append(nameMap[name]['timeSeriesId'][0])
              else:
                  timeSeriesIds.append(None)
          return timeSeriesIds
  
  
      def getDataByName(
          self,
          variables,
          timespan,
          interval,
          aggregateList=None,
          interpolationList=None,
          interpolationSpanList=None,
          requestBodyType=None,
          useWarmStore=False
      ):
          """Returns a dataframe with timestamps and values for the time series names given in "variables".
  
          Can be used to return data for single and multiple timeseries. Names must be exact matches.
  
          Args:
              variables (list): The variable names. Corresponds to the "name/Time Series Name" field of the time series instances.
              timespan (list): A list of two timestamps. First list element ist the start time, second element is the end time.
                  Example: timespan=['2019-12-12T15:35:11.68Z', '2019-12-12T17:02:05.958Z']
              interval (str): The time interval that is used during aggregation. Must follow the ISO-8601 duration format.
                  Example: interval="PT1M", for 1 minute aggregation.
              aggregateList (list): List of the aggregation methods to be used without interpolation:
                  ("min", "max", "sum", "avg", "first", "last", "median", "stdev").
                  The aggregation method to be used with interpolation:
                  ("twsum", "twavg", "left", "right")
              interpolationList (list): A list of interpolation methods. Either "Linear" or "Step".
              interpolationSpanList (str): The time interval that is used during aggregation. Must follow the ISO-8601 duration format.
                  Example: interpolation Boundary span ="P1D", for 1 day to the left and right of the search span to be used for Interpolation.
              requestBodyType (str): Type of the request, either "getSeries", "aggregateSeries" or "getEvents".
              useWarmStore (bool): If True, the query is executed on the warm storage (free of charge), otherwise on the cold storage. Defaults to False.
  
          Returns:
              A pandas dataframe with timeseries data. Columns are ordered the same way as the variable names.
  
          Raises:
              TSIStoreError: Raised if the was tried to execute on the warm store, but the warm store is not enabled.
              TSIQueryError: Raised if there was an error in the query arguments (e.g. wrong formatting).
  
          Example:
              >>> from TSIClient import TSIClient as tsi
              >>> client = tsi.TSIClient()
              >>> data = client.query.getDataByName(
              ...     variables=["timeseries_name_1", "timeseries_name_2"],
              ...     timespan=["2020-01-25T10:00:11.68Z", "2020-01-26T13:45:11.68Z"],
              ...     interval="PT5M",
              ...     aggregateList=["avg"],
              ...     useWarmStore=False
              ... )
          """
  
          authorizationToken = self.authorization_api._getToken()
          url = "https://" + self.environmentId + ".env.timeseries.azure.com/timeseries/query?"
          querystring = self.common_funcs._getQueryString(useWarmStore=useWarmStore)
          timeseries = self.getIdByName(variables)
          types = self.types_api.getTypeByName(variables)
  
          if isinstance(aggregateList, list):
              aggregate = aggregateList[0]
              if not (isinstance(interpolationList, list) and isinstance(interpolationSpanList, list)):
                  aggArray = ["min", "max", "sum", "avg", "first", "last", "median","stdev"]
                  """ Check if all aggragates are methods to be used without interpolation """
                  nonInterpolation = all(elem in aggArray for elem in aggregateList)
                  if nonInterpolation:
                      interpolationList = [None]*len(aggregateList)
                      interpolationSpanList = [None]*len(aggregateList)
  
          else:
              aggregate = aggregateList
          requestType = self.getRequestType(aggregate=aggregate,requestBodyType=requestBodyType)
  
          return self._getData(
              timeseries=timeseries,
              types = types,
              url=url,
              querystring=querystring,
              requestType=requestType,
              timespan=timespan,
              interval=interval,
              aggregateList=aggregateList,
              interpolationList=interpolationList,
              interpolationSpanList=interpolationSpanList,
              authorizationToken=authorizationToken,
              otherColNamesThanTimeseriesIds=variables,
          )
  
  
      def getDataByDescription(
          self,
          variables,
          TSName,
          timespan,
          interval,
          aggregateList=None,
          interpolationList=None,
          interpolationSpanList=None,
          requestBodyType=None,
          useWarmStore=False
      ):
          """Returns a dataframe with timestamp and values for the time series that match the description given in "variables".
  
          Can be used to return data for single and multiple timeseries. Descriptions must be exact matches.
  
          Args:
              variables (list): The variable descriptions. Corresponds to the "description" field of the time series instances.
              TSName (list): The column names for the refurned dataframe. Must be in the same order as the variable descriptions.
                  These names can be arbitrary and do not need to coincide with the timeseries names in TSI. 
              timespan (list): A list of two timestamps. First list element ist the start time, second element is the end time.
                  Example: timespan=['2019-12-12T15:35:11.68Z', '2019-12-12T17:02:05.958Z']
              interval (str): The time interval that is used during aggregation. Must follow the ISO-8601 duration format.
                  Example: interval="PT1M", for 1 minute aggregation. If "aggregate" is None, the raw events are returned.
              aggregateList (list): List of the aggregation methods to be used without interpolation:
                  ("min", "max", "sum", "avg", "first", "last", "median", "stdev").
                  The aggregation method to be used with interpolation:
                  ("twsum", "twavg", "left", "right")
              interpolationList (list): A list of interpolation methods. Either "Linear" or "Step".
              interpolationSpanList (str): The time interval that is used during aggregation. Must follow the ISO-8601 duration format.
                  Example: interpolation Boundary span ="P1D", for 1 day to the left and right of the search span to be used for Interpolation.
              requestBodyType (str): Type of the request, either "getSeries", "aggregateSeries" or "getEvents".
              useWarmStore (bool): If True, the query is executed on the warm storage (free of charge), otherwise on the cold storage. Defaults to False.
  
          Returns:
              A pandas dataframe with timeseries data. Columns are ordered the same way as the variable descriptions.
  
          Raises:
              TSIStoreError: Raised if the was tried to execute on the warm store, but the warm store is not enabled.
              TSIQueryError: Raised if there was an error in the query arguments (e.g. wrong formatting).
  
          Example:
              >>> from TSIClient import TSIClient as tsi
              >>> client = tsi.TSIClient()
              >>> data = client.query.getDataByDescription(
              ...     variables=["timeseries_description_1", "timeseries_description_2"],
              ...     TSName=["my_timeseries_name_1", "my_timeseries_name_2"]
              ...     timespan=["2020-01-25T10:00:11.68Z", "2020-01-26T13:45:11.68Z"],
              ...     interval="PT5M",
              ...     aggregateList=["avg"],
              ...     useWarmStore=False
              ... )
          """
  
          authorizationToken = self.authorization_api._getToken()
          url = "https://" + self.environmentId + ".env.timeseries.azure.com/timeseries/query?"
          querystring = self.common_funcs._getQueryString(useWarmStore=useWarmStore)
          timeseries = self.getIdByDescription(variables)
          types = self.types_api.getTypeByDescription(variables)
  
          if isinstance(aggregateList, list):
              aggregate = aggregateList[0]
              if not (isinstance(interpolationList, list) and isinstance(interpolationSpanList, list)):
                  aggArray = ["min", "max", "sum", "avg", "first", "last", "median","stdev"]
                  """ Check if all aggragates are methods to be used without interpolation """
                  nonInterpolation = all(elem in aggArray for elem in aggregateList)
                  if nonInterpolation:
                      interpolationList = [None]*len(aggregateList)
                      interpolationSpanList = [None]*len(aggregateList)
  
          else:
              aggregate = aggregateList
          requestType = self.getRequestType(aggregate=aggregate,requestBodyType=requestBodyType)
  
          return self._getData(
              timeseries=timeseries,
              types=types,
              url=url,
              querystring=querystring,
              requestType=requestType,
              timespan=timespan,
              interval=interval,
              aggregateList=aggregateList,
              interpolationList=interpolationList,
              interpolationSpanList=interpolationSpanList,
              authorizationToken=authorizationToken,
              otherColNamesThanTimeseriesIds=TSName
          )
  
  
      def getDataById(
          self,
          timeseries,
          timespan,
          interval,
          aggregateList=None,
          interpolationList=None,
          interpolationSpanList=None,
          requestBodyType=None,
          useWarmStore=False
      ):
          """Returns a dataframe with timestamp and values for the time series that match the description given in "timeseries".
  
          Can be used to return data for single and multiple timeseries. Timeseries ids must be an exact matches.
  
          Args:
              timeseries (list): The timeseries ids. Corresponds to the "timeSeriesId" field of the time series instances.
              timespan (list): A list of two timestamps. First list element ist the start time, second element is the end time.
                  Example: timespan=['2019-12-12T15:35:11.68Z', '2019-12-12T17:02:05.958Z']
              interval (str): The time interval that is used during aggregation. Must follow the ISO-8601 duration format.
                  Example: interval="PT1M", for 1 minute aggregation. If "aggregate" is None, the raw events are returned.
              aggregateList (list): List of the aggregation methods to be used without interpolation:
                  ("min", "max", "sum", "avg", "first", "last", "median", "stdev").
                  The aggregation method to be used with interpolation:
                  ("twsum", "twavg", "left", "right")
              interpolationList (list): A list of interpolation methods. Either "Linear" or "Step".
              interpolationSpanList (str): The time interval that is used during aggregation. Must follow the ISO-8601 duration format.
                  Example: interpolation Boundary span ="P1D", for 1 day to the left and right of the search span to be used for Interpolation.
              requestBodyType (str): Type of the request, either "getSeries", "aggregateSeries" or "getEvents".
              useWarmStore (bool): If True, the query is executed on the warm storage (free of charge), otherwise on the cold storage. Defaults to False.
  
          Returns:
              A pandas dataframe with timeseries data. Columns are ordered the same way as the timeseries ids.
  
          Raises:
              TSIStoreError: Raised if the was tried to execute on the warm store, but the warm store is not enabled.
              TSIQueryError: Raised if there was an error in the query arguments (e.g. wrong formatting).
  
          Example:
              >>> from TSIClient import TSIClient as tsi
              >>> client = tsi.TSIClient()
              >>> data = client.query.getDataById(
              ...     timeseries=["timeseries_id_1", "timeseries_id_2"],
              ...     timespan=["2020-01-25T10:00:11.68Z", "2020-01-26T13:45:11.68Z"],
              ...     interval="PT5M",
              ...     aggregateList=["avg"],
              ...     useWarmStore=False
              ... )
          """
  
          authorizationToken = self.authorization_api._getToken()
          url = "https://" + self.environmentId + ".env.timeseries.azure.com/timeseries/query?"
          querystring = self.common_funcs._getQueryString(useWarmStore=useWarmStore)
          types = self.types_api.getTypeById(timeseries)
  
          if isinstance(aggregateList, list):
              aggregate = aggregateList[0]
              if not (isinstance(interpolationList, list) and isinstance(interpolationSpanList, list)):
                  aggArray = ["min", "max", "sum", "avg", "first", "last", "median","stdev"]
                  """ Check if all aggragates are methods to be used without interpolation """
                  nonInterpolation = all(elem in aggArray for elem in aggregateList)
                  if nonInterpolation:
                      interpolationList = [None]*len(aggregateList)
                      interpolationSpanList = [None]*len(aggregateList)
  
          else:
              aggregate = aggregateList
          requestType = self.getRequestType(aggregate=aggregate,requestBodyType=requestBodyType)
  
          return self._getData(
              timeseries=timeseries,
              types=types,
              url=url,
              querystring=querystring,
              requestType=requestType,
              timespan=timespan,
              interval=interval,
              aggregateList=aggregateList,
              interpolationList=interpolationList,
              interpolationSpanList=interpolationSpanList,
              authorizationToken=authorizationToken,
          )
  
  
      def _getData(
          self,
          timeseries,
          types,
          url,
          querystring,
          requestType,
          timespan,
          interval,
          aggregateList,
          interpolationList,
          interpolationSpanList,
          authorizationToken,
          otherColNamesThanTimeseriesIds=None,
      ):
          df = pd.DataFrame()
          typeList = self.types_api.getTypeTsx()
          if not isinstance(types,list):
              types = [types]
          if not isinstance(timeseries,list):
              timeseries=[timeseries]
  
          if otherColNamesThanTimeseriesIds != None:
              colNames = otherColNamesThanTimeseriesIds
          else:
              colNames = timeseries
          
  
          for i, _ in enumerate(timeseries):
              if timeseries[i] == None:
                  logging.error("No such tag: {tag}".format(tag=colNames[i]))
                  continue
              if types[i] == None:
                  logging.error("Type not defined for {timeseries}".format(timeseries=timeseries[i]))
                  continue
              logging.info(f'Timeseries {colNames[i]} has type {typeList[types[i]]}')
              if requestType == 'aggregateSeries':
                  (inlineVarPayload, projectedVarNames) = self.getInlineVariablesAggregate(typeList=typeList,currType=types[i], aggregateList=aggregateList,\
                      interpolationList=interpolationList,interpolationSpanList=interpolationSpanList)
              elif requestType == 'getSeries':
                  inlineVarPayload = [{"kind":"numeric", "value": {"tsx": typeList[types[i]]}, "filter": None}]
                  projectedVarNames = ['tagData']
              elif requestType == 'getEvents':
                  projectedVarNames = None
              payload = {
                  requestType: {
                      "timeSeriesId": timeseries[i],
                      "timeSeriesName": None,
                      "searchSpan": {"from": timespan[0], "to": timespan[1]},
                      "filter": None,
                      "interval": interval,
                      "inlineVariables": {},
                      "take": 250000,
                      "projectedVariables": projectedVarNames,
                  }
              }
  
              if requestType == 'aggregateSeries':
                  if isinstance(aggregateList, list):
                      for j in range(0,len(aggregateList)):
                          payload[requestType]["inlineVariables"][projectedVarNames[j]] = inlineVarPayload[j]
                  else:
                      payload[requestType]["inlineVariables"][projectedVarNames[0]] = inlineVarPayload[0]
  
              elif requestType == 'getSeries':
                  payload[requestType]["inlineVariables"][projectedVarNames[0]] = inlineVarPayload[0]
  
              elif requestType == 'getEvents':
                  payload[requestType]["filter"] = {"tsx": "($event.value.Double != null) OR ($event.Status.String = 'Good')"}
                  """ If this line is ignored all properties will be returned """
                  payload[requestType]["projectedProperties"] = [{"name":"value", "type":"Double"}]
  
              headers = {
                  "x-ms-client-application-name": self._applicationName,
                  "Authorization": authorizationToken,
                  "Content-Type": "application/json",
                  "cache-control": "no-cache",
              }
              retry_nb=0
              timestamp_empty=False
              while True:
                  retry_nb = retry_nb +1
                  try:
                      jsonResponse = requests.request(
                          "POST",
                          url,
                          data=json.dumps(payload),
                          headers=headers,
                          params=querystring,
                      )
                      jsonResponse.raise_for_status()
                  except requests.exceptions.ConnectTimeout:
                      logging.error("TSIClient: The request to the TSI api timed out.")
                      raise
                  except requests.exceptions.HTTPError:
                      logging.error("TSIClient: The request to the TSI api returned an unsuccessfull status code.")
                      raise
  
                  response = json.loads(jsonResponse.text)
                  if "error" in response:
                      if "innerError" in response["error"]:
                          if response["error"]["innerError"]["code"] == "TimeSeriesQueryNotSupported":
                              raise TSIStoreError(
                                  "TSIClient: Warm store not enabled in TSI environment: {id}. Set useWarmStore to False."
                                      .format(id=self.environmentId),
                              )
                      else:
                          logging.error("TSIClient: The query was unsuccessful, check the format of the function arguments.")
                          raise TSIQueryError(response["error"])
                  if (response["timestamps"] == []):
                      if (retry_nb<15) :
                          logging.critical("No data in search span for tag: {tag} - Retry".format(tag=colNames[i]))
                      else:
                          timestamp_empty=True
                  else:
                      break
              if timestamp_empty:
                  continue
                  
              if requestType == 'aggregateSeries':
                  try:
                      assert i == 0
                      if isinstance(aggregateList, list):
                          for idx, agg in enumerate(aggregateList):
                              currColName = colNames[i] + "/" + agg
                              if idx == 0:
                                  df = pd.DataFrame(
                                      {
                                          "timestamp": response["timestamps"],
                                          currColName : response["properties"][idx]["values"],
                                      }
                                  )
                              else:
                                  df[currColName] = response["properties"][idx]["values"]
                      else:
                          df = pd.DataFrame(
                                      {
                                          "timestamp": response["timestamps"],
                                          colNames[i] : response["properties"][0]["values"],
                                      }
                                  )
                  except:
                      if df.empty:
                          if isinstance(aggregateList, list):
                              for idx, agg in enumerate(aggregateList):
                                  currColName = colNames[i] + "/" + agg
                                  if idx == 0:
                                      df = pd.DataFrame(
                                          {
                                              "timestamp": response["timestamps"],
                                              currColName : response["properties"][idx]["values"],
                                          }
                                      )
                                  else:
                                      df[currColName] = response["properties"][idx]["values"]
                          else:
                              df = pd.DataFrame(
                                          {
                                              "timestamp": response["timestamps"],
                                              colNames[i] : response["properties"][0]["values"],
                                          }
                                      )
                      else:
                          if isinstance(aggregateList, list):
                              for idx, agg in enumerate(aggregateList):
                                  currColName  = colNames[i] + "/" + agg
                                  df[currColName] = response["properties"][idx]["values"]
                          else:
                              df[colNames[i]] = response["properties"][0]["values"]
  
                  finally:
                      logging.critical("Loaded data for tag: {tag}".format(tag=colNames[i]))
  
              else:
                  result = response
                  while 'continuationToken' in list(response.keys()):
                      headers = {
                          "x-ms-client-application-name": self._applicationName,
                          "Authorization": authorizationToken,
                          "Content-Type": "application/json",
                          "cache-control": "no-cache",
                          'x-ms-continuation': response['continuationToken'],
                      }
                      jsonResponse = requests.request(
                          "POST",
                          url,
                          data=json.dumps(payload),
                          headers=headers,
                          params=querystring,
                      )
                      jsonResponse.raise_for_status()
  
                      if jsonResponse.text:
                          response = json.loads(jsonResponse.text)
                      result["timestamps"].extend(response["timestamps"])
  
                      result["properties"][0]["values"].extend(response["properties"][0]["values"])
                  try:
                      assert i == 0
                      df = pd.DataFrame(
                              {
                                  "timestamp": result["timestamps"],
                                  colNames[i] : result["properties"][0]["values"],
                              }
                          )
                      df['timestamp'] = pd.to_datetime(df['timestamp'])
                      df.sort_values(by=['timestamp'], inplace=True)
  
                  except:
                      if df.empty:
                          df = pd.DataFrame(
                                  {
                                      "timestamp": result["timestamps"],
                                      colNames[i] : result["properties"][0]["values"],
                                  }
                              )
                          df['timestamp'] = pd.to_datetime(df['timestamp'])
                          df.sort_values(by=['timestamp'], inplace=True)
                      else:
                          df_temp = pd.DataFrame(
                              {
                                  "timestamp": result["timestamps"],
                                  colNames[i] : result["properties"][0]["values"],
                              }
                          )
                          df_temp['timestamp'] = pd.to_datetime(df_temp['timestamp'])
                          df_temp.sort_values(by=['timestamp'], inplace=True)
                          """ Tolerance: Limits to merge asof so there will be placed Nones if no values"""
                          df = pd.merge_asof(df,df_temp,on=['timestamp'],direction='nearest',tolerance=pd.Timedelta(seconds=30))
                  finally:
                      logging.critical("Loaded data for tag: {tag}".format(tag=colNames[i]))
          return df