ConvertAPI / convertapi-library-python

A Python library for the ConvertAPI
https://www.convertapi.com
Other
73 stars 22 forks source link

Incompatibility with tkinter? #11

Closed BenH4 closed 4 years ago

BenH4 commented 4 years ago

I'm using the module convertapi to merge pdf files in a tkinter application in Python3.8. When I have some tkinter window in my code, if convertapi.convert('merge', {'Files': input_files}) is called, multiple instances of the tkinter window open. My script:

from tkinter import *
import convertapi

input_files = ["file1.pdf", "file2.pdf", "file3.pdf"]
output_file = "mergedFile.pdf"

def mergePDFs(input_files, output_file):
    convertapi.api_secret = 'my-api-secret'
    result = convertapi.convert('merge', {'Files': input_files})
    result.file.save(output_file)

root = Tk()
Button(root, text="Merge", command=lambda: mergePDFs(input_files, output_file)).pack()
root.mainloop()

A picture of the phenomenon It's a very weird behavior since even when I call the function in the console with the tkinter window closed beforehand, multiple windows still open up. I'm guessing there is some kind of incompatibility between the two modules but I can't be sure. If it can help, there are 10 more instances of the tkinter window that open up when the funstion is called.

After a bit of digging, it seems that it is due to the fact that multiple processes can't share the same root window in tkinter. And indeed in the code, there is a multiprocessing operation when multiple files are converted. In convertapi/task.py:

def __normalize_params(self):
        params = {}

        for k, v in self.params.items():
            if k == 'File':
                params[k] = file_param.build(v)
            elif k == 'Files':
                results = utils.map_in_parallel(file_param.build, v, convertapi.max_parallel_uploads)

                for idx, val in enumerate(results):
                    key = '%s[%i]' % (k, idx)
                    params[key] = val
            else:
                params[k] = v

        params.update(self.default_params)

        return params

And convertapi/utils.py:

import multiprocessing

def map_in_parallel(f, values, pool_size):
    pool = multiprocessing.Pool(pool_size)
    results = pool.map_async(f, values)
    pool.close()
    pool.join()

    return results.get()
BenH4 commented 4 years ago

I succesfully solved my problem by tinkering with the convertapi code a bit. I added the possibility to disable the multiprocess mapping, here are the changes I made:

def convert(to_format, params, from_format = None, multiproc=True, timeout = None): task = Task(from_format, to_format, params, multiproc, timeout = timeout) return task.run()

def user(timeout = None): return convertapi.client.get('user', timeout = timeout)


- And to convertapi/tasks.py:

class Task: def init(self, from_format, to_format, params, multiproc, timeout = None): self.from_format = from_format self.to_format = to_format self.params = params self.timeout = timeout or convertapi.conversion_timeout self.multiproc = multiproc

    self.default_params = {
        'Timeout': self.timeout,
        'StoreFile': True,
    }

def __normalize_params(self):
    params = {}

    for k, v in self.params.items():
        if k == 'File':
            params[k] = file_param.build(v)
        elif k == 'Files':
            if self.multiproc:
                results = utils.map_in_parallel(file_param.build, v, convertapi.max_parallel_uploads)
            else:
                results = map(file_param.build, v)
            for idx, val in enumerate(results):
                key = '%s[%i]' % (k, idx)
                params[key] = val
        else:
            params[k] = v

    params.update(self.default_params)

    return params

I have not tested the changes with anything else than the merging of PDFs from local files.
tomasr78 commented 4 years ago

@laurynas-baltsoft Should we add the fix suggested by @BenH4 ?

laurynas-convertapi commented 4 years ago

Hey @BenH4, have you tried adjusting max_parallel_uploads param? You could try with pool size 1: convertapi.max_parallel_uploads = 1

BenH4 commented 4 years ago

Hi @laurynas-baltsoft, actually I remember trying to use 1 for the pool-size and it open one more tkinter instance. I also tried 0 but obviously it crashed.

laurynas-convertapi commented 4 years ago

Hey @BenH4, adjusted not to use multiprocessing if max_parallel_uploads is set to 1. It is released in v1.3.0. Could you please check if it solves the issue?