1.4-Connector-Python - by - Ricardo Mamaní

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 20

Tecnicatura Universitaria en Desarrollo de Software

Programación 2
Módulo 1: Bases de Datos
Tema 4: Acceso a una base de datos desde Python
1.4 Acceso a una base de datos desde Python
1.4.1. Cómo funciona un conector

1.4.2. Instalación de Connector/Python

1.4.3. Usando el Connector/Python

1.4.3.1. Probando la instalación

1.4.3.2. Objetos y métodos de una conexión

1.4.3.3. Ejecutando consultas

1.4.4. Manejo de errores

1.4.5. Mejores prácticas

1.4.5.1. Seguridad

1.4.5.2. Usar objetos de manera idiomática en Python

1.4.5.3. Validación y verificación de datos

1.4.5.4. Otros

1.4.6. Siguientes pasos

Bibliografía
1.4.1. Cómo funciona un conector
MySQL permite conexiones a un servidor desde aplicaciones clientes a través de
Conectores. Los conectores son software (librerías) que funcionan como un adaptador entre
la interfaz del SGBD y un lenguaje de programación.

MySQL ofrece conectores oficiales para varios lenguajes y sistemas:

1. Connector/NET: conector para el protocolo ADO.NET de Microsoft.


2. Connector/ODBC: conector para el protocolo ODBC de Microsoft.
3. Connector/J: conector para el protocolo JDBC de Java.
4. Connector/Node.js: conector para Node.js
5. Connector/Python: conector para Python
6. Connector/C++: Conector para C++

Y también brinda conectores desarrollados por la comunidad, para lenguajes como PHP,
Ruby, Perl, Go, Rust, etc.

Figura 1.22

En el caso de Python, Connector/Python es una librería que contiene clases y funciones que
cumplimentan la especificación que tiene Python para una API de bases de datos, llamada
DB-API 2.0. Esto quiere decir que implementa clases preestablecidas con métodos
definidos que facilitan la interacción con la base de datos.
1.4.2. Instalación de Connector/Python
Existen dos formas de instalar el Conector.

1. Desde PYPI con el comando pip install mysql-connector-python.


2. Desde el instalador de MySQL. Esta es la manera recomendada por MySQL. Se debe
elegir la opción MySQL Connector/Python como uno de los productos a instalar. Es
necesario tener instalado Python (y debe estar en la variable de sistema “path”) para
realizar la instalación correctamente.

Figura 1.23

Una vez instalado el conector podemos probar si la instalación fue exitosa importando el
módulo mysql.connector y conectándonos a un servidor MySQL, como veremos a
continuación.
1.4.3. Usando el Connector/Python

1.4.3.1. Probando la instalación


Vamos a crear un pequeño script para conectarnos con el servidor MySQL instalado en
nuestra PC, para comprobar que la instalación fue correcta.

# prueba_mysql_connector.py

import mysql.connector

conn = mysql.connector.connect(user='juan',

password='contraseña',

host='127.0.0.1',

database='sakila')

conn.close()

En este simple script usamos un constructor del módulo connector, llamado connect(),
el cual toma los parámetros de la conexión, crea un nuevo objeto del tipo Connection, lo c
onecta con la base de datos y lo devuelve. Este objeto representa la conexión al servidor e i
mplementa diferentes métodos para ejecutar consultas sobre él.

En el script simplemente creamos una conexión con las credenciales de nuestro usuario e
indicamos qué host o servidor y qué base de datos vamos a usar.

• user y password son ambos str que deben ser las credenciales de un usuario
existente del servidor.

• host es un str indica a qué servidor conectarse. Un host (anfitrión) es cualquier


computadora que tenga una interfaz de red conectada, normalmente esa interfaz
tendrá asignada una dirección IP, con la cual identificarla.

En este caso, nos conectamos mediante la dirección IP 127.0.0.1. Esta es la llamada


dirección de loopback, simplemente apunta a nuestra misma PC. En lugar de esta
dirección IP, podemos pasar la cadena ‘localhost’, la cual es un alias de la dirección
de loopback.
• database debe ser un str con el nombre de una base de datos existente en el
servidor. Este parámetro es opcional y si no se incluye se usará la base de datos
activa.

