sql >> Base de Datos >  >> RDS >> Database

Manejo de bases de datos SQL con PyQt:conceptos básicos

La creación de aplicaciones que utilizan una base de datos SQL es una tarea de programación bastante común. Las bases de datos SQL están en todas partes y tienen un gran soporte en Python. En la programación de GUI, PyQt proporciona soporte de base de datos SQL robusto y multiplataforma que le permite crear, conectarse y administrar sus bases de datos de manera consistente.

La compatibilidad con SQL de PyQt se integra completamente con su arquitectura Model-View para ayudarlo en el proceso de creación de aplicaciones de bases de datos.

En este tutorial, aprenderá a:

  • Utilice la compatibilidad con SQL de PyQt para conectarse de manera confiable a una base de datos
  • Ejecutar consultas SQL en una base de datos usando PyQt
  • Usar la arquitectura Model-View de PyQt en aplicaciones de bases de datos
  • Muestre y edite datos usando diferentes widgets de PyQt

Los ejemplos de este tutorial requieren un conocimiento básico del lenguaje SQL, especialmente del sistema de gestión de bases de datos SQLite. Algunos conocimientos previos de programación de GUI con Python y PyQt también serán útiles.

Bonificación gratuita: 5 pensamientos sobre el dominio de Python, un curso gratuito para desarrolladores de Python que le muestra la hoja de ruta y la mentalidad que necesitará para llevar sus habilidades de Python al siguiente nivel.


Conectar PyQt a una base de datos SQL

Conectar una aplicación a una base de datos relacional y hacer que la aplicación cree, lea, actualice y elimine los datos almacenados en esa base de datos es una tarea común en la programación. Las bases de datos relacionales generalmente se organizan en un conjunto de tablas o relaciones . Una fila dada en una tabla se denomina registro o tupla , y una columna se denomina atributo .

Nota: El término campo se usa comúnmente para identificar una sola pieza de datos almacenada en una celda de un registro dado en una tabla. Por otro lado, el término nombre de campo se utiliza para identificar el nombre de una columna.

Cada columna almacena un tipo específico de información, como nombres, fechas o números. Cada fila representa un conjunto de datos estrechamente relacionados y cada fila tiene la misma estructura general. Por ejemplo, en una base de datos que almacena datos sobre los empleados de una empresa, una fila específica representa a un empleado individual.

La mayoría de los sistemas de bases de datos relacionales utilizan SQL (lenguaje de consulta estructurado) para consultar, manipular y mantener los datos contenidos en la base de datos. SQL es un lenguaje de programación declarativo y específico de dominio especialmente diseñado para comunicarse con bases de datos.

Los sistemas de bases de datos relacionales y SQL son ampliamente utilizados en la actualidad. Encontrará varios sistemas de administración de bases de datos diferentes, como SQLite, PostgreSQL, MySQL, MariaDB y muchos otros. Puede conectar Python a cualquiera de estos sistemas de bases de datos utilizando una biblioteca Python SQL dedicada.

Nota: Aunque el soporte SQL incorporado de PyQt es la opción preferida para administrar bases de datos SQL en PyQt, también puede usar cualquier otra biblioteca para manejar la conexión de la base de datos. Algunas de estas bibliotecas incluyen SQLAlchemy, pandas, SQLite, etc.

Sin embargo, usar una biblioteca diferente para administrar sus bases de datos tiene algunos inconvenientes. No podrá aprovechar la integración entre las clases SQL de PyQt y la arquitectura Model-View. Además, agregará dependencias adicionales a su aplicación.

Cuando se trata de la programación de GUI con Python y PyQt, PyQt proporciona un sólido conjunto de clases para trabajar con bases de datos SQL. Este conjunto de clases será tu mejor aliado cuando necesites conectar tu aplicación a una base de datos SQL.

Nota: Desafortunadamente, la documentación oficial de PyQt5 tiene algunas secciones incompletas. Para solucionar esto, puede consultar la documentación de PyQt4, la documentación de Qt para Python o la documentación original de Qt. En este tutorial, algunos enlaces lo llevan a la documentación original de Qt, que es una mejor fuente de información en la mayoría de los casos.

En este tutorial, aprenderá los conceptos básicos sobre cómo usar la compatibilidad con SQL de PyQt para crear aplicaciones GUI que interactúen de manera confiable con bases de datos relacionales para leer, escribir, eliminar y mostrar datos.


