boto/boto3 · error · ValueError

Fileobj must implement read

Error message

Fileobj must implement read

What it means

Raised by the injected `upload_fileobj` helper before an upload starts. boto3 streams bytes by calling `read()` on the supplied `Fileobj`, so it requires a genuine file-like object. Passing bytes, a string, a path, or any object lacking a `read` method is rejected immediately rather than failing mid-transfer. Use `upload_file` (not `upload_fileobj`) when you only have a filename.

Solutions

  1. If you have a path, switch to `client.upload_file(filename, bucket, key)` instead of `upload_fileobj`.
  2. If you have in-memory bytes, wrap them: `from io import BytesIO; client.upload_fileobj(BytesIO(data), bucket, key)`.
  3. If using a real file, open it in binary mode and pass the handle: `with open(path, 'rb') as f: client.upload_fileobj(f, bucket, key)`.
  4. Verify the object is not None before the call: assert the variable holding the body is assigned.

Example fix

# before
client.upload_fileobj(b'hello world', 'mybucket', 'key.txt')  # bytes has no .read

# after
from io import BytesIO
client.upload_fileobj(BytesIO(b'hello world'), 'mybucket', 'key.txt')
Defensive patterns

Strategy: validation

Validate before calling

def upload_body(obj):
    assert hasattr(obj, 'read'), 'upload_fileobj needs a file-like object with read()'
    return obj

client.upload_fileobj(upload_body(body), bucket, key)

Type guard

from io import IOBase
def is_readable_fileobj(o) -> bool:
    return hasattr(o, 'read') and callable(o.read)

Try / catch

try:
    client.upload_fileobj(body, bucket, key)
except ValueError as e:
    if 'must implement read' in str(e):
        from io import BytesIO
        client.upload_fileobj(BytesIO(bytes(body)), bucket, key)  # or upload_file with a path

Prevention

When it happens

Trigger: Calling `client.upload_fileobj(Bucket=..., Key=..., Body=<bytes|str|None|dict>)`, `bucket.upload_fileobj(<bytes>, key)`, or `s3.upload_fileobj(fileobj=<object-without-read>)`. Also triggered by an empty/None body variable that was expected to be an open file handle.

Common situations: Confusing `upload_file` (takes a path) with `upload_fileobj` (takes an open binary stream); passing `open(path)` in text mode `'r'` (this actually has `read` but corrupts binary data, and some wrappers still fail); forgetting to open the file; passing `b'data'` directly.

Related errors


AI-assisted analysis of boto/boto3@6e10b029c1 (2026-08-11). Data as JSON: /api/errors/194f6b08dab5d29e. Report an issue: GitHub.

Appendix: source

Thrown at boto3/s3/inject.py:660

    :type Key: str
    :param Key: The name of the key to upload to.

    :type ExtraArgs: dict
    :param ExtraArgs: Extra arguments that may be passed to the
        client operation. For allowed upload arguments see
        :py:attr:`boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS`.

    :type Callback: function
    :param Callback: A method which takes a number of bytes transferred to
        be periodically called during the upload.

    :type Config: boto3.s3.transfer.TransferConfig
    :param Config: The transfer configuration to be used when performing the
        upload.
    """
    if not hasattr(Fileobj, 'read'):
        raise ValueError('Fileobj must implement read')

    subscribers = None
    if Callback is not None:
        subscribers = [ProgressCallbackInvoker(Callback)]

    config = Config
    if config is None:
        config = TransferConfig()

    with create_transfer_manager(self, config) as manager:
        future = manager.upload(
            fileobj=Fileobj,
            bucket=Bucket,
            key=Key,
            extra_args=ExtraArgs,
            subscribers=subscribers,
        )
        return future.result()

View on GitHub (pinned to 6e10b029c1)