El constructor connect() puede recibir muchos más parámetros, para indicar puerto,
codificación de caracteres, etc., pero nosotros usaremos estos por ahora.

Luego, para concluir nuestra prueba, simplemente cerramos la conexión con el método
close(). Si este script se ejecuta sin errores, significa que la instalación fue correcta.

También podemos pasar los datos de la conexión mediante un diccionario y el operador **


(desempaquetado de diccionarios) como en el siguiente ejemplo:

import mysql.connector

config = {'user': 'juan',

'password': 'password',

'host': '127.0.0.1',

'database': 'sakila'}

conn = mysql.connector.connect(**config)

conn.close()

Veamos ahora como realizar consultas al servidor.

1.4.3.2. Objetos y métodos de una conexión


El objeto Connection, creado con el método connect(), posee muchos métodos y
propiedades para administrar la conexión y para ejecutar consultas SQL.

Los pasos a seguir para trabajar con una base de datos desde Python, son:

1. Establecer una conexión. Esto nos da un objeto Connection.


2. Crear un objeto cursor (para ejecutar operaciones en la base de datos).
3. Ejecutar sentencias. Pueden ser consultas o sentencias de manipulación de datos.
4. Cometer o guardar los cambios a la base de datos, si es que los hubo. O recuperar los
registros consultados.
5. Cerrar la conexión.
Vamos a ver los métodos básicos para interactuar con una base de datos:

• cursor(). Este método es un constructor para un objeto de tipo Cursor, el cual


puede ejecutar comandos y consultas SQL en la base de datos.

• commit(). Al ejecutar este método los cambios que se hayan hecho en la base de
datos serán establecidos como permanentes. O sea, si ejecutamos sentencias que
modifiquen la base de datos y luego cerramos la conexión sin ejecutar el método
commit(), los cambios no tendrán efecto. Si la tabla estaba usando una clave
primaria numérica autogenerada, esta habrá generado los valores para los registros
que se insertaron, pero no se guardaron definitivamente, y esos valores ya no se
podrán usar.

• close(). Sirve para cerrar la conexión a la base de datos. Después de ejecutar este
método, el objeto Connection seguirá existiendo, pero no podrá comunicarse con la
base de datos si no se conecta nuevamente con el método connect().

Cursor

Este objeto es el que vamos a usar para ejecutar consultas a la base de datos. Se crea
mediante el método cursor() del objeto Connection. Sus métodos más importantes son:

• execute(operacion, parametros). Ejecuta una consulta o comando SQL sobre


la base de datos. Debe incluir al menos un primer argumento conteniendo la
sentencia SQL a ejecutar, como str. También permite un segundo argumento en el
cual pueden adjuntarse parámetros de la consulta como tuple.

Si se ejecuta una consulta (SELECT) el result-set no se devolverá automáticamente,


sino que se debe obtener ejecutando uno de los métodos fetchone(), fetchall()
o fetchmany().

Si se ejecuta un comando de manipulación de datos se debe ejecutar el método


commit() del objeto Connection para que los cambios tengan efecto en la base de
datos.

Por ejemplo:

cursor.execute("SELECT * FROM peliculas")


• fetchone(). Devuelve una tupla que representa un registro del result-set
correspondiente a una consulta previamente hecha. Cada posición de la tupla
corresponde a cada columna de la consulta ejecutada. Si ya se obtuvieron todos los
registros devuelve None.

Por ejemplo, si se ejecuta la consulta SELECT titulo, año FROM peliculas;,


fetchone() devolverá el primer registro del result-set como una tupla de dos
elementos, el primero corresponderá al título y el segundo al año. Posteriormente se
puede seguir ejecutando este método, hasta que devuelva None.

• fetchall(). Devuelve una lista de todos los registros (tuplas) restantes de la


consulta. Si ya se obtuvieron todos los registros devuelve una lista vacía.

O sea, si una consulta devuelve 10 registros y primero ejecutamos fetchone(), solo


se devolverá la primera tupla (correspondiente al primer registro). Si luego
ejecutamos fetchall() se devolverá una lista con las 9 tuplas restantes.

• fetchmany(size=1). Obtiene y devuelve el siguiente grupo de registros como una


