SQLite (/ˌɛsˌkjuːˌɛlˈaɪt/, /ˈsiːkwəˌlaɪt/) は、パブリックドメインの軽量なリレーショナルデータベース管理システム (RDBMS) です。他の多くのデータベース管理システムとは対照的に、サーバとしてではなくアプリケーションに組み込んで利用するデータベースです。 一般的な RDBMS と違い、API は単純にライブラリを呼び出すだけであり、データの保存に単一のファイルを使用することが特徴でです。
SQLite のデータベースでバイナリーデータを扱うケースを、Qt for Python (Pyside2) の GUI で、PDF ファイルを選んで読み込んだ内容をデータベースに格納し、それを外部プログラムで開く、というサンプルを作って確認しましたので、備忘録にしました。
本ブログ記事では下記の OS 環境で動作確認をしています。
Fedora 33 (Workstation Edition) | x86_64 |
以下に、サンプル sqlite_binary_test.py を示しました。
from PySide2.QtWidgets import ( | |
QAction, | |
QApplication, | |
QComboBox, | |
QFileDialog, | |
QGridLayout, | |
QMainWindow, | |
QPushButton, | |
QSizePolicy, | |
QWidget, | |
) | |
import os | |
import platform | |
import sqlite3 | |
import subprocess | |
import sys | |
import tempfile | |
class Example(QMainWindow): | |
dbname = "test.sqlite" | |
def __init__(self): | |
super().__init__() | |
if not os.path.exists(self.dbname): | |
self.initDB() | |
self.initUI() | |
self.setWindowTitle('SQLite Binary Test') | |
self.show() | |
self.setGeometry(100, 100, 300, 0) | |
def initUI(self): | |
# menu in | |
openFile = QAction('Open', self) | |
openFile.setShortcut('Ctrl+O') | |
openFile.setStatusTip('Open New File') | |
menubar = self.menuBar() | |
fileMenu = menubar.addMenu('File(&F)') | |
fileMenu.addAction(openFile) | |
# Main Window | |
base = QWidget() | |
base.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) | |
self.setCentralWidget(base) | |
grid = QGridLayout() | |
base.setLayout(grid) | |
combo_file = QComboBox() | |
self.get_filelist(combo_file) | |
but_file = QPushButton('Open') | |
but_file.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) | |
grid.addWidget(combo_file, 0, 0) | |
grid.addWidget(but_file, 0, 1) | |
openFile.triggered.connect(lambda: self.showDialog(combo_file)) | |
but_file.clicked.connect(lambda: self.on_click_open(combo_file)) | |
def showDialog(self, combo: QComboBox): | |
dialog = QFileDialog() | |
dialog.setNameFilters(['PDF files (*.pdf)']) | |
if dialog.exec_(): | |
fname = dialog.selectedFiles()[0] | |
name_file = os.path.basename(fname) | |
f = open(fname, 'rb') | |
with f: | |
content = f.read() | |
# SQLite | |
con = sqlite3.connect(self.dbname) | |
cur = con.cursor() | |
cur.execute("INSERT INTO file VALUES(?, ?);", [name_file, content]) | |
con.commit() | |
con.close() | |
# update list of QComboBox | |
self.get_filelist(combo) | |
def get_filelist(self, combo: QComboBox): | |
combo.clear() | |
combo.clearEditText() | |
# SQLite | |
con = sqlite3.connect(self.dbname) | |
cur = con.cursor() | |
cur.execute("SELECT name_file FROM file;") | |
out = cur.fetchall() | |
con.close() | |
# add list of file to QComboBox | |
for name_file in out: | |
combo.addItem(name_file[0]) | |
def on_click_open(self, combo: QComboBox): | |
name_file = combo.currentText() | |
if len(name_file) == 0: | |
return | |
# set temporary location where to save name_file | |
out_file = os.path.join(tempfile.gettempdir(), name_file) | |
# SQLite | |
con = sqlite3.connect(self.dbname) | |
cur = con.cursor() | |
cur.execute("SELECT content FROM file WHERE name_file = ?;", [name_file]) | |
out = cur.fetchall() | |
con.close() | |
# save out_file | |
with open(out_file, 'wb') as f: | |
f.write(out[0][0]) | |
# open out_file with application | |
if platform.system() == 'Linux': | |
subprocess.Popen(['xdg-open', out_file]) | |
elif platform.system() == 'Darwin': | |
subprocess.Popen(['open', out_file]) | |
else: | |
os.startfile(out_file) | |
def initDB(self): | |
init_query = [ | |
'CREATE TABLE file (name_file TEXT UNIQUE, content NONE)', | |
] | |
con = sqlite3.connect(self.dbname) | |
cur = con.cursor() | |
for query in init_query: | |
cur.execute(query) | |
con.commit() | |
con.close() | |
def main(): | |
app = QApplication(sys.argv) | |
ex = Example() | |
sys.exit(app.exec_()) | |
if __name__ == '__main__': | |
main() |
サンプルを実行して、メニューから File(F) » Open をクリックします。
SQLite のデータベースファイル self.dbname (= 'test.sqlite') が存在しなければ、カレントディレクトリに作成します。
def initDB(self): init_query = [ 'CREATE TABLE file (name_file TEXT UNIQUE, content NONE)', ] con = sqlite3.connect(self.dbname) cur = con.cursor() for query in init_query: cur.execute(query) con.commit() con.close()
データベースに書き込む PDF ファイルを選択します。
ファイルをバイナリモードで読み込んでパスを除いたファイル名と、読み込んだバイナリをデータベースに書き込みます。
def showDialog(self, combo: QComboBox): dialog = QFileDialog() dialog.setNameFilters(['PDF files (*.pdf)']) if dialog.exec_(): fname = dialog.selectedFiles()[0] name_file = os.path.basename(fname) f = open(fname, 'rb') with f: content = f.read() con = sqlite3.connect(self.dbname) cur = con.cursor() cur.execute("INSERT INTO file VALUES(?, ?);", [name_file, content]) con.commit() con.close() self.get_filelist(combo)
コンボボックスに読み込んだファイルが追加されるので、 Open ボタンをクリックします。
コンボボックスに表示されているファイル名と一致するレコードのバイナリの内容を読み込んで、一時保存領域にファイル名で保存して、デフォルトのアプリケーションでファイルを開きます。
def on_click_open(self, combo: QComboBox): name_file = combo.currentText() if len(name_file) == 0: return out_file = os.path.join(tempfile.gettempdir(), name_file) con = sqlite3.connect(self.dbname) cur = con.cursor() cur.execute("SELECT content FROM file WHERE name_file = ?;", [name_file]) out = cur.fetchall() con.close() with open(out_file, 'wb') as f: f.write(out[0][0]) if platform.system() == 'Linux': subprocess.Popen(['xdg-open', out_file]) elif platform.system() == 'Darwin': subprocess.Popen(['open', out_file]) else: os.startfile(out_file)
GNOME のドキュメントビューア Evince が起動し、PDF の内容が表示されます。
動作確認をする目的で作成したサンプルであるため、無駄な重複が残っていますしエラー時の例外処理などもしていません。なお、今回扱った PDF ファイルは圧縮されているので、Python 側で更に圧縮する処理は加えていませんが、扱うバイナリファイルの内容により検討する必要があります。
参考サイト
0 件のコメント:
コメントを投稿