Search code examples
pythonpython-3.xpython-requestsaiohttp

Sending data and files as multipart request using aiohttp


I am trying to send a "multipart/form-data" request through aiohttp. I have already tried using requests and the request works fine. I have a class where self.files is an io.BytesIO object and self.data is a dictionary of string values.

Here's the relevant part of my code in this class:

params = {
    "url": self.url,
    "headers": self.headers,
    "timeout": self.timeout,
    "proxy": self.proxy
}
# No data is allowed in the body for GET requests
if self.method != "get":
    if self.header_content_type == "multipart/form-data":
        with aiohttp.MultipartWriter("form-data") as mp:
            if self.data:
                for key, value in self.data.items():
                    part = mp.append(value, {'content-type': 'form-data'})
                    part.set_content_disposition('form-data', name=key)
            if self.files:
                for key, value in self.files.items():
                    part = mp.append(value.read(), {'content-type': 'form-data'})
                    part.set_content_disposition('form-data', name=key)

            params["data"] = mp

            async with aiohttp.ClientSession(trust_env=False) as session:
                async with session.post(**params) as response:
                    print(params)
                    return response

However, this fails with a 422 error from the called API which means that the data is not properly formed.

The output of the params that I have printed above is follows:

{'url': 'http://localhost:9999/v2/toing', 'headers': {'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkY200Y2hlZSIsImV4cCI6MTcxODI4MzAyMX0.6d5XcxucVLvml4O0Z-JBfOvrJLtz254371aQ0XKiCuI'}, 'timeout': 500, 'proxy': None, 'data': <aiohttp.multipart.MultipartWriter object at 0x1149aff40>}

It seems that the "data" section is not properly formatted and that the files attribute is missing.

Here are the parameters that work for me using requests.post:

('http://localhost:9999/v2/toing', {'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkY200Y2hlZSIsImV4cCI6MTcxODMwMjQyMH0.lpAfQKISeIhUIcSf1OdM9ZN3eOUB0YHwcfVshY7y0VQ'}) {'data': {'request_data': '{"kk_shape": [2595, 2826], "algorithm_type": "trauma", "age": "33", "center": "toing", "is_spx": false, "detected_xx": "yyy", "kk": "hand"}'}, 'files': {'image_arr': <_io.BytesIO object at 0x116faed90>}, 'header_content_type': 'multipart/form-data'}

Is it possible to send files and data separately using aiohttp as it seems aiohttp.ClientSession.session.post does not have a files parameter as in requests.post? What am I doing wrong?


Solution

  • This is indeed possibleby using aiohttp.FormData instead of aiohttp.MultipartWriter. Here's a working modification of the code:

    params = {
        "method": self.method,
        "url": self.url,
        "headers": self.headers,
        "timeout": self.timeout,
        "proxy": self.proxy
    }
    # No data is allowed in the body for GET requests
    if self.method != "get":
        form_data = aiohttp.FormData()
        if self.header_content_type == "multipart/form-data":
            if self.data:
                for key, value in self.data.items():
                    form_data.add_field(name=key, value=value)
            if self.files:
                for key, value in self.files.items():
                    form_data.add_field(name=key, value=value)
    
            params["data"] = form_data
        else:
            params["data"] = self.data
    
    async with aiohttp.ClientSession(trust_env=False) as session:
        async with session.request(**params) as response:
            print(params)
            return response