Have you ever created an amazing tool or script in Python but couldn't share it due to the code being interpreted, having lots of dependencies, or you didn't want to give away your private API keys or other secrets?
Here is how to compile your Python app into a standalone executable for Mac, Windows, and Linux using PyInstaller.
We will ensure sensitive data like API keys are securely bundled and will even address problems like what to do about additional data files.
myapp.py) that runs locally. For our example we will use a basic console app that gets the current weather.Ensure your app works locally (python3 myapp.py).
If it uses API keys, like our example does, then store them securely in a .env file to avoid hardcoding.
Example: MyApp.py
import os
import requests
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("WEATHER_API_KEY")
if not API_KEY:
raise ValueError("API key not found.")
def fetch_weather(city):
url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY}&units=metric"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
return f"Temperature: {data['main']['temp']}°C, Weather: {data['weather'][0]['description']}"
except requests.RequestException as e:
return f"Error: {e}"
if __name__ == "__main__":
city = input("Enter city: ")
print(fetch_weather(city))
.envCreate a .env file and make sure it is in the same directory as your Python (eg. ./myapp.py):
WEATHER_API_KEY=your_actual_api_key_here
Next you will need to
python-dotenv: pip3 install python-dotenv..env to .gitignore to prevent accidentally sharing.Install PyInstaller using Pip, which we will use to compile your app:
pip3 install pyinstaller
Try pyinstaller --version to make sure it is installed ok.
If you run pyinstaller myapp.py it WILL compile, but you will get a bunch of weird or unwanted files. So we need a small extra step.
To create a single, standalone binary (avoiding extra files/folders), use the --onefile option. Include the .env file for API keys.
Run:
pyinstaller --onefile --add-data ".env;." --name MyApp myapp.py
--onefile: Bundles everything into one executable.--add-data ".env;.": Includes the .env file (use : for Mac/Linux, ; or ^; for Windows).--name MyApp: Names the binary MyApp (e.g., MyApp.exe on Windows).dist/MyApp (or dist/MyApp.exe): The executable for distribution.build/ and myapp.spec: Temporary files (delete after building)../dist/MyAppdist\MyApp.exe.env, instead of being a file in the directory it should now be bundled into the binary file, and the key should be hidden.To distribute for Mac, Windows, and Linux:
sudo apt-get install libgl1-mesa-glx on Ubuntu).requests, python-dotenv) on the build machine.dist/MyApp (the binary).build/, myapp.spec, and any dist/myapp/ folder.myapp.spec (edit hiddenimports):
hiddenimports=['requests', 'python-dotenv']
Re-run: pyinstaller myapp.spec..env Not Found:
--add-data ".env;." and load_dotenv() in myapp.py and ensure you use : and not ; on Mac/Linux.It is normal for --onefile to produce large files. We can reduce that down with UPX: pyinstaller --onefile --upx-dir /path/to/upx myapp.py.
UPX (Ultimate Packer for Executables) is a free, open-source tool that compresses executable files, such as those generated by PyInstaller, to reduce their file size. In the context of your goal to compile a Python textual app (myapp.py) into a single executable for cross-platform distribution (Mac, Windows, Linux) with secure API key handling, UPX can help make your PyInstaller-generated binaries smaller, improving distribution efficiency.
.env file keeps keys secure in the binary.pip install cython
cythonize -i myapp.py
from cryptography.fernet import Fernet
key = Fernet.generate_key() # Save securely
cipher = Fernet(key)
encrypted_key = cipher.encrypt(API_KEY.encode())
# Decrypt: API_KEY = cipher.decrypt(encrypted_key).decode()
.env in buildozer.spec (source.include_exts = py,env).