Add config file outside Pyinstaller --onefile exe into dist directory

My solution is similar to @Stefano-Giraldi 's excellent solution. I was getting permission denied when passing directories to the shutil.copyfile.

I ended up using shutil.copytree:

import sys, os, shutil

site_packages = os.path.join(os.path.dirname(sys.executable), "Lib", "site-packages")
added_files = [
                (os.path.join(site_packages, 'dash_html_components'), 'dash_html_components'),
                (os.path.join(site_packages, 'dash_core_components'), 'dash_core_components'),
                (os.path.join(site_packages, 'plotly'), 'plotly'),
                (os.path.join(site_packages, 'scipy', '.libs', '*.dll'), '.')
                ]
working_dir_files = [
                ('assets', 'assets'),
                ('csv', 'csv')
                ]

print('ADDED FILES: (will show up in sys._MEIPASS)')
print(added_files)
print('Copying files to the dist folder')

print(os.getcwd())
for tup in working_dir_files:
        print(tup)
        to_path = os.path.join(DISTPATH, tup[1])
        if os.path.exists(to_path):
                if os.path.isdir(to_path):
                        shutil.rmtree(to_path)
                else:
                        os.remove(to_path)
        if os.path.isdir(tup[0]):
                shutil.copytree(tup[0], to_path )
        else:
                shutil.copyfile(tup[0], to_path )

#### ... Rest of spec file
a = Analysis(['myapp.py'],
             pathex=['.', os.path.join(site_packages, 'scipy', '.libs')],
             binaries=[],
             datas=added_files,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='myapp',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True )

This avoids the _MEI folder and keeps it from copying config files that you want in your dist folder and not in a temp folder.

Hope that helps.


For anyone stumbling over this thread looking for how to access files that are on the same level as the output file. The trick is, that the sys.executable is where the one-file .exe is located. So simply this does the trick:

import sys
import os.path
CWD = os.path.abspath(os.path.dirname(sys.executable))

use it e.g. with

with open(os.path.join(CWD, "config.ini")) as config_file:
    print(config_file.read())

Reason why os.getcwd()/relative paths don't work

The executable is just a executable archive that is extracted on execution to a temporary directory, where the .pyc files are executed. So when you call os.getcwd() instead of the path to the executable you get the path to the temporary folder.


A repository on Github helped me to find a solution to my question.

I've used shutil module and .spec file to add extra data files (in my case a config-sample.ini file) to dist folder using Pyinstaller --onefile option.

Make a .spec file for pyinstaller

First of all I've create a makespec file with the options I need:

$ pyi-makespec --onefile --windowed --name exefilename scriptname.py

This comand create an exefilename.spec file to use with Pyinstaller

Modify exefilename.spec adding shutil.copyfile

Now I've edited the exefilename.spec adding at the end of the file the following code.

import shutil
shutil.copyfile('config-sample.ini', '{0}/config-sample.ini'.format(DISTPATH))
shutil.copyfile('whateveryouwant.ext', '{0}/whateveryouwant.ext'.format(DISTPATH))

This code copy the data files needed at the end of compile process. You could use all the methods available in shutil package.

Run PyInstaller

The final step is to run compile process

pyinstaller --clean exefilename.spec

The result is that in the dist folder you should have the compiled .exe file together with the data files copied.

Consideration

In the official documentation of Pyinstaller I didn't found an option to get this result. I think it could be considered as a workaround... that works.