7

I'm fairly new with programming (and with Python) and the Stack Overflow question/response system allowed me to resolve all my problems until now. I didn't find any post directly addressing my current issue, but have to admit that I don't really know what's wrong. Let me explain.

I'm trying to make an executable file of a *.py script using PyInstaller. There's no problem doing it with a simple Python script (using --onefile), but it does not work when it comes to a more complex program that uses other *.py and *.txt files. I know that I need to modify the specification file and tried many alternatives - adding hidden files for instance.

Here are the files:

  • UpdatingStrategy.py (the target file to transform in executable)
  • LPRfunctions.py (UpdatingStrategy.py imports functions from this file)

The following *.txt files are read by UpdatingStrategy.py:

  • Strategy_Observ.txt
  • Strategy_Problems.txt
  • Updating_Observ1.txt
  • Updating_Observ2.txt
  • Updating_Problems.txt

I'm using Python 3.5 and Windows 10. Tell me if you need extra information.

How do I use the specification file properly and modify it in order to make an executable file of UpdatingStrategy.py?

I have read the PyInstaller documentation, but I lack many key principles and I couldn't make it work.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
PL de Chantal
  • 75
  • 1
  • 5

2 Answers2

11

After the line

a = Analysis( ... )

add

a.datas += [
    ("/absolute/path/to/some.txt","txt_files/some.txt","DATA"),
    ("/absolute/path/to/some2.txt","txt_files/some2.txt","DATA"),
    ("/absolute/path/to/some3.txt","txt_files/some3.txt","DATA"),
]

Then in your program use the following to get the resource path of your .txt files.

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.environ.get("_MEIPASS2",os.path.abspath("."))

    return os.path.join(base_path, relative_path)

 ...


 txt_data = open(resource_path("txt_files/some.txt")).read()

Make sure you build it like python -m PyInstaller my_target.spec ... do not call PyInstaller directly against your .py file after you have edited your specification file or it will overwrite your edited file...

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
  • You may be able to help solve my [problem](http://stackoverflow.com/questions/41073132/pyinstaller-app-is-accessing-txt-files-but-not-writing-to-them-works-before-ap). – ballade4op52 Dec 10 '16 at 20:35
  • 1
    As lema explained in his answer, PyInstaller's behaviour changed. Now the data tuple is comprised only of `(source_path, dest_dir)`. So, considering data and .spec on the same folder, you could just write `a.datas += [ ("some.txt",".")]`. Also note that the `resource_path` adjust at runtime is still required. – mvbentes Aug 07 '21 at 00:02
3

Anyone reading this in 2021... I've just run into similar issue with Pyinstaller. Needed to add a text file to my python code:

pyinstaller --add-data "/my/path/to/mytextfile.txt:/path/mytextfile.txt" mypython.py --onedir or --onefile

No matter if onedir or onefile, my code just never found the text file. Infact it threw the error:

IsADirectoryError: [Errno 21] Is a directory: /path/mytextfile.txt

Which didn't make sense to me because that's a file not a folder. Only when I checked this with the --onedir flag and I followed the path, I realized that pyinstaller would not only create a folder path, but also folder mytextfile.txt and put mytextfile.txt in there... so really the --add-data flag only wants a destination folder not the path to the file.

pyinstaller --add-data "/my/path/to/mytextfile.txt:path" mypython.py

or in the spec file i.e mypython.spec

datas=[('/my/path/to/mytextfile.txt', 'path')]

Should fix this. Note also the "DATA" configuration in the spec file is gone.

lema
  • 80
  • 6