lista de tuplas. Si ya se obtuvieron todos los registros devuelve una lista vacía. El
parámetro size indica cuantos registros obtener en cada ejecución del método. Por
defecto el valor de size es 1.

• rowcount. Esta es una propiedad (no se usan paréntesis), y devuelve:

– La cantidad de registros obtenidos si se ejecutó una consulta. Siempre que ya


se hayan obtenido algunos de los métodos fetch (vistos arriba). Si se revisa
inmediatamente después de hacer la consulta devolverá -1. Es decir, no es
muy útil en este sentido.
– La cantidad de registros afectados si se ejecutó una sentencia de manipulación
de datos (como INSERT o UPDATE).
• lastrowid. Es otra propiedad, que devuelve el último valor generado por un atributo
AUTO_INCREMENT de la última sentencia INSERT o UPDATE. Si no hay tal valor
disponible, devuelve None.
1.4.3.3. Ejecutando consultas

Obtención simple de datos

Vamos a escribir un script simple que acceda a la base de datos sakila y obtenga
información de ella. Usaremos la sección de la base de datos llamada Inventory, la cual
almacena los datos de las películas.

Figura 1.24
# actores_por_film.py

"""

Este script accede a la base de datos sakila y

muestra los actores que participaron

en cierto film pasado por parámetro

"""

import mysql.connector

conn = mysql.connector.connect(user='usuario',

password='contraseña',

host='127.0.0.1',

database='sakila')

film_id = 100

consulta = """SELECT first_name, last_name

FROM actor

INNER JOIN film_actor

ON actor.actor_id = film_actor.actor_id

WHERE film_actor.film_id = %s"""

cur = conn.cursor()

cur.execute(consulta, (film_id,))

resultado = cur.fetchall()

print(resultado) # muestra una lista de tuplas con los datos

conn.close()

En este ejemplo podemos observar una práctica importante. No insertar los parámetros de
la consulta en la cadena, sino pasarlos por separado.
Podríamos pensar que es más sencillo directamente insertar el valor de film_id a la cadena
como un f-string: f"... WHERE film_actor.film_id = {film_id}". Y, si bien, en este
caso (en que directamente escribimos el valor 100) no habría problema, cuando escribamos
software que va a estar expuesto a usuarios que ingresen datos, nuestra base de datos va a
ser susceptible a ataques mediante inyección SQL.

Esto se evita si pasamos los parámetros por separado, ya que el Conector se encargará de
limpiar los parámetros de cualquier sentencia maliciosa, automáticamente. Por lo tanto, es
bueno acostumbrarnos a pasar siempre los parámetros por separado.

La forma de pasar parámetros con el conector de mysql es incluir en la sentencia los


caracteres %s en los lugares donde se insertarán los valores, como se ve en el ejemplo.
Luego los parámetros se pasan dentro de una tupla como el segundo parámetro del método
execute(). Si se quiere pasar un solo parámetro, hay que recordar que una tupla de un
elemento debe llevar una coma.

Usando una GUI

Veamos ahora otro ejemplo, más completo en el cual permitiremos al usuario seleccionar
de qué película buscar sus actores.

Primero creamos la GUI con tkinter, que mostrará un treeview con las películas
almacenadas en la base de datos. Para esto tenemos que hacer una consulta para obtener
todas las películas almacenadas.

SELECT film_id, title, release_year FROM film;

Luego, cuando el usuario seleccione una película tenemos que buscar los actores que
participaron en ella, usando la misma consulta del ejemplo anterior. Esta vez el id de la
película vendrá de la fila seleccionada del treeview.

SELECT first_name, last_name

FROM actor

INNER JOIN film_actor

ON actor.actor_id = film_actor.actor_id

WHERE film_actor.film_id = <param>;

Ver el archivo complementario gui_elige_film.py.

De este ejemplo, podemos observar dos cosas:


1. Estamos repitiendo en dos lugares exactamente el mismo código cuando nos
conectamos a la base de datos. Esto es una pista de que tenemos que separar esa
funcionalidad en una función o método aparte.

Por ejemplo, podríamos tener un método aparte que solo se conecte a la base de
datos y devuelva un objeto tipo Connection. Más aún, podríamos crear una clase
entera que se conecte a la base de datos en su método __init__ y que implemente
métodos que solo devuelvan los datos que le pedimos.

