from typing import Iterable


def unpack_generators(data, unwanted_types=None):
    """A utility function which recursively searches for generator objects
    within an iterable and turns them into tuples. Optionally, it also confirms
    that the iterable contains no un-expected data object types.

    Args:
        data (any): often an iterable containing generator objects, but can be
            anything
        unwanted_types (list): a list of all the object types that should not
            occur within data. None by default, meaning no screening for
            unwanted object types is done.

    Returns:
        tuple or any: a tuple containing only tuples, strings, and
            non-iterables. If the data argument was a string or not an
            iterable, it is returned un-changed.
    """
    if unwanted_types:
        for unwanted_type in unwanted_types:
            if isinstance(data, unwanted_type):
                raise TypeError("unwanted object type {} found: {}".format(unwanted_type, data))

    if not isinstance(data, str) and isinstance(data, Iterable):
        # As a side-effect this converts all lists, dictionaries, sets, etc. into tuples as well.
        return tuple(unpack_generators(item, unwanted_types) for item in data)
    else:
        return data