Este capítulo va a ser para enseñarte como funcionan los decoradores condicionales y las expresiones ternarias de Python. Para esto, resolveremos juntos los dos ejercicios que os propuse en el capítulo anterior.
Primero, vayamos a ver una solución para mostrar las columnas de las tablas de la forma que te pedí:
- -ID int. No admite valores nulos. Es clave primaria.
- -Name char(35) No admite valores nulos.
- -CountryCode char(3) No admite valores nulos. Es clave externa.
- -District char(20) No admite valores nulos.
- -Population int No admite valores nulos.
Para hacer esto, solo hay que modificar el bucle for (líneas 22 – 26), ya que es el que presenta los datos en la consola.
# Método para mostrar columnas @conexion def mostrar_columnas(self, nombre_bd, nombre_tabla): # Primero, se verifica si la base de datos existe sql = f"SHOW DATABASES LIKE '{nombre_bd}'" self.cursor.execute(sql) resultado = self.cursor.fetchone() # Si la base de datos no existe, muestra un mensaje de error y termina la función if not resultado: print(f'La base de datos {nombre_bd} no existe.') return # Establece la base de datos actual self.cursor.execute(f"USE {nombre_bd}") try: # Realiza la consulta para mostrar las columnas de la tabla especificada self.cursor.execute(f"SHOW COLUMNS FROM {nombre_tabla}") resultado = self.cursor.fetchall() # Se informa de que se están obteniendo las columnas print(f"Aquí tienes el listado de las columnas de la tabla '{nombre_tabla}':") # Recorre los resultados y los muestra por pantalla for columna in resultado: not_null = "No admite valores nulos." if columna[2] == "NO" else "" primary_key = "Es clave primaria." if columna[3] == "PRI" else "" foreign_key = "Es clave externa." if columna[3] == "MUL" else "" print(f"-{columna[0]} ({columna[1]}) {not_null} {primary_key} {foreign_key}") except: print("Ocurrió un error. Comprueba el nombre de la tabla.")
base_datos.mostrar_columnas("world", "city")
Resultado en la consola
Se abrió la conexión con el servidor.
Aquí tienes el listado de las columnas de la tabla ‘city’:
-ID (int) No admite valores nulos. Es clave primaria.
-Name (char(35)) No admite valores nulos.
-CountryCode (char(3)) No admite valores nulos. Es clave externa.
-District (char(20)) No admite valores nulos.
-Population (int) No admite valores nulos.
Se cerró la conexión con el servidor.
Seguramente, te hayas complicado bastante más que yo, puesto que no he explicado todavía como usar la expresión ternaria con Python.
Para que entiendas esto, tengo que explicar las expresiones ternarias en Python.
Expresiones ternarias en Python
Las expresiones ternarias de Python, permiten hacer evaluaciones condicionales en una misma línea. Es algo como las lambdas para las funciones normales.
Sintaxis de las expresiones ternarias de Python
La sintaxis de las expresiones ternarias de Python que estoy utilizando, es la siguiente:
variable = expresión_verdadera if expresión_condicional else expresión_falsa
Llevemos esto al código que te he puesto en el método de consultas de columnas.
Tenemos una variable donde almacenar el resultado (not_null).
Después, se pone lo que queremos almacenar. En caso de que la expresión sea verdadera, se guarda en la variable «No admite valores nulos.». La expresión condicional viene a continuación, «if columna[2]» == «NO». Finalmente, viene la expresión false con else, que no es más que un string vacío.
not_null = "No admite valores nulos." if columna[2] == "NO" else ""
Analicemos mejor los resultados de la tabla. Fíjate en este código en la línea 18. Aquí voy a imprimir toda la lista de tuplas que se almacena en la variable «resultado».
# Método para mostrar columnas @conexion def mostrar_columnas(self, nombre_bd, nombre_tabla): # Primero, se verifica si la base de datos existe sql = f"SHOW DATABASES LIKE '{nombre_bd}'" self.cursor.execute(sql) resultado = self.cursor.fetchone() # Si la base de datos no existe, muestra un mensaje de error y termina la función if not resultado: print(f'La base de datos {nombre_bd} no existe.') return # Establece la base de datos actual self.cursor.execute(f"USE {nombre_bd}") try: # Realiza la consulta para mostrar las columnas de la tabla especificada self.cursor.execute(f"SHOW COLUMNS FROM {nombre_tabla}") resultado = self.cursor.fetchall() print(resultado) # Se informa de que se están obteniendo las columnas print(f"Aquí tienes el listado de las columnas de la tabla '{nombre_tabla}':") # Recorre los resultados y los muestra por pantalla for columna in resultado: not_null = "No admite valores nulos." if columna[2] == "NO" else "" primary_key = "Es clave primaria." if columna[3] == "PRI" else "" foreign_key = "Es clave externa." if columna[3] == "MUL" else "" print(f"-{columna[0]} ({columna[1]}) {not_null} {primary_key} {foreign_key}") except: print("Ocurrió un error. Comprueba el nombre de la tabla.")
Al hacer la llamada, aparte de las columnas, me imprime esta parte:
- El nombre del campo corresponde a columna[0] de cada tupla.
- El tipo de dato, está en la posición columna[1].
- La columna[2], está reservada para sacar un string vacío o un ‘NO’. Si es ‘NO’, indica que no admite valores nulos.
- La columna[3] se reserva para indicar si es clave primaria, foránea o ninguna de estas. Si aparece ‘PRI’, quiere decir que la columna es clave primaria.
- La columna[4] sirve para indicar si la columna tiene un valor por defecto para rellenar esa columna en los registros. Por ejemplo, al crear un registro o fila en la tabla, si en ‘Population’ no indicamos nada, se almacena por defecto el valor 0.
- La columna[5] es para indicar si la columna es autoincrementable o no.
Por eso mismo, en la evaluación de la variable not_null, se pone columna[2] == ‘NO’. Si encuentra un valor igual a ‘NO’, Nos dirá «No admite valores nulos».
La expresión de si es == a ‘PRI’ o no, se evalúa con la columna[3].
También, se evalúa ‘MUL’ en la misma columna en la variable foreign_key, ya que puede aparecer en la misma columna que ‘PRI’.
Método decorador para comprobar bases de datos existentes
El siguiente ejercicio, era el de conseguir crear un método decorador para comprobar si existe una base de datos en concreto, puesto que esta comprobación, se necesita para varios métodos de la clase.
# Decorador para comprobar si existe una base de datos def comprueba_bd(funcion_parametro): def interno(self, nombre_bd, *args): # Verifica si la base de datos existe en el servidor sql = f"SHOW DATABASES LIKE '{nombre_bd}'" self.cursor.execute(sql) resultado = self.cursor.fetchone() # Si la base de datos no existe, muestra un mensaje de error if not resultado: print(f'La base de datos {nombre_bd} no existe.') return # Ejecuta la función decorada y devuelve el resultado return funcion_parametro(self, nombre_bd, *args) return interno
Analicemos las partes.
- El método interno() requiere del paso explícito del nombre_bd que se le pasa a la llamada del método decorado (funcion_parametro).
- Se verifica si el nombre proporcionado de base de datos existe.
- Si la base de datos no existe (es decir, si el valor de la variable resultado es None), el método interno muestra un mensaje de error con el nombre de la base de datos indicando que no existe y se finaliza la ejecución del método interno. En este caso, no se llega a ejecutar la función parámetro como hemos visto en los otros métodos decoradores.
- Si la base de datos existe, la función interno ejecuta la función decorada funcion_parametro y devuelve el resultado.
Probemos a decorar todos los métodos que sean necesarios y a eliminar el código duplicado (la parte que comprueba si existe la base de datos).
Recuerda, que la conexión siempre debe ir decorada antes que esta comprobación.
Los métodos decorados, de momento, serán los siguientes:
- crear_tabla()
- copia_bd()
- eliminar_tabla()
- mostrar_tablas()
- mostrar_columnas()
Básicamente, se decoran métodos con el parámetro nombre_bd, los cuales, requieren del uso de una base de datos.
Algunos como eliminar_bd() o crear_bd(), realmente no necesitan ser decorados, ya que funcionan bien independientemente de si la base de datos existe o no, pero los puedes decorar sin problemas siempre que lo quieras verificar antes de ejecutar un método en vano.
Probemos los métodos recién decorados.
Recuerda, cuando implementes decoradores a los métodos, pruébalos en diferentes circunstancias, por ejemplo, en crear tabla, pon un nombre inexistente de tabla o de base de datos. Ves probando todo lo que pueda salir mal.
Evidentemente, siempre pueden quedar excepciones sin cubrir, pero realmente no es 100% necesario, ya que estamos en fase de desarrollo, los errores de Python son buenos también. Lo importante, es manejar todo lo que el usuario pueda hacer mal en la aplicación final.
No te pierdas nada del curso Máster en Python.
Te dejo aquí el código completo del capítulo por aquí, para que puedas solucionar cualquier error te te pudiera estar ocurriendo.
import mysql.connector import os import subprocess import datetime #conexion con la base de datos acceso_bd = {"host" : "localhost", "user" : "root", "password" : "programacionfacil", } # --> Rutas #Obtenemos la raíz de la carpeta del proyecto carpeta_principal = os.path.dirname(__file__) carpeta_respaldo = os.path.join(carpeta_principal, "respaldo") class BaseDatos: #Conexión y cursor def __init__(self, **kwargs): self.conector = mysql.connector.connect(**kwargs) self.cursor = self.conector.cursor() self.host = kwargs["host"] self.usuario = kwargs["user"] self.contrasena = kwargs["password"] self.conexion_cerrada = False # Avisa de que se abrió la conexión con el servidor print("Se abrió la conexión con el servidor.") #Decoradora para el reporte de bases de datos en el servidor def reporte_bd(funcion_parametro): def interno(self, nombre_bd): funcion_parametro(self, nombre_bd) BaseDatos.mostrar_bd(self) return interno # Decorador para el cierre del cursor y la base de datos def conexion(funcion_parametro): def interno(self, *args, **kwargs): try: if self.conexion_cerrada: self.conector = mysql.connector.connect( host = self.host, user = self.usuario, password = self.contrasena ) self.cursor = self.conector.cursor() self.conexion_cerrada = False print("Se abrió la conexión con el servidor.") # Se llama a la función externa funcion_parametro(self, *args, **kwargs) except: # Se informa de un error en la llamada print("Ocurrió un error con la llamada.") finally: if self.conexion_cerrada: pass else: # Cerramos el cursor y la conexión self.cursor.close() self.conector.close() print("Se cerró la conexión con el servidor.") self.conexion_cerrada = True return interno # Decorador para comprobar si existe una base de datos def comprueba_bd(funcion_parametro): def interno(self, nombre_bd, *args): # Verifica si la base de datos existe en el servidor sql = f"SHOW DATABASES LIKE '{nombre_bd}'" self.cursor.execute(sql) resultado = self.cursor.fetchone() # Si la base de datos no existe, muestra un mensaje de error if not resultado: print(f'La base de datos {nombre_bd} no existe.') return # Ejecuta la función decorada y devuelve el resultado return funcion_parametro(self, nombre_bd, *args) return interno #Consultas SQL @conexion def consulta(self, sql): try: self.cursor.execute(sql) print("Esta es la salida de la instrucción que has introducido:") print(self.cursor.fetchall()) except: print("Ocurrió un error. Revisa la instrucción SQL.") @conexion def mostrar_bd(self): try: # Se informa de que se están obteniendo las bases de datos print("Aquí tienes el listado de las bases de datos del servidor:") # Realiza la consulta para mostrar las bases de datos self.cursor.execute("SHOW DATABASES") resultado = self.cursor.fetchall() # Recorre los resultados y los muestra por pantalla for bd in resultado: print(f"-{bd[0]}.") except: # Si ocurre una excepción, se avisa en la consola print("No se pudieron obtener las bases de datos. Comprueba la conexión con el servidor.") #Eliminar bases de datos @conexion @reporte_bd @comprueba_bd def eliminar_bd(self, nombre_bd): # Realiza la consulta para eliminar la base de datos self.cursor.execute(f"DROP DATABASE {nombre_bd}") print(f"Se eliminó la base de datos {nombre_bd} correctamente.") #Crear bases de datos @conexion @reporte_bd def crear_bd(self, nombre_bd): try: self.cursor.execute(f"CREATE DATABASE IF NOT EXISTS {nombre_bd}") print(f"Se creó la base de datos {nombre_bd} o ya estaba creada.") except: print(f"Ocurrió un error al intentar crear la base de datos {nombre_bd}.") #Crear backups de bases de datos @conexion @comprueba_bd def copia_bd(self, nombre_bd): #Obtiene la hora y fecha actuales self.fecha_hora = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S") #Se crea la copia de seguridad with open(f'{carpeta_respaldo}/{nombre_bd}_{self.fecha_hora}.sql', 'w') as out: subprocess.Popen(f'"C:/Program Files/MySQL/MySQL Workbench 8.0/"mysqldump --user=root --password={self.contrasena} --databases {nombre_bd}', shell=True, stdout=out) print("Se creó la copia correctamente.") @conexion @comprueba_bd def crear_tabla(self, nombre_bd, nombre_tabla, columnas): try: #String para guardar el string con las columnas y tipos de datos columnas_string = "" #Se itera la lista que se le pasa como argumento (cada diccionario) for columna in columnas: #formamos el string con nombre, tipo y longitud columnas_string += f"{columna['name']} {columna['type']}({columna['length']})" #Si es clave primaria, auto_increment o no admite valores nulos, lo añade al string if columna['primary_key']: columnas_string += " PRIMARY KEY" if columna['auto_increment']: columnas_string += " AUTO_INCREMENT" if columna['not_null']: columnas_string += " NOT NULL" #Hace un salto de línea después de cada diccionario columnas_string += ",\n" #Elimina al final del string el salto de línea y la coma columnas_string = columnas_string[:-2] #Le indica que base de datos utilizar self.cursor.execute(f"USE {nombre_bd}") #Se crea la tabla juntando la instrucción SQL con el string generado sql = f"CREATE TABLE {nombre_tabla} ({columnas_string});" #Se ejecuta la instrucción self.cursor.execute(sql) #Se hace efectiva self.conector.commit() # Se informa de que la creación se ha efectuado correctamente. print("Se creó la tabla correctamente.") except: print("Ocurrió un error al intentar crear la tabla.") @conexion @comprueba_bd def eliminar_tabla(self, nombre_bd, nombre_tabla): try: self.cursor.execute(f"USE {nombre_bd}") self.cursor.execute(f"DROP TABLE {nombre_tabla}") print(f"Tabla '{nombre_tabla}' eliminada correctamente de la base de datos {nombre_bd}.") except: print(f"No se pudo eliminar la tabla '{nombre_tabla}' de la base de datos '{nombre_bd}'.") # Método para mostrar las tablas de una base de datos @conexion @comprueba_bd def mostrar_tablas(self, nombre_bd): # Se selecciona la base de datos self.cursor.execute(f"USE {nombre_bd};") # Se informa de que se están obteniendo las tablas print("Aquí tienes el listado de las tablas de la base de datos:") # Realiza la consulta para mostrar las tablas de la base de datos actual self.cursor.execute("SHOW TABLES") resultado = self.cursor.fetchall() # Recorre los resultados y los muestra por pantalla for tabla in resultado: print(f"-{tabla[0]}.") @conexion @comprueba_bd def mostrar_columnas(self, nombre_bd, nombre_tabla): # Establece la base de datos actual self.cursor.execute(f"USE {nombre_bd}") try: # Realiza la consulta para mostrar las columnas de la tabla especificada self.cursor.execute(f"SHOW COLUMNS FROM {nombre_tabla}") resultado = self.cursor.fetchall() # Se informa de que se están obteniendo las columnas print(f"Aquí tienes el listado de las columnas de la tabla '{nombre_tabla}':") # Recorre los resultados y los muestra por pantalla for columna in resultado: not_null = "No admite valores nulos." if columna[2] == "NO" else "Admite valores nulos." primary_key = "Es clave primaria." if columna[3] == "PRI" else "" foreign_key = "Es clave externa." if columna[3] == "MUL" else "" auto_increment = "Es autoincrementable." if columna[5] == "auto_increment" else "" print(f"-{columna[0]} ({columna[1]}) {not_null} {primary_key} {foreign_key} {auto_increment}") except: print("Ocurrió un error. Comprueba el nombre de la tabla.")
Excelente curso, todavía sigo vivo 🙂
Muchas gracias por este estupendo curso. Lo estoy usando para aprender sql y estoy aprendiendo muchas cosas que desconocía de python. Mi duda es:
Por que me aparece una «b» precediendo al tipo de dato (int, char) al mostrar el contenido de una tabla.
[(‘ID’, b’int’, ‘NO’, ‘PRI’, None, ‘auto_increment’), (‘Name’, b’char(35)’, ‘NO’, », b», »), (‘CountryCode’, b’char(3)’, ‘NO’, ‘MUL’, b», »), (‘District’, b’char(20)’, ‘NO’, », b», »), (‘Population’, b’int’, ‘NO’, », b’0′, »)]
Un saludo.