Electron React Boilerplate + Python backend via zeroMQ.
First, clone the repo via git and install dependencies:
git clone --depth 1 --single-branch https://github.com/omdv/react-electron-python.git your-project-name
cd your-project-name
yarnSecond, create python environment with a tool of your choice (e.g. virtualenv, conda) and install dependencies:
conda create -n your-environment python=3.7
conda activate your-environment
pip install -r requirements.txtStart the app in the dev environment. This starts the renderer process in hot-module-replacement mode and starts a webpack dev server that sends hot updates to the renderer process:
yarn devStart the python backend in the separate window:
conda activate your-environment
python backend/python-backend-main.pyIt is possible to use compiled python binary for development, change the devUsePackaged in ./constants/python.json.
To package application for the local platform:
conda activate your-environment
yarn package-pythonThis command will package python in a binary (using pyinstaller) and include it into Electron distribution.
What follows below is the recommended set of steps to add your own logic. The short guide is targeted more towards Python crowd, who are less familiar with React and Typescript.
The high-level logic of the application is to use Electron as frontend, spawn the compiled Python script as child process (or connect to live script during development) and communicate with it via zeroMQ. This follows the frontend/backend pattern in web development, where Python serves as backend, Electron is providing a local frontend and the communication between the two is handled by zeroMQ which replaces REST or gRPC APIs.
This step may be seen as definining your API or content of the message you are going to exchange between Python and Electron.
This Electron boilerplate is relying on react-redux to manage the "UI logic". Depending on your level of familiarity you may want to go over some tutorials. Three fundamental concepts behind Redux are: Store, Actions and Reducers. Simply speaking Store is an internal state of the application; it can be changed only via issuing Actions, which in turn use Reducers to actually apply changes to Store.
Store is defined in types definitions. This example is using a nested state, where PythonCounter is part of the overall CounterState. Since we are using Typescript you want to define your state types explicitly. However, there is an option to just use a payload of string type, which can carry JSON or other format. You will then need to deserialize messages separately on frontend and backend sides.
In the published example the two key state properties responsible for the logic are value and operator. Value is going to be the value of the counter and operator is going to be either +1 or -1. When defining state keep the properties isFetching and error, because they may be used to block UI elements while you wait for the results of async request or show error messages. The example shows how it works.
Overall, it is recommended to spend a good amount of time at this step designing your state.
The next file you need to change is actions and reducers.
There are only three actions in this example - PYTHON_BACKEND_REQUEST, PYTHON_BACKEND_SUCCESS and PYTHON_BACKEND_FAILURE. They are used to communicate with python backend. Then we have two functions or reducers which actually perform the increment and decrement of the counter. They construct the message to backend by specifying the corresponding operator and then dispatch another async function pythonCalc, which is in turn REQUESTing backend and then handles either a SUCCESS or FAILURE.
At this step you want to have at least some idea on what your UI will look like, as later you will map the reducers defined here (like pythonIncrement and pythonDecrement) to your UI elements.
Next you need to map your reducers to your container (think of it as one UI page of the application). There is a container called PythonCounterPage.
Visual part of UI is handled by components. After you mapped your reducers to containers you can use them inside React components to bind them to specific UI elements. Similar to the comment on react-redux, spend some time to familiarize yourself with React in general. In this example we are binding two functions to two buttons, also bind the counter property of the state to the corresponding UI element. Also note how we disable buttons when isFetching property of the state is True, which is important to cover async behavior.
Python logic is placed in backend folder. You can organize your project in any way you want, but if your choose to rename your main script to something other than python-backend-main you'll need to change few hard-coded references, namely in the parts responsible for packaging the application.
The two required modules for Python are pyzmq and PyInstaller, as you can see in requirements.txt. ZeroMQ is a lightweight communication protocol. Python serves as a server, binding to the socket, creating an endpoint.
After receiving a message it attempts to parse it (assuming JSON) and perform the calculation. You can presumably use a logic of any complexity here as the connection is asynchronous and related UI elements are disabled during execution. Needless to stay that you need to use the same state structure you defined in Step 1.
In this example it is assumed that Python backend is stateless. The state is stored inside Electron React Redux store, so you need to organize your Python script logic appropriately.
During development it is recommended to use standalone Python script (like shown in the demo). For the production the command yarn package-python will compile your script and all dependencies into a binary for a local platform and add it to the Electron distribution. Production version of the application will spawn the Python binary as child process and kill it after Electron window is closed.
Please refer to upstream repository.
