Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

requests_toolbelt.MultipartEncoder fails to encode S3File objects due to missing len property #933

Open
kformanowicz-dotdata opened this issue Jan 21, 2025 · 3 comments

Comments

@kformanowicz-dotdata
Copy link

kformanowicz-dotdata commented Jan 21, 2025

Hello,

I'm unable to use a S3File object together with requests_toolbelt.MultipartEncoder (https://toolbelt.readthedocs.io/en/latest/user.html). The MultipartEncoder expects the File object to contain len property to calculate its size and fails with the following:

File "/home/krzysztof/venvs/py311/lib/python3.11/site-packages/requests_toolbelt/multipart/encoder.py", line 125, in __init__
    self._prepare_parts()
  File "/home/krzysztof/venvs/py311/lib/python3.11/site-packages/requests_toolbelt/multipart/encoder.py", line 246, in _prepare_parts
    self.parts = [Part.from_field(f, enc) for f in self._iter_fields()]
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/krzysztof/venvs/py311/lib/python3.11/site-packages/requests_toolbelt/multipart/encoder.py", line 246, in <listcomp>
    self.parts = [Part.from_field(f, enc) for f in self._iter_fields()]
                  ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/krzysztof/venvs/py311/lib/python3.11/site-packages/requests_toolbelt/multipart/encoder.py", line 495, in from_field
    return cls(headers, body)
           ^^^^^^^^^^^^^^^^^^
  File "/home/krzysztof/venvs/py311/lib/python3.11/site-packages/requests_toolbelt/multipart/encoder.py", line 488, in __init__
    self.len = len(self.headers) + total_len(self.body)
                                   ^^^^^^^^^^^^^^^^^^^^
  File "/home/krzysztof/venvs/py311/lib/python3.11/site-packages/requests_toolbelt/multipart/encoder.py", line 432, in total_len
    if hasattr(o, 'len'):
       ^^^^^^^^^^^^^^^^^
  File "/home/krzysztof/venvs/py311/lib/python3.11/site-packages/requests_toolbelt/multipart/encoder.py", line 573, in len
    return total_len(self.fd) - self.fd.tell()
           ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for -: 'NoneType' and 'int'

It works with a custom adapter like this:

class S3FileWithLenAdapter:
    def __init__(self, s3_file):
        self._s3_file = s3_file

    @property
    def len(self):
        return self._s3_file.size

    def __getattr__(self, name):
        return getattr(self._s3_file, name)

Could you consider adding len property to S3File objects?

@kformanowicz-dotdata kformanowicz-dotdata changed the title Add len property to S3File class Mrequests_toolbelt.MultipartEncoder fails to encode S3File objects due to missing len property Jan 21, 2025
@kformanowicz-dotdata kformanowicz-dotdata changed the title Mrequests_toolbelt.MultipartEncoder fails to encode S3File objects due to missing len property requests_toolbelt.MultipartEncoder fails to encode S3File objects due to missing len property Jan 21, 2025
@martindurant
Copy link
Member

Is it really len, or __len__() that you need?

One subtle problem, is that files already can be iterated, but the unit is one line rather than one character. So len(list(file)) will not be the same as then len we are talking about above.

@kformanowicz-dotdata
Copy link
Author

@martindurant Thanks for replying. The MultipartEncoder actually checks the following attrs in its total_len() function:

def total_len(o):
    if hasattr(o, '__len__'):
        return len(o)

    if hasattr(o, 'len'):
        return o.len

    if hasattr(o, 'fileno'):
        try:
            fileno = o.fileno()
        except io.UnsupportedOperation:
            pass
        else:
            return os.fstat(fileno).st_size

    if hasattr(o, 'getvalue'):
        # e.g. BytesIO, cStringIO.StringIO
        return len(o.getvalue())

So adding any of those properties would solve my issue.

@martindurant
Copy link
Member

OK, then let's add len as a property as you originally suggested. This can be done in the upstream AbstractBufferedFile in fsspec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants