Но простой и удобный. Тут надо отметить, что на основной работе я не разработчик, поэтому постоянной среды программирования на рабочем компе не имею и, когда это требуется, пишу на чем придется — bat, JScript, VBA в MSOffice (да, это Windows, корпоративные системы, тут нет bash и perl «из коробки»), макросы в разном ПО и т.д. Все это помогает решить текущую задачу, но уровень и возможности маленько не те, что хотелось бы иметь.
Короче, мне нужна интегрированная среда со встроенным языком программирования, в которой я мог разбирать и конвертировать файлы, лазить в базы данных, получать отчеты, вызывать веб-сервисы, плодить запросы в джире и т.д., и т.п.
Вы скажете, что сейчас есть инструменты на любой вкус и цвет, только выбирай. Лягушка aka TOAD под Oracle, SoapUI для шины и продукты GNU и Apache для всего остального.
Но проблема в том, что все они они специализированы под одну какую-то деятельность, а с другой стороны слишком универсальны — можно сделать многое, но многими действиями. А если возможность в продукте отсутствует, то добавить ее нельзя. Либо продукт закрытый, либо нужно разрабатывать/покупать плагин, либо качать исходники и в них разбираться. А мне нужен был инструмент, в котором простые действия делаются просто, а на сложные сначала тратится немного времени и дальше опять все просто.
Поэтому я решил собрать себе простейшую оболочку, из которой буду запускать нужные мне модули. Оболочка будет расширяемая, а модули простыми и от оболочки максимально независимы.
В качестве языка программирования нужно взять что-то не требующее компиляции, либо с минимальными затратами на неё, чтобы можно было легко перестроить под конкретную задачу.
Javascript хорош для небольших скриптов и подошел бы, но он не имеет оконного интерфейса, а локально поднимать NodeJS ради окошек и сражаться с браузером мне не интересно.
Perl, PHP — та же проблема.
Visual Basic и VBScript — ну, это под Windows. Да, большинство систем корпоративного ИТ, где я имею честь работать, это Windows. И на каждой есть Офис и, следовательно, VBA. Но уж если делать что-то, чем захочется постоянно пользоваться, то кроссплатформенное.
Выбор пал на Python+PyQt5. О существовании языка я узнал (помимо Хабра, конечно) от малинки Raspberry Pi, где Python был предустановлен. Пробой пера послужил бот для Telegram, ищущий синонимы фраз (на pymorphy2 и YARN, потом опишу, если интересно). А Qt я уже знал.
pip3 install pyqt5
Для начала сделаем универсальный модуль для выполнения запросов к базе данных. Причем так, чтобы запрос и его параметры определялись вне модуля, в ini-файле, а модуль занимался всей работой с интерфейсом, работой с БД и отображением данных.
Подключим PyQt. Именования в Qt строгие, поэтому импортируем все подряд, мешать не будет.
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtSql import *
Чтобы сообщения об ошибках и предупреждения Qt не терялись, подключим модуль с message handler, как предложено здесь
import meshandler
Подключение к базе вынесем в отдельный модуль, чтобы здесь не засорять
import dbpool
Создадим класс на основе QDialog (QWidget тоже подойдет, но в нем default кнопки не работают)
class PyExecutor(QDialog):
def __init__(self, iniFile, parent=None):
super(PyExecutor, self).__init__(parent)
self.setWindowFlags(self.windowFlags()
| Qt.WindowMinimizeButtonHint
| Qt.WindowMaximizeButtonHint
)
Заполним окно, сверху вниз
self.topLay = QVBoxLayout(self)
self.topLay.setContentsMargins(6,6,6,6)
Макет с местом для ввода параметров и кнопками
self.lay = QFormLayout()
self.topLay.addLayout(self.lay)
Место для вывода результата
self.resultLay = QVBoxLayout()
self.topLay.addLayout(self.resultLay)
И статусная строка, чтобы было
self.bar = QStatusBar(self)
self.topLay.addWidget(self.bar)
Загрузим ini-файл. Загрузку вынесем в отдельный метод, чтобы потом можно было его перекрыть, если понадобится.
self.loadIni(iniFile)
def loadIni(self, iniFile):
Для работы с ini-файлами я пользуюсь средствами Qt просто потому, что знаю, как там это делается. В Python-е наверняка тоже есть способы, но я не рыл. Чтобы не было в будущем проблем с русским языком, будем работать в UTF-8 во всех файлах.
ini = QSettings(iniFile, QSettings.IniFormat)
ini.setIniCodec("utf-8")
Загружаем параметры запроса из раздела «Input»
ini.beginGroup("Input")
for key in sorted(ini.childKeys()):
Параметр определяется строкой «Name=Метка: значение по умолчанию»
Название можно опустить вместе с двоеточием, тогда в интерфейсе будет Name.
v = ini.value(key).split(':')
if len(v)>1:
paramTitle = v[0]
paramValue = v[1]
else:
paramTitle = key
paramValue = v[0]
На каждый параметр создаем строку ввода, складываем себе в копилочку, вставляем вместе с меткой в интерфейс
self.params.append([key, paramTitle, paramValue])
if paramTitle != '':
le = QLineEdit()
self.inputs[key] = le
le.setText(paramValue)
le.paramTitle = paramTitle
self.lay.addRow(paramTitle, le)
ini.endGroup()
Начитываем параметры соединения с базой из раздела «DB»
ini.beginGroup("DB")
self.dbini = ini.value("DBConnect")
if self.dbini == "this":
self.dbini = iniFile
ini.endGroup()
И, наконец, начитываем текст запроса SQL.
В разделе «Run» либо будет ключ «SQL» с самим текстом запроса (его лучше взять в кавычки), либо будет ключ «SQLScript», в котором прописан sql-файл с запросом — это дает возможность создавать многострочные запросы. К тому же запросы в файле удобнее редактировать в FAR с подсветкой Colorer.
Как и ini, считаем, что sql-файл находится в кодировке UTF-8, только для перекодировки будем пользоваться 'utf-8-sig', чтобы избавиться от BOM в начале файла.
ini.beginGroup("Run")
if ini.contains("SQL"):
self.sql = ini.value("SQL")
else:
f = QFile(ini.value("SQLScript"))
f.open(QIODevice.ReadOnly)
self.sql = str(f.readAll(),'utf-8-sig')
ini.endGroup()
Последние штрихи — добавить кнопку запуска, расположить красиво.
self.runBtn = QPushButton("Run")
self.runBtn.setDefault(True)
self.btnLay = QHBoxLayout()
self.btnLay.addStretch()
self.btnLay.addWidget(self.runBtn)
self.lay.addRow(self.btnLay)
Кнопке назначим наш метод, запускающий запрос на выполнение
self.runBtn.clicked.connect(self.run)
Собственно метод запуска
def run(self):
self.runBtn.setEnabled(False) #Отключим кнопку, что на нее не нажали второй раз
self.clearResult() #Почистим предыдущие результаты, если были
Поехали работать с базой данных.
Получаем объект QSqlDatabase, он должен быть валиден и открыт. А если нет — упс, ничего не выйдет.
self.db = dbpool.openDatabase(self.dbini)
if self.db == None or not self.db.isValid() or not self.db.isOpen():
print("No opened DB", self.dbini)
self.endRun()
return
В Qt по сути один способ работы с запросами БД — это QSqlQuery
self.query = QSqlQuery(self.db)
Парсим sql-запрос, заполняем его параметры значениями из строк ввода
self.query.prepare(self.sql)
for p in self.params:
key = p[0]
if key in self.inputs:
le = self.inputs[key]
par = ':'+key
self.query.bindValue(par, le.text())
Чтобы не ждать, пока выполнится запрос, вынесем его выполнение в отдельный поток.
self.tr = QueryRunner(self.query)
self.tr.finished.connect(self.showQueryResult)
self.tr.start();
После завершения потока выполнится этот метод
def showQueryResult(self):
Создадим табличку QTableView такую, как нам надо
w = self.createTableView()
Но модель с результатом запроса передадим во view не сразу, а через прокси — это даст нам возможность сортировать табличку нажатием на столбец и сделать поиск, если понадобится.
w.sqlModel = QSqlQueryModel(w)
w.sqlModel.setQuery(self.query)
w.proxyModel = QSortFilterProxyModel(w)
w.proxyModel.setSourceModel(w.sqlModel)
w.setModel(w.proxyModel)
self.resultLay.addWidget(w)
self.endRun()
Сделаем запуск того, что получилось, чтобы проверить без оболочки
if __name__ == '__main__':
# Немного магии для Windows
import os
import PyQt5
import sys
pyqt = os.path.dirname(PyQt5.__file__)
QApplication.addLibraryPath(os.path.join(pyqt, "Qt", "plugins"))
И собственно запуск
app = QApplication(sys.argv)
ex = PyExecutor("artists.ini")
ex.show()
sys.exit(app.exec_())
Файл artists.ini
[Common]
Title=Поиск артиста
[Input]
Name=Имя артиста(маска):%r%
[DB]
DBConnect=sqlite.ini
[Run]
SQL="SELECT * FROM artists where :Name = '' or artist like :Name"
Проверили — работает
Теперь нам нужна собственно оболочка запуска.
В оболочке я хочу видеть дерево всех своих настроенных функций, и запускать их в отдельных окошках. И чтобы окошки не были модальными, т.е. можно было переключаться между ними и запускать новые.
Для простоты будем использовать MDI-окно, благо в Qt все для этого есть. Чтение дерева и его отображение взято полностью из примера PyQt, поэтому на нем останавливаться не будем.
Только определим, что в первом столбце у нас название функции, выводимое в строке дерева, во второй — описание функции, в третьей — ini-файл, передаваемый модулю
Тесты Тестовые папки
Поиск артистов Поиск артистов по маске artists.ini
Покажу, как создаем основное окно на QMainWindow
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
Основная часть MDI-окна — это специальный widget QMdiArea. В нем будут жить окна запускаемых модулей.
self.mdiArea = QMdiArea(self)
self.setCentralWidget(self.mdiArea)
Сделаем главное меню, пока с одним пунктом:
self.mainMenu = QMenuBar(self)
self.setMenuBar(self.mainMenu)
m = self.mainMenu.addMenu("Window")
a = m.addAction("Cascade windows")
a.triggered.connect(self.mdiArea.cascadeSubWindows)
Дерево будет в dock-панели слева.
self.treePanel = QDockWidget("Дерево задач", self)
w = QWidget(self.treePanel)
self.treePanel.setWidget(w)
lay = QVBoxLayout(w)
lay.setSpacing(1)
lay.setContentsMargins(1,1,1,1)
w.setLayout(lay)
self.tree = TreeWidget(self.treePanel)
lay.addWidget(self.tree)
В нижней части будет выводится описание функции (потом)
edit = QTextEdit(w)
lay.addWidget(edit)
На дабл-клик в дереве назначим обработчик и посадим панель в основное окно
self.tree.activated.connect(self.handle_dblclick)
self.addDockWidget(Qt.LeftDockWidgetArea, self.treePanel)
Обработчик дабл клика возьмет из модели дерева имя ini-файла, и создаст с ним класс из модуля. Наш класс является виджетом, его вставляем в клиентскую часть MDI-окна.
def handle_dblclick(self, index):
proc = index.data(Qt.UserRole)
if proc != None:
proc = proc.strip()
ex = PyExecutor(proc)
self.mdiArea.addSubWindow(ex)
ex.show()
Проверяем — работает:
Исходники выложены на github под лицензией MIT. Ссылка ведет на исходники, использованные для статьи, а с корня можно взять последнюю версию.
Подсказки:
1. В PyQt, как и в Qt не входит бинарный драйвер QOCI, нужный для доступа к Oracle. Драйвер надо собрать из исходников (C++), поставляемых с Qt, и положить в PyQt5\Qt\plugins\sqldrivers. Для сборки вам понадобятся dll из Oracle Client.
2. Python лучше ставить так, что путь к нему не содержал кириллицы. Иначе у PyQt маленько съезжает крыша, и он не может найти свои файлы.
Комментарии (22)
quarckster
27.08.2017 22:28-2Откройте для себя qt designer.
sshmakov Автор
27.08.2017 22:31+1Здесь тот самый случай, когда формочку в дизайнере рисовать дольше, чем написать ее в коде.
electronik777
27.08.2017 23:14-2Объясните пожалуйста, зачем к Qt еще и Python прикручивать? Неужели для таких задач самого Qt мало? И вообще зачем Python если есть Qt?
sshmakov Автор
27.08.2017 23:22В этом и есть главная фишка.
Да, Qt достаточно. И на нем вы сможете написать все тоже самое, и даже больше и лучше.
Но для чистого Qt нужен компилятор C++, желательно приличная среда (Qt Сreator очень хорош) и много времени на ожидание компиляции и отладку.
Но зачем ждать, если Python запускается сразу, как язык он гораздо лаконичней C++, а через PyQt можно пользоваться всей мощью Qt. И что самое вкусное — практически без потери производительности.firegurafiku
28.08.2017 05:12+1Позвольте не согласиться с вашими тезисами.
Но для чистого Qt нужен компилятор C++
Как будто это какая-то редкость. Для PyQt нужен интерпретатор Python со своей стандартной библиотекой, сам PyQt и, вдобавок, прекомпилированные бинарники Qt. Установить всё это может быть не так уж просто, особенно если в системе нет нормального пакетного менеджера. А если вы вдруг захотите самостоятельно пересобрать PyQt из исходников, вам точно понадобится и компилятор C++.
желательно приличная среда (Qt Сreator очень хорош)
Для питона тоже неплохо бы иметь хорошую среду с диагностиками и зачатками статического анализа (PyCharm?). Более того, как мне кажется, динамически типизированные языки предъявляют намного большие требования к качеству IDE: без них последствия банальной опечатки вы увидите уже после запуска программы.
и много времени на ожидание компиляции и отладку.
В Qt практически не используется высокооктановая шаблонная магия, зато широко применяется идиома PIMPL — это здорово ускоряет компиляцию. Времени на компиляцию столь простого приложения, как описывается в статье, потребуется немного.
Но зачем ждать, если Python запускается сразу, как язык он гораздо лаконичней C++
Для того, чтобы убрать два лишних звена (Python и PyQt) из технологического стека, получив в результате более отзывчивое приложение, потенциально более кроссплатформенное, с более простым процессом развёртывания и пакетирования.
а через PyQt можно пользоваться всей мощью Qt.
Через Qt можно делать то же самое. Как фреймворк Qt очень удобен: если приложение написано целиком на Qt, необходимости думать о низкоуровневых деталях практически не появляется.
И что самое вкусное — практически без потери производительности.
Ваше приложение пока что слишком простое, чтобы заметить разницу.
Nikobraz
28.08.2017 06:00+1Ну в Линуксе все ваше будет прекрасно работать, а для винды надо mingw ставить или того хуже MS Visual Studio.
Я настройку этого окружения так и не освоил. Неделю пролюбился с ним и бросил. Да и Python я знаю гораздо лучше С++.
Про более кросс-платформенное, его все-таки под разные платформы необходимо собирать. А с PyQt это может быть проще.firegurafiku
28.08.2017 06:15[...] его все-таки под разные платформы необходимо собирать, а с PyQt это может быть проще.
Так-то оно так, но перед тем как станет проще нужно, чтобы кто-нибудь добрый собрал под эти платформы PyQt.
Проблемы с пакетированием мне видятся вот в чём: представьте, что вы захотели сделать самодостаточное приложение. Чтобы конечному пользователю под виндами не мучиться со всеми этими зависимостями, придётся тащить с собой питон, все питоньи библиотеки и все необходимые для них бинарные либы, да ещё и настроить при запуске интерпретатор так, чтобы он эти либы находил. Весить будет мегабайт под сотню.
quarckster
28.08.2017 07:38Можно забандлить с помощью cxFreeze. Да будет занимать сотню другую мегабайт, ну и что?
firegurafiku
28.08.2017 08:29-1Дискового пространства, внезапно, жалко — если под диском понимать SSD, то оказывается, что оно достаточно дорогое и хотелось бы потратить его с большей пользой, чем держать очередную копию стандартной питоновской библиотеки вместе с pyc-файлами. К тому же, эти дубликаты засоряют дисковый кэш системы. По идее, нивелировать обе эти проблемы может ФС с поддержкой активной дедупликации, но покажите мне такую для винды? (В десктопных не-виндах ситуация не лучше.)
sshmakov Автор
28.08.2017 08:58Для PyQt нужен интерпретатор Python со своей стандартной библиотекой, сам PyQt и, вдобавок, прекомпилированные бинарники Qt. Установить всё это может быть не так уж просто, особенно если в системе нет нормального пакетного менеджера.
А вы попробуйте, узнаете, что это очень просто на большинстве систем. Для Windows инсталлятор Python качается с офф.сайта. В Linux он предустановлен, во FreeBCD ставится пакетом.
Чтобы поставить "сам PyQt и, вдобавок, прекомпилированные бинарники Qt", нужно лишь набрать команду, приведенную в статье
pip3 install pyqt5
Ваше приложение пока что слишком простое, чтобы заметить разницу.
Оно и должно оставаться простым. Зачем мне делать сложное, если достаточно простого?
dfm
28.08.2017 01:38Самого Qt? Вы спрашиваете почему писать на Python удобнее чем на C++? Или какой смысл вы вкладываете в "самого Qt"?
firegurafiku
28.08.2017 05:19-1Вы спрашиваете почему писать на Python удобнее чем на C++?
Как по мне, ваше (неявное) предположение о большем удобство Python по сравнению со связкой C++11 + Qt5 требует детального обоснования. Особенно в задачах разработки десктопных программ с нетривиальным пользовательским интерфейсом.
gadfi
28.08.2017 09:52пожлуйста, хоть я и не пишу софт для пк но только из моей практики:
1) отсутствие менеджера пакетов нужен был soap клиент колеги с бубном, мануалами и матюками кое как поставили за пару дней какую то библиотеку для soap в с++, но работала она отвратно (да, неопытность имеет место быть но о каком опыте идет речь когда нужен простой soap клиент?) я за пару минут поставил пает через pip и смотря на рабочий код сокрушался что на c# работа с soap приятнее, но все завелось за пару минут
2) Инструментарий очень холиварный вопрос но мне не нравится QTCreator, по сравнению с продуктами Jetbrains имхо сильно сыроват, в то время как Pyacharm очень хорошо понимает pyqt и дружит с qt designer, знаю есть clion, но когда мне нужен qt с clion он еще не дружил
3) Сложность я не хочу искать утечку памяти, ждать пока приложение скомпилируется и тд, когда речь идет о прикладном по это излишне.stanislav888
28.08.2017 18:401) Всяких библиотек в С++ туча. Даже на одно и тоже можно найти разные библиотеки.
2) Eclipce вроде ещё поддерживает С++\Qt. И Visual Studio. Надо было попробовать.
3) Утечки памяти сейчас ищут только упёртые С-ишники. Есть куча smart pointer-ов которые позволяют избежать этого. Компиляция маленьких программ занимает секунду. И на этом этапе вам расскажут об ошибках во всей программе, а не только куда вы явно зайдёте.gadfi
28.08.2017 23:12+1для установки библиотеки на python мне нужна одна команда в консоли, с++ этим похвастаться не может
eclipse и visual studio не далеко ушли от qt creator — работал с обоими
не спорю утечки сегодня не самая большая проблема, но одну и ту же прикладную задачу на питоне как правило писать значительно быстрее
mozg3000tm
28.08.2017 10:13«Мне нужен был инструмент. Острый, практичный, универсальный. Отвечающий всем моим требованиям и расширяемый по моему желанию.»
Такой инструмент — Posh (Powershell).
И в тоже время простой и удобный.
rbobot
29.08.2017 08:52Powershell + WPF?
По-моему очень странный выбор средства автоматизации Windows.sshmakov Автор
29.08.2017 09:25+1WPF был бы хорошим выбором при сочетании условий:
— если бы я его знал, хотя бы настолько же, насколько знаю Qt
— если бы студия была моим рабочим инструментом
— если Win была бы единственной операционкой в окружении
Выбор сочетания Python+PyQt в моем случае определялся низким порогом входа. В вашем это может быть другой набор инструментов.
mozg3000tm
29.08.2017 15:18Странное замечание.
Сейчас винда сама собой управляет через posh, поэтому если кто-то говорит что он работает в среде виндос и не использует его, то…
это можно объяснить только порогом вхождения.
А WPF это самый простой способ создания оконных приложений из ныне существующих.
_____
Пс. В одной книжке (или статье в инете) по wpf автор как раз в нем сделал консоль powershell со всякими плюшками. Идейно смысл близок к данной теме.sshmakov Автор
29.08.2017 15:46Ответ был мне?
поэтому если кто-то говорит что он работает в среде виндос и не использует его, то…
это можно объяснить только порогом вхождения.
Сейчас это Windows (на рабочих станциях). На пред-пред работе был Unix. Нет гарантии, что на следующей работе не будет чего-то еще. И это не считая виртуалок и серверов.
А WPF это самый простой способ создания оконных приложений из ныне существующих.
Есть такая поговорка «Самая короткая дорога — это та, которую вы знаете». Если вы знаете WPF — это прекрасно.
Cobolorum
Попытка написать MS Access или LO Base?