This document explains how the Python backend integration works in Context Forge Electron.
The application can spawn and manage a PyInstaller-packaged Python executable from the system tray menu. This allows you to:
- Start/stop a Python backend process on demand
- Monitor the process status (running/stopped, PID, uptime)
- Automatically clean up the process when the app quits
- Control everything from the tray icon menu
┌─────────────────────────────────────────────────────────────┐
│ Electron Main Process │
│ │
│ ┌──────────────────┐ ┌─────────────────────────┐ │
│ │ TrayManager │────────▶│ PythonProcessManager │ │
│ │ │ │ │ │
│ │ - Tray Menu │ │ - spawn() │ │
│ │ - Start/Stop UI │ │ - Process lifecycle │ │
│ │ - Status Display │ │ - Signal handling │ │
│ └──────────────────┘ └─────────────────────────┘ │
│ │ │
└──────────────────────────────────────────┼────────────────────┘
│
▼
┌─────────────────────────┐
│ Python Child Process │
│ │
│ PyInstaller Executable │
│ - backend.py │
│ - Signal handlers │
│ - JSON output │
└─────────────────────────┘
Manages the Python process lifecycle:
start(args?)- Spawns the Python executablestop()- Gracefully stops the process (SIGTERM, then SIGKILL after 5s)restart(args?)- Stops and restarts the processgetStatus()- Returns current status (running, PID, uptime, path)executableExists()- Checks if the executable is present
Key Features:
- Automatic path resolution (dev vs production)
- Platform-specific executable names (
.exeon Windows) - Graceful shutdown with timeout
- stdout/stderr logging
- Error handling
The tray menu includes a "Python Backend" submenu with:
- Status indicator: 🟢 Running / 🔴 Stopped /
⚠️ Not Found - Process info: PID and uptime (when running)
- Controls: Start, Stop, Restart buttons
- Executable path: Shows where the executable is located
The menu updates dynamically based on the process state.
Initializes the Python manager and integrates it with the tray:
// Create Python manager
pythonManager = new PythonProcessManager();
// Pass to tray manager
trayManager = new TrayManager(mainWindow, pythonManager);
// Clean up on quit
app.on('before-quit', async () => {
await pythonManager.stop();
});project-root/
└── python/
├── backend.py # Source script
└── dist/
└── backend # PyInstaller executable (macOS/Linux)
└── backend.exe # PyInstaller executable (Windows)
app-package/
└── resources/
└── python/
└── backend # Bundled executable (macOS/Linux)
└── backend.exe # Bundled executable (Windows)
- Start the backend: Click tray icon → Python Backend → Start Backend
- Check status: The menu shows if it's running, the PID, and uptime
- Stop the backend: Click tray icon → Python Backend → Stop Backend
- Restart: Click tray icon → Python Backend → Restart Backend
The backend automatically stops when you quit the app.
cd python
pip install pyinstaller
pyinstaller --onefile --name backend backend.pyThis creates python/dist/backend (or backend.exe on Windows).
- Build the executable (see above)
- Run the Electron app:
npm start - Use the tray menu to start/stop the backend
- Check the console for Python output
Edit python/backend.py to add your logic. Key requirements:
-
Handle signals for graceful shutdown:
signal.signal(signal.SIGTERM, self.handle_shutdown) signal.signal(signal.SIGINT, self.handle_shutdown)
-
Flush output so Electron receives it immediately:
print("message", flush=True)
-
Check running flag in your main loop:
while self.running: # Your logic here time.sleep(1)
Modify the tray menu to pass arguments:
// In tray-manager.ts
await this.pythonManager?.start(['--port', '5000', '--debug']);Handle them in Python:
import sys
args = sys.argv[1:] # ['--port', '5000', '--debug']Python prints to stdout, Electron logs it:
# Python
print(json.dumps({"status": "ready"}), flush=True)// TypeScript
pythonProcess.stdout?.on('data', (data) => {
console.log(`Python: ${data}`);
});Run a Flask/FastAPI server in Python:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/api/status')
def status():
return jsonify({"status": "running"})
app.run(port=5000)Call it from Electron:
const response = await fetch('http://localhost:5000/api/status');
const data = await response.json();Send data to Python via stdin:
// TypeScript
pythonProcess.stdin?.write(JSON.stringify({action: "process"}) + '\n');# Python
import sys
for line in sys.stdin:
data = json.loads(line)
# Process dataCause: The executable doesn't exist at the expected location.
Solution:
- Build the executable:
cd python && pyinstaller --onefile --name backend backend.py - Verify it exists:
ls python/dist/backend - Check permissions:
chmod +x python/dist/backend(macOS/Linux)
Cause: Python script doesn't handle signals properly.
Solution: Ensure your Python script has signal handlers:
signal.signal(signal.SIGTERM, self.handle_shutdown)The process will be force-killed after 5 seconds if it doesn't respond.
Cause: Python output is buffered.
Solution: Always use flush=True:
print("message", flush=True)Or disable buffering:
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)Cause: Python script has an error or exits immediately.
Solution:
- Test the executable directly:
./python/dist/backend - Check stderr output in Electron console
- Add error handling in Python:
try: backend.run() except Exception as e: print(f"Error: {e}", file=sys.stderr, flush=True)
Include the Python executable in your package:
export default {
packagerConfig: {
extraResource: [
'./python/dist/backend', // macOS/Linux
'./python/dist/backend.exe', // Windows
],
},
// ... rest of config
};PyInstaller creates platform-specific executables. Build on each target platform:
- macOS: Build on macOS
- Windows: Build on Windows
- Linux: Build on Linux
npm run makeThen test the generated app to ensure the Python backend works.
- Validate input: If passing user input to Python, validate and sanitize it
- Limit permissions: Run Python with minimal required permissions
- Secure communication: Use HTTPS if communicating over network
- Code signing: Sign both the Electron app and Python executable
- Update mechanism: Plan for updating the Python backend separately
- Lazy loading: Don't auto-start the backend; let users start it when needed
- Resource limits: Monitor Python process memory/CPU usage
- Graceful degradation: App should work even if Python backend fails
- Caching: Cache Python results to reduce process communication
- Batch operations: Send multiple requests together instead of one-by-one
- Replace
python/backend.pywith your actual implementation - Build the executable with PyInstaller
- Test start/stop/restart from the tray menu
- Add your custom logic and API endpoints
- Package and distribute with your Electron app