Construir aplicativos que usam um banco de dados SQL é uma tarefa de programação bastante comum. Os bancos de dados SQL estão em toda parte e têm um ótimo suporte em Python. Na programação de GUI, o PyQt fornece suporte a banco de dados SQL robusto e multiplataforma que permite criar, conectar e gerenciar seus bancos de dados de forma consistente.
O suporte SQL do PyQt integra-se totalmente com sua arquitetura Model-View para ajudá-lo no processo de construção de aplicativos de banco de dados.
Neste tutorial, você aprenderá a:
- Use o suporte a SQL do PyQt para se conectar de forma confiável a um banco de dados
- Executar consultas SQL em um banco de dados usando PyQt
- Use a arquitetura Model-View do PyQt em aplicativos de banco de dados
- Exiba e edite dados usando diferentes widgets do PyQt
Os exemplos deste tutorial requerem um conhecimento básico da linguagem SQL, especialmente do sistema de gerenciamento de banco de dados SQLite. Algum conhecimento prévio de programação GUI com Python e PyQt também será útil.
Bônus grátis: 5 Pensamentos sobre o domínio do Python, um curso gratuito para desenvolvedores de Python que mostra o roteiro e a mentalidade de que você precisa para levar suas habilidades de Python para o próximo nível.
Conectando o PyQt a um banco de dados SQL
Conectar um aplicativo a um banco de dados relacional e fazer com que o aplicativo crie, leia, atualize e exclua os dados armazenados nesse banco de dados é uma tarefa comum na programação. Os bancos de dados relacionais geralmente são organizados em um conjunto de tabelas , ou relações . Uma determinada linha em uma tabela é chamada de registro ou tupla , e uma coluna é chamada de atributo .
Observação: O termo campo é comumente usado para identificar um único dado armazenado em uma célula de um determinado registro em uma tabela. Por outro lado, o termo nome do campo é usado para identificar o nome de uma coluna.
Cada coluna armazena um tipo específico de informação, como nomes, datas ou números. Cada linha representa um conjunto de dados intimamente relacionados e cada linha tem a mesma estrutura geral. Por exemplo, em um banco de dados que armazena dados sobre os funcionários de uma empresa, uma linha específica representa um funcionário individual.
A maioria dos sistemas de banco de dados relacional usa SQL (linguagem de consulta estruturada) para consultar, manipular e manter os dados contidos no banco de dados. SQL é uma linguagem de programação declarativa e específica de domínio especialmente projetada para comunicação com bancos de dados.
Sistemas de banco de dados relacionais e SQL são amplamente utilizados atualmente. Você encontrará vários sistemas de gerenciamento de banco de dados diferentes, como SQLite, PostgreSQL, MySQL, MariaDB e muitos outros. Você pode conectar o Python a qualquer um desses sistemas de banco de dados usando uma biblioteca Python SQL dedicada.
Observação: Embora o suporte SQL integrado do PyQt seja a opção preferida para gerenciar bancos de dados SQL no PyQt, você também pode usar qualquer outra biblioteca para lidar com a conexão do banco de dados. Algumas dessas bibliotecas incluem SQLAlchemy, pandas, SQLite e assim por diante.
No entanto, usar uma biblioteca diferente para gerenciar seus bancos de dados tem algumas desvantagens. Você não poderá aproveitar a integração entre as classes SQL do PyQt e a arquitetura Model-View. Além disso, você adicionará dependências extras ao seu aplicativo.
Quando se trata de programação GUI com Python e PyQt, o PyQt fornece um conjunto robusto de classes para trabalhar com bancos de dados SQL. Este conjunto de classes será seu melhor aliado quando você precisar conectar sua aplicação a um banco de dados SQL.
Observação: Infelizmente, a documentação oficial do PyQt5 tem algumas seções incompletas. Para contornar isso, você pode conferir a documentação do PyQt4, a documentação do Qt For Python ou a documentação original do Qt. Neste tutorial, alguns links levam você à documentação original do Qt, que é uma fonte de informação melhor na maioria dos casos.
Neste tutorial, você aprenderá o básico de como usar o suporte SQL do PyQt para criar aplicativos GUI que interagem de maneira confiável com bancos de dados relacionais para ler, gravar, excluir e exibir dados.
Criando uma conexão de banco de dados
Conectar seus aplicativos a um banco de dados SQL físico é uma etapa importante no processo de desenvolvimento de aplicativos de banco de dados com PyQt. Para executar esta etapa com sucesso, você precisa de algumas informações gerais sobre como seu banco de dados está configurado.
Por exemplo, você precisa saber em qual sistema de gerenciamento de banco de dados seu banco de dados é construído e também pode precisar de um nome de usuário, uma senha, um nome de host e assim por diante.
Neste tutorial, você usará o SQLite 3, que é um sistema de banco de dados bem testado com suporte em todas as plataformas e requisitos mínimos de configuração. O SQLite permite que você leia e grave diretamente em bancos de dados em seu disco local sem a necessidade de um processo de servidor separado. Isso o torna uma opção amigável para aprender o desenvolvimento de aplicativos de banco de dados.
Outra vantagem de usar o SQLite é que a biblioteca vem com Python e também com PyQt, então você não precisa instalar mais nada para começar a trabalhar com ela.
No PyQt, você pode criar uma conexão de banco de dados usando o
QSqlDatabase
aula. Essa classe representa uma conexão e fornece uma interface para acessar o banco de dados. Para criar uma conexão, basta chamar .addDatabase()
em QSqlDatabase
. Este método estático usa um driver SQL e um nome de conexão opcional como argumentos e retorna uma conexão de banco de dados:QSqlDatabase.addDatabase(
driver, connectionName=QSqlDatabase.defaultConnection
)
O primeiro argumento,
driver
, é um argumento obrigatório que contém uma string contendo o nome de um driver SQL compatível com PyQt. O segundo argumento, connectionName
, é um argumento opcional que contém uma string com o nome da conexão. connectionName
o padrão é QSqlDatabase.defaultConnection
, que normalmente contém a string "qt_sql_default_connection"
. Se você já tem uma conexão chamada
connectionName
, essa conexão será removida e substituída por uma nova conexão e .addDatabase()
retorna a conexão de banco de dados recém-adicionada de volta ao chamador. Uma chamada para
.addDatabase()
adiciona uma conexão de banco de dados a uma lista de conexões disponíveis. Esta lista é um registro global que o PyQt mantém nos bastidores para acompanhar as conexões disponíveis em um aplicativo. Registrando suas conexões com um connectionName
significativo permitirá que você gerencie várias conexões em um aplicativo de banco de dados. Depois de criar uma conexão, talvez seja necessário definir vários atributos nela. O conjunto específico de atributos dependerá do driver que você está usando. Em geral, você precisará definir atributos como o nome do banco de dados, o nome de usuário e a senha para acessar o banco de dados.
Aqui está um resumo dos métodos setter que você pode usar para definir os atributos ou propriedades mais usados de uma conexão de banco de dados:
Método | Descrição |
---|---|
.setDatabaseName(name) | Define o nome do banco de dados como name , que é uma string que representa um nome de banco de dados válido |
.setHostName(host) | Define o nome do host como host , que é uma string que representa um nome de host válido |
.setUserName(username) | Define o nome de usuário como username , que é uma string que representa um nome de usuário válido |
.setPassword(password) | Define a senha como password , que é uma string que representa uma senha válida |
Observe que a senha que você passa como argumento para
.setPassword()
é armazenado em texto simples e pode ser recuperado posteriormente chamando .password()
. Este é um sério risco de segurança que você deve evitar introduzir em seus aplicativos de banco de dados. Você aprenderá uma abordagem mais segura na seção Abrindo uma Conexão de Banco de Dados posteriormente neste tutorial. Para criar uma conexão com um banco de dados SQLite usando
QSqlDatabase
, abra uma sessão interativa do Python e digite o seguinte código:>>>
>>> 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'
Este código criará um objeto de conexão de banco de dados usando
"QSQLITE"
como driver da conexão e "contacts.sqlite"
como o nome do banco de dados da conexão. Como você não passa um nome de conexão para .addDatabase()
, a recém-criada se torna sua conexão padrão, cujo nome é "qt_sql_default_connection"
. No caso de bancos de dados SQLite, o nome do banco de dados normalmente é um nome de arquivo ou um caminho que inclui o nome do arquivo do banco de dados. Você também pode usar o nome especial
":memory:"
para um banco de dados na memória. Como lidar com várias conexões
Pode haver situações em que você precise usar várias conexões com um único banco de dados. Por exemplo, você pode querer registrar as interações dos usuários com o banco de dados usando uma conexão específica para cada usuário.
Em outras situações, pode ser necessário conectar seu aplicativo a vários bancos de dados. Por exemplo, você pode querer se conectar a vários bancos de dados remotos para coletar dados para preencher ou atualizar um banco de dados local.
Para lidar com essas situações, você pode fornecer nomes específicos para suas diferentes conexões e fazer referência a cada conexão por seu nome. Se você quiser dar um nome à sua conexão de banco de dados, passe esse nome como o segundo argumento para
.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'
Aqui, você cria duas conexões diferentes para o mesmo banco de dados,
contacts.sqlite
. Cada conexão tem seu próprio nome de conexão. Você pode usar o nome da conexão para obter uma referência a uma conexão específica a qualquer momento em seu código de acordo com suas necessidades. Para fazer isso, você pode chamar .database()
com um nome de conexão:>>>
>>> from PyQt5.QtSql import QSqlDatabase
>>> db = QSqlDatabase.database("con1", open=False)
>>> db.databaseName()
'contacts.sqlite'
>>> db.connectionName()
'con1'
Neste exemplo, você vê que
.database()
recebe dois argumentos:connectionName
contém o nome da conexão que você precisa usar. Se você não passar um nome de conexão, a conexão padrão será usada.open
contém um valor booleano que informa.database()
se você deseja abrir a conexão automaticamente ou não. Seopen
éTrue
(o padrão) e a conexão não estiver aberta, a conexão será aberta automaticamente.
O valor de retorno de
.database()
é uma referência ao objeto de conexão chamado connectionName
. Você pode usar nomes de conexão diferentes para obter referências a objetos de conexão específicos e usá-los para gerenciar seu banco de dados. Usando diferentes SQL Divers
Até agora, você aprendeu como criar uma conexão de banco de dados usando o driver SQLite . Este não é o único driver disponível no PyQt. A biblioteca fornece um rico conjunto de drivers SQL que permitem usar diferentes tipos de sistemas de gerenciamento de banco de dados de acordo com suas necessidades específicas:
Nome do driver | Sistema de gerenciamento de banco de dados |
---|---|
QDB2 | IBM Db2 (versão 7.1 e superior) |
QIBASE | Borland InterBase |
QMYSQL/MARIADB | MySQL ou MariaDB (versão 5.0 e superior) |
QOCI | Interface de chamada do Oracle |
QODBC | Conectividade de banco de dados aberta (ODBC) |
QPSQL | PostgreSQL (versões 7.3 e superiores) |
QSQLITE2 | SQLite 2 (obsoleto desde Qt 5.14) |
QSQLITE | SQLite 3 |
QTDS | Sybase Adaptive Server (obsoleto desde Qt 4.7) |
A coluna Nome do driver contém as strings identificadoras que você precisa passar para
.addDatabase()
como seu primeiro argumento para usar o driver associado. Ao contrário do driver SQLite, quando você usa um driver diferente, pode ser necessário definir vários atributos, como databaseName
, hostName
, userName
e password
, para que a conexão funcione corretamente. Drivers de banco de dados são derivados de
QSqlDriver
. Você pode criar seus próprios drivers de banco de dados subclassificando QSqlDriver
, mas esse tópico vai além do escopo deste tutorial. Se você estiver interessado em criar seus próprios drivers de banco de dados, confira Como escrever seu próprio driver de banco de dados para obter mais detalhes. Abrindo uma conexão de banco de dados
Depois de ter uma conexão de banco de dados, você precisa abrir essa conexão para poder interagir com seu banco de dados. Para fazer isso, você chama
.open()
no objeto de conexão. .open()
tem as duas variações a seguir:.open()
abre uma conexão de banco de dados usando os valores de conexão atuais..open(username, password)
abre uma conexão de banco de dados usando ousername
fornecido epassword
.
Ambas as variações retornam
True
se a conexão for bem sucedida. Caso contrário, eles retornam False
. Se a conexão não puder ser estabelecida, você pode chamar .lastError()
para obter informações sobre o que aconteceu. Esta função retorna informações sobre o último erro reportado pelo banco de dados. Observação: Como você aprendeu antes,
.setPassword(password)
armazena senhas como texto simples, o que é um risco de segurança. Por outro lado, .open()
não armazena senhas. Ele passa a senha diretamente para o driver ao abrir a conexão. Depois disso, ele descarta a senha. Então, usando .open()
gerenciar suas senhas é o caminho a percorrer se você quiser evitar problemas de segurança. Aqui está um exemplo de como abrir uma conexão de banco de dados SQLite usando a primeira variação de
.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
No exemplo acima, você primeiro cria uma conexão com seu banco de dados SQLite e abre essa conexão usando
.open()
. Desde .open()
retorna True
, a conexão é bem-sucedida. Neste ponto, você pode verificar a conexão usando .isOpen()
, que retorna True
se a conexão estiver aberta e False
por outro lado. Observação: Se você chamar
.open()
em uma conexão que usa o driver SQLite e o arquivo de banco de dados não existe, um arquivo de banco de dados novo e vazio será criado automaticamente. Em aplicativos do mundo real, você precisa se certificar de que tem uma conexão válida com seu banco de dados antes de tentar realizar qualquer operação em seus dados. Caso contrário, seu aplicativo pode quebrar e falhar. Por exemplo, e se você não tiver permissões de gravação para o diretório no qual está tentando criar esse arquivo de banco de dados? Você precisa ter certeza de que está lidando com qualquer erro que possa ocorrer ao abrir uma conexão.
Uma maneira comum de chamar
.open()
é envolvê-lo em uma instrução condicional. Isso permite lidar com erros que podem ocorrer ao abrir a conexão:>>>
>>> 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)
Envolvendo a chamada para
.open()
em uma instrução condicional permite que você lide com qualquer erro que ocorra quando você abre a conexão. Dessa forma, você pode informar seus usuários sobre quaisquer problemas antes que o aplicativo seja executado. Observe que o aplicativo sai com um status de saída de 1
, que normalmente é usado para indicar uma falha do programa. No exemplo acima, você usa
.open()
em uma sessão interativa, então você usa print()
para apresentar mensagens de erro aos usuários. No entanto, em aplicativos GUI, em vez de usar print()
, você normalmente usa um QMessageBox
objeto. Com QMessageBox
, você pode criar pequenas caixas de diálogo para apresentar informações aos usuários. Aqui está um aplicativo GUI de amostra que ilustra uma maneira de lidar com erros de conexão:
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_())
O
if
A instrução na linha 14 verifica se a conexão não foi bem-sucedida. Se o /home/
diretório não existe ou se você não tem permissão para escrever nele, então a chamada para .open()
falha porque o arquivo de banco de dados não pode ser criado. Nessa situação, o fluxo de execução insere o if
bloco de código de instrução e mostra uma mensagem na tela. Se você alterar o caminho para qualquer outro diretório no qual possa escrever, a chamada para
.open()
será bem-sucedido e você verá uma janela exibindo a mensagem Connection Successfully Opened!
Você também terá um novo arquivo de banco de dados chamado contacts.sqlite
no diretório selecionado. Observe que você passa
None
como o pai da mensagem porque, no momento de mostrar a mensagem, você ainda não criou uma janela, então você não tem um pai viável para a caixa de mensagem. Executando consultas SQL com PyQt
Com uma conexão de banco de dados totalmente funcional, você está pronto para começar a trabalhar com seu banco de dados. Para fazer isso, você pode usar consultas SQL baseadas em string e
QSqlQuery
objetos. QSqlQuery
permite que você execute qualquer tipo de consulta SQL em seu banco de dados. Com QSqlQuery
, você pode executar instruções de linguagem de manipulação de dados (DML), como SELECT
, INSERT
, UPDATE
e DELETE
, bem como instruções de linguagem de definição de dados (DDL), como CREATE TABLE
e assim por diante. O construtor de
QSqlQuery
tem várias variações, mas neste tutorial, você aprenderá sobre duas delas:-
QSqlQuery(query, connection)
constrói um objeto de consulta usando umaquery
SQL baseada em string e um banco de dadosconnection
. Se você não especificar uma conexão ou se a conexão especificada for inválida, a conexão de banco de dados padrão será usada. Sequery
não é uma string vazia, então ela será executada imediatamente.
-
QSqlQuery(connection)
constrói um objeto de consulta usandoconnection
. Seconnection
for inválido, a conexão padrão será usada.
Você também pode criar
QSqlQuery
objetos sem passar nenhum argumento para o construtor. Nesse caso, a consulta usará a conexão de banco de dados padrão, se houver. Para executar uma consulta, você precisa chamar
.exec()
no objeto de consulta. Você pode usar .exec()
de duas maneiras diferentes:-
.exec(query)
executa a consulta SQL baseada em string contida emquery
. Ele retornaTrue
se a consulta foi bem-sucedida e, caso contrário, retornaráFalse
.
-
.exec()
executa uma consulta SQL previamente preparada. Ele retornaTrue
se a consulta foi bem-sucedida e, caso contrário, retornaráFalse
.
Observação: PyQt também implementa variações de
QSqlQuery.exec()
com o nome .exec_()
. Eles fornecem compatibilidade com versões anteriores do Python nas quais exec
era uma palavra-chave da linguagem. Agora que você conhece os fundamentos do uso de
QSqlQuery
para criar e executar consultas SQL, você está pronto para aprender como colocar seu conhecimento em prática. Executando consultas SQL estáticas
Para começar a criar e executar consultas com PyQt, você vai abrir seu editor de código ou IDE favorito e criar um script Python chamado
queries.py
. Salve o script e adicione o seguinte código a ele: 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())
Neste script, você começa importando os módulos e classes com os quais vai trabalhar. Então você cria uma conexão de banco de dados usando
.addDatabase()
com o driver SQLite. Você define o nome do banco de dados como "contacts.sqlite"
e abra a conexão. Para criar sua primeira consulta, você instancia
QSqlQuery
sem nenhum argumento. Com o objeto de consulta no lugar, você chama .exec()
, passando uma consulta SQL baseada em string como um argumento. Esse tipo de consulta é conhecido como consulta estática porque não recebe nenhum parâmetro de fora da consulta. A consulta SQL acima cria uma nova tabela chamada
contacts
em seu banco de dados. Essa tabela terá as quatro colunas a seguir:Coluna | Conteúdo |
---|---|
id | Um inteiro com a chave primária da tabela |
name | Uma string com o nome de um contato |
job | Uma string com o cargo de um contato |
email | Uma string com o e-mail de um contato |
A última linha do script acima imprime a lista de tabelas contidas em seu banco de dados. Se você executar o script, notará que um novo arquivo de banco de dados chamado
contacts.sqlite
é criado em seu diretório atual. Você também receberá algo como ['contacts', 'sqlite_sequence']
impresso em sua tela. Esta lista contém os nomes das tabelas em seu banco de dados. Observação: Uma consulta SQL baseada em string deve usar uma sintaxe apropriada de acordo com o banco de dados SQL específico que você está consultando. Se a sintaxe estiver errada, então
.exec()
ignora a consulta e retorna False
. No caso do SQLite, a consulta pode conter apenas uma instrução por vez.
Chamando
.exec()
em uma QSqlQuery
object é uma maneira comum de executar imediatamente consultas SQL baseadas em string em seus bancos de dados, mas e se você quiser preparar suas consultas antecipadamente para execução posterior? Esse é o tema da próxima seção. Executando consultas dinâmicas:formatação de string
Até agora, você aprendeu como executar consultas estáticas em um banco de dados. As consultas estáticas são aquelas que não aceitam parâmetros , para que a consulta seja executada como está. Embora essas consultas sejam bastante úteis, às vezes você precisa criar consultas que recuperam dados em resposta a determinados parâmetros de entrada.
As consultas que aceitam parâmetros em tempo de execução são conhecidas como consultas dinâmicas . O uso de parâmetros permite ajustar a consulta e recuperar dados em resposta a valores de parâmetros específicos. Valores diferentes produzirão resultados diferentes. Você pode obter parâmetros de entrada em uma consulta usando uma das duas abordagens a seguir:
- Crie a consulta dinamicamente, usando formatação de string para interpolar valores de parâmetro.
- Prepare a consulta usando parâmetros de espaço reservado e vincule valores específicos aos parâmetros.
A primeira abordagem permite criar consultas dinâmicas rapidamente. No entanto, para usar essa abordagem com segurança, você precisa ter certeza de que seus valores de parâmetro vêm de uma fonte confiável. Caso contrário, você pode enfrentar ataques de injeção de SQL.
Aqui está um exemplo de como usar a formatação de string para criar consultas dinâmicas no 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
Neste exemplo, você usa uma string f para criar uma consulta dinâmica interpolando valores específicos em uma consulta SQL baseada em string. A consulta final insere dados em seus
contacts
tabela, que agora contém dados sobre Linda
. Observação: Mais adiante neste tutorial, você verá como recuperar e navegar pelos dados armazenados em um banco de dados.
Observe que, para que esse tipo de consulta dinâmica funcione, você precisa garantir que os valores a serem inseridos tenham o tipo de dados correto. Portanto, você usa aspas simples ao redor do espaço reservado na string f porque esses valores precisam ser strings.
Executando consultas dinâmicas:parâmetros de espaço reservado
A segunda abordagem para executar consultas dinâmicas exige que você prepare suas consultas com antecedência usando um modelo com espaços reservados para parâmetros. O PyQt suporta dois estilos de espaço reservado para parâmetros:
- Estilo Oracle usa espaços reservados nomeados como
:name
ou:email
. - Estilo ODBC usa um ponto de interrogação (
?
) como um espaço reservado posicional.
Observe que esses estilos não podem ser misturados na mesma consulta. Você pode conferir Abordagens para valores de associação para obter exemplos extras sobre como usar espaços reservados.
Observação: ODBC significa Open Database Connectivity.
Para criar esse tipo de consulta dinâmica no PyQt, você primeiro cria um modelo com um espaço reservado para cada parâmetro de consulta e depois passa esse modelo como um argumento para
.prepare()
, que analisa, compila e prepara o modelo de consulta para execução. Se o modelo tiver algum problema, como um erro de sintaxe SQL, então .prepare()
falha ao compilar o modelo e retorna False
. Se o processo de preparação for bem-sucedido,
prepare()
retorna True
. Depois disso, você pode passar um valor específico para cada parâmetro usando .bindValue()
com parâmetros nomeados ou posicionais ou usando .addBindValue()
com parâmetros posicionais. .bindValue()
tem as duas variações a seguir:.bindValue(placeholder, val)
.bindValue(pos, val)
Na primeira variação,
placeholder
representa um espaço reservado no estilo Oracle. Na segunda variação, pos
representa um número inteiro baseado em zero com a posição de um parâmetro na consulta. Em ambas as variações, val
contém o valor a ser vinculado a um parâmetro específico. .addBindValue()
adiciona um valor à lista de espaços reservados usando a associação posicional. Isso significa que a ordem das chamadas para .addBindValue()
determina qual valor será vinculado a cada parâmetro de espaço reservado na consulta preparada. Para começar a usar consultas preparadas, você pode preparar um
INSERT INTO
Instrução SQL para preencher seu banco de dados com alguns dados de amostra. Volte para o script que você criou na seção Executing Static SQL Queries e adicione o seguinte código logo após a chamada para 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()
O primeiro passo é criar um
QSqlQuery
objeto. Então você chama .prepare()
no objeto de consulta. Nesse caso, você usa o estilo ODBC para os espaços reservados. Sua consulta terá valores para o name
do seu contato , job
e email
, então você precisa de três espaços reservados. Como o id
column é um número inteiro autoincrementado, você não precisa fornecer valores para ele. Em seguida, você cria alguns dados de amostra para preencher o banco de dados.
data
contém uma lista de tuplas e cada tupla contém três itens:o nome, o trabalho e o e-mail de cada contato. A etapa final é vincular os valores que você deseja passar para cada espaço reservado e, em seguida, chamar
.exec()
para executar a consulta. Para fazer isso, você usa um for
ciclo. O cabeçalho do loop descompacta cada tupla em data
em três variáveis separadas com nomes convenientes. Então você chama .addBindValue()
no objeto de consulta para vincular os valores aos espaços reservados. Observe que você está usando marcadores posicionais , então a ordem na qual você chama
.addBindValue()
irá definir a ordem em que cada valor é passado para o espaço reservado correspondente. 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:Método | 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. Observação: 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
tabela. 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()
novamente. 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"
coluna. If "name"
doesn’t exist, then .indexOf()
retorna -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()
. Observação: 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 onQAbstractItemModel
. 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 onQAbstractItemView
. 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 isQAbstractItemDelegate
.
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
objects. 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
database: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 | Descrição |
---|---|
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.
É isso! 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 ofSELECT *
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.
Conclusão
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.