Muchos patrones de diseño de software aplican una separación por capas, en los que
cada capa se encarga de alguna sección independiente de la aplicación. Y casi
siempre existe una capa de acceso a datos en donde se centra toda la funcionalidad
que tenga que ver con la/las base/s de datos. Vamos a hacer eso en una próxima
iteración de la pequeña aplicación.

2. Por otro lado, la primera consulta a la base de datos devuelve 1000 registros de
películas, estos son todos los que existen en la tabla, ya que no restringimos la
consulta. Este no es un número tan grande y la aplicación lo puede soportar sin
problema. Pero hay que tener cuidado al hacer este tipo de consultas y usar el
método fetchall(), ya que si la tabla a la que estamos consultando tiene muchos
registros (millones) y solicitamos varias columnas, nuestra PC puede tener
problemas para cargar los datos ya que estos se almacenan en la memoria RAM
cuando los obtenemos, y esta es limitada.

Una solución para cuidar la memoria RAM es ir obteniendo resultados parciales con
fetchmany() y paginar los resultados.

Otra, es directamente no mostrar todos los registros de una sola vez y en su lugar
hacer un buscador para reducir la cantidad de registros que devuelve una consulta.

Implementando un buscador

En esta versión, hemos creado una clase que se va a encargar del acceso a datos. Tiene
métodos que directamente devuelve los result-sets como una lista de tuplas al programa
principal.

Además, hemos añadido un buscador que obliga al usuario a ingresar algún fragmento del
nombre de una película para recién obtener las coincidencias. De esta forma no tenemos
que obtener todos los registros.

Para implementar la búsqueda, vamos a consultar a la base de datos usando la cláusula


LIKE de la siguiente forma.
SELECT film_id, title, release_year

FROM film

WHERE title LIKE %<param>%

ORDER BY title;

Para poder hacer uso de LIKE a través del Conector, al formular la consulta, debemos dejar
solamente los caracteres %s donde irá el parámetro de búsqueda, sin incluir el comodín de
SQL %, ya que esto no funciona con el Conector. El carácter comodín de SQL % debe ser
agregado al parámetro antes de pasarlo al método execute(), como se puede ver en el
siguiente ejemplo.

a_buscar = "venger"

consulta = """SELECT film_id, title, release_year

FROM film WHERE title LIKE %s

ORDER BY title"""

parametro = f"%{a_buscar}%" # forma la cadena "%venger%"

cursor.execute(consulta, (parametro,))

Ver el archivo complementario gui_elige_film2.py.

Notar que esta consulta de búsqueda no funcionará muy bien si se escriben palabras
separadas por espacios ya que el SGBD intentará encontrar la frase exacta, más allá de los
comodines al inicio y fin. Si queremos poder escribir varias palabras clave para una
búsqueda, como, por ejemplo: “avengers game” para intentar encontrar “Avengers: End
Game.”, vamos a tener que separar cada palabra y agregar una cláusula LIKE en la consulta
para cada una. Esto es más complicado de automatizar ya que pueden ser dos palabras, o
tres, o cualquier cantidad.

Cargando datos

Ahora vamos a ver cómo cargar y eliminar registros desde Python.

Vamos a usar la base de datos que construimos en la teoría de SQL, la base de datos cine.

Levantar backup de la base de datos, si es necesario: cine.sql. Recordar que se


debe crear la base de datos primero y luego correr el restore.

Para esta base de datos, vamos a crear una GUI que nos permita cargar datos de nuevas
películas.
Si bien podríamos dejar separada la carga de la película de la del director, también lo
podemos hacer en conjunto, lo cual es más intuitivo para el usuario. Lo único que debemos
tener en cuenta es que al momento de insertar el campo director en la tabla peliculas,
el director con ese id debe existir primero en la tabla directores. Ya que la restricción de
clave foránea ya está establecida en la base de datos.

Vamos a comenzar creando un formulario para cada dato de la película en nuestra GUI. Los
campos de título, duración y año deben ser ttkEntry, mientras que el campo de clave
foránea de directores lo vamos a llenar seleccionando el nombre de un director de los ya
cargados y pasando el id.