Crear una conexión a la base de datos

Conectar sus aplicaciones a una base de datos SQL física es un paso importante en el proceso de desarrollo de aplicaciones de bases de datos con PyQt. Para realizar este paso con éxito, necesita información general sobre cómo está configurada su base de datos.

Por ejemplo, necesita saber en qué sistema de administración de base de datos se basa su base de datos y es posible que también necesite tener un nombre de usuario, una contraseña, un nombre de host, etc.

En este tutorial, utilizará SQLite 3, que es un sistema de base de datos bien probado con soporte en todas las plataformas y requisitos mínimos de configuración. SQLite le permite leer y escribir directamente en bases de datos en su disco local sin necesidad de un proceso de servidor separado. Eso lo convierte en una opción fácil de usar para aprender a desarrollar aplicaciones de bases de datos.

Otra ventaja de usar SQLite es que la biblioteca viene con Python y también con PyQt, por lo que no necesita instalar nada más para comenzar a trabajar con ella.

En PyQt, puede crear una conexión de base de datos utilizando QSqlDatabase clase. Esta clase representa una conexión y proporciona una interfaz para acceder a la base de datos. Para crear una conexión, simplemente llame a .addDatabase() en QSqlDatabase . Este método estático toma un controlador SQL y un nombre de conexión opcional como argumentos y devuelve una conexión a la base de datos:

QSqlDatabase.addDatabase(
    driver, connectionName=QSqlDatabase.defaultConnection
)

El primer argumento, driver , es un argumento obligatorio que contiene una cadena que contiene el nombre de un controlador SQL compatible con PyQt. El segundo argumento, connectionName , es un argumento opcional que contiene una cadena con el nombre de la conexión. connectionName el valor predeterminado es QSqlDatabase.defaultConnection , que normalmente contiene la cadena "qt_sql_default_connection" .

Si ya tiene una conexión llamada connectionName , luego esa conexión se elimina y se reemplaza con una nueva conexión, y .addDatabase() devuelve la conexión de base de datos recién agregada a la persona que llama.

Una llamada a .addDatabase() agrega una conexión de base de datos a una lista de conexiones disponibles. Esta lista es un registro global que PyQt mantiene entre bastidores para realizar un seguimiento de las conexiones disponibles en una aplicación. Registrando sus conexiones con un connectionName significativo le permitirá administrar varias conexiones en una aplicación de base de datos.

Una vez que haya creado una conexión, es posible que deba establecer varios atributos en ella. El conjunto específico de atributos dependerá del controlador que esté utilizando. En general, deberá establecer atributos como el nombre de la base de datos, el nombre de usuario y la contraseña para acceder a la base de datos.

Aquí hay un resumen de los métodos de establecimiento que puede usar para establecer los atributos o propiedades más utilizados de una conexión de base de datos:

Método Descripción
.setDatabaseName(name) Establece el nombre de la base de datos en name , que es una cadena que representa un nombre de base de datos válido
.setHostName(host) Establece el nombre de host en host , que es una cadena que representa un nombre de host válido
.setUserName(username) Establece el nombre de usuario en username , que es una cadena que representa un nombre de usuario válido
.setPassword(password) Establece la contraseña en password , que es una cadena que representa una contraseña válida

Tenga en cuenta que la contraseña que pasa como argumento a .setPassword() se almacena en texto sin formato y se puede recuperar más tarde llamando a .password() . Este es un grave riesgo de seguridad que debe evitar introducir en sus aplicaciones de base de datos. Aprenderá un enfoque más seguro en la sección Apertura de una conexión de base de datos más adelante en este tutorial.

Para crear una conexión a una base de datos SQLite usando QSqlDatabase , abra una sesión interactiva de Python y escriba el siguiente 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 creará un objeto de conexión a la base de datos usando "QSQLITE" como controlador de la conexión y "contacts.sqlite" como el nombre de la base de datos de la conexión. Dado que no pasa un nombre de conexión a .addDatabase() , la recién creada se convierte en su conexión predeterminada, cuyo nombre es "qt_sql_default_connection" .

En el caso de las bases de datos SQLite, el nombre de la base de datos normalmente es un nombre de archivo o una ruta que incluye el nombre de archivo de la base de datos. También puede usar el nombre especial ":memory:" para una base de datos en memoria.



Manejo de múltiples conexiones

