docs: 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