Utility method to extract the base item type from a collection/iterable item type.
    Throws
    * a TypeError if the collection_object_type a Dict with non-string keys.
    * an AttributeError if the collection_object_type is actually not a collection
    * a TypeInformationRequiredError if somehow the inner type can't be found from the collection type (either if dict,
    list, set, tuple were used instead of their typing module equivalents (Dict, List, Set, Tuple), or if the latter
    were specified without inner content types (as in Dict instead of Dict[str, Foo])

    :param collection_object_type:
    :return: a tuple containing the collection's content type (which may itself be a Tuple in case of a Tuple) and the
    collection's content key type for dicts (or None)

def _extract_collection_base_type(collection_object_type, exception_if_none: bool = True,
                                  resolve_fwd_refs: bool = True) -> Tuple[Type, Optional[Type]]:
    """
    Utility method to extract the base item type from a collection/iterable item type.
    Throws
    * a TypeError if the collection_object_type a Dict with non-string keys.
    * an AttributeError if the collection_object_type is actually not a collection
    * a TypeInformationRequiredError if somehow the inner type can't be found from the collection type (either if dict,
    list, set, tuple were used instead of their typing module equivalents (Dict, List, Set, Tuple), or if the latter
    were specified without inner content types (as in Dict instead of Dict[str, Foo])

    :param collection_object_type:
    :return: a tuple containing the collection's content type (which may itself be a Tuple in case of a Tuple) and the
    collection's content key type for dicts (or None)
    """
    contents_item_type = None
    contents_key_type = None

    check_var(collection_object_type, var_types=type, var_name='collection_object_type')

    is_tuple = False
    if is_tuple_type(collection_object_type):  # Tuple is a special construct, is_generic_type does not work
        is_tuple = True
        # --old: hack into typing module
        # if hasattr(collection_object_type, '__args__') and collection_object_type.__args__ is not None:
        # contents_item_type = collection_object_type.__args__

        # --new : using typing_inspect
        # __args = get_last_args(collection_object_type)
        # this one works even in typevar+config cases such as t = Tuple[int, Tuple[T, T]][Optional[int]]
        __args = get_args(collection_object_type, evaluate=True)
        if len(__args) > 0:
            contents_item_type = __args

    elif issubclass(collection_object_type, Mapping):  # Dictionary-like
        if is_generic_type(collection_object_type):
            # --old: hack into typing module
            # if hasattr(collection_object_type, '__args__') and collection_object_type.__args__ is not None:
            # contents_key_type, contents_item_type = collection_object_type.__args__

            # --new : using typing_inspect
            # __args = get_last_args(collection_object_type)
            # this one works even in typevar+config cases such as d = Dict[int, Tuple[T, T]][Optional[int]]
            __args = get_args(collection_object_type, evaluate=True)
            if len(__args) > 0:
                contents_key_type, contents_item_type = __args
                if not issubclass(contents_key_type, str):
                    raise TypeError('Collection object has type Dict, but its PEP484 type hints declare '
                                    'keys as being of type ' + str(contents_key_type) + ' which is not supported. Only '
                                    'str keys are supported at the moment, since we use them as item names')

    elif issubclass(collection_object_type, Iterable):  # List or Set. Should we rather use Container here ?
        if is_generic_type(collection_object_type):
            # --old: hack into typing module
            # if hasattr(collection_object_type, '__args__') and collection_object_type.__args__ is not None:
            # contents_item_type = collection_object_type.__args__[0]

            # --new : using typing_inspect
            # __args = get_last_args(collection_object_type)
            # this one works even in typevar+config cases such as i = Iterable[Tuple[T, T]][Optional[int]]
            __args = get_args(collection_object_type, evaluate=True)
            if len(__args) > 0:
                contents_item_type, = __args

    elif issubclass(collection_object_type, dict) or issubclass(collection_object_type, list)\
            or issubclass(collection_object_type, tuple) or issubclass(collection_object_type, set):
        # the error is now handled below with the other under-specified types situations
        pass

    else:
        # Not a collection
        raise AttributeError('Cannot extract collection base type, object type ' + str(collection_object_type)
                             + ' is not a collection')

    # Finally return if something was found, otherwise tell it
    try:
        if contents_item_type is None or contents_item_type is Parameter.empty:
            # Empty type hints
            raise TypeInformationRequiredError.create_for_collection_items(collection_object_type, contents_item_type)
        
        elif is_tuple:
            # --- tuple: Iterate on all sub-types
            resolved = []
            for t in contents_item_type:
                # Check for empty type hints
                if contents_item_type is None or contents_item_type is Parameter.empty:
                    raise TypeInformationRequiredError.create_for_collection_items(collection_object_type, t)

                # Resolve any forward references if needed
                if resolve_fwd_refs:
                    t = resolve_forward_ref(t)
                resolved.append(t)

                # Final type hint compliance
                if not is_valid_pep484_type_hint(t):
                    raise InvalidPEP484TypeHint.create_for_collection_items(collection_object_type, t)

            if resolve_fwd_refs:
                contents_item_type = tuple(resolved)
        
        else:
            # --- Not a tuple
            # resolve any forward references first
            if resolve_fwd_refs:
                contents_item_type = resolve_forward_ref(contents_item_type)

            # check validity then
            if not is_valid_pep484_type_hint(contents_item_type):
                # Invalid type hints
                raise InvalidPEP484TypeHint.create_for_collection_items(collection_object_type, contents_item_type)

    except TypeInformationRequiredError as e:
        # only raise it if the flag says it
        if exception_if_none:
            raise e.with_traceback(e.__traceback__)

    return contents_item_type, contents_key_type