Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Приветствую, земляне, сегодня я спешу к вам с очередным подарком. Несколько месяцев я разрабатывал программный комплекс, который позволяет в некоторой степени упростить разработку узкоспециализированных программ и использовать их в одном приложении. Ранее проект был анонсирован в моем телеграм канале, подписывайтесь, чтобы быть в курсе и иметь возможность принять участие в разработке. Эта статья дает старт рубрике ШБР (школа борцов с рутиной), в которой будут обсуждаться вопросы автоматизации некоторых задач.
Перед тем как начать, скажу пару слов о себе. Я являюсь разработчиком-энтузиастом, все мои продукты — это плод самообучения и нескольких лет практики. Честно сказать, я не слишком люблю корпеть над книгами и мануалами, а предпочитаю учиться в процессе решения реальных задач. Я признаю, что могу использовать в своей работе не самые лучшие практики, но это дело вкуса, главное, чтобы машина работала. Профи мой стиль скорее всего окажется не по душе, поэтому если вы испытываете лютую боль ниже спина при виде кода, написанного не по «канонам», то в таком случае лучше здесь и закончить, всех остальных приглашаю к прочтению.
Итак, что же я в очередной раз сотворил и зачем? Дело в том, что я в свое время вдохновила книга «Автоматизация рутинных задач с помощью Python. Практическое руководство для начинающих», и начал писать разные скрипты для решения всяких специфических задач. Скрипты копились, я забывал о них и их предназначении, также у них не было графического интерфейса, что я считаю, не очень круто. Поэтому я принял решение написать программу, которая бы позволила навести порядок во всем этом зоопарке, и в некоторых аспектах ускорить разработку, к тому же мне давно хотелось сделать программу, которая бы состояла из подключаемых модулей(плагинов). И да, я не соврал, когда говорил, что это швейцарский нож, но с одним нюансом – пока есть только рукоятка и напильник.
Так на свет появился комплекс PUSSY (Python Universal Script System for You) – набор средств для создания утилит с графическим интерфейсом на базе Python и PySide6 и программы управления ими. Он предоставляет простые инструменты создания структур для хранения пользовательских настроек, достаточно определить один класс с перечнем свойств, для определения одного параметра достаточно одной строчки кода, а программа сама организует средства для ввода/вывода и хранения этих данных.
Далее я буду называть программу управления как Менеджер, а утилиту плагином. По сути, каждый плагин — это независимое Qt-приложение, программа управления никак не вмешивается в их работу, только оповещает их о некоторых пользовательских действиях. Разработчику только нужно определить класс интерфейса, который Менеджер встроит в свой интерфейс. В данном случае интерфейс здесь является отправной точкой, а за ним может скрываться совершенно любая реализация.
Обзор Менеджера
Перед тем как приступить к разработке плагина, проведу обзор возможностей Менеджера, именно через него пользователь будет взаимодействовать с плагинами. Качаем код проекта и запускаем скрипт PUSSY.pyw, для запуска потребуется Python 3 и PySide6.
При запуске программы нас встречает страница со всеми активными плагинами, как можно догадаться каждому из них отведена отдельная вкладка, как в вашем любимом браузере, с помощью которого вы читаете данную статью. Плагины инициализируются не сразу, а при переходе во вкладку, или если активирована соответствующая опция, о ней расскажу далее. У неинициализированных плагинов перед именем стоит звездочка.
Быстренько пробежимся по настройкам Менеджера, чтобы перейти на страницу с ними нажмите Ctrl+Alt+S или выберите соответствующий пункт в верхнем меню. Тут все очень минималистично, настройки чтобы немного настроить внешний вид интерфейса и прочее. Только остановлюсь на «Display error info in Logs», если отмечена, то на странице логов в случае ошибки будет выводиться дополнительная информация о перехваченном исключении; «External plugin directories»- здесь перечислены дополнительные директории, в которых будет производится поиск плагинов. Чтобы сохранить настройки нужно прожать «Apply Settings», a кнопка «Delete Invalid Data» нужна чтобы удалить данные конфигурации для плагинов, которые были удалены.
Нажатие комбинации Ctrl+Alt+P приведет нас на страницу со списком плагинов. Что мы тут видим (слева-направо): имя плагина, его активность, кнопка для перехода к настройкам, дополнительные опции (Plugin info – вывод информации о плагине; Reset settings – сброс до базовых настроек; Initialize on startup – отметка о том, что нужно инициализировать плагин при запуске Менеджера.
При на нажатии на кнопку (3) покажется окно редактирования настроек, если они определены, если нет, то кнопка будет неактивной. Меню настроек по умолчанию выглядит как на нижнем рисунке, если форма вывода не определена кодером иным образом. По умолчанию они выводятся в два столбца: слева - имена свойств; справа – виджеты для ввода значений.
И в завершении прожмите Ctrl+Alt+L чтобы перейти на страницу логов, тут ничего мудреного нет, некоторые события будут отмечаться здесь, зеленым будут выделены те, с которыми все окей, в противном случае – красным. Если «Display error info in Logs» установлена, то в случае ошибки будет выведена информация трассировки. В некоторых ситуациях это поможет выявить ошибки в коде. Если ошибка возникнет в процессе отрисовки главного интерфейса или интерфейса редактирования настроек, то вы увидите примерно вот это:
Думаю, всем понятно как устроен Менеджер, тут все предельно просто и интуитивно, разработка тоже предельно проста, будет достаточно минут 30 максимум чтобы во все разобраться.
Также необходимо учитывать, что конфигурация плагина привязана к абсолютному пути к папке с ним, если он будет перемещен, то будет создана новая конфигурация, если на его место будет установлен другой, то программа попытается применить старую конфигурацию, в таком случае используйте функции удаления устаревших данных и сброса настроек.
Как написать свой плагин? Набираем обороты.
Для работы нам потребуется:
Python3;
PySide6;
Инструмент для написания кода (я предпочитаю IDE PyCharm).
Если PySide6 у вас по какому-то недоразумению не установлен, то выполняем команду в терминале:
pip install PySide6
Далее качаем исходный код проекта, если ранее не сделали. Можете первым делом запустить файл PUSSY.pyw, чтобы все пощупать непосредственно. Кстати, на данный момент эта статья единственное существующее руководство, читайте внимательнее.
Комплекс состоит из двух частей: 1-Менеджер (папка manager), 2-Фреймворк (папка PyUB). Если желаете знать почему основной пакет носит имя PyUB, то все просто, первоначальное рабочее название Utilities Box. Итак, что есть главном пакете:
Пакет App – его трогать не нужно, он обеспечивает работу Менеджера;
Пакет Types – содержит все необходимые компоненты для разработки:
o Пакет InputWidgets – здесь классы с виджетами для ввода информации;
o Пакет Properties – здесь классы свойств, которые служат для ввода пользовательских настроек;
o UBWidget – класс-родитель для
мордыинтерфейса вашего плагина, он и будет являться отправной точкой, является модифицированным потомком QWidget;o PropertyContainer – класс куда вы будете складывать свои свойства, это по моей задумке, но при желании это можно обыграть иначе, главное чтобы интерфейсы остались совместимыми;
o UBHelper – класс со вспомогательными функциями;
utils – модуль с необходимым функциями.
Как видите, арсенал вполне минималистичный, все только самое необходимое.
Пишем код плагина
Сделаем пробный плагин как на первой картинке, кодовый замок, конечно, это просто игрушка для демонстрации. Код от «замка» устанавливается через настройки, если код введен верно, то поле загорается зелеными, иначе – красным.
Посмотрим на плагин в общих чертах, по сути это пакет Python - папка, содержащая файл __init__.py, размещается она в папке manager/Plugins, либо во внешних директориях, указанных в настройках. В папке Plugins есть шаблон (папка Template) скопируйте его, когда будете писать свой плагин.
Внимание! Пакеты с именем Template игнорируются при загрузке.
В нашем случае плагин будет разделен на 4 файла (в исходных файла он находится в Plugins/Code lock): __init__.py – здесь будет производится регистрация класса с интерфейсом и размещается дополнительная информация, ничего более тут выполнять не нужно; View.py – здесь определен главный класс интерфейса; Settings.py – класс с настройками; ui_form.py – код виджета, сгенерированный в Qt Designer.
Так выглядит код View.py:
from PyUB.Types import UBWidget
from PySide6.QtWidgets import QPushButton
from .ui_form import Ui_Form
from .Settings import Settings
class CodeLock(UBWidget):
ub_settings = Settings
def __init__(self):
super().__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
self._init_gui()
self.code = self.ub_settings.get_property_value("code")
def _init_gui(self):
for name in dir(self.ui):
attr = getattr(self.ui, name)
if type(attr) is QPushButton:
attr.clicked.connect(self.on_btn)
self.ui.lineEdit.setStyleSheet("")
self.ui.lineEdit.textChanged.connect(self.text_changed)
self.ui.lineEdit.setInputMask(self.ub_settings.get_property("code").p_input_mask)
def on_btn(self):
if not self.ui.lineEdit.isEnabled(): return
s = self.sender()
self.ui.lineEdit.setText(self.ui.lineEdit.text() + s.text())
def text_changed(self, text):
if len(text) == len(self.code):
if text == self.code:
self.ui.lineEdit.setStyleSheet(u"background-color: rgb(85, 255, 127);")
else:
self.ui.lineEdit.setStyleSheet(u"background-color: rgb(240, 54, 8);")
self.ui.lineEdit.setEnabled(False)
self.id = self.startTimer(5000)
def timerEvent(self, event) -> None:
self.ui.lineEdit.setStyleSheet("")
self.ui.lineEdit.clear()
self.ui.lineEdit.setEnabled(1)
self.killTimer(self.id)
def settings_edit_finished(self, changed:bool) -> None:
if not changed: return
self.code = self.ub_settings.get_property_value("code")
self.ui.lineEdit.clear()
В данном коде определен класса интерфейса, также в нем описана вся логика программы. Этот класс и будет встраиваться в окно просмотра в качестве вкладки. Код в методе __init__() выполняется, когда пользователь перешел во вкладку, в которой размещается экземпляр данного класса, или при запуске Менеджера, если пользователь ранее активировал параметр «Инициализировать при запуске» (Init on startup).
Класс UBWidget дополняет QWidget следующими атрибутами:
Поля класса:
ub_settings - (опционально) ссылка на класс с пользовательскими настройками (потомок UBPropertyContainer);
ub_name:str– (опционально) имя плагина, которое будет отображаться в менеджера, если не определено, то будет использовано имя папки.
Методы:
__init__(self) - инициализация виджета;
retranslate(self) –перевод интерфейса виджета на другой язык (на момент написания статьи функция не реализована);
app_closing(self) – выполняется Менеджером перед его закрытием, когда пользователь запустил процедуру закрытия главного окна, нужно чтобы выполнить требуемые действия перед закрытием, например, остановить исполняемые процессы, сохранить данные;
settings_edit_started(self) – выполняется Менеджером перед началом редактирования настроек;
settings_edit_finished (self, changed:bool) – выполняется после завершения редактирования настроек, changed принимает значения: True – если хотя бы одно из свойств было изменено, иначе – False;
deactivated(self) – выполняется, если плагин был деактивирован пользователем, перед тем как экземпляр данного класса будет удален из окна просмотра.
Все перечисленные методы выполняются Менеджером при наступлении упомянутых событий, это нужно, например, для согласования работы потоков во избежание нежелательных коллизий. Учтите, что эти методы могут быть вызваны только у инициализированных экземпляров классов.
Перейдем к файлу с настройками (Settings.py). Его код:
from PyUB.Types import PropertyContainer
from PyUB.Types.Properties import StringProperty
class Settings(PropertyContainer):
code: StringProperty(default_value="12345", input_mask="00000000", name="Код")
Здесь мы определяем класс-контейнер со свойствами. Все классы свойств находятся в пакете Properties. Свойства записываются в теле класса как аннотации. Формат объявления свойств следующий:
<имя свойства>: <Свойство>(<значения параметров>)
Красота, всего одна строчка кода. В следующих сериях мы поговорим о механизме функционирования свойств и их взаимодействии с контейнером, следите за анонсами, пишите свои предложения и идеи, я планирую и дальше развивать проект по мере возможностей.
На данный момент выбор следующий:
1) BoolProperty – свойство для ввода булевых значений;
2) ComboBoxProperty – выпадающий список, возвращает числовой индекс элемента, чтобы получить текст элемента у свойства нужно вызвать метод get_current_item_text();
3) FilePathListProperty-список путей к файлам или папкам;
4) FilePathProperty – путь к файлу или папке;
5) FloatProperty – ввод вещественных чисел;
6) FontSelectProperty- выпадающий список для выбора шрифта;
7) IntProperty– ввод целых чисел;
8) NamedFilePathListProperty - именованный список путей к файлам или папкам;
9) PasswordStringProperty – однострочное поле ввода паролей;
10) StringListProperty– список строк;
11) StringProperty– однострочное поле ввода строк.
Не буду сейчас подробно расписывать каждый из них, можно догадаться по сигнатуре вызова конструктора и именам аргументов. Есть три параметра, которые присутствуют у всех свойств: name-имя, оно выводится окне редактирования настроек в качестве подписи, default_value – значение по умолчанию, tool_tip – всплывающая подсказка.
Чтобы контейнер был полезен, нам надо получать данные, которые он хранит. Для этого есть два способа:
1. Использовать метод get_property_value()
<контейнер>. get_property_value(<имя свойства>)
2. Создать экземпляр класса контейнера и обратиться по имени свойства:
<экземпляр контейнера>. <имя свойства>
При необходимости можно получить доступ к самому свойству:
<контейнер>. get_property (<имя свойства>)
Не исключено, что возникнет потребность получить доступ к параметрам, чтобы прочитать или изменить их. Параметры, которые передаются конструктору можно извлечь, только имейте в виду, что согласно соглашению имен внутри экземпляра класса их имена начинаются с префикса «p_», таким образом, поле name получит имя p_name, но для извлечения имени есть метод get_name(). Данному соглашению нужно следовать, когда будете писать собственные классы свойств.
Для справки: параметры свойств и их значения загружаются при запуске Менеджера в самом начале; значения свойств сохраняются после того, как пользователь их изменил; если параметры изменяются в процессе выполнения, то необходимо их сохранить на диске вызовом метода save_settings_parameters() класса UBHelper.
Итак, все важные классы мы уже определили, теперь нужно сообщить Менеджеру какой класс нужно устанавливать. Для этого в модуле utils есть функция register_ubwidget(), в нее надо передать ссылку на класс-потомок UBWidget, в плагине можно зарегистрировать только один класс, это действие нужно выполнить в файле __init__.py. Также в этом файле по желанию можно определить дополнительную информацию о плагине, создав переменную ub_info ={…}, это типичный словарь; допускаются ключи и значения только строкового типа.
| Ключ | Пример | Описание |
1 | description | “Этот плагина выводит спутник на орбиту” | Описание плагина |
2 | author | “Иван Иванов” | Имя автора |
3 | author_webpage | “ivan_ivanov.com” | веб-страница автора |
4 | author_email | “ivan_ivanov@mail.com” | электронная почта автора |
5 | version | “1.3.4” | версия плагина |
6 | wiki_url | “ivan_ivanov.wiki.com” | ссылка на страницу вики |
Диалоговое окно с информацией будет выглядеть следующим образом.
Гиперссылки активные и при нажатии будут открыты в браузере или почтовом клиенте. Если какое-то из ключей не определено, то будет выведена советующая информация.
В комплекте еще один класс, который остался без внимания. Чтобы его использовать надо создать экземпляр, передав в конструктор ссылку на класс-потомок UBWidget, это нужно для того, чтобы система могла идентифицировать плагин. Вот перечень методов:
__init__(self, <класс-потомок UBWidget>) - принимает ссылку на класс UBWidget, которая в дальнейшем используется как ключ для идентификации плагина при вызове методов;
save_settings_parameters (self)- сохраняет параметры свойств на жестком диске, которые находятся в классе PropertyContainer, присвоенный атрибуту ub_settings (метод является потокобезопасным), это нужно в том случае, если плагин изменяет параметры свойств в процессе работы;
get_plugin_dir(self)->str-выводит абсолютный путь к папке, в которой находится плагин;
open_localstorage(self, flag='c', protocol=None, writeback=False)-возвращает объект базы данных, является оберткой для функции open() из модуля shelve, узнать подробнее можно по ссылке. Файлы базы данных создаются в папке localdata в корневой папке плагина.
Теперь немного о неудачах. Некоторые функции были удалены, например, изначально была функция перезагрузки плагинов по желанию пользователя, как оказалось, перезагружать ранее загруженные модули со всеми зависимостями оказалось нетривиальной задачей. Также функционал интернационализации не реализован в полной мере, возможно текущие наработки будут пересмотрены. Вполне возможно, я переработаю архитектуру Менеджера, слишком уж он монолитный получился.
Вот и подошла к концу вводная часть, в следующих сериях мы поговорим о Свойствах и как они работают в составе контейнера, разработаем какой-нибудь класс свойства для расширения коллекции, и может быть разработаем какой-нибудь полезный плагин, жду от вас идей. Если есть какие-то вопросы и идеи, то добро пожаловать на мой Discord сервер. Также же я хочу сделать сайт вики, но не могу определиться с движком, хочется, чтобы интернационализация работала из коробки, работал на PHP, MySQL, кто имеет опыт напишите мне.
Ссылки
Страничка проекта (скачать бесплатно)
Актуальные обновления в Telegram-канале
Почтовый ящик для отзывов и предложений
Поддержать проект