2021-02-18

【備忘録】SQLite のデータベースでバイナリデータを扱う

SQLite (/ˌɛsˌkjuːˌɛlˈaɪt/, /ˈsiːkwəˌlaɪt/) は、パブリックドメインの軽量なリレーショナルデータベース管理システム (RDBMS) です。他の多くのデータベース管理システムとは対照的に、サーバとしてではなくアプリケーションに組み込んで利用するデータベースです。 一般的な RDBMS と違い、API は単純にライブラリを呼び出すだけであり、データの保存に単一のファイルを使用することが特徴でです。

Wikipedia より引用、編集

SQLite のデータベースでバイナリーデータを扱うケースを、Qt for Python (Pyside2) の GUI で、PDF ファイルを選んで読み込んだ内容をデータベースに格納し、それを外部プログラムで開く、というサンプルを作って確認しましたので、備忘録にしました。

本ブログ記事では下記の OS 環境で動作確認をしています。

Fedora 33 (Workstation Edition) x86_64

以下に、サンプル sqlite_binary_test.py を示しました。

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_binary_test.py の実行例 (1)

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 ファイルを選択します。

sqlite_binary_test.py の実行例 (2)

ファイルをバイナリモードで読み込んでパスを除いたファイル名と、読み込んだバイナリをデータベースに書き込みます。

    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 ボタンをクリックします。

sqlite_binary_test.py の実行例 (3)

コンボボックスに表示されているファイル名と一致するレコードのバイナリの内容を読み込んで、一時保存領域にファイル名で保存して、デフォルトのアプリケーションでファイルを開きます。

    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 の内容が表示されます。

Evince による PDF ファイルの表示

動作確認をする目的で作成したサンプルであるため、無駄な重複が残っていますしエラー時の例外処理などもしていません。なお、今回扱った PDF ファイルは圧縮されているので、Python 側で更に圧縮する処理は加えていませんが、扱うバイナリファイルの内容により検討する必要があります。

参考サイト

  1. SQLite Home Page
  2. SQLiteで利用可能なデータ型 | SQLite入門

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村

0 件のコメント: