I would like to reduce the volume of my pyinstaller generated exe that I have to deploy every time.
I create my app using pyinstaller. when I use the onefile flag I get a ~750MB exe file. Without the onefile flag, the exe is ~50MB and the _internal folder ~1.6GB (650MB when as zipped). I cannot remove dependencies to reduce the volume.
Is it possible to direct the exe to take the _internal folder from a specified location (for example: C:\Program Files\MyAppDeps v3_internal) such that I can deploy the _internal folder only once in a while (~once per quarter, when there is a new 3rd party dependency) and then distribute only the small exe file every time?
Users cannot copy the _internal/exe each time to the same directory. Users cannot create symbolic links.
I want to do this because I update my app very frequently (several times per week) and deploy to machines that are not connected to the any network making the distribution process long and difficult, reducing the distribution volume will improve productivity greatly.
4 Answers 4
My assumptions
I am answering this question without a clear understanding of your deployment method, but I'll assume that:
- Your users get the update either by manually downloading the executable, or the executable updates itself using some kind of remote index.
- I also assume you have access to some kind of index/repository (I may be using the wrong word) from which people in your organization can download the software (but may require technical expertise) . In any case I hope this answer is useful to you and anyone else in the same predicament.
Splitting the exe from the data.
As you mention in your question, it seems wise to separate the internal data from the program itself, and ideally work with 50MB executables, and a compressed 650MB internal data zip.
I would advise that when your executable runs for the first time: you check the existence of your internal data at a predefined location such as C:\Program Files\MyAppDeps v3_internal (as you pointed out in your question). If this data does not exist it is installed from an index to that specified location. Of course, you add version-checking logic that ensures the existing data is up to date, and if not, you let the use know they should update the data using your application when appropriate.
You could also have your executable check if it is up to date with version on the index and follow the same logic as above.
I hope this was useful, please let me know if I should expand on this solution.
1 Comment
I found a solution for this problem. Instead of a symbolic link, use a Junction:
mklink src dst /J
Unlike a symbolic link, a junction doesn't require elevated permissions to create so I can run it on the customer station. I wrapped this in a .bat file to run the program.
The only downside is that the users will have to get used to running a .bat file instead of an .exe
mklink "%~dp0bin\_internal" "C:\installation_dir\bin\_internal" /J
call "%~dp0bin\my_app.exe"`
1 Comment
Approach 1: Python path manipulation
Put your third party libraries in a known location. In your program startup, before importing libraries, run something like this:
import sys
sys.path.append('C:\\path\\to\\packages')
Then tell PyInstaller to ignore all those dependencies.
Approach 2: Use a different deployment approach
If having the PyInstaller exe is not a requirement, there would be various possible ways to deploy third party libraries and Python itself, separately from your app code. If you haven't already, check out uv with its uv tool install command that might be able to meet your needs.
2 Comments
Traceback (most recent call last): File "C:\envs\***\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py", line 63, in <module> _pyi_rthook() File "C:\envs\***\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py", line 58, in _pyi_rthook pyimod02_importers.PyiFrozenImporter, AttributeError: module 'pyimod02_importers' has no attribute 'PyiFrozenImporter'Then you should first built an installer
Approach:
The installer doesn't need to use you're env since it only downloads your executable and resources while being lightweight at the same time
- on launch the installer checks directory if they is a old version it just proceed to upgrade it without redownloading it all over (you'll need to index changes between versions so the installer only download files it needs)
- but if nothing is present in directory it start a new installation from the most recent upgrade
the tradeoffs are indexing changes per upgrades and maintaining a server from which you're installer can download from
The installer becomes the only things a users needs to upgrade you're app and their can upgrade to a newer installer later or use the same to upgrade app so it works out well and users won't have to copy anything just download and run
PyInstallerat all