Puede haber situaciones en las que necesite usar múltiples conexiones a una sola base de datos. Por ejemplo, es posible que desee registrar las interacciones de los usuarios con la base de datos utilizando una conexión específica para cada usuario.

En otras situaciones, es posible que deba conectar su aplicación a varias bases de datos. Por ejemplo, es posible que desee conectarse a varias bases de datos remotas para recopilar datos para completar o actualizar una base de datos local.

Para manejar estas situaciones, puede proporcionar nombres específicos para sus diferentes conexiones y hacer referencia a cada conexión por su nombre. Si desea darle un nombre a la conexión de su base de datos, pase ese nombre como segundo argumento a .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'

Aquí, crea dos conexiones diferentes a la misma base de datos, contacts.sqlite . Cada conexión tiene su propio nombre de conexión. Puede usar el nombre de la conexión para obtener una referencia a una conexión específica en cualquier momento posterior en su código de acuerdo con sus necesidades. Para hacer esto, puede llamar a .database() con un nombre de conexión:

>>>
>>> from PyQt5.QtSql import QSqlDatabase

>>> db = QSqlDatabase.database("con1", open=False)

>>> db.databaseName()
'contacts.sqlite'
>>> db.connectionName()
'con1'

En este ejemplo, verá que .database() toma dos argumentos:

  1. connectionName contiene el nombre de la conexión que necesita usar. Si no pasa un nombre de conexión, se utilizará la conexión predeterminada.
  2. open tiene un valor booleano que le dice a .database() si desea abrir automáticamente la conexión o no. Si open es True (predeterminado) y la conexión no está abierta, entonces la conexión se abre automáticamente.

El valor de retorno de .database() es una referencia al objeto de conexión llamado connectionName . Puede usar diferentes nombres de conexión para obtener referencias a objetos de conexión específicos y luego usarlos para administrar su base de datos.



Uso de diferentes SQL Divers

Hasta ahora, ha aprendido cómo crear una conexión de base de datos usando el controlador SQLite . Este no es el único controlador disponible en PyQt. La biblioteca proporciona un amplio conjunto de controladores SQL que le permiten utilizar diferentes tipos de sistemas de gestión de bases de datos según sus necesidades específicas:

Nombre del controlador Sistema de gestión de bases de datos
QDB2 IBM Db2 (versión 7.1 y superior)
QIBASE Borland InterBase
QMYSQL/MARIADB MySQL o MariaDB (versión 5.0 y superior)
QOCI Interfaz de llamadas de Oracle
QODBC Conectividad abierta de base de datos (ODBC)
QPSQL PostgreSQL (versiones 7.3 y superiores)
QSQLITE2 SQLite 2 (obsoleto desde Qt 5.14)
QSQLITE SQLite 3
QTDS Sybase Adaptive Server (obsoleto desde Qt 4.7)

La columna Nombre del controlador contiene las cadenas de identificación que necesita pasar a .addDatabase() como su primer argumento para usar el controlador asociado. A diferencia del controlador SQLite, cuando usa un controlador diferente, es posible que deba establecer varios atributos, como databaseName , hostName , userName y password , para que la conexión funcione correctamente.

Los controladores de base de datos se derivan de QSqlDriver . Puede crear sus propios controladores de base de datos subclasificando QSqlDriver , pero ese tema va más allá del alcance de este tutorial. Si está interesado en crear sus propios controladores de base de datos, consulte Cómo escribir su propio controlador de base de datos para obtener más detalles.



Apertura de una conexión de base de datos

Una vez que tenga una conexión a la base de datos, debe abrir esa conexión para poder interactuar con su base de datos. Para hacerlo, llama a .open() en el objeto de conexión. .open() tiene las siguientes dos variaciones:

  1. .open() abre una conexión de base de datos utilizando los valores de conexión actuales.
  2. .open(username, password) abre una conexión de base de datos utilizando el username proporcionado y password .

Ambas variaciones devuelven True si la conexión es exitosa. De lo contrario, devuelven False . Si no se puede establecer la conexión, puede llamar a .lastError() para obtener información sobre lo sucedido. Esta función devuelve información sobre el último error informado por la base de datos.

Nota: Como aprendiste antes, .setPassword(password) almacena contraseñas como texto sin formato, lo cual es un riesgo de seguridad. Por otro lado, .open() no almacena contraseñas en absoluto. Pasa la contraseña directamente al conductor al abrir la conexión. Después de eso, descarta la contraseña. Entonces, usando .open() administrar sus contraseñas es el camino a seguir si desea evitar problemas de seguridad.

