lunes, 14 de octubre de 2019

Módulo de Login y Registro en Python - Conexión con MySQL

Hasta los momentos hemos creado una aplicación donde el almacenamiento de los datos se logra haciendo uso de SQLite. Esta primera integración se realizó con este gestor de base de datos ya que viene integrado por defecto en la instalación de Python, sin embargo no es recomendable para app que gestione grandes cantidades de datos. Es por ello que en esta ocasión se mostrará la integración con MySQL. El código de esté capitulo lo puedes descargar en mi cuenta github.

Es importante que tengas instalado MySQL en tu ordenador y que dispongas del usuario y contraseña para realizar la conexión; además se debe instalar un paquete que hace de interfaz entre python-msyql.
# pip install pymysql

ACTUALIZACIÓN DE LA ESTRUCTURA DE LA APP

create_sqlite_db.py # Archivo para la creación de la db y tablas
create_mysql_db.py # Archivo para la creación de la db y tablas
app  # paquete app
  |-> main.py  # archivo principal del app
  |-> menu.py  # funciones para los menús del app
  |-> validators.py # funciones para validar el email y password
  |-> db_conection.py # funciones para gestionar la db


Para esta ocasión se agregó el archivo create_mysql_db.py en el directorio principal con el propósito de crear la base de datos y tablas en MySQL.

CREACIÓN DE LA BASE DE DATOS

El archivo create_mysql_db.py contiene el script para crear la base de datos y la tabla. El código es el siguiente:
import pymysql

DB_HOST = "localhost"
DB_USER = "your_user"
DB_PASS = "your_password"

db = pymysql.connect(DB_HOST, DB_USER, DB_PASS)
cursor = db.cursor()
cursor.execute('CREATE DATABASE LoginDB')
cursor.execute('USE LoginDB')
cursor.execute('''
 CREATE TABLE users (
       username VARCHAR(50) PRIMARy KEY,
       email VARCHAR(50) NOT NULL,
       password VARCHAR(200) NOT NULL
       )
       '''
)
  • Lo primero que se hace es importar el módulo de pymysql.
  • Luego se crean las variables para la conexión a la base de datos: DB_HOST, DB_USER, DB_PASS. Para las últimas dos variables usted debe colocar el usuario y contraseña que creó al momento de instalar MySQL en su computador.
  • Posteriormente se crea una conexión pasando los datos de las variables, asimismo se crea un cursor de esa conexión.
  • En la siguientes líneas se ejecutan las instrucciones SQL para:
    • Crear la Base de Datos.
    • Usar la base de datos una vez creada.
    • Crear la tabla user.
Este script se debe correr una sola vez mediante la siguiente instrucción en shell:
# python create_mysql_db.py

MODIFICACIÓN DE DB_CONECTION

Para poder adaptar la aplicación con la gestión de los datos mediante MySQL se a modificado el archivo db_conection.py. En él no se ha eliminado la clase de conexión con SQLite sino que, por el contrario, se agregó una nueva clase llamada MySQLConection, cuyos nombres de los métodos son similares a la clase de conexión con SQL para facilitar la integración en el app. El archivo db_conection queda así:
import sqlite3
import pymysql

class SQLiteConection():
# Conexión con la Base de Datos
    def connect(self):
        self.con = sqlite3.connect('../LoginDB.db')
        self.cur = self.con.cursor()

    def create_user(self, username, email, password):
        self.connect()
        self.cur.execute(
         'INSERT INTO users (username, email, password) VALUES (?, ?, ?);',
            (username, email, password)
        )
        result = self.cur.lastrowid
        self.con.commit()
        self.con.close()
        return result

    def find_user_by_username(self, username):
        self.connect()
        self.cur.execute('SELECT * FROM users WHERE username = "{}"'.format(username))
        result = self.cur.fetchone()
        self.con.close()
        return result

    def find_user_by_email(self, email):
        self.connect()
        self.cur.execute('SELECT * FROM users WHERE email = "{}"'.format(email))
        result = self.cur.fetchone()
        self.con.close()
        return result


