Search code examples
python-3.xpyinstallerexe

pyinstaller onefile can't access external resources


I'm very new to Python and I need help with some basic stuff. I wrote a small program that I want do convert to .exe. The program works fine if I run it with Python.

But when I use pyinstaller I can't access external resources. My goal is to have myprogram.exe in the same directory as an assets folder. The assets folder contains images and a json file that I W/R ...

The command I'm using is just this, and no other config:

pyinstaller --onefile --name myprogram gui.py

So for the code I have this:

OUTPUT_PATH = Path(__file__).parent
ASSETS_PATH = OUTPUT_PATH / Path("./assets")

def relative_to_assets(path: str) -> Path:
    return ASSETS_PATH / Path(path)

Using like so:

canvas.place(x = 0, y = 0)
    image_image_1 = PhotoImage(
    file=relative_to_assets("image_1.png"))
image_1 = canvas.create_image(
    225.0,
    114.0,
    image=image_image_1
)

And to load/read the JSON file:

def loadJSONFile():
data = {}
if os.path.isfile(relative_to_assets("data.json")) and os.access(ASSETS_PATH, os.R_OK):
    WALLETSLISTFILE = open(relative_to_assets("data.json"),"r")
    data = json.loads(WALLETSLISTFILE.read())
else:
    print("Either file is missing or is not readable, creating file...")
    with io.open(os.path.join(ASSETS_PATH, 'data.json'), 'w') as db_file:
        db_file.write(json.dumps({}))
    WALLETSLISTFILE = open(relative_to_assets("data.json"), "r")
    data = json.loads(WALLETSLISTFILE.read())
return data

Project Structure with python (which works):

enter image description here

So what I want is to generate the gui.exe with --onefile and later join the folder "assets" in same .exe directory.

But with that code above the path to the files is incorrect and the program doesn´t starts (because I'm not handling the exception right now)


Solution

  • So to answear to my own question I changed my code regarding to the IO operations for the JSON file and added a few options when using pyinstaller for the other resources.

    So regarding to pyinstaller:

    pyinstaller --onefile --name "gui" --add-data "assets/*.png;./assets" --add-data "assets/*.ico;./assets" gui.py
    

    So when using the .exe generated by pyinstaller and using these path definitions:

    OUTPUT_PATH = Path(__file__).parent
    ASSETS_PATH = OUTPUT_PATH / Path("./assets")
    
    def relative_to_assets(path: str) -> Path:
       return ASSETS_PATH / Path(path)
    
    canvas.place(x = 0, y = 0)
       image_image_1 = PhotoImage(
          file=relative_to_assets("image_1.png"))
          image_1 = canvas.create_image(
          225.0,
          114.0,
          image=image_image_1
    )
    

    My assets are stored in a temp folder:

    C:\Users\username\AppData\Local\Temp\_MEI89482\assets
    

    That´s ok for static resources like images and files to only read, because when the program closes the temp folder also goes away.

    So to my JSON file I had to change the Path to a more persistent location. So I used the APPDATA OS env. var., like so:

    import os
    
    APPDATA_PATH = os.environ['APPDATA']
    PROGRAM_FOLDER = 'MyData'
    
    def relative_to_appdata_folder():
        return os.path.join(APPDATA_PATH,PROGRAM_FOLDER)
    
    def relative_to_json_file(path: str) -> Path:
        return relative_to_appdata_folder() / Path(path)
    
    def loadJSONFile():
        data = {}
        if os.path.isfile(relative_to_json_file("data.json")) and 
    os.access(relative_to_appdata_folder(), os.R_OK):
        WALLETSLISTFILE = open(relative_to_json_file("data.json"), "r")
        data = json.loads(WALLETSLISTFILE.read())
    else:
        print("Either file is missing or is not readable, creating file...")
        with io.open(os.path.join(relative_to_appdata_folder(), 'data.json'), 'w') as db_file:
            db_file.write(json.dumps({}))
        WALLETSLISTFILE = open(relative_to_json_file("data.json"), "r")
        data = json.loads(WALLETSLISTFILE.read())
    return data
    

    Also I needed to check if the directory exists do create the file "data.json" in the previous Path:

    if not os.path.exists(relative_to_appdata_folder()):
        os.makedirs(relative_to_appdata_folder())
    

    So that's how I got it to work and my data.json file is stored here:

    C:\Users\username\AppData\Roaming\MyData
    

    If anyone has a more elegant solution let me know, I love to learn more.