Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Doc/library/hashlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,10 @@ a file or file-like object.
*fileobj* must be a file-like object opened for reading in binary mode.
It accepts file objects from builtin :func:`open`, :class:`~io.BytesIO`
instances, SocketIO objects from :meth:`socket.socket.makefile`, and
similar. The function may bypass Python's I/O and use the file descriptor
similar. *fileobj* must be opened in blocking mode, otherwise a
:exc:`BlockingIOError` may be raised.

The function may bypass Python's I/O and use the file descriptor
from :meth:`~io.IOBase.fileno` directly. *fileobj* must be assumed to be
in an unknown state after this function returns or raises. It is up to
the caller to close *fileobj*.
Expand Down Expand Up @@ -301,6 +304,10 @@ a file or file-like object.

.. versionadded:: 3.11

.. versionchanged:: next
Now raises a :exc:`BlockingIOError` if the file is opened in blocking
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this meant to be "BlockingIOError if the file is not opened in blocking mode" or "BlockingIOError if the file is opened in non-blocking mode."?

As written it contradicts the previous addition 30 lines up and the NEWS.d entry.

Copy link
Contributor

@cmaloney cmaloney Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be "if the file is opened in non-blocking mode."

More specifically:
If .readinto returns None because it has no data and would block raises BlockingIOError.

(PR to update the docs welcome if you're interested in contributing directly; happy to help review)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

mode. Previously, spurious null bytes were added to the digest.


Key derivation
--------------
Expand Down
2 changes: 2 additions & 0 deletions Lib/hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18):
view = memoryview(buf)
while True:
size = fileobj.readinto(buf)
if size is None:
raise BlockingIOError("I/O operation would block.")
if size == 0:
break # EOF
digestobj.update(view[:size])
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,15 @@ def test_file_digest(self):
with self.assertRaises(ValueError):
hashlib.file_digest(None, "sha256")

class NonBlocking:
def readinto(self, buf):
return None
def readable(self):
return True

with self.assertRaises(BlockingIOError):
hashlib.file_digest(NonBlocking(), hashlib.sha256)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:func:`hashlib.file_digest` now raises :exc:`BlockingIOError` when no data
is available during non-blocking I/O. Before, it added spurious null bytes
to the digest.
Loading