class MySQLConection():
    DB_HOST = "localhost"
    DB_USER = "your_user"
    DB_PASS = "your_pass"
    DB_NAME = "LoginDB"

    def connect(self):
        self.con = pymysql.connect(self.DB_HOST, self.DB_USER, self.DB_PASS, self.DB_NAME)
        self.cur = self.con.cursor()

    def create_user(self, username, email, password):
        self.connect()
        try:
            self.cur.execute(
                'INSERT INTO users (username, email, password) VALUES ("{0}", "{1}", "{2}")'.format(
                    username, email, password.decode("utf-8"))
            )
            self.con.commit()
            result = True

        except:
            result = False

        self.con.close()
        return result

    def find_user_by_username(self, username):
        self.connect()
        self.cur.execute('SELECT * FROM users WHERE username = "{}"'.format(username))
        result = self.cur.fetchone()
        self.con.close()
        return result

    def find_user_by_email(self, email):
        self.connect()
        self.cur.execute('SELECT * FROM users WHERE email = "{}"'.format(email))
        result = self.cur.fetchone()
        self.con.close()
        return result

  • La primera modificación que se realizar es importar el módulo de pymysql.
  • Posteriormente se agrega la clase MySQLConection, las cuales tiene los atributos para la conexión a la base de datos: DB_HOST, DB_USER, DB_PASS, DB_NAME (Acá deben sustituir el usuario y password que crearon al instalar MySQL).
  • Los métodos son los mismos que la clase usada para conectarte a SQLite, con la salvedad que fueron modificados para adaptarlo a la sintaxis de MySQL. 
ACTUALIZACIÓN DEL MAIN

El main es el archivo principal de la aplicación y para adaptarlo a los nuevos requerimientos se harán ajustes menores.
# -*- coding: utf-8 -*-
import time
import bcrypt
from menu import MenuTemplate
from db_conection import MySQLConection

def main():
    menu = MenuTemplate()
    opcion = menu.main_menu()
    mysql_db = MySQLConection()

    if opcion == "1":
        ''' Sección para el registro de usuario '''
        data = menu.register()
        
        if "username" in data:
            if mysql_db.find_user_by_username(data["username"]):
                print(f'Este username: <{data["username"]}> ya se encuentra registrado.')
            elif mysql_db.find_user_by_email(data["email"]):
                print(f'Este Email: <{data["email"]}> ya se encuentra registrado.')
            else:
                password_hashed = bcrypt.hashpw(
                    data["password1"].encode('utf8'), 
                    bcrypt.gensalt()
                )
                
                new_user = mysql_db.create_user(
                    data["username"],
                    data["email"],
                    password_hashed
                )
                
                if new_user:
                    print ("Se ha registrado en el sistema.")
                else:
                    print ("Ha ocurrido un error en la base de datos.")
        else:
            print(f"\n{data}")

        input("\nPresione una tecla para continuar...")
        main()

    elif opcion == "2":
        ''' Sección para el login de usuario '''
        data = menu.login()

        result = mysql_db.find_user_by_username(data["username"])
        if result:
            if not bcrypt.checkpw(data["password"].encode('utf8'), result[2].encode('utf8')):
                print("Username o password inválido.")
            else:
                print("Usted ha ingresado al sistema.")
        else:
            print("Username o password inválido.")
        
        input("\nPresione una tecla para continuar...")
        main()

    elif opcion == "3":
        ''' Sección para salir del sistema'''
        menu.exit_text()
        time.sleep(2)
        menu.clean_screen()

    else:
        ''' En caso de no coincidir la opciones anteriores '''
        print("\n Opción incorrecta. Intente de nuevo")
        time.sleep(2)
        main()

main()

  • Lo primero que vemos se ha modificado es la importación de la clase MySQLConection.
  • Luego se crea un objeto llamado mysql_db la cual es una instancia de la Clase MySQLConection.
  • Y con respecto a la sección anterior se reemplaza la instancia sqlite_db por mysql_db en todo el archivo main.
Con esto hemos concluido la adaptación de la aplicación para que trabaje con MySQL, así como también damos por concluido esta segunda fase, la cual consiste en establecer un mecanismo de permanencia de los datos almacenando los mismos en una base de datos. En el próximo capítulo iniciaremos la fase tres, en la que crearemos un entorno gráfico para la app, dejando atrás la interfaz de consola que hemos estado utilizando.