code: def determine_how_to_read_input(input_obj):
      
  
      get_chunk = None
  
      if isinstance(input_obj, Queue):
          log_msg = "queue"
          get_chunk = get_queue_chunk_reader(input_obj)
  
      elif callable(input_obj):
          log_msg = "callable"
          get_chunk = get_callable_chunk_reader(input_obj)
  
      # also handles stringio
      elif hasattr(input_obj, "read"):
          log_msg = "file descriptor"
          get_chunk = get_file_chunk_reader(input_obj)
  
      elif isinstance(input_obj, basestring):
          log_msg = "string"
          get_chunk = get_iter_string_reader(input_obj)
  
      elif isinstance(input_obj, bytes):
          log_msg = "bytes"
          get_chunk = get_iter_string_reader(input_obj)
  
      elif isinstance(input_obj, GeneratorType):
          log_msg = "generator"
          get_chunk = get_iter_chunk_reader(iter(input_obj))
  
      else:
          try:
              it = iter(input_obj)
          except TypeError:
              raise Exception("unknown input object")
          else:
              log_msg = "general iterable"
              get_chunk = get_iter_chunk_reader(it)
  
      return get_chunk, log_msg
docs: given some kind of input object, return a function that knows how to
      read chunks of that input object.
      
      each reader function should return a chunk and raise a DoneReadingForever
      exception, or return None, when there's no more data to read
  
      NOTE: the function returned does not need to care much about the requested
      buffering type (eg, unbuffered vs newline-buffered).  the StreamBufferer
      will take care of that.  these functions just need to return a
      reasonably-sized chunk of data.