docs: def read(self, size=-1):
          """Read at most `size` bytes from the file (less if there
          isn't enough data).
  
          The bytes are returned as an instance of :class:`str` (:class:`bytes`
          in python 3). If `size` is negative or omitted all data is read.
  
          :Parameters:
            - `size` (optional): the number of bytes to read
          """
          self._ensure_file()
  
          if size == 0:
              return EMPTY
  
          remainder = int(self.length) - self.__position
          if size < 0 or size > remainder:
              size = remainder
  
          received = 0
          data = StringIO()
          while received < size:
              chunk_data = self.readchunk()
              received += len(chunk_data)
              data.write(chunk_data)
  
          # Detect extra chunks.
          max_chunk_n = math.ceil(self.length / float(self.chunk_size))
          chunk = self.__chunks.find_one({"files_id": self._id,
                                          "n": {"$gte": max_chunk_n}})
          # According to spec, ignore extra chunks if they are empty.
          if chunk is not None and len(chunk['data']):
              raise CorruptGridFile(
                  "Extra chunk found: expected %i chunks but found "
                  "chunk with n=%i" % (max_chunk_n, chunk['n']))
  
          self.__position -= received - size
  
          # Return 'size' bytes and store the rest.
          data.seek(size)
          self.__buffer = data.read()
          data.seek(0)
          return data.read(size)