Skip to content

gh-155419: honor the file object's current position in hashlib.file_digest - #155422

Open
picnixz wants to merge 1 commit into
python:mainfrom
picnixz:meta/crypto/py/hashlib/file-digest-honor-position-155419
Open

gh-155419: honor the file object's current position in hashlib.file_digest#155422
picnixz wants to merge 1 commit into
python:mainfrom
picnixz:meta/crypto/py/hashlib/file-digest-honor-position-155419

Conversation

@picnixz

@picnixz picnixz commented Aug 9, 2026

Copy link
Copy Markdown
Member

@picnixz
picnixz requested a review from gpshead as a code owner August 9, 2026 11:39
@picnixz picnixz added needs backport to 3.13 bugs and security fixes needs backport to 3.14 bugs and security fixes needs backport to 3.15 pre-release feature fixes, bugs and security fixes labels Aug 9, 2026
Comment thread Lib/hashlib.py
@@ -233,7 +233,8 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18):

if hasattr(fileobj, "getbuffer"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duck typing this is just checking "does it have a getbuffer" method. I could see getbuffer implementations which only return the data which will show up in read vs. what BytesIO does (the whole allocated data). I don't think there is a formalized "what does getbuffer do" with this case covered.

For new feature 3.16+ I think this approach is reasonable / if people have other getbuffer implementations they'll let us know. As a bugfix backport it seems like a subtle breaking change to performance sensitive code. To backport I think should explicitly check isinstance(fileobj, io.BytesIO).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

How can it be a breaking change? it was already like that for the past years

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

And fileobj.getbuffer() returns a view on the buffer and buf[fileobj.tell():] returns a view as well, so 0-copy is still ensure, or am I missing something?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

FTR, we document file_digest as follows:

fileobj must be a file-like object opened for reading in binary mode. It accepts file objects from builtin open(), BytesIO instances, SocketIO objects from socket.socket.makefile(), and similar. fileobj must be opened in blocking mode, otherwise a BlockingIOError may be raised.

So if people are doing something else, they're on their own.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

context: the file_digest code was shipped in 3.11 and has had few changes since: https://github.com/python/cpython/pull/31930/changes.

While the comment asserts "this is a BytesIO" custom I/O stacks for Python exist. Searching there are quite a few custom getbuffer implementations outside of io.BytesIO (Many for display buffers, but some I/O buffers). Those pass this code and the typeshed stub protocol (https://github.com/python/typeshed/blob/6875aaf17c8322b00499f79276340fec4cc87451/stdlib/hashlib.pyi#L97-L109) which just assert "getbuffer method required". Custom optimized I/O stacks would likely implement a getbuffer which produces the result the codebase cares about. That might pay attention to the file offset, dropping already read bytes or might contain all bytes ever written.

The change here:

  1. Makes it so in addition to getbuffer() the object must support a tell() method unconditionally
  2. Currently implements that the file offset / tell result is required to go from "all data" to the "data not yet read".

Neither of those are "required" to me from the typeshed protocol or duck typing. The added tell requirement would break code which runs fine today and so is a breaking change.

If we "narrow" this to just BytesIO explicitly then requiring tell isn't too big of a hurdle and to me it does fix what feels like unintended behavior. Adding a requirement for tell is changing the API meaningfully hence 3.16+.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting core review needs backport to 3.13 bugs and security fixes needs backport to 3.14 bugs and security fixes needs backport to 3.15 pre-release feature fixes, bugs and security fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants