docs: def upload_part(self, data, index=None, display_progress=False, report_progress_fn=None, **kwargs):
          """
          :param data: Data to be uploaded in this part
          :type data: str or mmap object, bytes on python3
          :param index: Index of part to be uploaded; must be in [1, 10000]
          :type index: integer
          :param display_progress: Whether to print "." to stderr when done
          :type display_progress: boolean
          :param report_progress_fn: Optional: a function to call that takes in two arguments (self, # bytes transmitted)
          :type report_progress_fn: function or None
          :raises: :exc:`dxpy.exceptions.DXFileError` if *index* is given and is not in the correct range, :exc:`requests.exceptions.HTTPError` if upload fails
  
          Uploads the data in *data* as part number *index* for the
          associated file. If no value for *index* is given, *index*
          defaults to 1. This probably only makes sense if this is the
          only part to be uploaded.
          """
          if not USING_PYTHON2:
              # In python3, the underlying system methods use the 'bytes' type, not 'string'
              assert(isinstance(data, bytes))
  
          req_input = {}
          if index is not None:
              req_input["index"] = int(index)
  
          md5 = hashlib.md5()
          if hasattr(data, 'seek') and hasattr(data, 'tell'):
              # data is a buffer; record initial position (so we can rewind back)
              rewind_input_buffer_offset = data.tell()
              while True:
                  bytes_read = data.read(MD5_READ_CHUNK_SIZE)
                  if bytes_read:
                      md5.update(bytes_read)
                  else:
                      break
              # rewind the buffer to original position
              data.seek(rewind_input_buffer_offset)
          else:
              md5.update(data)
  
          req_input["md5"] = md5.hexdigest()
          req_input["size"] = len(data)
  
          def get_upload_url_and_headers():
              # This function is called from within a retry loop, so to avoid amplifying the number of retries
              # geometrically, we decrease the allowed number of retries for the nested API call every time.
              if 'max_retries' not in kwargs:
                  kwargs['max_retries'] = dxpy.DEFAULT_RETRIES
              elif kwargs['max_retries'] > 0:
                  kwargs['max_retries'] -= 1
  
              if "timeout" not in kwargs:
                  kwargs["timeout"] = FILE_REQUEST_TIMEOUT
  
              resp = dxpy.api.file_upload(self._dxid, req_input, **kwargs)
              url = resp["url"]
              return url, _validate_headers(resp.get("headers", {}))
  
          # The file upload API requires us to get a pre-authenticated upload URL (and headers for it) every time we
          # attempt an upload. Because DXHTTPRequest will retry requests under retryable conditions, we give it a callback
          # to ask us for a new upload URL every time it attempts a request (instead of giving them directly).
          dxpy.DXHTTPRequest(get_upload_url_and_headers,
                             data,
                             jsonify_data=False,
                             prepend_srv=False,
                             always_retry=True,
                             timeout=FILE_REQUEST_TIMEOUT,
                             auth=None,
                             method='PUT')
  
          self._num_uploaded_parts += 1
  
          if display_progress:
              warn(".")
  
          if report_progress_fn is not None:
              report_progress_fn(self, len(data))