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.

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