skip to Main Content

I have a problem trying to upload a file via multipart/form-data. The problem is not with requests/toolbelt but the API I am working with (is an API that mainly uses requests for a particular platform called Mercado Libre, similar to Ebay).

I’m posting the relevant code:

The particular method of the API:

def post(self, path, body=None, params=None, extra_headers=None):
    params = params or {}
    headers = {'Accept': 'application/json', 'User-Agent':self.SDK_VERSION, 'Content-type':'application/json'}
    if extra_headers:
        headers.update(extra_headers)
    uri = self.make_path(path)
    if body:
        body = json.dumps(body)

    response = self._requests.post(uri, data=body, params=urlencode(params), headers=headers)
    return response

My code:

from requests_toolbelt import MultipartEncoder

encoder = MultipartEncoder(
    fields={
        'file': (
        'myfile.txt',
            open('/tmp/myfile.txt', 'rb'),
            'text/plain'
        )
    }
)

self.post(path='the-url-path', body=encoder, extra_headers={'Content-type': encoder.content_type})

Of course this wil gives an error because the method line: body = json.dumps(body):

TypeError: Object of type ‘MultipartEncoder’ is not JSON serializable

What I am doing wrong or how I can fix this?

Thanks in advance.

3

Answers


  1. Chosen as BEST ANSWER

    For now, I just implemented the solution using pycurl as @kcorlidy suggested till developers of that API write the right way to do it with requests. So, I've added an alternative method to the API to upload multipart/form-data files (also a utility class to return response similar to requests):

    def post_files(self, path, files, access_token):
        '''
            Método adicional para subir archivos dado que el método post()
            de esta api no permite la subida mediante requets, param files.
        '''
        headers = [
            'Accept:application/json',
            'User-Agent:{}'.format(self.SDK_VERSION),
            'Content-type:multipart/form-data'
        ] 
        url = os.path.join(self.make_path(path), '?access_token={}'.format(access_token))     
    
        curl = pycurl.Curl()
        data = BytesIO()
        header_data = BytesIO()
    
        curl.setopt(pycurl.URL, url) 
        curl.setopt(pycurl.HTTPHEADER, headers)
        for name, filename in files.items():
            curl.setopt(pycurl.HTTPPOST, [
                (name, (
                    pycurl.FORM_FILE, filename
                )),
            ])
        #curl.setopt(pycurl.VERBOSE, 1)
    
        # Escribimos response y headers
        curl.setopt(pycurl.WRITEFUNCTION, data.write)
        curl.setopt(pycurl.HEADERFUNCTION, header_data.write)
    
        curl.perform()
    
        # Parseamos el header para obtener status message
        status_line = header_data.getvalue().decode('utf-8').splitlines()[0]
        match = re.match(r'HTTP/S*s*d+s*(.*?)s*$', status_line)
        if match:
            status_message = match.groups(1)[0]
        else:
            status_message = ''
    
        # Objeto response de curl 
        response = CurlResponse(
            response=data.getvalue(),
            status_code=curl.getinfo(pycurl.HTTP_CODE),
            status_message=status_message,
            url=url
        )
    
        curl.close()
    
        return response
    
    
    class CurlResponse:
        '''
            Clase para armar la respuesta de pycurl y simular
            la respuesta del módulo requests
        '''
        def __init__(self, **kwargs):
            self.__dict__.update(**kwargs)
    
        def __str__(self):
            return str('Response {} {}'.format(
                self.__dict__['status_code'],
                self.__dict__['status_message']
                )
            )
    
        def json(self):
            return json.loads(self.__dict__['response'])
    

    Thanks @kcorlidy for the support!


  2. You need to operate with the file a little bit differently, by using the with keyword:

    from requests_toolbelt import MultipartEncoder
    with open('/tmp/myfile.txt', 'rb') as f:
        encoder = MultipartEncoder(
            fields={
                'file': (
                'myfile.txt',
                    f,
                    'text/plain'
                )
            }
        )
    
        self.post(path='the-url-path', body=encoder, extra_headers={'Content-type': encoder.content_type})
    
    Login or Signup to reply.
  3. I imitate the request from curl. But i did not have account in Mercado Libre, therefore i can not promise script is correct. However, if example on upload file document in right that this script should run successfully.

    import requests
    
    ACCESS_TOKEN = ""
    
    url = "https://api.mercadolibre.com/pictures?access_token={}".format(ACCESS_TOKEN)
    
    
    def post_file(s):
        path = "bg/background_hd_01.jpg"
    
        headers = {
                    "Accept": "application/json"}
        files = {
            "file":("my.jpg",open(path,"rb"),"image/jpeg")
        }
    
        resp = s.post(url,files=files,headers=headers)
        data = resp.json()
        if data.get("id"):
            with open("log.txt","a+") as log:
                log.write("{}n{}n".format(path,resp.text))
    
    with requests.Session() as s:
        post_file(s)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search