В последней статье (написанной более года назад) мы показали хитрый способ склеить Python и Electron с помощью хорошо структурированной архитектуры. Сама по себе архитектура была мощной, но все еще сложной в использовании, поэтому в итоге я создал на ее основе библиотеку. Сегодня мы обсудим, как его правильно использовать, на примере приложения, а также поговорим об изменениях, внесенных в package.json, чтобы сделать его доступным для поставки.

«Кстати, я никогда не ожидал, что люди заинтересуются проектом, так как это был крайний случай использования, поэтому мне не хотелось продолжать серию статей, поскольку это было слишком много работы за слишком маленькое вознаграждение. Но теперь, когда я вижу, как люди просят меня поддерживать библиотеку и использовать ее в своих проектах, я чувствую себя обязанным вернуть что-то сообществу ». Без промедления, приступим ...

В этой статье мы поговорим о двух вещах:

  • Как использовать электронный балансировщик нагрузки для выполнения фоновых задач в Python
  • Как упаковать ваше приложение

Использование библиотеки



Библиотека, которую мы будем использовать, называется балансировщик электронных нагрузок. Это очень минимальная библиотека, которая предлагает пользователям несколько API-интерфейсов для использования области Python в основном для двух целей:

  1. Повторное использование библиотек Python, которые не были реализованы / портированы в JS.
  2. Использование потоков Python для удобной фоновой обработки.

Обзор…

Прежде чем мы перейдем к примеру приложения, которое я создал для вас, ребята, позвольте мне кратко рассказать вам об API, который у меня есть для вас, а также о возможных ограничениях, которые вам необходимо иметь в виду.

  1. регистр (ipcMain, registerPaths)
  2. начало (ipcRenderer, имя процесса, значения)
  3. остановить (ipcRenderer, processName)
  4. задание (ipcRenderer, processName, func, cleanup_func)
  5. onReceiveData (ipcRenderer, имя процесса, функция)
  6. sendData (ipcRenderer, имя процесса, значение)

Вот и все. Это все, что есть в библиотеке. Документация для API подробно описана с примерами в документации. Если вы обнаружите что-то непонятное, не стесняйтесь поднимать вопрос, и я решу его как можно скорее.

Теперь быстро поговорим об ограничениях библиотеки.

  • Вам необходимо заранее зарегистрировать возможные фоновые задачи во время компиляции. Это не такая уж большая проблема, но стоит упомянуть.
  • Нет возможности для автоматической сборки мусора неиспользуемых / скрытых процессов за пределами области действия. На данный момент у нас нет этой функции, включенной в библиотеку. Разработчик будет нести ответственность за создание и уничтожение фоновых задач с помощью подходящих хуков и предоставленных вызовов API. Это, вероятно, единственное серьезное ограничение, которое я пока не устранял в библиотеке.

Пример приложения (Факториальный калькулятор)

Идите и клонируйте репозиторий на вашем компьютере.



Перво-наперво взгляните на package.json. Начните с npm install. При этом будут установлены все необходимые зависимости, а также электронный балансировщик нагрузки. Если вы хотите добавить эту библиотеку в свой проект, вы должны сделать что-то подобное в корне вашего проекта.

npm install --save electron-load-balancer

Есть много способов подумать и подойти к постановке задачи, но я хотел бы показать вам два моих любимых. В обоих случаях мы будем делать то же самое, но в одном случае мы будем делать это как однократную задачу, а в другом мы будем делать это, используя непрерывный упреждающий цикл. .

Если вы запустите приложение в режиме разработки, у вас появятся две кнопки. Текст на двух кнопках должен показывать, какой сценарий они представляют. Давайте посмотрим, что происходит под капотом.

1. Одноразовое задание

При таком подходе мы создаем фоновый процесс только тогда, когда пользователь нажимает кнопку «Запустить», и уничтожаем его сразу после получения результата. Запишем формально для сравнения.

начало: когда пользователь нажимает кнопку. Несколько щелчков мыши создадут несколько фоновых процессов.

Промежуточный уровень. Задача выполняется в основном потоке Python.

конец:. Выбор прекращения процесса может быть передан пользователю с помощью кнопки остановки. В этом случае мы останавливаемся и убираем с помощью крючков. Обычно, когда фоновый процесс возвращает результат, мы его очищаем.

Исходя из вышеизложенного, такой способ организации может быть полезен, когда задачи относительно недолговечны и не слишком сложны.

Например: дать предварительно обученной модели фотографию и попросить ее пометить ее.

2. Упреждающий цикл

Это один из моих личных фаворитов, поскольку он позволяет создать мост между областью Python и JS и может использоваться для решения очень сложных задач в реальном времени.

start: обычно запускается сразу после запуска электронного приложения. Не является обязательным и может быть изменен соответствующим образом.

промежуточный: обычно имеет основной цикл, который принимает команды из области JS и порождает вспомогательные потоки, которые продолжают отправлять данные обратно в область JS. Очень эффективен для решения задач в реальном времени.

