docs: def upload_local_file(filename=None, file=None, media_type=None, keep_open=False,
                        wait_on_close=False, use_existing_dxfile=None, show_progress=False,
                        write_buffer_size=None, multithread=True, **kwargs):
      '''
      :param filename: Local filename
      :type filename: string
      :param file: File-like object
      :type file: File-like object
      :param media_type: Internet Media Type
      :type media_type: string
      :param keep_open: If False, closes the file after uploading
      :type keep_open: boolean
      :param write_buffer_size: Buffer size to use for upload
      :type write_buffer_size: int
      :param wait_on_close: If True, waits for the file to close
      :type wait_on_close: boolean
      :param use_existing_dxfile: Instead of creating a new file object, upload to the specified file
      :type use_existing_dxfile: :class:`~dxpy.bindings.dxfile.DXFile`
      :param multithread: If True, sends multiple write requests asynchronously
      :type multithread: boolean
      :returns: Remote file handler
      :rtype: :class:`~dxpy.bindings.dxfile.DXFile`
  
      Additional optional parameters not listed: all those under
      :func:`dxpy.bindings.DXDataObject.new`.
  
      Exactly one of *filename* or *file* is required.
  
      Uploads *filename* or reads from *file* into a new file object (with
      media type *media_type* if given) and returns the associated remote
      file handler. The "name" property of the newly created remote file
      is set to the basename of *filename* or to *file.name* (if it
      exists).
  
      Examples::
  
        # Upload from a path
        dxpy.upload_local_file("/home/ubuntu/reads.fastq.gz")
        # Upload from a file-like object
        with open("reads.fastq") as fh:
            dxpy.upload_local_file(file=fh)
  
      '''
      fd = file if filename is None else open(filename, 'rb')
  
      try:
          file_size = os.fstat(fd.fileno()).st_size
      except:
          file_size = 0
  
      file_is_mmapd = hasattr(fd, "fileno")
  
      if write_buffer_size is None:
          write_buffer_size=dxfile.DEFAULT_BUFFER_SIZE
  
      if use_existing_dxfile:
          handler = use_existing_dxfile
      else:
          # Set a reasonable name for the file if none has been set
          # already
          creation_kwargs = kwargs.copy()
          if 'name' not in kwargs:
              if filename is not None:
                  creation_kwargs['name'] = os.path.basename(filename)
              else:
                  # Try to get filename from file-like object
                  try:
                      local_file_name = file.name
                  except AttributeError:
                      pass
                  else:
                      creation_kwargs['name'] = os.path.basename(local_file_name)
  
          # Use 'a' mode because we will be responsible for closing the file
          # ourselves later (if requested).
          handler = new_dxfile(mode='a', media_type=media_type, write_buffer_size=write_buffer_size,
                               expected_file_size=file_size, file_is_mmapd=file_is_mmapd, **creation_kwargs)
  
      # For subsequent API calls, don't supply the dataobject metadata
      # parameters that are only needed at creation time.
      _, remaining_kwargs = dxpy.DXDataObject._get_creation_params(kwargs)
  
      num_ticks = 60
      offset = 0
  
      handler._ensure_write_bufsize(**remaining_kwargs)
  
      def can_be_mmapd(fd):
          if not hasattr(fd, "fileno"):
              return False
          mode = os.fstat(fd.fileno()).st_mode
          return not (stat.S_ISCHR(mode) or stat.S_ISFIFO(mode))
  
      def read(num_bytes):
          """
          Returns a string or mmap'd data containing the next num_bytes of
          the file, or up to the end if there are fewer than num_bytes
          left.
          """
          # If file cannot be mmap'd (e.g. is stdin, or a fifo), fall back
          # to doing an actual read from the file.
          if not can_be_mmapd(fd):
              return fd.read(handler._write_bufsize)
  
          bytes_available = max(file_size - offset, 0)
          if bytes_available == 0:
              return b""
  
          return mmap.mmap(fd.fileno(), min(handler._write_bufsize, bytes_available), offset=offset, access=mmap.ACCESS_READ)
  
      handler._num_bytes_transmitted = 0
  
      def report_progress(handler, num_bytes):
          handler._num_bytes_transmitted += num_bytes
          if file_size > 0:
              ticks = int(round((handler._num_bytes_transmitted / float(file_size)) * num_ticks))
              percent = int(round((handler._num_bytes_transmitted / float(file_size)) * 100))
  
              fmt = "[{done}{pending}] Uploaded {done_bytes:,} of {total:,} bytes ({percent}%) {name}"
              sys.stderr.write(fmt.format(done='=' * (ticks - 1) + '>' if ticks > 0 else '',
                                          pending=' ' * (num_ticks - ticks),
                                          done_bytes=handler._num_bytes_transmitted,
                                          total=file_size,
                                          percent=percent,
                                          name=filename if filename is not None else ''))
              sys.stderr.flush()
              sys.stderr.write("\r")
              sys.stderr.flush()
  
      if show_progress:
          report_progress(handler, 0)
  
      while True:
          buf = read(handler._write_bufsize)
          offset += len(buf)
  
          if len(buf) == 0:
              break
  
          handler.write(buf,
                        report_progress_fn=report_progress if show_progress else None,
                        multithread=multithread,
                        **remaining_kwargs)
  
      if filename is not None:
          fd.close()
  
      handler.flush(report_progress_fn=report_progress if show_progress else None, **remaining_kwargs)
  
      if show_progress:
          sys.stderr.write("\n")
          sys.stderr.flush()
  
      if not keep_open:
          handler.close(block=wait_on_close, report_progress_fn=report_progress if show_progress else None, **remaining_kwargs)
  
      return handler