Aquí hay un ejemplo de cómo abrir una conexión de base de datos SQLite usando la primera variación 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

En el ejemplo anterior, primero crea una conexión a su base de datos SQLite y abre esa conexión usando .open() . Desde .open() devuelve True , la conexión es exitosa. En este punto, puede verificar la conexión usando .isOpen() , que devuelve True si la conexión está abierta y False de lo contrario.

Nota: Si llama a .open() en una conexión que utiliza el controlador SQLite y el archivo de base de datos no existe, se creará automáticamente un archivo de base de datos nuevo y vacío.

En las aplicaciones del mundo real, debe asegurarse de tener una conexión válida a su base de datos antes de intentar realizar cualquier operación en sus datos. De lo contrario, su aplicación puede fallar y fallar. Por ejemplo, ¿qué sucede si no tiene permisos de escritura para el directorio en el que intenta crear ese archivo de base de datos? Debe asegurarse de que está manejando cualquier error que pueda ocurrir al abrir una conexión.

Una forma común de llamar a .open() es envolverlo en una declaración condicional. Esto le permite manejar los errores que pueden ocurrir al abrir la conexión:

>>>
>>> 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)

Envolviendo la llamada a .open() en una declaración condicional le permite manejar cualquier error que ocurra cuando abre la conexión. De esta manera, puede informar a sus usuarios sobre cualquier problema antes de que se ejecute la aplicación. Tenga en cuenta que la aplicación sale con un estado de salida de 1 , que se suele utilizar para indicar una falla del programa.

En el ejemplo anterior, usa .open() en una sesión interactiva, por lo que usa print() para presentar mensajes de error a los usuarios. Sin embargo, en aplicaciones GUI, en lugar de usar print() , normalmente usas un QMessageBox objeto. Con QMessageBox , puede crear pequeños diálogos para presentar información a sus usuarios.

Aquí hay una aplicación GUI de muestra que ilustra una forma de manejar los errores de conexión:

 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_())

El if declaración en la línea 14 comprueba si la conexión no tuvo éxito. Si el /home/ directorio no existe o si no tiene permiso para escribir en él, entonces la llamada a .open() falla porque no se puede crear el archivo de la base de datos. En esta situación, el flujo de ejecución ingresa el if bloque de código de declaración y muestra un mensaje en la pantalla.

Si cambia la ruta a cualquier otro directorio en el que pueda escribir, la llamada a .open() tendrá éxito y verá una ventana que muestra el mensaje Connection Successfully Opened! También tendrá un nuevo archivo de base de datos llamado contacts.sqlite en el directorio seleccionado.

Tenga en cuenta que pasa None como principal del mensaje porque, en el momento de mostrar el mensaje, aún no ha creado una ventana, por lo que no tiene un padre viable para el cuadro de mensaje.




Ejecución de consultas SQL con PyQt

Con una conexión de base de datos completamente funcional, está listo para comenzar a trabajar con su base de datos. Para hacer eso, puede usar consultas SQL basadas en cadenas y QSqlQuery objetos. QSqlQuery le permite ejecutar cualquier tipo de consulta SQL en su base de datos. Con QSqlQuery , puede ejecutar declaraciones de lenguaje de manipulación de datos (DML), como SELECT , INSERT , UPDATE y DELETE , así como declaraciones de lenguaje de definición de datos (DDL), como CREATE TABLE y así sucesivamente.

El constructor de QSqlQuery tiene varias variaciones, pero en este tutorial aprenderá sobre dos de ellas:

  1. QSqlQuery(query, connection) construye un objeto de consulta utilizando un query de SQL basado en cadenas y una base de datos connection . Si no especifica una conexión, o si la conexión especificada no es válida, se utiliza la conexión de base de datos predeterminada. Si query no es una cadena vacía, se ejecutará de inmediato.

  2. QSqlQuery(connection) construye un objeto de consulta usando connection . Si connection no es válido, se utiliza la conexión predeterminada.

También puede crear QSqlQuery objetos sin pasar ningún argumento al constructor. En ese caso, la consulta utilizará la conexión de base de datos predeterminada, si corresponde.