конец:. Фоновые процессы должны быть остановлены непосредственно перед закрытием приложения. Не является обязательным и может быть изменен соответствующим образом.

Для вышеперечисленных пунктов такой способ организации может быть полезен, когда у вас есть фоновый процесс, выполняющий вычисления в режиме реального времени и постоянно обновляющий пользовательский интерфейс данными.

Например: предварительно обученная модель, которая извлекает кадры из видеопотока и маркирует их в режиме реального времени.

Отражение концепций в коде

Давайте посмотрим на структуру каталогов:

.
├── background_tasks
│   ├── one_shot.html
│   └── preemptive_loop.html
├── package.json
├── package-lock.json
├── public
│   ├── electron.js
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── scripts
│   ├── one_shot.py
│   └── preemptive_loop.py
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── Home.js
│   ├── index.css
│   ├── index.js
│   └── serviceWorker.js
└── utils

Хорошо, чтобы продемонстрировать два упомянутых выше фоновых подхода, нам понадобятся два файла HTML, которые будут действовать как скрытый процесс рендеринга, запускающий скрипты Python в фоновом режиме. Таким образом, вы увидите два файла в каталоге background_tasks. Файлам были присвоены значащие имена.

background_tasks
|
|--one_shot.html
|--preemptive_loop.html

Содержимое файлов прокомментировано соответствующим образом.

Перейдем к скриптам. В этом каталоге вы должны написать весь код Python, который хотите выполнить. Я добавил сюда два файла по тем же причинам, что и выше.

scripts
|
|--one_shot.py
|--preemptive_loop.py

Примечание. Связь между областью JS и областью Python осуществляется с помощью файлов HTML. Они более или менее похожи на мосты между электронными скриптами и скриптами Python. Также обратите внимание, что обмен данными происходит с использованием stdin и stdout. Чтобы сохранить структуру, я использовал JSON в качестве метода сериализации. Это также помогает при отладке, если вам это нужно.

Итак, наши фоновые задачи готовы. Теперь нам нужно изменить основной поток электронов, чтобы ретранслировать сообщения, поступающие из фоновых задач. Если вы прочитаете документацию библиотеки, вы увидите упоминания о том, что это пробная задача, которую я решил не выполнять с помощью библиотеки.

Перейдите в общедоступный каталог и посмотрите:

public
|
|--electron.js

Большая часть кода здесь представляет собой стандартный шаблонный код, я прокомментировал добавленную часть. Имейте в виду, что нам также необходимо зарегистрировать здесь фоновые задачи со всеми необходимыми деталями, как указано в библиотеке. Вы увидите два оператора, которые регистрируют две созданные нами фоновые задачи. Вы также увидите несколько прослушивателей событий, которые мы разместили для пересылки сообщения от фоновых задач в поток пользовательского интерфейса.

Наконец, перейдите в каталог src, чтобы написать стандартный код ReactJS. Я добавил несколько комментариев в App.js и Home.js. Обратите на них пристальное внимание.

src
|
|--App.js
|--Home.js

Код должен быть довольно простым, и, как я уже упоминал ранее, очистка должна выполняться разработчиками. Поэтому убедитесь, что вы понимаете, как и когда это происходит в примере кода.

Теперь все готово. Давайте по очереди запустим каждую из двух техник.

1. Одноразовое задание

Как вы можете видеть, нажимая на первую кнопку, вы получаете один вывод каждый раз, когда нажимаете на нее. По сути, это решение проблемы с помощью одноразового подхода. Консоль даст вам хорошее представление о том, что мы получаем от области Python.

2. Упреждающая петля

Он каждый раз, когда вы щелкаете, вы видите имя потока «fact_thead_x» вместе со значениями, разделенными запятыми. Итак, вернувшись в нашу область Python, у нас есть цикл, ожидающий данных. Мы передаем массив целых чисел от 0 до 15 и просим его найти факториал каждого из них. Он возвращает данные по одному номеру за раз, чтобы имитировать данные потоковой передачи приложений в реальном времени. Это всего лишь один поток в действии. Быстрое последовательное нажатие на кнопку несколько раз запустит новые потоки, работающие параллельно. Следовательно, вы можете видеть новую строку, которая печатается с каждым новым щелчком. Это более надежный подход к решению проблемы.

Я постарался сделать статью как можно более исчерпывающей, но если вы все еще запутались, не стесняйтесь оставить проблему в любом из репозиториев, упомянутых выше, или просто оставьте комментарий здесь, на Medium. Я свяжусь с вами, как только смогу.

Упаковка

Упаковка

Все, что вам понадобится для настройки конвейера упаковки, помещается в ключ build. В коде это будет выглядеть примерно так.

