Мне нужен был инструмент. Острый, практичный, универсальный. Отвечающий всем моим требованиям и расширяемый по моему желанию.

image

Но простой и удобный. Тут надо отметить, что на основной работе я не разработчик, поэтому постоянной среды программирования на рабочем компе не имею и, когда это требуется, пишу на чем придется — 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)


  1. Cobolorum
    27.08.2017 16:07
    -1

    Попытка написать MS Access или LO Base?


  1. quarckster
    27.08.2017 22:28
    -2

    Откройте для себя qt designer.


    1. sshmakov Автор
      27.08.2017 22:31
      +1

      Здесь тот самый случай, когда формочку в дизайнере рисовать дольше, чем написать ее в коде.


  1. electronik777
    27.08.2017 23:14
    -2

    Объясните пожалуйста, зачем к Qt еще и Python прикручивать? Неужели для таких задач самого Qt мало? И вообще зачем Python если есть Qt?


    1. sshmakov Автор
      27.08.2017 23:22

      В этом и есть главная фишка.

      Да, Qt достаточно. И на нем вы сможете написать все тоже самое, и даже больше и лучше.
      Но для чистого Qt нужен компилятор C++, желательно приличная среда (Qt Сreator очень хорош) и много времени на ожидание компиляции и отладку.

      Но зачем ждать, если Python запускается сразу, как язык он гораздо лаконичней C++, а через PyQt можно пользоваться всей мощью Qt. И что самое вкусное — практически без потери производительности.


      1. 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, необходимости думать о низкоуровневых деталях практически не появляется.


        И что самое вкусное — практически без потери производительности.

        Ваше приложение пока что слишком простое, чтобы заметить разницу.


        1. Nikobraz
          28.08.2017 06:00
          +1

          Ну в Линуксе все ваше будет прекрасно работать, а для винды надо mingw ставить или того хуже MS Visual Studio.
          Я настройку этого окружения так и не освоил. Неделю пролюбился с ним и бросил. Да и Python я знаю гораздо лучше С++.

          Про более кросс-платформенное, его все-таки под разные платформы необходимо собирать. А с PyQt это может быть проще.


          1. firegurafiku
            28.08.2017 06:15

            [...] его все-таки под разные платформы необходимо собирать, а с PyQt это может быть проще.

            Так-то оно так, но перед тем как станет проще нужно, чтобы кто-нибудь добрый собрал под эти платформы PyQt.


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


            1. quarckster
              28.08.2017 07:38

              Можно забандлить с помощью cxFreeze. Да будет занимать сотню другую мегабайт, ну и что?


              1. firegurafiku
                28.08.2017 08:29
                -1

                Дискового пространства, внезапно, жалко — если под диском понимать SSD, то оказывается, что оно достаточно дорогое и хотелось бы потратить его с большей пользой, чем держать очередную копию стандартной питоновской библиотеки вместе с pyc-файлами. К тому же, эти дубликаты засоряют дисковый кэш системы. По идее, нивелировать обе эти проблемы может ФС с поддержкой активной дедупликации, но покажите мне такую для винды? (В десктопных не-виндах ситуация не лучше.)


        1. sshmakov Автор
          28.08.2017 08:58

          Для PyQt нужен интерпретатор Python со своей стандартной библиотекой, сам PyQt и, вдобавок, прекомпилированные бинарники Qt. Установить всё это может быть не так уж просто, особенно если в системе нет нормального пакетного менеджера.

          А вы попробуйте, узнаете, что это очень просто на большинстве систем. Для Windows инсталлятор Python качается с офф.сайта. В Linux он предустановлен, во FreeBCD ставится пакетом.
          Чтобы поставить "сам PyQt и, вдобавок, прекомпилированные бинарники Qt", нужно лишь набрать команду, приведенную в статье
          pip3 install pyqt5

          Ваше приложение пока что слишком простое, чтобы заметить разницу.

          Оно и должно оставаться простым. Зачем мне делать сложное, если достаточно простого?


    1. dfm
      28.08.2017 01:38

      Самого Qt? Вы спрашиваете почему писать на Python удобнее чем на C++? Или какой смысл вы вкладываете в "самого Qt"?


      1. firegurafiku
        28.08.2017 05:19
        -1

        Вы спрашиваете почему писать на Python удобнее чем на C++?

        Как по мне, ваше (неявное) предположение о большем удобство Python по сравнению со связкой C++11 + Qt5 требует детального обоснования. Особенно в задачах разработки десктопных программ с нетривиальным пользовательским интерфейсом.


        1. gadfi
          28.08.2017 09:52

          пожлуйста, хоть я и не пишу софт для пк но только из моей практики:
          1) отсутствие менеджера пакетов нужен был soap клиент колеги с бубном, мануалами и матюками кое как поставили за пару дней какую то библиотеку для soap в с++, но работала она отвратно (да, неопытность имеет место быть но о каком опыте идет речь когда нужен простой soap клиент?) я за пару минут поставил пает через pip и смотря на рабочий код сокрушался что на c# работа с soap приятнее, но все завелось за пару минут
          2) Инструментарий очень холиварный вопрос но мне не нравится QTCreator, по сравнению с продуктами Jetbrains имхо сильно сыроват, в то время как Pyacharm очень хорошо понимает pyqt и дружит с qt designer, знаю есть clion, но когда мне нужен qt с clion он еще не дружил

          3) Сложность я не хочу искать утечку памяти, ждать пока приложение скомпилируется и тд, когда речь идет о прикладном по это излишне.


          1. stanislav888
            28.08.2017 18:40

            1) Всяких библиотек в С++ туча. Даже на одно и тоже можно найти разные библиотеки.
            2) Eclipce вроде ещё поддерживает С++\Qt. И Visual Studio. Надо было попробовать.
            3) Утечки памяти сейчас ищут только упёртые С-ишники. Есть куча smart pointer-ов которые позволяют избежать этого. Компиляция маленьких программ занимает секунду. И на этом этапе вам расскажут об ошибках во всей программе, а не только куда вы явно зайдёте.


            1. gadfi
              28.08.2017 23:12
              +1

              для установки библиотеки на python мне нужна одна команда в консоли, с++ этим похвастаться не может
              eclipse и visual studio не далеко ушли от qt creator — работал с обоими
              не спорю утечки сегодня не самая большая проблема, но одну и ту же прикладную задачу на питоне как правило писать значительно быстрее


  1. mozg3000tm
    28.08.2017 10:13

    «Мне нужен был инструмент. Острый, практичный, универсальный. Отвечающий всем моим требованиям и расширяемый по моему желанию.»

    Такой инструмент — Posh (Powershell).
    И в тоже время простой и удобный.


  1. rbobot
    29.08.2017 08:52

    Powershell + WPF?
    По-моему очень странный выбор средства автоматизации Windows.


    1. sshmakov Автор
      29.08.2017 09:25
      +1

      WPF был бы хорошим выбором при сочетании условий:
      — если бы я его знал, хотя бы настолько же, насколько знаю Qt
      — если бы студия была моим рабочим инструментом
      — если Win была бы единственной операционкой в окружении

      Выбор сочетания Python+PyQt в моем случае определялся низким порогом входа. В вашем это может быть другой набор инструментов.


    1. mozg3000tm
      29.08.2017 15:18

      Странное замечание.
      Сейчас винда сама собой управляет через posh, поэтому если кто-то говорит что он работает в среде виндос и не использует его, то…
      это можно объяснить только порогом вхождения.
      А WPF это самый простой способ создания оконных приложений из ныне существующих.

      _____
      Пс. В одной книжке (или статье в инете) по wpf автор как раз в нем сделал консоль powershell со всякими плюшками. Идейно смысл близок к данной теме.


      1. sshmakov Автор
        29.08.2017 15:46

        Ответ был мне?

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

        Сейчас это Windows (на рабочих станциях). На пред-пред работе был Unix. Нет гарантии, что на следующей работе не будет чего-то еще. И это не считая виртуалок и серверов.
        А WPF это самый простой способ создания оконных приложений из ныне существующих.

        Есть такая поговорка «Самая короткая дорога — это та, которую вы знаете». Если вы знаете WPF — это прекрасно.


        1. mozg3000tm
          29.08.2017 15:54

          Ответ был rbobot'у.
          Ход ваших (sshmakov) мыслей мне понятен)))