Skip to content

How to upload binary data files? #183

@mikeholler

Description

@mikeholler

Hello there,

I considered posting this to Gitter, but it does not seem very active so I wanted to post here instead.

I'm evaluating using uplink for our API clients, and so far it looks like a wonderful tool, checking all of the boxes I'd expect. However, we have a few routes that accept binary data (think simple document store), and I don't see a way to upload unstructured binary data using uplink. Here's what I tried:

from uplink import Consumer, Body, post

class SampleApi(Consumer):
    @post("post")
    def create(self, body: Body):
        """Post some data to httpbin's /post method"""

Then I start httpbin locally with docker run --rm -p 80:80 kennethreitz/httpbin, and fire up an interpreter and tried a few things (which you'll see below). I've combed the docs and cannot find any examples of arbitrary data being uploaded, and when I search binary, bytes, file, or bytearray in this project I didn't see much. I'd like to know how to do this, and I'm thinking it should probably be added to the project's documentation as well.

>>> api = Sample(base_url="http://localhost")
>>> api.create(body=b'1234')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/builder.py", line 99, in __call__
    self._request_definition.define_request(request_builder, args, kwargs)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/commands.py", line 267, in define_request
    self._argument_handler.handle_call(
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/arguments.py", line 153, in handle_call
    self.handle_call_args(request_builder, call_args)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/arguments.py", line 158, in handle_call_args
    annotation.modify_request(request_builder, call_args[name])
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/arguments.py", line 182, in modify_request
    self._modify_request(request_builder, converter(value))
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/converters/interfaces.py", line 6, in __call__
    return self.convert(*args, **kwargs)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/converters/standard.py", line 19, in convert
    return self._converter(value)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/converters/interfaces.py", line 6, in __call__
    return self.convert(*args, **kwargs)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/converters/standard.py", line 30, in convert
    dumped = json.dumps(value, default=self._default_json_dumper)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/json/__init__.py", line 234, in dumps
    return cls(
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/converters/standard.py", line 25, in _default_json_dumper
    return obj.__dict__  # pragma: no cover
AttributeError: 'bytes' object has no attribute '__dict__'
>>> api.create(body=bytearray([1, 2, 3]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/builder.py", line 99, in __call__
    self._request_definition.define_request(request_builder, args, kwargs)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/commands.py", line 267, in define_request
    self._argument_handler.handle_call(
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/arguments.py", line 153, in handle_call
    self.handle_call_args(request_builder, call_args)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/arguments.py", line 158, in handle_call_args
    annotation.modify_request(request_builder, call_args[name])
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/arguments.py", line 182, in modify_request
    self._modify_request(request_builder, converter(value))
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/converters/interfaces.py", line 6, in __call__
    return self.convert(*args, **kwargs)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/converters/standard.py", line 19, in convert
    return self._converter(value)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/converters/interfaces.py", line 6, in __call__
    return self.convert(*args, **kwargs)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/converters/standard.py", line 30, in convert
    dumped = json.dumps(value, default=self._default_json_dumper)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/json/__init__.py", line 234, in dumps
    return cls(
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/home/mjholler/.pyenv/versions/3.8.0/lib/python3.8/site-packages/uplink/converters/standard.py", line 25, in _default_json_dumper
    return obj.__dict__  # pragma: no cover
AttributeError: 'bytearray' object has no attribute '__dict__'

Then, when this didn't work, I just wanted to try something with plain requests, which of course did work:

>>> import requests
>>> r = requests.post("http://localhost/post", data=b'1234')
>>> r.status_code
200
>>> r.json()
{'args': {}, 'data': '1234', 'files': {}, 'form': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'Content-Length': '4', 'Host': 'localhost', 'User-Agent': 'python-requests/2.22.0'}, 'json': 1234, 'origin': '172.17.0.1', 'url': 'http://localhost/post'}
>>> 

Note the 'json' field has 1234 which I posted is binary, but it's just coincidentally rendering it as valid JSON (since it is). I don't know if httpbin has something for binary files, so this was what I thought I could use.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions