Without using any environment variables here we are giving full path to our Python file. The command also contains first the path to destination directory D:\\my_app then the path to my soruce file D:\\testing\\your_script.py
C:/Users/---Path here --/python3.11.exe -m PyInstaller --onefile --windowed --distpath D:\\my_appD:\\testing\\your_script.py
clock.py : Source code or our main file. D:\\testing\\clock.py : Full Path to our source file. D:\\my_app : Destination path where our executable file will be stored.
Check the table below for details of all commands used here .
Simple Calculator application
Source code for simple Calculator application → calculator.py : Source code or our main file. D:\\testing\\calculator.py : Full Path to our source file. D:\\my_app : Destination path where our executable file will be stored.
To allow our main file mysql1.py application to access the data from the JSON file at runtime (so different users can update their own data), we should avoid bundling the JSON file inside the executable. Instead, we can place the JSON file in the same directory as the executable, so it is accessible and editable by the user ( to update user login details ) .
Ensure the JSON is external to the executable
Instead of bundling the JSON file inside the app, keep it separate so the users can modify it directly.
Modify our mysql1.py to look for the JSON file in the current working directory:
Our code should now look like this:
import os
import sys
import json
from sqlalchemy import create_engine, text
# Define the JSON file's relative path
json_file = 'db_config.json'
# Check if the JSON file exists in the current working directory
if not os.path.exists(json_file):
raise FileNotFoundError(f"Configuration file {json_file} not found!")
# Load database configuration from JSON
with open(json_file, 'r') as f:
config = json.load(f)
# Establishing database connection using config
my_conn = create_engine(config['database_url']).connect()
# Rest of your Tkinter app logic
Use PyInstaller without bundling the JSON
Generate our executable without the --add-data option, so that the JSON file stays external and can be modified by the user:
Instructions for Users
Tell your users to place the db_config.json file in the same directory as the executable. User can edit this file to use their own database credentials or settings.
import tkinter as tk
import os
import sys
import json
from sqlalchemy import create_engine, text
# Define the JSON file's relative path
json_file = 'db_config.json'
# Check if the JSON file exists in the current working directory
if not os.path.exists(json_file):
raise FileNotFoundError(f"Configuration file {json_file} not found!")
# Load database configuration from JSON
with open(json_file, 'r') as f:
config = json.load(f)
# Establishing database connection using config
my_conn = create_engine(config['database_url']).connect()
r_set = my_conn.execute(text("SELECT count(*) as no from STUDENT"))
data_row = r_set.fetchone()
no_rec = data_row[0]
limit = 8
# Tkinter window setup
my_w = tk.Tk()
my_w.geometry("350x200")
def my_display(offset):
global my_conn
my_conn.close()
my_conn = create_engine(config['database_url']).connect()
q = "SELECT * from student LIMIT " + str(offset) + "," + str(limit)
r_set = my_conn.execute(text(q))
for widget in my_w.grid_slaves():
widget.grid_forget()
i = 0
for student in r_set:
for j in range(len(student)):
e = tk.Entry(my_w, width=10, fg='blue')
e.grid(row=i, column=j)
e.insert(tk.END, student[j])
i += 1
while i < limit:
for j in range(len(student)):
e = tk.Entry(my_w, width=10, fg='blue')
e.grid(row=i, column=j)
e.insert(tk.END, "")
i += 1
back = offset - limit
next = offset + limit
b1 = tk.Button(my_w, text='Next >', command=lambda: my_display(next))
b1.grid(row=12, column=4)
b2 = tk.Button(my_w, text='< Prev', command=lambda: my_display(back))
b2.grid(row=12, column=1)
if no_rec <= next:
b1["state"] = "disabled"
else:
b1["state"] = "active"
if back >= 0:
b2["state"] = "active"
else:
b2["state"] = "disabled"
my_display(0)
my_w.mainloop()
Benefits:
Users can easily modify their own JSON file to change settings.
The app dynamically reads the configuration at runtime, meaning no recompilation is needed for different user
Bundling a JSON Configuration File with PyInstaller
When creating a standalone executable using PyInstaller, you may encounter an issue where external files, such as a JSON configuration file, are not found by the bundled application. This typically occurs because PyInstaller runs the executable from a temporary directory, and files like the JSON are not automatically included.
The Problem
By default, PyInstaller doesn’t bundle files like .json unless specified. When using a database connection file in JSON format, the app may try to access the file from a temporary directory, leading to file-not-found errors.
The Solution: Using --add-data
To resolve this, you can explicitly tell PyInstaller to bundle the JSON file with the executable using the --add-data option. Here's the step-by-step approach:
Structuring Your Project
You will need two files:
main.py: The main application script. db_config.json: The JSON file containing your database connection details.
Make sure both files are in the same directory.
Modify Your Python Script to Handle Bundling
In your main.py, ensure the script can locate the JSON file when the application is run as an executable by PyInstaller:
import os
import sys
import json
from sqlalchemy import create_engine, text
# Check if the application is running as an executable or script
if getattr(sys, 'frozen', False):
base_dir = sys._MEIPASS # Temporary folder when running PyInstaller executable
else:
base_dir = os.path.dirname(os.path.abspath(__file__))
# Load JSON file
with open(os.path.join(base_dir, 'db_config.json'), 'r') as f:
config = json.load(f)
# Establishing database connection using config
my_conn = create_engine(config['database_url']).connect()
# Rest of your Tkinter app logic
This modification ensures that the path to the JSON file works both when running the script directly and when it's bundled into an executable by PyInstaller.
Use the PyInstaller --add-data Option
Now, you need to tell PyInstaller to bundle the JSON file into the final executable:
Explanation: --onefile: Combines everything into a single executable file. --add-data "db_config.json;.": This tells PyInstaller to include the db_config.json file in the same directory as the executable. On Windows, the semicolon (;) is used to separate the source path and destination path. For Linux/macOS, use a colon (:) instead.
Run the Executable
Once PyInstaller finishes, the resulting .exe file will include the db_config.json and will be able to access it without searching the temporary directory.
Conclusion
By using the --add-data option with PyInstaller and making small adjustments to how the file is loaded, you can ensure that external configuration files like JSON are correctly bundled and accessed, solving the issue of missing files during runtime. This approach is essential for packaging Tkinter applications or any Python app that relies on external data files.
Table of common PyInstaller commands with descriptions:
Command
Description
pyinstaller your_script.py
Creates a bundled application from the Python script with the default settings.
--onefile
Packages the application into a single executable file.
--windowed
Generates an application without a console window (for GUI applications).
--add-data "source;destination"
Adds non-Python files (like images or config files) to the bundle.
--icon=myicon.ico
Includes a custom icon for the generated executable.
--clean
Removes temporary files from previous builds before creating the executable.
--name my_app_name
Sets a custom name for the output executable.
--noconfirm
Automatically overwrites existing output files without prompting for confirmation.