"build": {        
  "appId": "Factorial Calculator",        
  "productName": "F-Cal",        
  "copyright": "Copyright © 2019-2020 Mr. Mallik",        
  "asar": false,        
  "linux": {            
    "target": ["deb","snap"],            
    "category": "Utility"
  },        
  "snap": {            
    "confinement": "classic"        
  },        
  "win": {            
    "target": ["nsis"],            
    "legalTrademarks": "Copyright © 2019-2020 Mr. Mallik", 
    "signingHashAlgorithms": ["sha1","sha256"]
  },
  "nsis": {
    "oneClick": true        
  },        
  "mac": {            
    "category": "public.app-category.education",             
    "target": ["default"]        
  },            
  "files": [            
    "build/**/*",            
    "node_modules/**/*",            
    "utils/**/*",            
    "scripts/**/*",            
    "background_tasks/**/*"        
  ],        
  "directories": {            
    "buildResources": "assets"        
  },        
  "extends": null    
}

Примечание. Этот способ добавления конфигурации сборки в package.json может быть устаревшим подходом, и, насколько мне известно, это делается с использованием отдельного файла JSON. Но то, что вы здесь узнаете, можно будет передавать практически без изменений.

Давайте попробуем разобраться в конфигурации по одному ключу за раз.

appID, productName, авторские права

Это довольно очевидно.

Асар

Пакет asar, также известный как Электронный архив, представляет собой стандартный механизм для создания пакета из вашего кода. Причина этого обычно заключается в том, чтобы пользователи не могли ковыряться в вашем коде. К сожалению, чтобы привязка python работала, вы не можете позволить электронному сборщику скрывать ваш код python внутри пакета asar. Следовательно, мы оставим его False.

Linux, Win, Mac, оснастка, NISC

Имена должны были намекать на то, что это ключи, которые позволяют разработчикам электронов знать, к каким платформам вы стремитесь и какие специальные метаданные вы хотите передать. Для простоты остановимся на Linux. В Linux я решил создать пакеты deb (Debian) и snap.

Для людей, которые не знают, есть несколько способов установить что-то на машины Linux. Для систем Linux, производных от debian, вы можете полагаться на файлы .deb. Snap оказался новичком, который запускает приложения в изолированной среде, такой как среда, и вы устанавливаете пакеты snap из магазина snap.

Вы можете заметить еще один ключ для самого snap. Это связано с тем, что некоторым пакетам могут быть предоставлены определенные метаданные. Здесь я попросил разработчика электронов создать пакет, работающий в классическом режиме.

Аналогичные концепции будут применяться и для остальных платформ, которые вы хотите охватить.

файлы

Этот ключ играет очень важную роль в обеспечении правильной работы привязки Python. Вы предоставляете ему список путей, которые необходимо скопировать в ваш установочный пакет как есть. Без оптимизации, без постобработки. Здесь вы можете видеть, что мы передали все каталоги, содержащие файлы, которые:

  • либо не имеют прямого отношения к электрону (build - создается при создании оптимизированной сборки из CRA - обсуждалось в предыдущих статьях этой серии ),
  • написаны на Python
  • написаны на чистом JS и не входят в область React.

Electron-builder скопирует их без изменений, сохраняя при этом структуру каталогов (следовательно, ваши относительные пути будут работать нормально)

каталоги

Это специальный каталог, в котором хранятся все ваши активы, такие как значки и изображения.

расширяет

Если вы пропустите это, вы, скорее всего, получите ошибку.



Итак, причина этого в том, что когда Electron-builder обнаруживает, что вы используете CRA, он будет использовать встроенный файл конфигурации для приложений Electron-React, но мы написали нашу собственную конфигурацию для создания обходных путей, чтобы приложения Electron-React работали. хорошо с питоном. Следовательно, мы хотим переопределить встроенный config. Следовательно, мы передадим ему null.

Запуск сборки через npm

Итак, когда все готово, давайте взглянем на ключ scripts в package.json.

{
.
.
. 
 
  "scripts":{
    .
    .
    .
    "react-build": "react-scripts build",
    "electron-build-linux": "electron-builder --linux -c.extraMetadata.main=build/electron.js",
    "build-linux": "npm run react-build && npm run electron-build-linux"
     .
     .
     .
  }
.
.
.

Чтобы начать процесс сборки, вы запустите следующую команду:

npm run build-linux

Сначала будет создана оптимизированная версия приложения React и все это будет помещено в каталог build, затем будет вызван electronics-builder, который затем используйте конфигурацию, которую мы только что написали в предыдущем разделе, для создания необходимых пакетов deb и snap в новом каталоге dist.

Примечание. Иногда сборщик электронов останавливается при загрузке необходимых пакетов. Пожалуйста, проявите немного терпения ...

Почетные упоминания

Возможно, вы видели, что electronic-log используется повсюду в образце кода. Эта конкретная библиотека сэкономит вам массу времени при отладке в разных сферах. Console.log просто не будет работать, если вы работаете на чисто электронной стороне. Вам придется положиться на эту библиотеку, чтобы вести журналы и исправлять ошибки, которые могут быть вам не очевидны на первый взгляд.