25

I'm trying to add an image to the one file produced by Pyinstaller. I've read many questions/forums like this one and that one and still it's not working.

I know that for one file operation, Pyinstller produce a temp folder that could be reached by sys.MEIPASS. However I don't know where exactly in my script I should add this sys.MEIPASS.

Kindly show the following:

1- Where and how sys.MEIPASS should be added? In the python script, or in the spec file?

2- What is the exact command to use? I've tried

pyinstaller --onefile --windowed --add-data="myImag.png;imag" myScript.py

or

pyinstaller --onefile --windowed myScript.py

and then add ('myImag.png','imag') to the spec file and then run

pyinstller myScript.spec

None has worked.

Note: I have python 3.6 under windows 7

AhmedWas
  • 1,205
  • 3
  • 23
  • 38

5 Answers5

44

When packaged to a single file with PyInstaller, running the .exe will unpack everything to a folder in your TEMP directory, run the script, then discard the temporary files. The path of the temporary folder changes with each running, but a reference to its location is added to sys as sys._MEIPASS.

To make use of this, when your Python codes reads any file that will also be packaged into your .exe, you need to change the files location to be located under sys._MEIPASS. In other words, you need to add it to your python code.

Here is an example that using the code from the link you referenced to adjust the file path to correct location when packaged to a single file.

Example

# data_files/data.txt
hello
world

# myScript.py
import sys
import os

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)

def print_file(file_path):
    file_path = resource_path(file_path)
    with open(file_path) as fp:
        for line in fp:
            print(line)

if __name__ == '__main__':
    print_file('data_files/data.txt')

Running PyInstaller with the following options packages the file:

pyinstaller --onefile --add-data="data_files/data.txt;data_files" myScript.py

builds myScript.exe which runs correctly and can open and read the packaged data file.

James
  • 32,991
  • 4
  • 47
  • 70
  • Thank you very much for this! Was banging my head on the wall to understand how to package data files with my pyinstaller. – Toutsos May 31 '19 at 20:02
  • 1
    Piling on the thanks for providing a working minimal example! NB the ';' delimiter will only work on Windows. It's ':' for Linux and Mac, as described here: https://stackoverflow.com/a/59710336/3684641 – rmharrison Mar 31 '20 at 04:23
4

I tried changed my python script's working directory, and it seems to work:

import os 
import sys

os.chdir(sys._MEIPASS)
os.system('included\\text.txt')

my pyinstaller command:

pyinstaller --onefile --nowindow --add-data text.txt;included winprint.py --distpath .
sc0ut
  • 41
  • 2
2

I used a command prompt command instead of a .spec

The command excludes shapely and then adds it back in (I guess this invokes a different import process). This shows how to add folders instead of just files.

pyinstaller --clean --win-private-assemblies --onefile --exclude-module shapely --add-data C:\\Python27\\Lib\\site-packages\\shapely;.\\shapely --add-data C:\\Python27\\tcl\\tkdnd2.8;tcl main.py
nda
  • 541
  • 7
  • 18
1

I put together a simple function that will grab the resource from a local path when running your *.py file as a script (i.e., in a debugger or via the command line) or from the temp directory when running as a pyinstaller single-file executable

import sys
from pathlib import Path

def fetch_resource(resource_path: Path) -> Path:
    try:  # running as *.exe; fetch resource from temp directory
        base_path = Path(sys._MEIPASS)
    except AttributeError:  # running as script; return unmodified path
        return resource_path
    else:  # return temp resource path
        return base_path.joinpath(resource_path)

This way, if your script has any hardcoded paths you can use them as-is, e.g.: self.iconbitmap(fetch_resource(icon_path)) and icon_path will be updated appropriately based on the environment.

You'll need to tell pyinstaller to --add-data for any assets you wish to use this way, e.g. to add your entire 'assets' folder:

pyinstaller -w -F --add-data "./src/assets/;assets"

Or you can add things directly to the datas list in your pyinstaller spec file (in the a = Analysis block)

datas = ['(./src/assets)', 'assets']
JRiggles
  • 4,847
  • 1
  • 12
  • 27
0

a simpler way of accessing the temp folder if by doing this:

bundle_dir = getattr(sys, '_MEIPASS', path.abspath(path.dirname(__file__)))
data_path = os.path.abspath(path.join(bundle_dir, 'data_file.dat'))

Got it from read the docs

LolaKid
  • 51
  • 2