docs: def query(self, tool, filters=None, source='cumulative', sort=None,
                direction='desc', func=None, func_params=None, req_size=1000,
                scan=None, directory=None, **filterset):
          """query tool, [filters], [req_size], [list, of, filters]
          This function attempts to make it a lot easier to run vuln and lce
          searches within the Security Center API.  This function will query the
          API for all of the items that match this query and will then
          return the end result back as a single block.  This means that running
          large queries with this function can consume a large amount of memory
          so some care must be taken.
  
          If any filters need to be specified that cannot simply be expressed as
          filterName=value then you must fully build that specific filter or list
          of filters.  If this is the case, then you must overload the filters
          value with a list of the filter dictionary sets that are needed.  For
          example:
  
          [{'filterName': FILTERNAME, 'operator': '=', 'value': VALUE}]
  
          While we dont expect this to be used much, its there incase it's needed.
  
          The default request size is also set to 1000.  This means that the
          maximum request size that a singular query can consume is 1000
          individual results before rolling on to the next query.
  
          If the mitigated flag is set, the results returned back will be from
          the mitigated dataset instead of the cumulative dataset.
  
          If the source is `"individual"`, `scan` should be passed a scan id and
          `directory` should be passed a date directory as a string `"YYYY-mm-dd"`
          or as a `datetime` to be converted.
  
          For a list of the available filters that can be performed, please
          consult the Security Center API documentation.
  
          """
  
          data = []       # This is the list that we will be returning back to
                          # the calling function once we complete.
          payload = {}    # The dataset that we will be sending to the API via the
                          # raw_query function.
  
          # A simple data dictionary to determine the module that we will be used
          stype = {
              'cumulative': 'vuln',
              'mitigated': 'vuln',
              'patched': 'vuln',
              'individual': 'vuln',
              'lce': 'events',
          }
  
          # When the source is "individual", scan and directory should be provided
          # as well, and will be set in the payload.
          if source == "individual" and scan is not None and directory is not None:
              # convert directory passed as datetime to string if necessary
              if isinstance(directory, date):
                  directory = directory.strftime("%Y-%m-%d")
  
              payload["scanID"] = scan
              payload["dateDirectory"] = directory
              payload["view"] = "all"
  
          # Check to see if filters was set.  If it wasnt, then lets go ahead and
          # initialize it as an empty dictionary.
          if filters is None:
              filters = []
  
          # Here is where we expand the filterset dictionary to something that the
          # API can actually understand.
          for item in filterset:
              filters.append({
                  'filterName': item,
                  'operator': '=',
                  'value': filterset[item]
              })
  
          # Next we need to populate the payload with the information needed to
          # fire a complete API request.
          payload['tool'] = tool
          payload['sourceType'] = source
          payload['filters'] = filters
  
          # Then we will check to see if a sort field was specified.  If it was,
          # then we will add the sortField and sortDir elements to the query.
          if sort is not None:
              payload['sortField'] = sort
              payload['sortDir'] = direction
  
          # Now that we have everything we need, a quick sanity check first to
          # make sure some idiot didn't give us a completely empty filterset to
          # work with.  If they did for some reason, just return an empty list.
          #if len(filters) < 1:
          #    return []
  
          # Everything is set, checks out, and is ready to go.  Now we have to
          # start running through the query loop and actually pull everything
          # together.  We know that we will
          items = []      # This is the resultset.  It'll be different every time
                          # we loop.  to get things going however, we will just
                          # set it to an empty list.
          count = 0       # A simple counter to track the total number of results
                          # that have been returned.
          while len(items) == req_size or count == 0:
              # The API requires that we set an offset for the start and end of
              # the request, so we will add these to the payload here.
              payload['startOffset'] = count
              payload['endOffset'] = count + req_size
  
              # Now lets run the query, assign the items, and inc the counter.
              response = self.raw_query(stype[source], 'query', data=payload)
              items = response['results']
              count += len(items)
  
              # New we will either flatten the data to the data list, or if func
              # is defined, we will pass the data for this query on to that
              # function for parsing.
              if func is not None and hasattr(func, '__call__'):
                  if func_params is not None:
                      func(items, **func_params)
                  else:
                      func(items)
              else:
                  for item in items:
                      data.append(item)
  
              # Lastly lets check to see if we entered into an empty dataset.  If
              # we did, then we will need to break out of the loop so that we
              # don't run into an infinite loop and thrash the API.
              if response['totalRecords'] == '0':
                  break
          return data