Skip to content Skip to sidebar Skip to footer

How To Set Up Relative Paths To Make A Portable .exe Build In Pyinstaller With Python 3?

I've looked up everywhere and got no definite response to a rather trivial question. I have a Python project in PyCharm on Windows 7 that contains multiple .py files (which are co

Solution 1:

Using --onefile bundles all the datas together into the .exe file.

When you execute the file, these files are "unpacked" to a temporary file location. On Windows, this is usually C:\Users\<You>\AppData\Local\Temp\MEIxxx.

So, when you are developing your script, the data files (your text files in this example) will be located at

C:\\Users\\%username%\\PycharmProjects\\%project_folder%\\%project_folder%\txt_files\

but when the app is compiled, they will be extracted to the temporary directory mentioned above. So you need a way to tell the script whether you are developing, or it has been compiled. This is where you can use the 'frozen' flag (see the docs here)

An approach I have used before, is to create a utility function like this

def resolve_path(path):
    if getattr(sys, "frozen", False):
        # If the 'frozen' flag is set, we are in bundled-app mode!
        resolved_path = os.path.abspath(os.path.join(sys._MEIPASS, path))
    else:
        # Normal development mode. Use os.getcwd() or __file__ as appropriate in your case...
        resolved_path = os.path.abspath(os.path.join(os.getcwd(), path))

    return resolved_path

Then whenever you want to use a path in your script, for example accessing your text files you can do

withopen(resolve_path("txt_files/file1.txt"), "r") as txt:
    ...

which should resolve the correct path whichever mode you are in.

A note on your .spec file

  1. You don't have to specify all the text files individually. You can of course, and you may have a good reason for doing so which is fine. But you could do

datas=[('txt_files', '.')]

which puts the contents of txt_files directory in the root of your bundle. Be careful with this however, because now the paths to your text files will be <dev directory>\txt_files\file1.txt but in the bundled app, they will be <MEIPASS directory>\file1.txt. You may want to keep the 'relative' part of the path the same by doing

datas=[('txt_files', 'txt_files')]

which will mirror the file structure between your development folder and your bundled app.

  1. Also consider if you build with the spec file, remove the COLLECT part in order to produce a onefile bundled executable.

Solution 2:

I have the following FS

  • GitRepoForMySomeProject
    • MySomeProject
      • main.py
    • MyGitSubmodule
    • ExeSettings.spec
    • ExeVersion.py
    • ExeMySomeProject.bat ("pyinstaller ExeSettings.spec")

In main.py are using some submodule which placed into subfolder "MyGitSubmodule/ReportGenerator.py"

from MyGitSubmodule import ReportGenerator as _report_

Here is my ExeSettings.spec file, with relative path pathex=['../MySomeProject']

# -*- mode: python -*-

block_cipher = None


a = Analysis(['MySomeProject/main.py'],
             pathex=['../MySomeProject'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          name='MySomeProject',
          debug=False,
          strip=False,
          upx=True,
          console=False,
          icon='my.ico',
          version='ExeVersion.py')

if forget to add this path, we will have

ModuleNotFoundError: No module named "MyGitSubmodule"

Post a Comment for "How To Set Up Relative Paths To Make A Portable .exe Build In Pyinstaller With Python 3?"