どんな記事?
-
Python
のライブラリを使ってGUIアプリを作った - Excelファイルの読み込みやDB(MySQL)への接続などを行った
はじめに
社外製の業務システムを運用することになりました
ネットワークを介してデータの通信を行うWebアプリケーションではなく、ローカルPCにDB(MySQL)を用意して接続・利用するスタンドアロン型のシステムです
しかしこのシステム、元々社外での利用を想定していない状況で作られたアプリケーションなので、
開発者でない人が使用することを想定していません
よって、利用するパラメータやしきい値の設定は直接DBをいじって変更するしかないようです
またテーブル構成の仕様上、同じ入力値を何回も入力する必要があったりする部分が多々あります
というか、そもそも部署内の方は非エンジニアの人が多く、SQLクエリを記述してデータ更新~といった操作は難しい...
-> 非エンジニアでもDB内のパラメータを簡単に操作できるツールとか作れる?
要は「入力しやすいインタフェースを作る」って感じです
という経緯で、GUIアプリケーションを作ることになりました
自身の知識の定着も兼ねて、記事として残しておきます
機能概要
運用イメージ
- 入力シートとしてExcelファイルを作成、このファイルにパラメータ等を入力する
- 1で作成したシートを元にCSVを生成
- 2で作成したCSVをDBにインポート
という流れで利用するツールとして作成していこうと思います
別にCSV生成を挟まなくても実現できそうなのですが、部署内から「一度入力した設定が上書きされて復元不可能になるのが怖い」との意見が出たので、バックアップ的な役割としてワンクッション置いておきます
2つの機能
入力シートのフォーマットは別として、今回作成するアプリケーションには以下の機能が最低限必要です
- (パラメータ等を入力した)Excelファイルを元にしてCSVファイル生成
- 生成したCSVファイルをDBにインポート
できたもの
動作例はこんな感じ
構成
使用したツール・ライブラリなど
- 使用言語は
Python
- DBとの接続(インポート機能)には
mysql.connector
ライブラリを使用 - Excelファイルへの値の読み書きは
openpyxl
ライブラリを使用 - コマンドプロンプトで操作するのは不便なのでGUI作成に
PySimpleGUI
ライブラリを使用 - python実行環境がないPCでも使えるようパッケージング(exe化)を行う。
pyinstaller
ライブラリを使用
動作環境
- Python 3.6.1
- openpyxl 3.0.5
- PySimpleGUI 4.34.0
- pyinstaller 4.0
- MySQL 5.7.19
ソースコード
以下、ソースコード
import openpyxl as ex
import PySimpleGUI as sg
import os
import csv
import datetime
import mysql.connector as mydb
class Person:
def __init__(self, name: str, mail_address:str, age: str) -> None:
self.name = name
self.mail_address = mail_address
self.age = age
def toList(self):
return [
self.name,
self.mail_address,
self.age
]
class Persons:
# ExcelファイルからデータをLoad
def __init__(self, inputsheet_name: str) -> None:
self.inputsheet_name = inputsheet_name # データ元のファイル名
self.persons = []
wb = ex.load_workbook(f"./InputSheet/{inputsheet_name}")
ws = wb["Sheet1"]
for index in range(3, 9999):
name = ws.cell(index, 3).value
mail_address = ws.cell(index, 5).value
age = ws.cell(index, 7).value
if name is None:
break
self.persons.append(Person(name, mail_address, age))
# Personの属性名をリスト形式で出力
@staticmethod
def attributeList():
return [
"name",
"mail_address",
"age"
]
# 格納されているデータをすべてリスト表示
def print_all(self):
print(self.attributeList())
for p in self.persons:
print(p.toList())
return 0
# csvファイルに出力
def output_csv(self):
# List形式でpersonsの情報を格納
out = []
out.append(self.attributeList())
for record in self.persons:
out.append(record.toList())
# csv書き出し
filename = f"persons_{datetime.datetime.now():%Y%m%d_%H%M%S}"
csvFilePath = f"./csv/{filename}.csv"
with open(csvFilePath, "w", encoding="utf-8") as f:
writer = csv.writer(
f,
lineterminator="\n",
escapechar=" "
)
writer.writerows(out)
print(f"Created file {csvFilePath}")
return 0
# csvデータからDBにインポート
def importCSVTable(csvfile_name):
# *一部本来の記述から変更しています
conn = mydb.connect(
host="localhost",
port=3306,
user="sampleuser",
password="*****",
database="sample_db",
allow_local_infile=True
)
# DB操作用カーソル
cur = conn.cursor()
# 既存レコード全削除
table_name = "persons"
cur.execute(f"TRUNCATE {table_name} ;")
# csvファイルからレコード読み込み
csvfile_path = f"./csv/{csvfile_name}"
enclosed = "\"" # 囲み文字
terminated = "," # 区切り文字
lines = "\\r\\n" # 改行コード
sql = f"""
LOAD DATA LOCAL INFILE
"{csvfile_path}"
INTO TABLE {table_name}
FIELDS TERMINATED BY '{terminated}'
OPTIONALLY ENCLOSED BY '{enclosed}'
LINES TERMINATED BY '{lines}'
IGNORE 1 LINES ;
"""
cur.execute(sql)
# DBに反映
conn.commit()
conn.close()
return 0
def Layout():
textbox_width = 50
textbox_height = 8
inputsheet_list = os.listdir("./InputSheet")
tab1_layout = [
[sg.Text("InputSheet list")],
[sg.Listbox(
inputsheet_list,
size=(textbox_width, textbox_height),
key="inputsheet"
)],
[sg.Button("CREATE csv file", key="create_csv")]
]
csv_list = os.listdir("./csv")
tab2_layout = [
[sg.Text("csv list")],
[sg.Listbox(
csv_list,
size=(textbox_width, textbox_height),
key="select_csv",
enable_events=True
)],
[sg.Button("IMPORT to database", key="import_csv")]
]
layout = [
[sg.TabGroup(
[[
sg.Tab("csv生成", tab1_layout),
sg.Tab("DBへインポート", tab2_layout)
]],
enable_events=True
)],
# Log出力
[sg.Text("Log")],
[sg.Output( size=(textbox_width, textbox_height*0.5) )]
]
return layout
def run():
window = sg.Window(
title="SampleApp",
layout=Layout(),
)
while True:
event, values = window.read(timeout=10)
if event is sg.WIN_CLOSED:
break
elif event=="create_csv" and len(values['inputsheet'])!=0:
persons = Persons(inputsheet_name=values['inputsheet'][0]) # Personsインスタンス生成
persons.output_csv() # CSV形式で書き出し
sg.popup("csvファイルへの書き出しが完了しました", title="")
elif event=="import_csv" and len(values['select_csv'])!=0:
csvfile_name = values['select_csv'][0]
print(csvfile_name)
importCSVTable(csvfile_name)
sg.popup("DBへのインポートが完了しました", title="")
if __name__ == "__main__":
run()
まとめ
「直接ではなくExcelファイルなどの入力でDBの中身を変更したい」という要望に対して
pythonを用いて簡単なGUIアプリケーションを作成しました
-
PySimpleGUI
pythonではGUI作成ライブラリとしてtkinter
が標準インストールされているのですが、今回使用したPySimpleGUI
はそれと比べ直感的にコーディングでき、公式リファレンスが非常にわかりやすいのでとても使いやすかったです -
pyinstaller
コマンドのみで簡単にexeファイルを生成でき、python実行環境が無いPCでもアプリケーションを動作させられるのも良かったです
パッケージングライブラリはpyinstaller
の他にもいくつかあるようですが、pyinstaller
はその中でも非常に簡単に利用できるので採用しました
ただ、生成したexeのファイルサイズが大きめだったり、起動時間が長くなったりするなどの欠点はあります -
openpyxl
こちらに関しては別記事でも解説を行っています