Vamos a usar un combobox para que el usuario seleccione al director de los que ya existen
en la base de datos, y obtendremos el id de allí. Pero dejaremos el combobox editable, así
puede cargar uno nuevo si este no existe.

Si se carga un director nuevo, hay que obtener su id (generado automáticamente por el


SGBD) para luego agregarlo a la película, esto lo haremos inmediatamente después de
insertarlo con el valor obtenido de lastrowid, ya que la clave primaria id_director
estaba configurada como AUTO_INCREMENT.

Ver el archivo complementario agrega_pelicula.py.

Como se ve, en este ejemplo en particular, es imposible insertar una película sin director,
ya que la GUI no permite campos vacíos. Aunque, recordemos que, la base de datos sí
permite a la clave foránea como NULL.

Por otra parte, podemos ver que en ningún momento casteamos los valores a insertar en la
base de datos, simplemente tomamos los str que devuelve la GUI. Recordemos que el
título es una cadena y, la duración y el año son enteros en la base de datos. Esto es porque
el conector siempre trabaja con str, como vimos en la Figura 1.27 al comienzo de este tema.
Por lo tanto, el SGBD se encarga de interpretar y castear correctamente el valor del str que
le llega como una consulta SQL.

Esto no quiere decir que no debemos validar los datos. En nuestro ejemplo no lo hicimos por
una cuestión de minimizar el código. Pero la aplicación debería validar que, para la duración,
por ejemplo, se escriban solo valores numéricos enteros, por más de que se guarden en un
str. Ya que, si se pasa una cadena con caracteres alfabéticos para ese campo, el SGBD
fallará en el casteo interno y se producirá un error.

Finalmente, como se puede ver en el ejemplo, hemos hecho uso de manejo de errores por
posibles errores en la base de datos. Hablaremos de eso a continuación.
1.4.4. Manejo de errores
MySQL Connector/Python incluye en su librería el módulo mysql.connector.errors, en
el cual implementa distintas excepciones, las cuales se mapean a la numeración de los
códigos de error del servidor MySQL.

La mayoría de las clases definidas en este módulo están disponibles cuando se importa
mysql.connector. Por lo tanto, podemos escribir mysql.connector.Error, como lo
hicimos en el ejemplo.

La excepción base es Error y tiene una subclase importante, DatabaseError. Esta


implementa como subclases a todos los errores más comunes al interactuar con una base
de datos MySQL.

De todas formas, si se quiere usar una única sentencia except como lo hicimos en nuestro
ejemplo, la excepción Error va a capturarlos a todos ya que es la clase base.

Si queremos manejar excepciones a un nivel más detallado, debemos usar alguna de las
subclases de DatabaseError, como se ve en la jerarquía de clases siguiente:

• Error: Esta excepción es la clase base para todas las demás excepciones en el
módulo errores.
– DatabaseError: Esta excepción es la predeterminada para cualquier error de
MySQL que no se ajuste a las otras excepciones.
• DataError: Esta excepción se lanza cuando hubo problemas con los
datos.
• OperationalError: Esta excepción se lanza para errores
relacionados con las operaciones de MySQL.
• NotSupportedError: Esta excepción se lanza cuando se utilizó
alguna función que no es compatible con la versión de MySQL que
devolvió el error.
• IntegrityError: Esta excepción se lanza cuando la integridad
relacional de los datos se ve afectada.
• ProgrammingError: Esta excepción se genera en errores de
programación, por ejemplo, cuando tiene un error de sintaxis en su
SQL o no se encontró una tabla.
• InternalError: Esta excepción se lanza cuando el servidor MySQL
encuentra un error interno, por ejemplo, cuando se produjo un
interbloqueo.
1.4.5. Mejores prácticas

1.4.5.1. Seguridad
Por razones de seguridad no es recomendable escribir (hardcode) los datos de conexión del
servidor y credenciales del usuario de la base de datos en el archivo principal de la
aplicación, como lo hicimos en los ejemplos.

Lo recomendado, en Python, es separar esos datos en un archivo aparte usualmente llamado


config.py y obtenerlos en tiempo de ejecución.

Más seguro aún, es configurar las credenciales en variables de entorno del sistema y luego
obtenerlas desde la aplicación.

