Bundling data files with PyInstaller (--onefile)

Solution 1:

Newer versions of PyInstaller do not set the env variable anymore, so Shish's excellent answer will not work. Now the path gets set as sys._MEIPASS:

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

Solution 2:

pyinstaller unpacks your data into a temporary folder, and stores this directory path in the _MEIPASS2 environment variable. To get the _MEIPASS2 dir in packed-mode and use the local directory in unpacked (development) mode, I use this:

def resource_path(relative):
    return os.path.join(
        os.environ.get(
            "_MEIPASS2",
            os.path.abspath(".")
        ),
        relative
    )

Output:

# in development
>>> resource_path("app_icon.ico")
"/home/shish/src/my_app/app_icon.ico"

# in production
>>> resource_path("app_icon.ico")
"/tmp/_MEI34121/app_icon.ico"

Solution 3:

All of the other answers use the current working directory in the case where the application is not PyInstalled (i.e. sys._MEIPASS is not set). That is wrong, as it prevents you from running your application from a directory other than the one where your script is.

A better solution:

import sys
import os

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)

Solution 4:

Perhaps i missed a step or did something wrong but the methods which are above, didn't bundle data files with PyInstaller into one exe file. Let me share the steps what i have done.

  1. step:Write one of the above methods into your py file with importing sys and os modules. I tried both of them. The last one is:

    def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
        base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
        return os.path.join(base_path, relative_path)
    
  2. step: Write, pyi-makespec file.py, to the console, to create a file.spec file.

  3. step: Open, file.spec with Notepad++ to add the data files like below:

    a = Analysis(['C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.py'],
                 pathex=['C:\\Users\\TCK\\Desktop\\Projeler'],
                 binaries=[],
                 datas=[],
                 hiddenimports=[],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher)
    #Add the file like the below example
    a.datas += [('Converter-GUI.ico', 'C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico', 'DATA')]
    pyz = PYZ(a.pure, a.zipped_data,
         cipher=block_cipher)
    exe = EXE(pyz,
              a.scripts,
              exclude_binaries=True,
              name='Converter-GUI',
              debug=False,
              strip=False,
              upx=True,
              #Turn the console option False if you don't want to see the console while executing the program.
              console=False,
              #Add an icon to the program.
              icon='C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico')
    
    coll = COLLECT(exe,
                   a.binaries,
                   a.zipfiles,
                   a.datas,
                   strip=False,
                   upx=True,
                   name='Converter-GUI')
    
  4. step: I followed the above steps, then saved the spec file. At last opened the console and write, pyinstaller file.spec (in my case, file=Converter-GUI).

Conclusion: There's still more than one file in the dist folder.

Note: I'm using Python 3.5.

EDIT: Finally it works with Jonathan Reinhart's method.

  1. step: Add the below codes to your python file with importing sys and os.

    def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
        base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
        return os.path.join(base_path, relative_path)
    
  2. step: Call the above function with adding the path of your file:

    image_path = resource_path("Converter-GUI.ico")
    
  3. step: Write the above variable that call the function to where your codes need the path. In my case it's:

        self.window.iconbitmap(image_path)
    
  4. step: Open the console in the same directory of your python file, write the codes like below:

        pyinstaller --onefile your_file.py
    
  5. step: Open the .spec file of the python file and append the a.datas array and add the icon to the exe class, which was given above before the edit in 3'rd step.
  6. step: Save and exit the path file. Go to your folder which include the spec and py file. Open again the console window and type the below command:

        pyinstaller your_file.spec
    

After the 6. step your one file is ready to use.