Създаването на приложения, които използват SQL база данни, е доста често срещана задача за програмиране. SQL бази данни са навсякъде и имат страхотна поддръжка в Python. При програмирането с графичен интерфейс PyQt предоставя стабилна и междуплатформена поддръжка на SQL база данни, която ви позволява да създавате, свързвате и управлявате своите бази данни последователно.
SQL поддръжката на PyQt се интегрира напълно с неговата Model-View архитектура, за да ви помогне в процеса на изграждане на приложения за бази данни.
В този урок ще научите как да:
- Използвайте поддръжката на SQL на PyQt за надеждно свързване с база данни
- Изпълнете SQL заявки в база данни с помощта на PyQt
- Използвайте архитектурата на изглед на модел на PyQt в приложения за бази данни
- Показване и редактиране на данни с помощта на различни приспособления на PyQt
Примерите в този урок изискват основни познания на езика SQL, особено на системата за управление на база данни SQLite. Някои предишни познания за GUI програмирането с Python и PyQt също ще бъдат полезни.
Безплатен бонус: 5 Thoughts On Python Mastery, безплатен курс за разработчици на Python, който ви показва пътната карта и начина на мислене, от който ще се нуждаете, за да изведете уменията си в Python на следващото ниво.
Свързване на PyQt към SQL база данни
Свързването на приложение към релационна база данни и накарането на приложението да създава, чете, актуализира и изтрива данните, съхранявани в тази база данни, е често срещана задача в програмирането. Релационните бази данни обикновено са организирани в набор от таблици , или отношения . Даден ред в таблица се нарича запис или кортежа , а колона се нарича атрибут .
Забележка: Терминътполе обикновено се използва за идентифициране на единична част от данни, съхранявана в клетка на даден запис в таблица. От друга страна, терминът име на поле се използва за идентифициране на името на колона.
Всяка колона съхранява определен вид информация, като имена, дати или числа. Всеки ред представлява набор от тясно свързани данни и всеки ред има една и съща обща структура. Например, в база данни, която съхранява данни за служителите в дадена компания, конкретен ред представлява отделен служител.
Повечето системи за релационни бази данни използват SQL (структуриран език за заявки) за запитване, манипулиране и поддържане на данните, съхранявани в базата данни. SQL е декларативен и специфичен за домейн език за програмиране, специално създаден за комуникация с бази данни.
Системите за релационни бази данни и SQL са широко използвани в наши дни. Ще намерите няколко различни системи за управление на бази данни, като SQLite, PostgreSQL, MySQL, MariaDB и много други. Можете да свържете Python към всяка от тези системи за бази данни, като използвате специална Python SQL библиотека.
Забележка: Въпреки че вградената SQL поддръжка на PyQt е предпочитаната опция за управление на SQL бази данни в PyQt, можете също да използвате всяка друга библиотека, за да управлявате връзката с базата данни. Някои от тези библиотеки включват SQLAlchemy, pandas, SQLite и т.н.
Въпреки това, използването на различна библиотека за управление на вашите бази данни има някои недостатъци. Няма да можете да се възползвате от интеграцията между SQL класовете на PyQt и архитектурата Model-View. Освен това ще добавяте допълнителни зависимости към приложението си.
Когато става въпрос за GUI програмиране с Python и PyQt, PyQt предоставя стабилен набор от класове за работа със SQL бази данни. Този набор от класове ще бъде най-добрият ви съюзник, когато трябва да свържете приложението си към SQL база данни.
Забележка: За съжаление, официалната документация на PyQt5 има някои непълни раздели. За да заобиколите това, можете да разгледате документацията на PyQt4, документацията на Qt For Python или оригиналната документация на Qt. В този урок някои връзки ви отвеждат до оригиналната документация на Qt, която в повечето случаи е по-добър източник на информация.
В този урок ще научите основите как да използвате SQL поддръжката на PyQt за създаване на GUI приложения, които надеждно взаимодействат с релационни бази данни за четене, запис, изтриване и показване на данни.
Създаване на връзка с база данни
Свързването на вашите приложения към физическа база данни на SQL е важна стъпка в процеса на разработване на приложения за бази данни с PyQt. За да изпълните тази стъпка успешно, имате нужда от обща информация за това как е настроена вашата база данни.
Например, трябва да знаете на каква система за управление на база данни е изградена вашата база данни и може да се наложи да имате потребителско име, парола, име на хост и т.н.
В този урок ще използвате SQLite 3, който е добре тествана система за бази данни с поддръжка на всички платформи и минимални изисквания за конфигурация. SQLite ви позволява да четете и пишете директно в бази данни на вашия локален диск, без да е необходим отделен сървърен процес. Това го прави удобна за потребителя опция за изучаване на разработка на приложения за бази данни.
Друго предимство на използването на SQLite е, че библиотеката се доставя с Python, а също и с PyQt, така че не е необходимо да инсталирате нищо друго, за да започнете да работите с нея.
В PyQt можете да създадете връзка с база данни, като използвате QSqlDatabase
клас. Този клас представлява връзка и предоставя интерфейс за достъп до базата данни. За да създадете връзка, просто извикайте .addDatabase()
на QSqlDatabase
. Този статичен метод приема SQL драйвер и незадължително име на връзка като аргументи и връща връзка към базата данни:
QSqlDatabase.addDatabase(
driver, connectionName=QSqlDatabase.defaultConnection
)
Първият аргумент, driver
, е задължителен аргумент, който съдържа низ, съдържащ името на поддържан от PyQt SQL драйвер. Вторият аргумент, connectionName
, е незадължителен аргумент, който съдържа низ с името на връзката. connectionName
по подразбиране е QSqlDatabase.defaultConnection
, който обикновено съдържа низа "qt_sql_default_connection"
.
Ако вече имате връзка, наречена connectionName
, след това тази връзка се премахва и заменя с нова връзка и .addDatabase()
връща новодобавената връзка към базата данни обратно към повикващия.
Извикване на .addDatabase()
добавя връзка към база данни към списък с налични връзки. Този списък е глобален регистър които PyQt поддържа зад кулисите, за да следи наличните връзки в дадено приложение. Регистриране на вашите връзки със смислено connectionName
ще ви позволи да управлявате няколко връзки в приложение за база данни.
След като създадете връзка, може да се наложи да зададете няколко атрибута за нея. Конкретният набор от атрибути ще зависи от драйвера, който използвате. По принцип ще трябва да зададете атрибути като името на базата данни, потребителското име и паролата за достъп до базата данни.
Ето обобщение на методите за настройка, които можете да използвате, за да зададете по-често използваните атрибути или свойства на връзка с база данни:
Метод | Описание |
---|---|
.setDatabaseName(name) | Задава името на базата данни на name , което е низ, представляващ валидно име на база данни |
.setHostName(host) | Задава името на хоста на host , което е низ, представляващ валидно име на хост |
.setUserName(username) | Задава потребителското име на username , което е низ, представляващ валидно потребителско име |
.setPassword(password) | Задава паролата на password , което е низ, представляващ валидна парола |
Имайте предвид, че паролата, която предавате като аргумент на .setPassword()
се съхранява в обикновен текст и може да бъде извлечен по-късно чрез извикване на .password()
. Това е сериозенриск за сигурността които трябва да избягвате да въвеждате във вашата база данни приложения. Ще научите по-безопасен подход в раздела Отваряне на връзка с база данни по-късно в този урок.
За да създадете връзка към база данни на SQLite с помощта на QSqlDatabase
, отворете интерактивна сесия на Python и въведете следния код:
>>> from PyQt5.QtSql import QSqlDatabase
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con
<PyQt5.QtSql.QSqlDatabase object at 0x7f0facec0c10>
>>> con.databaseName()
'contacts.sqlite'
>>> con.connectionName()
'qt_sql_default_connection'
Този код ще създаде обект за връзка с база данни, използвайки "QSQLITE"
като драйвер на връзката и "contacts.sqlite"
като име на базата данни на връзката. Тъй като не предавате име на връзка на .addDatabase()
, новосъздадената става вашата връзка по подразбиране, чието име е "qt_sql_default_connection"
.
В случай на бази данни на SQLite, името на базата данни обикновено е име на файл или път, който включва името на файла на базата данни. Можете също да използвате специалното име ":memory:"
за база данни в паметта.
Обработване на множество връзки
Може да има ситуации, в които трябва да използвате множество връзки към една база данни. Например, може да искате да регистрирате взаимодействията на потребителите с базата данни, като използвате конкретна връзка за всеки потребител.
В други ситуации може да се наложи да свържете приложението си с няколко бази данни. Например, може да искате да се свържете с няколко отдалечени бази данни, за да събирате данни за попълване или актуализиране на локална база данни.
За да се справите с тези ситуации, можете да предоставите конкретни имена за различните си връзки и да посочите всяка връзка с нейното име. Ако искате да дадете име на връзката си с базата данни, тогава предайте това име като втори аргумент на .addDatabase()
:
>>> from PyQt5.QtSql import QSqlDatabase
>>> # First connection
>>> con1 = QSqlDatabase.addDatabase("QSQLITE", "con1")
>>> con1.setDatabaseName("contacts.sqlite")
>>> # Second connection
>>> con2 = QSqlDatabase.addDatabase("QSQLITE", "con2")
>>> con2.setDatabaseName("contacts.sqlite")
>>> con1
<PyQt5.QtSql.QSqlDatabase object at 0x7f367f5fbf90>
>>> con2
<PyQt5.QtSql.QSqlDatabase object at 0x7f3686dd7510>
>>> con1.databaseName()
'contacts.sqlite'
>>> con2.databaseName()
'contacts.sqlite'
>>> con1.connectionName()
'con1'
>>> con2.connectionName()
'con2'
Тук създавате две различни връзки към една и съща база данни, contacts.sqlite
. Всяка връзка има свое собствено име на връзка. Можете да използвате името на връзката, за да получите препратка към конкретна връзка по всяко време по-късно във вашия код според вашите нужди. За да направите това, можете да извикате .database()
с име на връзка:
>>> from PyQt5.QtSql import QSqlDatabase
>>> db = QSqlDatabase.database("con1", open=False)
>>> db.databaseName()
'contacts.sqlite'
>>> db.connectionName()
'con1'
В този пример виждате, че .database()
приема два аргумента:
connectionName
съдържа името на връзката, което трябва да използвате. Ако не подадете име на връзка, ще се използва връзката по подразбиране.open
съдържа булева стойност, която казва на.database()
ако искате автоматично да отворите връзката или не. Акоopen
еTrue
(по подразбиране) и връзката не е отворена, тогава връзката се отваря автоматично.
Връщаната стойност на .database()
е препратка към обекта за връзка, наречен connectionName
. Можете да използвате различни имена на връзки, за да получите препратки към конкретни обекти за връзка и след това да ги използвате за управление на вашата база данни.
Използване на различни SQL Divers
Досега научихте как да създадете връзка с база данни с помощта на драйвера SQLite . Това не е единственият наличен драйвер в PyQt. Библиотеката предоставя богат набор от SQL драйвери, които ви позволяват да използвате различни видове системи за управление на бази данни според вашите специфични нужди:
Име на драйвер | Система за управление на бази данни |
---|---|
QDB2 | IBM Db2 (версия 7.1 и по-нова) |
QIBASE | Borland InterBase |
QMYSQL/MARIADB | MySQL или MariaDB (версия 5.0 и по-нова) |
QOCI | Интерфейс за повикване на Oracle |
QODBC | Отворена връзка с база данни (ODBC) |
QPSQL | PostgreSQL (версии 7.3 и по-нови) |
QSQLITE2 | SQLite 2 (остарял от Qt 5.14) |
QSQLITE | SQLite 3 |
QTDS | Sybase Adaptive Server (остарял от Qt 4.7) |
Колоната "Име на драйвер" съдържа низовете на идентификатора които трябва да предадете на .addDatabase()
като негов първи аргумент за използване на свързания драйвер. За разлика от драйвера на SQLite, когато използвате различен драйвер, може да се наложи да зададете няколко атрибута, като databaseName
, hostName
, username
и password
, за да работи връзката правилно.
Драйверите за база данни са получени от QSqlDriver
. Можете да създадете свои собствени драйвери за база данни, като подкласирате QSqlDriver
, но тази тема излиза извън обхвата на този урок. Ако се интересувате от създаване на свои собствени драйвери за база данни, вижте Как да напишете своя собствен драйвер за база данни за повече подробности.
Отваряне на връзка с база данни
След като имате връзка с база данни, трябва да отворите тази връзка, за да можете да взаимодействате с вашата база данни. За да направите това, извиквате .open()
върху обекта на свързване. .open()
има следните две разновидности:
.open()
отваря връзка с база данни, използвайки текущите стойности на връзката..open(username, password)
отваря връзка с база данни, използвайки предоставенотоusername
иpassword
.
И двата варианта връщат True
ако връзката е успешна. В противен случай те връщат False
. Ако връзката не може да бъде установена, тогава можете да извикате .lastError()
за да получите информация за случилото се. Тази функция връща информация за последната грешка, отчетена от базата данни.
Забележка: Както научихте преди, .setPassword(password)
съхранява паролите като обикновен текст, което представлява риск за сигурността. От друга страна, .open()
изобщо не съхранява пароли. Той предава паролата директно на драйвера при отваряне на връзката. След това той отхвърля паролата. И така, използвайки .open()
да управлявате паролите си е начинът, по който искате да предотвратите проблеми със сигурността.
Ето пример за това как да отворите връзка с база данни на SQLite, като използвате първия вариант на .open()
:
>>> from PyQt5.QtSql import QSqlDatabase
>>> # Create the connection
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> # Open the connection
>>> con.open()
True
>>> con.isOpen()
True
В горния пример първо създавате връзка към вашата база данни SQLite и отваряте тази връзка с помощта на .open()
. Тъй като .open()
връща True
, връзката е успешна. В този момент можете да проверите връзката с помощта на .isOpen()
, което връща True
ако връзката е отворена и False
иначе.
Забележка: Ако извикате .open()
при връзка, която използва драйвера на SQLite и файлът на базата данни не съществува, тогава нов и празен файл с база данни ще бъде създаден автоматично.
В приложенията от реалния свят трябва да се уверите, че имате валидна връзка с вашата база данни, преди да се опитате да извършите каквито и да е операции с вашите данни. В противен случай приложението ви може да се счупи и да се провали. Например, какво ще стане, ако нямате разрешения за запис за директорията, в която се опитвате да създадете този файл на базата данни? Трябва да се уверите, че обработвате всяка грешка, която може да възникне при отваряне на връзка.
Често срещан начин за извикване на .open()
е да го обвиете в условно изявление. Това ви позволява да обработвате грешки, които могат да възникнат при отваряне на връзката:
>>> import sys
>>> from PyQt5.QtSql import QSqlDatabase
>>> # Create the connection
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> # Open the connection and handle errors
>>> if not con.open():
... print("Unable to connect to the database")
... sys.exit(1)
Обвиване на повикването към .open()
в условен израз ви позволява да обработвате всяка грешка, която се случва, когато отворите връзката. По този начин можете да информирате потребителите си за всякакви проблеми, преди приложението да се стартира. Имайте предвид, че приложението излиза със състояние на изход 1
, който обикновено се използва за обозначаване на програмна грешка.
В горния пример използвате .open()
в интерактивна сесия, така че използвате print()
да представя съобщения за грешки на потребителите. Въпреки това, в GUI приложения, вместо да използвате print()
, обикновено използвате QMessageBox
обект. С QMessageBox
, можете да създавате малки диалогови прозорци, за да представите информация на вашите потребители.
Ето примерно GUI приложение, което илюстрира начин за обработка на грешки при свързване:
1import sys
2
3from PyQt5.QtSql import QSqlDatabase
4from PyQt5.QtWidgets import QApplication, QMessageBox, QLabel
5
6# Create the connection
7con = QSqlDatabase.addDatabase("QSQLITE")
8con.setDatabaseName("/home/contacts.sqlite")
9
10# Create the application
11app = QApplication(sys.argv)
12
13# Try to open the connection and handle possible errors
14if not con.open():
15 QMessageBox.critical(
16 None,
17 "App Name - Error!",
18 "Database Error: %s" % con.lastError().databaseText(),
19 )
20 sys.exit(1)
21
22# Create the application's window
23win = QLabel("Connection Successfully Opened!")
24win.setWindowTitle("App Name")
25win.resize(200, 100)
26win.show()
27sys.exit(app.exec_())
if
изявление в ред 14 проверява дали връзката е била неуспешна. Ако /home/
директория не съществува или ако нямате разрешение да пишете в нея, тогава извикването на .open()
не успява, защото файлът на базата данни не може да бъде създаден. В тази ситуация впотокът на изпълнение въвежда if
блок с код на изявлението и показва съобщение на екрана.
Ако промените пътя към всяка друга директория, в която можете да пишете, тогава извикването на .open()
ще успее и ще видите прозорец, показващ съобщението Connection Successfully Opened!
Ще имате и нов файл с база данни, наречен contacts.sqlite
в избраната директория.
Имайте предвид, че предавате None
като родител на съобщението защото в момента на показване на съобщението все още не сте създали прозорец, така че нямате жизнеспособен родител за кутията за съобщение.
Изпълнение на SQL заявки с PyQt
С напълно функционална връзка с база данни, вие сте готови да започнете да работите с вашата база данни. За да направите това, можете да използвате базирани на низове SQL заявки и QSqlQuery
обекти. QSqlQuery
ви позволява да изпълнявате всякакъв вид SQL заявка във вашата база данни. С QSqlQuery
, можете да изпълнявате изрази на езика за манипулиране на данни (DML), като SELECT
, INSERT
, UPDATE
и DELETE
, както и изрази за език за дефиниране на данни (DDL), като CREATE TABLE
и така нататък.
Конструкторът на QSqlQuery
има няколко варианта, но в този урок ще научите за две от тях:
-
QSqlQuery(query, connection)
конструира обект на заявка, използвайки базирана на низ SQLquery
иconnection
на база данни . Ако не посочите връзка или ако посочената връзка е невалидна, тогава се използва връзката към базата данни по подразбиране. Акоquery
не е празен низ, тогава той ще бъде изпълнен веднага. -
QSqlQuery(connection)
конструира обект на заявка, използвайкиconnection
. Акоconnection
е невалидно, тогава се използва връзката по подразбиране.
Можете също да създадете QSqlQuery
обекти без да предава никакви аргументи на конструктора. В този случай заявката ще използва връзката с базата данни по подразбиране, ако има такава.
За да изпълните заявка, трябва да извикате .exec()
върху обекта на заявката. Можете да използвате .exec()
по два различни начина:
-
.exec(query)
изпълнява базираната на низове SQL заявка, съдържаща се вquery
. ВръщаTrue
ако заявката е била успешна и в противен случай връщаFalse
. -
.exec()
изпълнява предварително подготвена SQL заявка. ВръщаTrue
ако заявката е била успешна и в противен случай връщаFalse
.
Забележка: PyQt също така внедрява вариации на QSqlQuery.exec()
с името .exec_()
. Те осигуряват обратна съвместимост с по-стари версии на Python, в които exec
беше ключова дума на езика.
Сега, когато знаете основите на използването на QSqlQuery
за да създавате и изпълнявате SQL заявки, вие сте готови да научите как да приложите знанията си на практика.
Изпълнение на статични SQL заявки
За да започнете да създавате и изпълнявате заявки с PyQt, ще стартирате любимия си редактор на код или IDE и ще създадете скрипт на Python, наречен queries.py
. Запазете скрипта и добавете следния код към него:
1import sys
2
3from PyQt5.QtSql import QSqlDatabase, QSqlQuery
4
5# Create the connection
6con = QSqlDatabase.addDatabase("QSQLITE")
7con.setDatabaseName("contacts.sqlite")
8
9# Open the connection
10if not con.open():
11 print("Database Error: %s" % con.lastError().databaseText())
12 sys.exit(1)
13
14# Create a query and execute it right away using .exec()
15createTableQuery = QSqlQuery()
16createTableQuery.exec(
17 """
18 CREATE TABLE contacts (
19 id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
20 name VARCHAR(40) NOT NULL,
21 job VARCHAR(50),
22 email VARCHAR(40) NOT NULL
23 )
24 """
25)
26
27print(con.tables())
В този скрипт започвате с импортиране на модулите и класовете, с които ще работите. След това създавате връзка с база данни с помощта на .addDatabase()
с драйвера на SQLite. Задавате името на базата данни на "contacts.sqlite"
и отворете връзката.
За да създадете първата си заявка, инстанцирате QSqlQuery
без никакви аргументи. Когато обектът на заявката е на място, извиквате .exec()
, предавайки базирана на низ SQL заявка като аргумент. Този вид заявка е известна като статична заявка защото не получава никакви параметри извън заявката.
Горната SQL заявка създава нова таблица, наречена contacts
във вашата база данни. Тази таблица ще има следните четири колони:
Колона | Съдържание |
---|---|
id | Цяло число с първичен ключ на таблицата |
name | Низ с името на контакт |
job | Низ с длъжността на контакт |
email | Низ с имейл на контакт |
Последният ред в горния скрипт отпечатва списъка с таблици, съдържащи се във вашата база данни. Ако стартирате скрипта, тогава ще забележите, че нов файл с база данни, наречен contacts.sqlite
се създава във вашата текуща директория. Ще получите също нещо като ['contacts', 'sqlite_sequence']
отпечатани на вашия екран. Този списък съдържа имената на таблиците във вашата база данни.
Забележка: Базираната на низове SQL заявка трябва да използва подходящ синтаксис според конкретната SQL база данни, която отправяте. Ако синтаксисът е грешен, тогава .exec()
игнорира заявката и връща False
.
В случая на SQLite, заявката може да съдържа само един израз наведнъж.
Извикване на .exec()
на QSqlQuery
обектът е често срещан начин за незабавно изпълнение на базирани на низ SQL заявки във вашите бази данни, но какво ще стане, ако искате да подготвите заявките си предварително за по-късно изпълнение? Това е темата на следващия раздел.
Изпълнение на динамични заявки:форматиране на низ
Досега сте се научили как да изпълнявате статични заявки в база данни. Статичните заявки са тези, които не приемат параметри , така че заявката работи както е. Въпреки че тези заявки са доста полезни, понякога трябва да създавате заявки, които извличат данни в отговор на определени входни параметри.
Заявките, които приемат параметри по време на изпълнение, са известни като динамични заявки . Използването на параметри ви позволява да прецизирате заявката и да извличате данни в отговор на конкретни стойности на параметрите. Различните стойности ще доведат до различни резултати. Можете да вземете входни параметри в заявка, като използвате един от следните два подхода:
- Създайте заявката динамично, като използвате форматиране на низ за интерполиране на стойностите на параметрите.
- Подгответе заявката, като използвате параметри за място и след това свържете конкретни стойности с параметрите.
Първият подход ви позволява бързо да създавате динамични заявки. Въпреки това, за да използвате безопасно този подход, трябва да сте сигурни, че стойностите на вашите параметри идват от доверен източник. В противен случай може да се сблъскате с атаки с инжектиране на SQL.
Ето пример за това как да използвате форматиране на низ за създаване на динамични заявки в PyQt:
>>>>>> from PyQt5.QtSql import QSqlQuery, QSqlDatabase
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con.open()
True
>>> name = "Linda"
>>> job = "Technical Lead"
>>> email = "[email protected]"
>>> query = QSqlQuery()
>>> query.exec(
... f"""INSERT INTO contacts (name, job, email)
... VALUES ('{name}', '{job}', '{email}')"""
... )
True
В този пример използвате f-низ за създаване на динамична заявка чрез интерполиране на конкретни стойности в SQL заявка, базирана на низ. Последната заявка вмъква данни във вашите contacts
таблица, която вече съдържа данни за Linda
.
Забележка: По-късно в този урок ще видите как да извличате и навигирате в данните, съхранявани в база данни.
Имайте предвид, че за да работи този вид динамична заявка, трябва да се уверите, че стойностите, които трябва да се вмъкнат, имат правилния тип данни. Така че, вие използвате единични кавички около заместващия елемент в f-низа, защото тези стойности трябва да са низове.
Изпълнение на динамични заявки:Заместващи параметри
Вторият подход за изпълнение на динамични заявки изисква предварително да подготвите заявките си, като използвате шаблон с заместители за параметри. PyQt поддържа два стила на заместител на параметъра:
- Стил на Oracle използва именувани заместители като
:name
или:email
. - ODBC стил използва въпросителен знак (
?
) като позиционен заместител.
Имайте предвид, че тези стилове не могат да се смесват в една и съща заявка. Можете да разгледате Подходи към обвързващите стойности за допълнителни примери как да използвате заместители.
Забележка: ODBC означава Open Database Connectivity.
За да създадете този вид динамична заявка в PyQt, първо създавате шаблон с заместител за всеки параметър на заявката и след това предавате този шаблон като аргумент на .prepare()
, който анализира, компилира и подготвя шаблона на заявката за изпълнение. Ако шаблонът има някакви проблеми, като например синтактична грешка на SQL, тогава .prepare()
не успява да компилира шаблона и връща False
.
Ако процесът на подготовка е успешен, тогава prepare()
връща True
. След това можете да подадете конкретна стойност на всеки параметър, като използвате .bindValue()
с наименувани или позиционни параметри или с помощта на .addBindValue()
с позиционни параметри. .bindValue()
има следните две разновидности:
.bindValue(placeholder, val)
.bindValue(pos, val)
В първия вариант placeholder
представлява заместител в стил Oracle. Във втория вариант pos
представлява базирано на нула цяло число с позицията на параметър в заявката. И в двата варианта val
съдържа стойността, която трябва да бъде обвързана с конкретен параметър.
.addBindValue()
добавя стойност към списъка с заместители, използвайки позиционно обвързване. Това означава, че редът на извикванията към .addBindValue()
определя коя стойност ще бъде обвързана с всеки параметър за място в подготвената заявка.
За да започнете да използвате подготвени заявки, можете да подготвите INSERT INTO
SQL израз за попълване на вашата база данни с някои примерни данни. Върнете се към скрипта, който сте създали в секцията Изпълнение на статични SQL заявки и добавете следния код веднага след извикването на print()
:
28# Creating a query for later execution using .prepare()
29insertDataQuery = QSqlQuery()
30insertDataQuery.prepare(
31 """
32 INSERT INTO contacts (
33 name,
34 job,
35 email
36 )
37 VALUES (?, ?, ?)
38 """
39)
40
41# Sample data
42data = [
43 ("Joe", "Senior Web Developer", "[email protected]"),
44 ("Lara", "Project Manager", "[email protected]"),
45 ("David", "Data Analyst", "[email protected]"),
46 ("Jane", "Senior Python Developer", "[email protected]"),
47]
48
49# Use .addBindValue() to insert data
50for name, job, email in data:
51 insertDataQuery.addBindValue(name)
52 insertDataQuery.addBindValue(job)
53 insertDataQuery.addBindValue(email)
54 insertDataQuery.exec()
Първата стъпка е да създадете QSqlQuery
обект. След това извиквате .prepare()
върху обекта на заявката. В този случай използвате стила ODBC за заместителите. Заявката ви ще приеме стойности за name
на вашия контакт , job
и email
, така че имате нужда от три заместителя. Тъй като id
колоната е автоматично нарастващо цяло число, не е необходимо да предоставяте стойности за него.
След това създавате някои примерни данни за попълване на базата данни. data
съдържа списък с кортежи и всеки кортеж съдържа три елемента:име, работа и имейл на всеки контакт.
Последната стъпка е да обвържете стойностите, които искате да предадете на всеки контейнер и след това да извикате .exec()
за изпълнение на заявката. За да направите това, използвайте for
цикъл. Заглавката на цикъла разопакова всеки кортеж в data
в три отделни променливи с удобни имена. След това извиквате .addBindValue()
on the query object to bind the values to the placeholders.
Note that you’re using positional placeholders , so the order in which you call .addBindValue()
will define the order in which each value is passed to the corresponding placeholder.
This approach for creating dynamic queries is handy when you want to customize your queries using values that come from your user’s input. Anytime you take the user’s input to complete a query on a database, you face the security risk of SQL injection.
In PyQt, combining .prepare()
, .bindValue()
, and .addBindValue()
fully protects you against SQL injection attacks, so this is the way to go when you’re taking untrusted input to complete your queries.
Navigating the Records in a Query
If you execute a SELECT
statement, then your QSqlQuery
object will retrieve zero or more records from one or more tables in your database. The query will hold records containing data that matches the query’s criteria. If no data matches the criteria, then your query will be empty.
QSqlQuery
provides a set of navigation methods that you can use to move throughout the records in a query result:
Метод | Retrieves |
---|---|
.next() | The next record |
.previous() | The previous record |
.first() | The first record |
.last() | The last record |
.seek(index, relative=False) | The record at position index |
All these methods position the query object on the retrieved record if that record is available. Most of these methods have specific rules that apply when using them. With these methods, you can move forward, backward, or arbitrarily through the records in a query result. Since they all return either True
or False
, you can use them in a while
loop to navigate all the records in one go.
These methods work with active queries . A query is active when you’ve successfully run .exec()
on it, but the query isn’t finished yet. Once an active query is on a valid record, you can retrieve data from that record using .value(index)
. This method takes a zero-based integer number, index
, and returns the value at that index (column) in the current record.
Забележка: If you execute a SELECT *
type of query, then the columns in the result won’t follow a known order. This might cause problems when you use .value()
to retrieve the value at a given column because there’s no way of knowing if you’re using the right column index.
You’ll look at a few examples of how to use some of the navigation methods to move throughout a query below. But first, you need to create a connection to your database:
>>>>>> from PyQt5.QtSql import QSqlDatabase, QSqlQuery
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con.open()
True
Here, you create and open a new connection to contacts.sqlite
. If you’ve been following along with this tutorial so far, then this database already contains some sample data. Now you can create a QSqlQuery
object and execute it on that data:
>>> # Create and execute a query
>>> query = QSqlQuery()
>>> query.exec("SELECT name, job, email FROM contacts")
True
This query retrieves data about the name
, job
, and email
of all the contacts stored in the contacts
маса. Since .exec()
returned True
, the query was successful and is now an active query. You can navigate the records in this query using any of the navigation methods you saw before. You can also retrieve the data at any column in a record using .value()
:
>>> # First record
>>> query.first()
True
>>> # Named indices for readability
>>> name, job, email = range(3)
>>> # Retrieve data from the first record
>>> query.value(name)
'Linda'
>>> # Next record
>>> query.next()
True
>>> query.value(job)
'Senior Web Developer'
>>> # Last record
>>> query.last()
True
>>> query.value(email)
'[email protected]'
With the navigation methods, you can move around the query result. With .value()
, you can retrieve the data at any column in a given record.
You can also iterate through all the records in your query using a while
loop along with .next()
:
>>> query.exec()
True
>>> while query.next():
... print(query.value(name), query.value(job), query.value(email))
...
Linda Technical Lead [email protected]
Joe Senior Web Developer [email protected]
...
With .next()
, you navigate all the records in a query result. .next()
works similar to the iterator protocol in Python. Once you’ve iterated over the records in a query result, .next()
starts returning False
until you run .exec()
отново. A call to .exec()
retrieves data from a database and places the query object’s internal pointer one position before the first record, so when you call .next()
, you get the first record again.
You can also loop in reverse order using .previous()
:
>>> while query.previous():
... print(query.value(name), query.value(job), query.value(email))
...
Jane Senior Python Developer [email protected]
David Data Analyst [email protected]
...
.previous()
works similar to .next()
, but the iteration is done in reverse order. In other words, the loop goes from the query pointer’s position back to the first record.
Sometimes you might want to get the index that identifies a given column in a table by using the name of that column. To do that, you can call .indexOf()
on the return value of .record()
:
>>> query.first()
True
>>> # Get the index of name
>>> name = query.record().indexOf("name")
>>> query.value(name)
'Linda'
>>> # Finish the query object if unneeded
>>> query.finish()
>>> query.isActive()
False
The call to .indexOf()
on the result of .record()
returns the index of the "name"
column. If "name"
doesn’t exist, then .indexOf()
returns -1
. This is handy when you use a SELECT *
statement in which the order of columns is unknown. Finally, if you’re done with a query object, then you can turn it inactive by calling .finish()
. This will free the system memory associated with the query object at hand.
Closing and Removing Database Connections
In practice, some of your PyQt applications will depend on a database, and others won’t. An application that depends on a database often creates and opens a database connection just before creating any window or graphical component and keeps the connection open until the application is closed.
On the other hand, applications that don’t depend on a database but use a database to provide some of their functionalities typically connect to that database only when needed, if at all. In these cases, you can close the connection after use and free the resources associated with that connection, such as system memory.
To close a connection in PyQt, you call .close()
on the connection. This method closes the connection and frees any acquired resources. It also invalidates any associated QSqlQuery
objects because they can’t work properly without an active connection.
Here’s an example of how to close an active database connection using .close()
:
>>> from PyQt5.QtSql import QSqlDatabase
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con.open()
True
>>> con.isOpen()
True
>>> con.close()
>>> con.isOpen()
False
You can call .close()
on a connection to close it and free all its associated resources. To make sure that a connection is closed, you call .isOpen()
.
Note that QSqlQuery
objects remain in memory after closing their associated connection, so you must make your queries inactive by calling .finish()
or .clear()
, or by deleting the QSqlQuery
object before closing the connection. Otherwise, residual memory is left out in your query object.
You can reopen and reuse any previously closed connection. That’s because .close()
doesn’t remove connections from the list of available connections, so they remain usable.
You can also completely remove your database connections using .removeDatabase()
. To do this safely, first finish your queries using .finish()
, then close the database using .close()
, and finally remove the connection. You can use .removeDatabase(connectionName)
to remove the database connection called connectionName
from the list of available connections. Removed connections are no longer available for use in the application at hand.
To remove the default database connection, you can call .connectionName()
on the object returned by .database()
and pass the result to .removeDatabase()
:
>>> # The connection is closed but still in the list of connections
>>> QSqlDatabase.connectionNames()
['qt_sql_default_connection']
>>> # Remove the default connection
>>> QSqlDatabase.removeDatabase(QSqlDatabase.database().connectionName())
>>> # The connection is no longer in the list of connections
>>> QSqlDatabase.connectionNames()
[]
>>> # Try to open a removed connection
>>> con.open()
False
Here, the call to .connectionNames()
returns the list of available connections. In this case, you have only one connection, the default. Then you remove the connection using .removeDatabase()
.
Забележка: Before closing and removing a database connection, you need to make sure that everything that uses the connection is deleted or set to use a different data source. Otherwise, you can have a resource leak .
Since you need a connection name to use .removeDatabase()
, you call .connectionName()
on the result of .database()
to get the name of the default connection. Finally, you call .connectionNames()
again to make sure that the connection is no longer in the list of available connections. Trying to open a removed connection will return False
because the connection no longer exists.
Displaying and Editing Data With PyQt
A common requirement in GUI applications that use databases is the ability to load, display, and edit data from the database using different widgets. Table, list, and tree widgets are commonly used in GUIs to manage data.
PyQt provides two different kind of widgets for managing data:
- Standard widgets include internal containers for storing data.
- View widgets don’t maintain internal data containers but use models to access data.
For small GUI applications that manage small databases, you can use the first approach. The second approach is handy when you’re building complex GUI applications that manage large databases.
The second approach takes advantage of PyQt’s Model-View programming. With this approach, you have widgets that represent views such as tables, lists, and trees on one hand and model classes that communicate with your data on the other hand.
Understanding PyQt’s Model-View Architecture
The Model-View-Controller (MVC) design pattern is a general software pattern intended to divide an application’s code into three general layers, each with a different role.
The model takes care of the business logic of the application, the view provides on-screen representations, and the controller connects the model and the view to make the application work.
Qt provides a custom variation of MVC. They call it the Model-View architecture, and it’s available for PyQt as well. The pattern also separates the logic into three components:
-
Models communicate with and access the data. They also define an interface that’s used by views and delegates to access the data. All models are based on
QAbstractItemModel
. Some commonly used models includeQStandardItemModel
,QFileSystemModel
, and SQL-related models. -
Views are responsible for displaying the data to the user. They also have similar functionality to the controller in the MVC pattern. All views are based on
QAbstractItemView
. Some commonly used views areQListView
,QTableView
, andQTreeView
. -
Delegates paint view items and provide editor widgets for modifying items. They also communicate back with the model if an item has been modified. The base class is
QAbstractItemDelegate
.
Separating classes into these three components implies that changes on models will be reflected on associated views or widgets automatically, and changes on views or widgets through delegates will update the underlying model automatically.
In addition, you can display the same data in different views without the need for multiple models.
Using Standard Widget Classes
PyQt provides a bunch of standard widgets for displaying and editing data in your GUI applications. These standard widgets provide views such as tables, trees, and lists. They also provide an internal container for storing data and convenient delegates for editing the data. All these features are grouped into a single class.
Here are three of these standard classes:
Standard Class | Displays |
---|---|
QListWidget | A list of items |
QTreeWidget | A hierarchical tree of items |
QTableWidget | A table of items |
QTableWidget
is arguably the most popular widget when it comes to displaying and editing data. It creates a 2D array of QTableWidgetItem
обекти. Each item holds an individual value as a string. All these values are displayed and organized in a table of rows and columns.
You can perform at least the following operations on a QTableWidget
object:
- Editing the content of its items using delegate objects
- Adding new items using
.setItem()
- Setting the number of rows and columns using
.setRowCount()
and.setColumnCount()
- Adding vertical and horizontal header labels using
setHorizontalHeaderLabels()
and.setVerticalHeaderLabels
Here’s a sample application that shows how to use a QTableWidget
object to display data in a GUI. The application uses the database you created and populated in previous sections, so if you want to run it, then you need to save the code into the same directory in which you have the contacts.sqlite
база данни:
If you double-click any cell of the table, then you’ll be able to edit the content of the cell. However, your changes won’t be saved to your database.
Here’s the code for your application:
1import sys
2
3from PyQt5.QtSql import QSqlDatabase, QSqlQuery
4from PyQt5.QtWidgets import (
5 QApplication,
6 QMainWindow,
7 QMessageBox,
8 QTableWidget,
9 QTableWidgetItem,
10)
11
12class Contacts(QMainWindow):
13 def __init__(self, parent=None):
14 super().__init__(parent)
15 self.setWindowTitle("QTableView Example")
16 self.resize(450, 250)
17 # Set up the view and load the data
18 self.view = QTableWidget()
19 self.view.setColumnCount(4)
20 self.view.setHorizontalHeaderLabels(["ID", "Name", "Job", "Email"])
21 query = QSqlQuery("SELECT id, name, job, email FROM contacts")
22 while query.next():
23 rows = self.view.rowCount()
24 self.view.setRowCount(rows + 1)
25 self.view.setItem(rows, 0, QTableWidgetItem(str(query.value(0))))
26 self.view.setItem(rows, 1, QTableWidgetItem(query.value(1)))
27 self.view.setItem(rows, 2, QTableWidgetItem(query.value(2)))
28 self.view.setItem(rows, 3, QTableWidgetItem(query.value(3)))
29 self.view.resizeColumnsToContents()
30 self.setCentralWidget(self.view)
31
32def createConnection():
33 con = QSqlDatabase.addDatabase("QSQLITE")
34 con.setDatabaseName("contacts.sqlite")
35 if not con.open():
36 QMessageBox.critical(
37 None,
38 "QTableView Example - Error!",
39 "Database Error: %s" % con.lastError().databaseText(),
40 )
41 return False
42 return True
43
44app = QApplication(sys.argv)
45if not createConnection():
46 sys.exit(1)
47win = Contacts()
48win.show()
49sys.exit(app.exec_())
Here’s what’s happening in this example:
- Lines 18 to 20 create a
QTableWidget
object, set the number of columns to4
, and set user-friendly labels for each column’s header. - Line 21 creates and executes a
SELECT
SQL query on your database to get all the data in thecontacts
table. - Line 22 starts a
while
loop to navigate the records in the query result using.next()
. - Line 24 increments the number of rows in the table by
1
using.setRowCount()
. - Lines 25 to 28 add items of data to your table using
.setItem()
. Note that since the values in theid
columns are integer numbers, you need to convert them into strings to be able to store them in aQTableWidgetItem
object.
.setItem()
takes three arguments:
row
holds a zero-based integer that represents the index of a given row in the table.column
holds a zero-based integer that represents the index of a given column in the table.item
holds theQTableWidgetItem
object that you need to place at a given cell in the table.
Finally, you call .resizeColumnsToContents()
on your view to adjust the size of the columns to their content and provide a better rendering of the data.
Displaying and editing database tables using standard widgets can become a challenging task. That’s because you’ll have two copies of the same data. In other words you’ll have a copy of the data in two locations:
- Outside the widget, in your database
- Inside the widget, in the widget’s internal containers
You’re responsible for synchronizing both copies of your data manually, which can be an annoying and error-prone operation. Luckily, you can use PyQt’s Model-View architecture to avoid most of these problems, as you’ll see in the following section.
Using View and Model Classes
PyQt’s Model-View classes eliminate the problems of data duplication and synchronization that may occur when you use standard widget classes to build database applications. The Model-View architecture allows you to use several views to display the same data because you can pass one model to many views.
Model classes provide an application programming interface (API) that you can use to manipulate data. View classes provide convenient delegate objects that you can use to edit data in the view directly. To connect a view with a given module, you need to call .setModel()
on the view object.
PyQt offers a set of view classes that support the Model-View architecture:
View Class | Displays |
---|---|
QListView | A list of items that take values directly from a model class |
QTreeView | A hierarchical tree of items that take values directly from a model class |
QTableView | A table of items that take values directly from a model class |
You can use these view classes along with model classes to create your database applications. This will make your applications more robust, faster to code, and less error-prone.
Here are some of the model classes that PyQt provides for working with SQL databases:
Model Class | Описание |
---|---|
QSqlQueryModel | A read-only data model for SQL queries |
QSqlTableModel | An editable data model for reading and writing records in a single table |
QSqlRelationalTableModel | An editable data model for reading and writing records in a relational table |
Once you’ve connected one of these models to a physical database table or query, you can use them to populate your views. Views provide delegate objects that allow you to modify the data directly in the view. The model connected to the view will update the data in your database to reflect any change in the view. Note that you don’t have to update the data in the database manually. The model will do that for you.
Here’s an example that shows the basics of how to use a QTableView
object and a QSqlTableModel
object together to build a database application using PyQt’s Model-View architecture:
To edit the data in a cell of the table, you can double-click the cell. A convenient delegate widget will show in the cell, allowing you to edit its content. Then you can hit Enter to save the changes.
The ability to automatically handle and save changes in the data is one of the more important advantages of using PyQt’s Model-View classes. The Model-View architecture will improve your productivity and reduce the errors that can appear when you have to write data manipulation code by yourself.
Here’s the code to create the application:
1import sys
2
3from PyQt5.QtCore import Qt
4from PyQt5.QtSql import QSqlDatabase, QSqlTableModel
5from PyQt5.QtWidgets import (
6 QApplication,
7 QMainWindow,
8 QMessageBox,
9 QTableView,
10)
11
12class Contacts(QMainWindow):
13 def __init__(self, parent=None):
14 super().__init__(parent)
15 self.setWindowTitle("QTableView Example")
16 self.resize(415, 200)
17 # Set up the model
18 self.model = QSqlTableModel(self)
19 self.model.setTable("contacts")
20 self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
21 self.model.setHeaderData(0, Qt.Horizontal, "ID")
22 self.model.setHeaderData(1, Qt.Horizontal, "Name")
23 self.model.setHeaderData(2, Qt.Horizontal, "Job")
24 self.model.setHeaderData(3, Qt.Horizontal, "Email")
25 self.model.select()
26 # Set up the view
27 self.view = QTableView()
28 self.view.setModel(self.model)
29 self.view.resizeColumnsToContents()
30 self.setCentralWidget(self.view)
31
32def createConnection():
33 con = QSqlDatabase.addDatabase("QSQLITE")
34 con.setDatabaseName("contacts.sqlite")
35 if not con.open():
36 QMessageBox.critical(
37 None,
38 "QTableView Example - Error!",
39 "Database Error: %s" % con.lastError().databaseText(),
40 )
41 return False
42 return True
43
44app = QApplication(sys.argv)
45if not createConnection():
46 sys.exit(1)
47win = Contacts()
48win.show()
49sys.exit(app.exec_())
Here’s what’s happening in this code:
- Line 18 creates an editable
QSqlTableModel
object. - Line 19 connects your model with the
contacts
table in your database using.setTable()
. - Line 20 sets the edit strategy of the model to
OnFieldChange
. This strategy allows the model to automatically update the data in your database if the user modifies any of the data directly in the view. - Lines 21 to 24 set some user-friendly labels to the horizontal headers of the model using
.setHeaderData()
. - Line 25 loads the data from your database and populates the model by calling
.select()
. - Line 27 creates the table view object to display the data contained in the model.
- Line 28 connects the view with the model by calling
.setModel()
on the view with your data model as an argument. - Line 29 calls
.resizeColumnsToContents()
on the view object to adjust the table to its content.
That’s it! You now have a fully-functional database application.
Using SQL Databases in PyQt:Best Practices
When it comes to using PyQt’s SQL support effectively, there are some best practices that you might want to use in your applications:
-
Favor PyQt’s SQL support over Python standard library or third-party libraries to take advantage of the natural integration of these classes with the rest of PyQt’s classes and infrastructure, mostly with the Model-View architecture.
-
Use previously prepared dynamic queries with placeholders for parameters and bind values to those parameters using
.addBindValue()
and.bindValue()
. This will help prevent SQL injection attacks. -
Handle errors that can occur when opening a database connection to avoid unexpected behaviors and application crashes.
-
Close and remove unneeded database connections and queries to free any acquired system resources.
-
Minimize the use of
SELECT *
queries to avoid problems when retrieving data with.value()
. -
Pass your passwords to
.open()
instead of to.setPassword()
to avoid the risk of compromising your security. -
Take advantage of PyQt’s Model-View architecture and its integration with PyQt’s SQL support to make your applications more robust.
This list isn’t complete, but it’ll help you make better use of PyQt’s SQL support when developing your database applications.
Заключение
Using PyQt’s built-in support to work with SQL databases is an important skill for any Python developer who’s creating PyQt GUI applications and needs to connect them to a database. PyQt provides a consistent set of classes for managing SQL databases.
These classes fully integrate with PyQt’s Model-View architecture, allowing you to develop GUI applications that can manage databases in a user-friendly way.
In this tutorial, you’ve learned how to:
- Use PyQt’s SQL support to connect to a database
- Execute SQL queries on a database with PyQt
- Build database applications using PyQt’s Model-View architecture
- Display and edit data from a database using PyQt widgets
With this knowledge, you can improve your productivity when creating nontrivial database applications and make your GUI applications more robust.