Para ejecutar una consulta, debe llamar a .exec() en el objeto de consulta. Puede usar .exec() de dos maneras diferentes:

  1. .exec(query) ejecuta la consulta SQL basada en cadenas contenida en query . Devuelve True si la consulta fue exitosa y devuelve False .

  2. .exec() ejecuta una consulta SQL previamente preparada. Devuelve True si la consulta fue exitosa y devuelve False .

Nota: PyQt también implementa variaciones de QSqlQuery.exec() con el nombre .exec_() . Estos proporcionan compatibilidad con versiones anteriores de Python en las que exec era una palabra clave del idioma.

Ahora que conoce los conceptos básicos del uso de QSqlQuery para crear y ejecutar consultas SQL, está listo para aprender a poner en práctica sus conocimientos.


Ejecución de consultas SQL estáticas

Para comenzar a crear y ejecutar consultas con PyQt, debe iniciar su editor de código o IDE favorito y crear un script de Python llamado queries.py. . Guarde el script y agréguele el siguiente código:

 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())

En este script, comienzas importando los módulos y las clases con las que vas a trabajar. Luego crea una conexión de base de datos usando .addDatabase() con el controlador SQLite. Establece el nombre de la base de datos en "contacts.sqlite" y abre la conexión.

Para crear su primera consulta, instancia QSqlQuery sin ningún argumento. Con el objeto de consulta en su lugar, llama a .exec() , pasando una consulta SQL basada en cadenas como argumento. Este tipo de consulta se conoce como consulta estática. porque no obtiene ningún parámetro fuera de la consulta.

La consulta SQL anterior crea una nueva tabla llamada contacts en su base de datos. Esa tabla tendrá las siguientes cuatro columnas:

Columna Contenido
id Un número entero con la clave principal de la tabla
name Una cadena con el nombre de un contacto
job Una cadena con el cargo de un contacto
email Una cadena con el correo electrónico de un contacto

La última línea del script anterior imprime la lista de tablas contenidas en su base de datos. Si ejecuta el script, notará que un nuevo archivo de base de datos llamado contacts.sqlite se crea en su directorio actual. También obtendrá algo como ['contacts', 'sqlite_sequence'] impreso en su pantalla. Esta lista contiene los nombres de las tablas en su base de datos.

Nota: Una consulta SQL basada en cadenas debe usar una sintaxis adecuada de acuerdo con la base de datos SQL específica que está consultando. Si la sintaxis es incorrecta, entonces .exec() ignora la consulta y devuelve False .

En el caso de SQLite, la consulta puede contener solo una declaración a la vez.

Llamando a .exec() en un QSqlQuery object es una forma común de ejecutar inmediatamente consultas SQL basadas en cadenas en sus bases de datos, pero ¿qué sucede si desea preparar sus consultas de antemano para su ejecución posterior? Ese es el tema de la siguiente sección.



Ejecución de consultas dinámicas:formato de cadena

Hasta ahora, ha aprendido a ejecutar consultas estáticas en una base de datos. Las consultas estáticas son aquellas que no aceptan parámetros , por lo que la consulta se ejecuta tal cual. Aunque estas consultas son bastante útiles, a veces es necesario crear consultas que recuperen datos en respuesta a ciertos parámetros de entrada.

Las consultas que aceptan parámetros en tiempo de ejecución se conocen como consultas dinámicas. . El uso de parámetros le permite ajustar la consulta y recuperar datos en respuesta a valores de parámetros específicos. Valores diferentes producirán resultados diferentes. Puede tomar parámetros de entrada en una consulta utilizando uno de los dos enfoques siguientes:

  1. Cree la consulta dinámicamente, utilizando el formato de cadena para interpolar los valores de los parámetros.
  2. Prepare la consulta utilizando parámetros de marcador de posición y luego vincule valores específicos a los parámetros.

El primer enfoque le permite crear consultas dinámicas rápidamente. Sin embargo, para usar este enfoque de manera segura, debe asegurarse de que los valores de sus parámetros provengan de una fuente confiable. De lo contrario, podría enfrentar ataques de inyección SQL.

Aquí hay un ejemplo de cómo usar el formato de cadena para crear consultas dinámicas en 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

En este ejemplo, utiliza una cadena f para crear una consulta dinámica mediante la interpolación de valores específicos en una consulta SQL basada en cadenas. La consulta final inserta datos en tus contacts tabla, que ahora contiene datos sobre Linda .

Nota: Más adelante en este tutorial, verá cómo recuperar y navegar por los datos almacenados en una base de datos.