Por otro lado, recordamos la práctica de pasar los parámetros de las consultas por separado
de las sentencias SQL.

1.4.5.2. Usar objetos de manera idiomática en Python


Programar de forma idiomática quiere decir usar las características del lenguaje en el que
se está escribiendo.

Tanto los objetos Connection, como los objetos Cursor están implementados como
Gestores de Contexto, esto quiere decir que los podemos usar con la sentencia with de
Python.

Un Gestor Contexto implementa operaciones de inicialización y finalización


automáticamente, en el caso de una conexión, implementa la conexión y cierre
automáticamente. Por ejemplo:

with mysql.connector.connect(**config) as conn:

cur = conn.cursor()

cur.execute("SELECT * FROM tabla")

resultado = cur.fetchall()

print(resultado)

Por otro lado, se puede crear una clase propia que implemente los protocolos de un Gestor
de Contexto para hacer su uso más cómodo aún.
1.4.5.3. Validación y verificación de datos
Como comentamos anteriormente, siempre debemos revisar que los valores que ingrese el
usuario sean compatibles con los campos de la tabla correspondiente. Si bien el Conector
se encarga de limpiar los datos de código malicioso, nosotros debemos asegurarnos de que
lo que se quiere almacenar corresponda al tipo de dato de cada campo de una tabla, ya sea
un entero, una cadena o una fecha.

Dado que los conjuntos de resultados de las consultas SQL pueden ser muy grandes, es
recomendable utilizar el método apropiado para recuperar elementos del result-set a
medida que los recorre. fetchone() recupera un solo elemento, cuando sabe que el
conjunto de resultados contiene una sola fila. fetchall() recupera todos los elementos,
cuando sabe que el conjunto de resultados contiene un número limitado de filas que caben
cómodamente en la memoria. fetchmany() es el método de propósito general cuando no
puede predecir el tamaño del conjunto de resultados: sigue llamándolo y recorriendo los
elementos devueltos, hasta que no haya más resultados para procesar.

1.4.5.4. Otros
Una recomendación es declarar una clave primaria numérica para cada tabla, que ofrece la
forma más rápida de buscar valores y puede actuar como un puntero a valores asociados en
otras tablas (una clave foránea).

Por otro lado, al momento de definir los campos de una tabla, hacer uso de los tipos de datos
de columna más compactos que cumplen con los requerimientos de la aplicación ayuda al
rendimiento y la escalabilidad porque eso permite que el servidor mueva menos datos entre
la memoria y el disco.

Sobre el manejo de errores, podemos decir que es útil la creación de una excepción
personalizada, para lanzar al momento de capturar una excepción de MySQL. Esto nos
permite implementar nuestro propio mensaje de error en nuestro idioma, en lugar de usar
los mensajes de MySQL que están en inglés.

1.4.6. Siguientes pasos


Ya pensando en implementar el Frontend de una aplicación web tenemos que pensar en los
datos que queremos mostrar y pedir al usuario.

La creación de formularios y distintas visualizaciones van a depender de los datos que


almacenemos. Y viceversa, a veces para que cierto componente del Frontend funcione
correctamente es necesario incluir algún dato extra en la base de datos.
Por ejemplo, si queremos que nuestra aplicación muestre la última vez que ingresamos,
debemos haber guardado este dato anteriormente. Este dato quizá no es fundamental para
el funcionamiento de la aplicación en cuanto a la lógica del negocio, pero puede ser
importante para la experiencia del usuario. Esto, en definitiva, lo hace importante para la
aplicación.

Para entender qué se puede y qué no se puede hacer en una interfaz web debemos al menos
conocer los elementos de la misma y cómo se construye. Veremos todo esto en el próximo
módulo.
Bibliografía
1. https://dev.mysql.com/doc/connector-python/en/connector-python-installation-
binary.html
2. https://es.wikipedia.org/wiki/Loopback
3. https://es.wikipedia.org/wiki/Localhost
4. https://dev.mysql.com/doc/connector-python/en/connector-python-
reference.html
5. https://dev.mysql.com/doc/connector-python/en/connector-python-api-
errors.html
6. https://dev.mysql.com/doc/connector-python/en/connector-python-api-
errorcode.html

También podría gustarte