Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Executing python commands using electron

Electron and Standalone Flask server

I had a app idea, but the bottleneck was that I wanted to execute python scripts using Electron. Electron is an open-source library for developing cross-platform applications using HTML, CSS and JS. The solution I came up with is executing python scripts through a Flask server and performing a api call. Interesting fact is, python scripts can be turned into standalone executable files. This allows me to spawn the Flask server when starting the Electron app and distribute the app without needing to have python pre-installed on the target machine. Now lets explore how to do it.

Dependencies:

  • First of all you need to have NodeJS and Python installed in you computer

Now lets install Electron and Electron-Builder

Pre-build binaries
> npm install electron --save-dev

or install globally
> npm install electron -g
> npm install electron-builder --save-dev

App directory structure

app_folder
|
|----main.js
|
|----package.json
|
|----app{folder}
|      |---server.py
|      |---dist{foder that contain python executable}
|           |---server
|                 |---server.executable
|                 |---other folders and dependencies
|
|----node_modules{folder}

Content of package.json file

{
  "name": "appname",
  "version": "1.0.0",
  "description": "App description",
  "author": {
    "name": "AuthorName",
    "email": "test@gmail.com"
  },
  "homepage": "www.domainname.com/",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "pack": "build --dir",
    "dist": "build"
  },
  "repository": "",
  "keywords": [
    "Electron"
  ],
  "license": "CC0-1.0",
  "devDependencies": {
    "electron": "~1.7.8",
    "electron-builder": "^19.53.7"
  },
  "dependencies": {
    "electron-pug": "^1.5.1",
    "express": "^4.16.2",
    "request-promise": "^4.2.2"
  },
  "build": {
    "appId": "com.domainname.appname",
    "extraResources": "app/dist/server/**",
    "dmg": {
      "contents": [
        {
          "x": 110,
          "y": 150
        },
        {
          "x": 240,
          "y": 150,
          "type": "link",
          "path": "/Applications"
        }
      ]
    },
    "linux": {
      "target": [
        "AppImage",
        "deb"
      ]
    },
    "win": {
      "target": "squirrel",
      "icon": "build/icon.ico"
    }
  }
}

Content of main.js file

const {app, BrowserWindow} = require('electron');
const path = require('path');
const url = require('url');
const rq  = require('request-promise');

/**Resource path:: Uncomment after testing  */
var pathF = path.resolve(process.resourcesPath, 'app/dist/server/server'); //<---Executable python server path

/**Testing path:: Comment out after testing */
//var pathF = path.resolve('./app/dist/server/server');

//Spawn python server
var subproc = require('child_process').spawn(pathF);

//app urlloc
var appurl = 'http://localhost:5555';

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.

let win

function createWindow() {

  // Create the browser window.
  win = new BrowserWindow({
    width: 1000, 
    height: 600,
    backgroundColor: '#222328'
  });
  
  // hide window default menu
  win.setMenu(null);

  //load the python flask server
  win.loadURL(appurl);

  // Open the DevTools.
  //win.webContents.openDevTools()

  // Emitted when the window is closed.
  win.on('closed', () => {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    win = null

    //close the open server
    subproc.kill('SIGINT');
  })
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', function(){
  //createWindow()
  prom();
})

function prom(){
//wait until the url started
rq(appurl).then(function(htmlString){
  console.log("server started");
  createWindow();
}).catch(function(err){
  //console.log(err);
  prom();   //<---make sure the server is up
})
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow()
  }
})

Once these two files are in your app folder you can execute the following command to install necessary dependencies.

npm install

Now lets work on the python server. First create app folder which will hold the python files.

Following is the simple python server file

from flask import Flask, request

app = Flask(__name__)

def shutdown_server():
    func = request.environ.get('werkzeug.server.shutdown')
    if func is None:
        raise RuntimeError('Not running')
    func()

@app.route("/")
def index():
    return 'testing electron'

@app.route('/shutdown')
def shutdown():
    shutdown_server()
    return 'server shutting down'

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5555)

You can test the server by executing following command and navigating to localhost:5555 in your browser

python server.py

If you get dependency errors, install them as;

pip install Flask

Now the interesting part, turning our server.py into standalone executable that can be started with electron.

You can use py2exe, bbfreeze or pyinstaller. For this purpose I'am using pyinstaller.

pip install pyinstaller

After installing we can execute following command to turn the python server to executable.

pyinstaller server.py

* Important: .exe file format is generated if you execute above command in a windows OS

This command will generate build and dist folders, our server executable file is located in /dist/server/. This path is set on main.js under pathF variable. If you want to test the app uncomment main.js testing path and execute;

npm start

After this we can package electron app for distribution; * Remember to comment out testing path;

npm run dist
@etillier

This comment has been minimized.

Copy link

etillier commented Nov 8, 2018

Thank you so much for that tutorial, it helped me tremendously.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.