Tenga en cuenta que para que este tipo de consulta dinámica funcione, debe asegurarse de que los valores que se insertarán tengan el tipo de datos correcto. Entonces, usa comillas simples alrededor del marcador de posición en la cadena f porque esos valores deben ser cadenas.



Ejecución de consultas dinámicas:parámetros de marcador de posición

El segundo enfoque para ejecutar consultas dinámicas requiere que prepare sus consultas de antemano utilizando una plantilla con marcadores de posición. para parámetros. PyQt admite dos estilos de marcador de posición de parámetros:

  1. Estilo oráculo utiliza marcadores de posición con nombre como :name o :email .
  2. Estilo ODBC usa un signo de interrogación (? ) como marcador de posición.

Tenga en cuenta que estos estilos no se pueden mezclar en la misma consulta. Puede consultar Enfoques para vincular valores para obtener ejemplos adicionales sobre cómo usar marcadores de posición.

Nota: ODBC significa Conectividad abierta de bases de datos.

Para crear este tipo de consulta dinámica en PyQt, primero crea una plantilla con un marcador de posición para cada parámetro de consulta y luego pasa esa plantilla como argumento a .prepare() , que analiza, compila y prepara la plantilla de consulta para su ejecución. Si la plantilla tiene algún problema, como un error de sintaxis SQL, .prepare() no puede compilar la plantilla y devuelve False .

Si el proceso de preparación tiene éxito, entonces prepare() devuelve True . Después de eso, puede pasar un valor específico a cada parámetro usando .bindValue() con parámetros con nombre o posicionales o usando .addBindValue() con parámetros posicionales. .bindValue() tiene las siguientes dos variaciones:

  1. .bindValue(placeholder, val)
  2. .bindValue(pos, val)

En la primera variación, placeholder representa un marcador de posición de estilo Oracle. En la segunda variación, pos representa un número entero de base cero con la posición de un parámetro en la consulta. En ambas variaciones, val contiene el valor que se vinculará a un parámetro específico.

.addBindValue() agrega un valor a la lista de marcadores de posición mediante el enlace posicional. Esto significa que el orden de las llamadas a .addBindValue() determina qué valor se vinculará a cada parámetro de marcador de posición en la consulta preparada.

Para comenzar a usar consultas preparadas, puede preparar un INSERT INTO Declaración SQL para llenar su base de datos con algunos datos de muestra. Vuelva al script que creó en la sección Ejecución de consultas SQL estáticas y agregue el siguiente código justo después de la llamada a 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()

El primer paso es crear un QSqlQuery objeto. Luego llamas a .prepare() en el objeto de consulta. En este caso, utiliza el estilo ODBC para los marcadores de posición. Su consulta tomará valores para el name de su contacto , job y email , por lo que necesita tres marcadores de posición. Desde el id columna es un número entero incrementado automáticamente, no es necesario que proporcione valores para él.

Luego crea algunos datos de muestra para llenar la base de datos. data contiene una lista de tuplas, y cada tupla contiene tres elementos:el nombre, el trabajo y el correo electrónico de cada contacto.

El paso final es vincular los valores que desea pasar a cada marcador de posición y luego llamar a .exec() para ejecutar la consulta. Para hacer eso, usa un for círculo. El encabezado del ciclo desempaqueta cada tupla en data en tres variables separadas con nombres convenientes. Luego llamas a .addBindValue() en el objeto de consulta para vincular los valores a los marcadores de posición.

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:

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.

Nota: 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 mesa. 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() otra vez. 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" columna. If "name" doesn’t exist, then .indexOf() devuelve -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() .

Nota: 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:

  1. Standard widgets include internal containers for storing data.
  2. 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:

  1. 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 include QStandardItemModel , QFileSystemModel , and SQL-related models.

  2. 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 are QListView , QTableView , and QTreeView .

  3. 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 objetos. 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 to 4 , 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 the contacts 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 the id columns are integer numbers, you need to convert them into strings to be able to store them in a QTableWidgetItem object.

.setItem() takes three arguments:

  1. row holds a zero-based integer that represents the index of a given row in the table.
  2. column holds a zero-based integer that represents the index of a given column in the table.
  3. item holds the QTableWidgetItem 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:

  1. Outside the widget, in your database
  2. 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 Descripción
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.

¡Eso es todo! 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.



Conclusión

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.