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.

domingo, 15 de septiembre de 2019

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

En la primera parte construimos el proyecto para trabajar con listas y guardar allí los datos de los usuarios. El principal problema con esto es que los datos no se almacenan de forma permanente, es decir, una vez se cerraba el programa, los usuario registrados se borraban. Por esta razón, en esta segunda parte se añaden funcionalidades que nos permitirán insertar y consultar información en una base de datos. 

En este post el manejador de base de datos que utilizaremos es SQLite; iniciamos con él debido que viene integrado con python y no requiere instalación extra para su uso. Más adelante haremos una integración con MySQL. El código de esté capitulo lo puedes descargar en mi cuenta github.
ACTUALIZACIÓN DE LA ESTRUCTURA DE LA APP

create_sqlite_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 agregaron dos archivos uno en el directorio principal (create_sqlite_db.py) y otro dentro del directorio app (db_conection.py).

CREACIÓN DE LA BASE DE DATOS

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

# -*- coding: utf-8 -*-
import sqlite3

#Crea el archivo de Base de Datos si no ha sido creada
conn = sqlite3.connect('LoginDB.db') 
c = conn.cursor()

# Creación de la Tabla - USER
c.execute('''
 CREATE TABLE users (
     username text PRIMARY KEY,
     email text NOT NULL,
     password text NOT NULL
  )'''
)              
conn.commit()

  • Lo primero que se hace es importar el módulo de sqlite.
  • Posteriormente se crea una conexión con el archivo LoginDB.db, sino existe lo crea (conn = sqlite3.connect('LoginDB.db')); es justo con esta instrucción la que crea un archivo con el nombre LoginDB.db en el mismo directorio donde se encuentra el script.
  • Se crea un cursor la cual abre una interfaz para acceder a la base de datos (c = conn.cursor()).
  • Luego se crea la tabla haciendo uso del método execute y dentro de él la sentencia SQL para la creación de la tabla. La tabla se llama users, cuyos campos son: username de tipo text y la asignamos como llave primaria, email tipo text y no nulo y password también tipo text y no nulo.
  • Con el método commit se fija la ejecución del SQL.
Una vez creado este script es necesario ejecutarlo, esto lo hacemos:

# python create_sqlite_db.py

DESARROLLANDO EL MÓDULO DE CONEXIÓN

El archivo db_conection.py contiene las clases y métodos que permiten insertar y obtener datos de la base de datos.

import sqlite3

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

Se crea una clase llamada SQLiteConection, cuyos métodos son:  
  • connect: La que abre la conexión con la DB y fija el cursor. Acá se crea dos atributos con y cur que se usarán en los otros métodos de la clase.
  • create_user: A este método se le pasa los parámetros username, email y password, las cuales se concatenan con la instrucción SQL INSERT para insertar dentro de la tabla users este nuevo usuario.
  • find_user_by_username: Es un método para buscar al usuario por username, para lo cual se le pasa como parámetro el nombre de usuario y con un SELECT se busca el usuario, retornando un tupla con el primer valor encontrado gracias al fetchone.
  • find_user_by_email: Hace lo mismo que el método anterior, pero en esa ocasión la búsqueda se hace mediante el email.
MODIFICACIÓN DEL MAIN

Para poder adaptar la aplicación con la gestión de los datos mediante base de datos es necesario adaptar el main.

# -*- coding: utf-8 -*-
import time
import bcrypt
from menu import MenuTemplate
from db_conection import SQLiteConection

def main():
    menu = MenuTemplate()
    opcion = menu.main_menu()
    sqlite_db = SQLiteConection()

    if opcion == "1":
        ''' Sección para el registro de usuario '''
        data = menu.register()
        
        if "username" in data:
            if sqlite_db.find_user_by_username(data["username"]):
                print(f'Este username: <{data["username"]}> ya se encuentra registrado.')
            elif sqlite_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 = sqlite_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 = sqlite_db.find_user_by_username(data["username"])
        if result:
            if not bcrypt.checkpw(data["password"].encode('utf8'), result[2]):
                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()

Estudiemos los nuevos cambios ingresados al código por parte:

# -*- coding: utf-8 -*-
import time
import bcrypt
from menu import MenuTemplate
from db_conection import SQLiteConection
El primer segmento modificado se encuentra al inicio. En ella se agregó:
  • La importación de la clase SQLiteConection desde el archivo db_conection (from db_conection import SQLiteConection).
  • Se eliminan las listas username, email y password ya que no se usarán para almacenar los datos.
También se agrega un línea después de la función main() para crear una instancia de la clase SQLiteConection (sqlite_db = SQLiteConection()).

Otro segmento fuertemente modificado se encuentra en la exploración de la opción 1.

    if opcion == "1":
        ''' Sección para el registro de usuario '''
        data = menu.register()
        
        if "username" in data:
            if sqlite_db.find_user_by_username(data["username"]):
                print(f'Este username: <{data["username"]}> ya se encuentra registrado.')
            elif sqlite_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 = sqlite_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()

Nótese que ahora pasa verificar si el username y el email ya se encuentran registrado usamos los métodos find_user_by_username y find_user_by_email, respectivamente. Si no se consigue un usuario por su username o por su email, entonces se puede crear ese nuevo usuario mediante el método create_user al que se le pasa como argumento el username, email y password_hashed.

La sección donde se explora la opción 2 también sufre modificación.

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

        result = sqlite_db.find_user_by_username(data["username"])
        if result:
            if not bcrypt.checkpw(data["password"].encode('utf8'), result[2]):
                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()

Acá, después de recibir los datos ingresados por el usuario, se busca al usuario en la base de datos por su username si encuentra un resultado comparamos el dato del password (result[2]) almacenado en la db. Se accede al password de esa forma debido que el resultado obtenido es una tupla y la información de la contraseña se ubica en la posición 2 de la misma.

Ahora ejecutando nuestra aplicación podemos registrarnos y logearnos como lo haciamos anteriormente, con la diferencia que si salimos del sistema y lo volvemos a ejecutar el usuario registrado anteriormente no se habrá perdido.

Con esto concluimos este capítulo. Con SQLite podemos gestionar bases de datos para aplicaciones pequeñas, pero si requerimos un mejor manejador de base de datos deberemos utilizar alguno como MySQL, y es justo lo que haremos en el próximo capítulo. ¡Hasta pronto!


viernes, 13 de septiembre de 2019

Módulo de Login y Registro en Python - Validación de Datos

Bienvenido a un nuevo capítulo del proyecto Login y Registro de usuarios en python; en la que se añadirá validaciones importantes a los datos ingresados al usuario. En el capítulo anterior se avanzó en la captura de los datos para el registro y login de usuarios. Estos datos (si el usuario desea registrarse) se agregan en listas creadas para almacenarlos; pero antes de guardarlos se valida si el username o email de ese nuevo usuario se encuentran ya registrados, así como también se verifica que el ingreso del password y la repetición del mismo fuesen iguales.

Mas hacen falta mejores validaciones, sobre todo en el dato del email o para el password. Por ejemplo, para el email no se valida el formato, por lo que el usuario podría escribir user_test y se guarda igualmente, cuando realmente debería ser algo como user_test@example.com. Con el password no hay mayor validaciones, sin embargo podemos obligar al usuario que escriba un password que tenga al menos 8 caracteres, 1 número, 1 caracter especial, entre otros. Pues bien, justamente esto es lo que haremos hoy. El código de este capítulo lo encontrarán acá.

Comencemos instalando el paquete password_strength que nos ayudará con las validaciones del password.
# pip install password-strength

ACTUALIZACIÓN DE LA ESTRUCTURA DE LA APP

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

En el directorio principal debemos agregar un nuevo archivo llamado validators.py . Acá nos encargaremos de validar el formato del email y la fortaleza y características del password.

DESARROLLANDO EL VALIDATOR
En el archivo validator.py se crea un clase para el manejo de las excepciones y dos funciones para validar el email y password.
# -*- coding: utf-8 -*-
import re
from password_strength import PasswordStats

PASSWORD_STRENGTH = 0.3
PASSWORD_LENGTH = 8
PASSWORD_NUMBERS = 1
PASSWORD_UPPERCASE = 1
PASSWORD_SPECIAL = 1


class ValidationError(Exception):
    message = "Ha ocurrido un error en la validación"

    def __init__(self, message=None):
        Exception.__init__(self)
        if message is not None:
            self.message = message
        self.error_response()

    def error_response(self):
        return {"message": self.message}

def validar_email(email):
    if not re.match('^[(a-z0-9\_\-\.)]+@[(a-z0-9\_\-\.)]+\.[(a-z)]{2,15}$', email.lower()):
        raise ValidationError("email: El formato del email no es válido")

    return email

def validar_password(password1, password2):
    if not password1 == password2:
        raise ValidationError("password: Las contraseñas no coinciden")

    errors = []
    # password strength. Value range [0 : 1]
    if PasswordStats(password1).strength() < PASSWORD_STRENGTH:
        errors.append('Fortaleza: La contraseña es muy simple.')

    # Min length: 8
    if PasswordStats(password1).length < PASSWORD_LENGTH:
        errors.append(f'Tamaño: La contraseña debe tener al menos {PASSWORD_LENGTH} caracteres.')

    # Need min. 1 number
    if PasswordStats(password1).numbers < PASSWORD_NUMBERS:
        errors.append(f'Numeros: La contraseña debe tener al menos {PASSWORD_NUMBERS} número.')
    
    # Need min. 1 uppercase letters
    if PasswordStats(password1).letters_uppercase < PASSWORD_UPPERCASE:
        errors.append(f'Mayúscula: La contraseña debe tener al menos {PASSWORD_UPPERCASE} mayúscula.')

    # Need min. 1 special characters
    if PasswordStats(password1).special_characters < PASSWORD_SPECIAL:
        errors.append(f'Especial: La contraseña debe tener al menos {PASSWORD_SPECIAL} caracater especial.')

    if errors:
        raise ValidationError(errors)

Acá se puede observar:
  • Al inicio se hacen dos importaciones import re  y el módulo PasswordStats del paquete password_strength. El primer se utiliza para explorar una variedad de caracteres dentro de una cadena de caracter; es muy útil cuando se busca coincidencias de una palabra o letra dentro de un texto. El segundo se usa para verificar ciertas características de un string, en este caso el password.
  • Posteriormente se crean una variables que inicia con el prefijo PASSWORD. Estas son las condiciones que queremos darle a nuestro password, por ejemplo su fortaleza, tamaño, si debe llevar números, letra en mayúscula, caracter especial y cuantos de cada uno de ellos. La fortaleza debe ser un valor entre 0 a 1; para esta ocasión he colocado 0.3.
  • Mas abajo se encuentra la clase ValidationError para manejar la excepciones que se generarán de las validaciones. Esta clase hereda las propiedades y métodos de la clase Exception. Esta clase al ser invocada recibe como argumento el texto que explica el error ocurrido.
  • Luego encontramos la función validar_email la cual recibe como argumento el email ingresado por el usuario. Esa información es pasado por el método re.match la cual verifica si el email contiene el arroba (@)  y el punto (.) en él. Esta validación es simple, en la web se pueden conseguir otras formas de validar correos electrónicos, sin embargo con esta verificación es suficiente para nosotros. Si el email no cumple con el formato se lanza la excepción y se le pasa el mensaje a mostrar.
  • Finalmente tenemos la función validar_password que recibe las dos contraseña ingresadas por el usuario. En esta función se verifica que las contraseñas coincida (anteriormente estaba en el main.py), así como también las características de fortaleza, tamaño cantidad de números, mayúsculas y caracteres especiales. Para mostrar los errores de estas características se crea una lista y se van agregando cada error en él si lo hubiese, de esta forma se muestran todos los errores que se cometieron al ingresar el password.
ACTUALIZACIÓN DEL MENU Y MAIN

Para integrar la validación al sistema es necesario actualizar los archivos menu.py y main.py. Veamos primero el main:
if opcion == "1":
        ''' Sección para el registro de usuario '''
        data = menu.register()
        
        if "username" in data:
            if data["username"] in username:
                print(f'Este username: <{data["username"]}> ya se encuentra registrado.')
            elif data["email"] in email:
                print(f'Este Email: <{data["email"]}> ya se encuentra registrado.')
            else:
                password_hashed = bcrypt.hashpw(
                    data["password1"].encode('utf8'), 
                    bcrypt.gensalt()
                )
                
                username.append(data["username"])
                email.append(data["email"])
                password.append(password_hashed)

                print ("Se ha registrado en el sistema.")
        else:
            print(f"\n{data}")

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

Del main solo fue modificado la sección donde se procesa el registro. En ella se eliminó la verificación de coincidencia de password1 y password2 (ver codigo del capítulo anterior) ya que ahora ésto se hace en la función de validar_password en el archivo validators.py. Ademas se agregó un nivel en el condifcional, noten que antes de verificar si data["username"] esta en la lista username hay un if que pregunta si el string username se encuentra en el diccionario data. Se hace esto debido que si se retorna un error desde el método register no procese ese código sino el que se encuentra en else el cual imprime el error ocurrido.

El archivo menu.py sufre varias modificaciones. Para verlos sólo mostraré las secciones que cambian.

# -*- coding: utf-8 -*-
import os
from validators import ValidationError, validar_email, validar_password

Lo primero que se agrega es la importación de las funciones para validar el email y passowrd y la clase  de la excepción. Esto es necesario para poder usarlos dentro del archivo menu.py. El otro cambio se hace en el método register de la clase MenuTemplate. Veámoslo:

def register(self):
        self.clean_screen()
        self.head()
        try:
            username = input("Ingrese username: ")
            email = validar_email(input("Ingrese email: "))
            password1 = input("Ingrese password: ")
            password2 = input("Repita el password: ")
            validar_password(password1, password2)
        except ValidationError as e:
            return e.message

        return {
            "username": username,
            "email": email,
            "password1": password1,
            "password2": password2,
        }

En esta sección se agrega un bloque try - exception en el ingreso de los datos, con el propósito de capturar la excepción s se encuentra un error en la validaciones. La validaciones las encontramos:
  • Cuando se solicita el email se coloca email = validar_email(input("Ingrese email: ")). Pues resulta que con esto se verifica el formato del email, si es correcto se guarda en la variable el dato ingresado, sino se levanta la excepción.
  • Después que se ingresan las dos contraseñas se valida el password, la que, de igual forma, valida la información y si hay un error se dispara la excepción.
Si ocurre un error de validación entramos en el bloque se exception en la que retornamos el error en cuestión.

Con esto hemos concluido este capítulo y con la Parte 1 del Proyecto. Ahora iniciaremos la Parte 2 la cual permitirá almacenar la información en una base de datos.

jueves, 12 de septiembre de 2019

Módulo de Login y Registro en Python - Gestión de Datos

Este artículo pertenece al proyecto Login y Registro de usuarios en python y es la continuación del capítulo de Template y menú del sistema. En esta ocasión introduciremos al sistema la gestión de los datos ingresado a través del menú creado anteriormente. El código lo puede encontrar acá.

Antes de iniciar debemos activar el entorno virtual e instalar bcrypt paquete que se usará para la encryptación del password. Para instalar bcrypt ejecutamos:

# pip install bcrypt

ACTUALIZACIÓN DEL MAIN

En esta ocasión solo actualizaremos la carpeta main.py, quedando de la siguiente forma:

# -*- coding: utf-8 -*-
import time
import bcrypt
from menu import MenuTemplate

username = []
email = []
password = []

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

    if opcion == "1":
        ''' Sección para el registro de usuario '''
        data = menu.register()

        if data["username"] in username:
            print(f'Este username: <{data["username"]}> ya se encuentra registrado.')
        elif data["email"] in email:
            print(f'Este Email: <{data["email"]}> ya se encuentra registrado.')
        elif not data["password1"] == data["password2"]:
            print(f'Las contraseñas no coinciden.')
        else:
            password_hashed = bcrypt.hashpw(
                data["password1"].encode('utf8'), 
                bcrypt.gensalt()
            )
            
            username.append(data["username"])
            email.append(data["email"])
            password.append(password_hashed)

            print ("Se ha registrado en el sistema.")

        time.sleep(2)
        main()

    elif opcion == "2":
        ''' Sección para el login de usuario '''
        data = menu.login()
        index = 0
        try:
            index = username.index(data['username'])
            
            password_hashed = password[index]
            if not bcrypt.checkpw(data["password"].encode('utf8'), password_hashed):
                print("Username o password inválido.")
            else:
                print("Se ha autenticado de forma correcta.")

        except ValueError as e:
            print("Username o password inválido.")

        time.sleep(2)
        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()

Iniciemos estudiando los nuevos cambios ingresados al código por parte:

import time
import bcrypt
from menu import MenuTemplate

username = []
email = []
password = []

El primer segmento de código modificado se encuentra justamente al inicio. En ella se agregó:
  • La importación del módulo bcrypt (import bcrypt); éste módulo será requerido para encryptar la contraseña que el usuario ingresa al registrarse.
  • Luego creamos tres listas vacías: username, email, password, para almacenar los datos ingresados por el usuario.
La otra modificación se realizó cuando la opción es 1; es decir, cuando el usuario elige registrarse.

if opcion == "1":
        ''' Sección para el registro de usuario '''
        data = menu.register()

        if data["username"] in username:
            print(f'Este username: <{data["username"]}> ya se encuentra registrado.')
        elif data["email"] in email:
            print(f'Este Email: <{data["email"]}> ya se encuentra registrado.')
        elif not data["password1"] == data["password2"]:
            print(f'Las contraseñas no coinciden.')
        else:
            password_hashed = bcrypt.hashpw(
                data["password1"].encode('utf8'), 
                bcrypt.gensalt()
            )
            
            username.append(data["username"])
            email.append(data["email"])
            password.append(password_hashed)

            print ("Se ha registrado en el sistema.")

        time.sleep(2)
        main()

Nótese que la variable data recibe la información del registro en forma de diccionario. Luego:
  • Se verifica si el nombre de usuario (username) ingresado por el usuario se encuentra en la lista username (if data["username"] in username). De encontrarse se muestra un mensaje indicando que ese username ya se encuentra registrado.
  • También se verifica que el email ingresado por el usuario no se encuentre registrado (elif data["email"] in email). Si se encuentra registrado muestra un mensaje indicándolo.
  • Mas adelante se valida que el password1 y password2 coincida (elif not data["password1"] == data["password2"]). Si no coinciden se muestra un mensaje de error. 
  • Todas estas validaciones básicas permiten filtrar la unicidad del username e email, evitando que se registren dos usuarios con el mismo username e email. Asimismo que cuando se ingrese el password (las dos veces) sean coincidente.
  • Si no se cumple se procede a encryptar el password guardándolo en una variable llamada password_hashed, para posteriormente agregar los nuevos datos a las listas. Esto se hace con la función append, la cual permite agregar un nuevo elemento a las estructuras tipo lista.
  • Nótese que al mostrar los mensajes de error se concatena el texto con el diccionario dato colocando como prefijo f, siendo éste una forma elegante de combinar texto y variable. También se pudo haber hecho:
    • print("Este username: <"+data["username"]+"> ya se encuentra registrado").
    • print("Este username: <{}> ya se encuentra registrado".format(data["username"])).
Otra sección que sufrió modificación es donde se verifica la opcion 2, que corresponde al login de usuario.

elif opcion == "2":
        ''' Sección para el login de usuario '''
        data = menu.login()
        index = 0
        try:
            index = username.index(data['username'])
            
            password_hashed = password[index]
            if not bcrypt.checkpw(data["password"].encode('utf8'), password_hashed):
                print("Username o password inválido.")
            else:
                print("Se ha autenticado de forma correcta.")

        except ValueError as e:
            print("Username o password inválido.")

        time.sleep(2)
        main()

De igual forma se recibe la información del login en forma de diccionario en la variable data. Después se:
  •  Crea una variable index cuyo valor por defecto es 0 (index = 0). Esta variable se utilizará para buscar el password en la posición donde se almacenó el username.
  • Se construye un bloque try - except esto debido a que dentro del try se ejecuta la instrucción username.index(data["username"]) la que permite obtener el index donde se encuentra el nombre de usuario ingresado por teclado. Si no se encuentra se dispara una excepción ValueError la cual capturamos en except. En otras palabras, se buscan dentro de la lista username el nombre se usuario teclado, si se encuentra se devuelve el index donde se encontró, sino se dispara la excepción. En la excepción mostramos un mensaje indicando que el Username o Password es inválido.
  • Si el nombre de usuario se encuentra dentro de la lista username se obtiene el index el cual es utilizado para extraer de la lista password el password encryptado (password_hashed = passsword[index]).
  • Usando una función de bcrypt.checkpw se compara el password ingresado con el encryptado. Si no hay coincidencia se muestra un mensaje indicando Username o Password inválido. De lo contrario se muestra un mensaje indicado que se ha logeado correctamente.
Nótese que aunque en el login sea erróneo el username o el password se muestra el mismo mensaje "Username o password inválido", esto se hace por cuestiones de seguridad. Ya que si mostramos el error por separado le estamos indicios a un posible atacante sobre cual de los dos datos ingresado es el erróneo.

Con esto finalizamos esta capítulo. En el próximo añadiremos validaciones más específicas al dato del email y password.


Creación de entorno virtual para Python

ENTORNO VIRTUAL PARA PYTHON

Para éste artículo hemos de suponer que posee instalado Python3 en su sistema operativo, de no ser así recomendamos seguir el post de djangogirls. Bien, una vez instalado python tenemos la posibilidad de comenzar a desarrollar aplicaciones con él; y como este lenguaje de programación es lo máximo, es probable que te enganche y desees iniciar varios proyectos con él. Esto está muy bien y de hecho en Tratado Tecnológico te felicitamos si es así, sin embargo debes procurar separar todos tus proyectos con entornos virtuales para cada uno.
¿Por qué crear entornos virtuales? Pues imagina que estas trabajando en un Proyecto A en la que necesitas instalar paquetes o módulos especiales para desarrollar un característica en él; pues resulta que todos esos paquetes se instalan en el path principal de tu sistema operativo. ¿Esto es malo? realmente no. Pero supongamos que tu Proyecto A es funcionando perfectamente y un año después inicias otra app diferente la cual llamaremos Proyecto B, que casualmente usará un módulo ya instalado por A, mas este módulo tiene una actualización que B requiere; entonces resulta que lo actualizas y... oh no! El Proyecto A de hace una año deja de funcionar. ¿Por qué ocurrió esto? Es muy probable que la actualización del módulo no sea compatible con algunas funciones o métodos de la versión anterior, generando un problema.
Para evitar este tipo de inconveniente (que resultan ser un dolor de cabeza) se recomienda crear entorno virtuales para cada proyecto. Un entorno virtual es como una cápsula para cada app, en la que todo lo que se instala allí solo estará disponible para el proyecto en cuestión. Tomando el ejemplo anterior, si tenemos un entorno virtual para A y otro para B de forma independiente, la instalación de módulos en cada uno también lo será.

CREACIÓN DE ENTORNO VIRTUAL

Hay varias formas de crear un entorno virtual, seguramente en la web conseguirá varias maneras. La que usaremos acá hará uso de una herramienta llamada virtualenv. Para ello es necesario, como se comentó al inicio, tener instalado Python 3, y el módulo virtualenv. Para instalarlo solo debemos usar el gestor de paquete de python pip (pip ya viene por defecto con la instalación de python).

mi/directorio# pip install virtualenv

Con esto ya puedes crear un entorno virtual para cada proyecto de la siguiente forma:

mi/directorio# virtualenv env --python=python3

En la sintaxis de la instrucción tenemos varias cosa a considerar. Se ejecuta virtualenv para crear el entorno virtual y se dice que todo será gestionado por la carpeta env (la cual se creará después de la ejecución de esta instrucción). El nombre env se colocó como una notación general, pero usted puede darle el nombre que deseé, por ejemplo: myenv, venv, myentorno, etc., luego le indicamos cual versión de python deseamos usar, en cual, en este caso, es python3. Sino se coloca tomará como versión la que tengas por defecto.

Al pasar el tiempo es muy probable que deba actualizar la versión de python; por ejemplo, actualmente tengo instalado python2.7, python3.5, python3.7, entonces es muy ambiguo dejar en blanco la versión de python con la que se creará el entorno virtual. De hecho se puede ser más específico; note que en la instrucción anterior indiqué --python=python3, lo cual, en mi caso, puede ser 3.5 o 3.7, entonces para ser más específico le indico la versión de python3 a usar:

mi/directorio# virtualenv env --python=python3.7

Y listo. Eso creará una carpeta llamada env que permitirá gestionar todas las instalaciones de módulos para ese proyecto.

ACTIVACIÓN DEL ENTORNO VIRTUAL

Ya teniendo la carpeta del entorno virtual solo debemos activar el entorno virtual. Esto se hace justo en el mismo directorio donde se encuentra la carpeta env tecleando lo siguiente:

mi/directorio# source env/bin/activate

Esto hará que al inicio de la línea de comando aparezca el prefijo (env), tal como se muestra a continuación:
 
(env) mi/directorio# source env/bin/activate

Con lo cual nos indica que se encuentra activo el entorno virtual. Ya con esto podemos instalar todos los paquetes/módulos que necesita nuestro proyecto sin afectar las instalaciones de los otros, ya que estas instalaciones sólo tendrán efecto en este entorno virtual. Para desactivar el entorno virtual, solo debemos escribir:

(env) mi/directorio# deactivate

y volvemos a estado inicial.

Espero haya sido útil esta información y cómo recomendación, para cada proyecto sugerimos crear un entorno virtual independiente para evitar conflicto este todos tus app.

martes, 10 de septiembre de 2019

Programación en Python - Funciones

Con este artículo seguimos avanzando en la construcción del Curso de Programación en Python. Hasta los momentos hemos estudiado las estructuras más básicas que se pueden usar en Python: Variable, listas, tuplas, diccionarios, sentencias de control de flujo y de repetición, las cuales, sin embargo, nos han permitido crear programas completos y funcionales. Mas, lo visto hasta ahora esta muy lejos de ser lo mejor de los lenguajes de programación, aun quedan muchas cosas interesantes en python que nos permitirán elaborar aplicaciones más complejas. El tema que estudiaremos hoy son las funciones.

FUNCIONES

Una buena práctica en la programación es dividir el programa en partes, ya que nos permite reutilizar un segmento de código en diferentes partes del programa, pues esto se puede lograr gracias a las funciones. Supongamos que deseamos realizar una operación matemática, por ejemplo la resolvente, en diferentes partes de un programa; una opción es repetir el código la veces que sean necesario dentro del programa, pero la mejor es separar en una función esas instrucciones y llamar a la función cuando se requiera ejecutar dicho código.

Como se comentó anteriormente un función es un fragmento de código con un nombre asociado que realiza una serie de tareas y devuelve un valor. En otros lenguaje también se habla de procedimiento para algo similar que no devuelven valor. Pues en python no hay procedimientos, pero se puede emular al devolver un None (Nada).

La forma de declarar un función en python es la siguiente:

def mi_primera_funcion(dato1, dato2):
    print(dato1)
    print(dato2)

Lo que define una función es la palabra def, posteriormente, el nombre del función mi_primera_funcion y luego, entre paréntesis y separados, los argumentos dato1, dato2 que se pasan a la función. Debajo esto e indexado las instrucciones que se ejecutarán al llamar la función. En este caso imprimir el dato1 y dato2.

Lo que hemos hecho con lo anterior es definir la función. Para hacer uso de ella es necesario llamarla. Esto se logra colocando el nombre de la función y pasándole los datos correspondiente a los argumentos:

mi_primera_funcion("Tratado Tecnólogico", 3)

Al invocar esta función se ejecutará el código interno la cual consiste en imprimir lo que corresponde al dato1 (el string Tratado Tecnológico) y el dato2 (el número 3). Como nos podemos dar cuenta la asociación de argumento con el valor es de izquierda a derecha, es decir el argumento dato1 esta asociado al string y el dato2 al número.

Sin embargo también es posible modificar el orden de los parámetros si indicamos el nombre del parámetro al que asociar el valor a la hora de llamar a la función:

mi_primera_funcion(dato2 = 3, dato1 = "Tratado Tecnólogico")

Es importante que la cantidad de datos a pasar a la función coincida con la cantidad de argumentos que la función puede recibir. En este caso son dos argumentos y se pasan dos valores. Si se intentase pasar uno o más del permitido se obtendría un error.

También es posible, no obstante, definir funciones con un número variable de argumentos, o bien asignar valores por defecto a los parámetros para el caso de que no se indique ningún valor para ese parámetro al llamar a la función.

Los valores por defecto para los parámetros se definen situando un signo igual después del nombre del parámetro y a continuación el valor por defecto:

def mi_primera_funcion(dato1, dato2 = 4):
    print(dato1)
    print(dato2)

En la función anterior si le pasamos un solo dato no ocurre el error ya que el argumento dato2 tiene como valor por defecto 4. Por ejemplo:

mi_primera_funcion("Tratado Tecnólogico")

Si se le pase el valor correspondiente será ese el que utilice. Para definir funciones con un número variable de argumentos colocamos un último parámetro para la función cuyo nombre debe precederse de un signo * :

def varios_arg(dato1, dato2, *arg):
    print(dato1)
    print(dato2)
    for val in arg:
        print (val)

Esta sintaxis funciona ya que crea una tupla (en este caso de nombre arg) en la que se almacenan los valores de todos los parámetros extra pasados como argumento. Una forma de probar esto es llamando a la función pasando valores extra como parámetros:

varios_arg(1, "hola")
varios_arg(1, "hola", 3)
varios_arg(1, "hola", 3, "mundo")

Para la primera llamada, varios_arg(1, "hola") , la tupla otros estaría vacía dado que no se han pasado más parámetros que los dos definidos por defecto, por lo tanto no se imprimiría nada. extra En la segunda llamada arg valdría (3, ) , y en la tercera (3, "mundo") .

También se puede preceder el nombre del último parámetro con ** , en cuyo caso en lugar de una tupla se utilizaría un diccionario. Las claves de este diccionario serían los nombres de los parámetros indicados al llamar a la función y los valores del diccionario, los valores asociados a estos parámetros. Veamos un ejemplo de ello:

def varios_arg(dato1, dato2, **arg):
    print(dato1)
    print(dato2)
    print(arg["tercero"])

varios_arg("Hola", 2, tercero = "Mundo")

Fijense que en esta ocasión el tercer parámetro es un dato cuyo nombre es tercero y su valor, luego dentro de la función se asocia como un diccionario con una clave arg["tercero"]. Es importante que las claves del diccionario sean iguales a los nombres de los parámetros extras, de no ser así ocasionaría un error.

EJERCICIOS

Bien, es hora de practicar. La solución de estos ejercicios los podrás encontrar acá.

Ejercicio Nro. 1: Crear un programa que tenga una función que pase en mayúscula una cadena de caracteres y otra que la pase a minúscula.

# -*- coding: utf-8 -*-
def pasar_mayuscula(texto):
    return texto.upper()

def pasar_minuscula(texto):
    return texto.lower()

t = input("Ingrese el texto a formatear: ")
t_may = pasar_mayuscula(t)
print("El texto en mayúscula es: "+t_may)
t_min = pasar_minuscula(t)
print("El texto en minúscula es: "+t_min)

Explicación:
  • Se crean dos funciones pasar_mayuscula y pasar_minuscula las que reciben como argumento el texto a cambiar el formato. Al inicio se declara y posteriormente se llaman. La función pasar_mayuscula recibe como parámetro una cadena de caracteres y se formatea con upper(), cuyo objetivo es transformar el texto en mayúscula. Por otro lado, la función pasar_minúscula es similar con la diferencia que convierte todo el texto en minúscula gracias a lower(). En ambas funciones se retorna el resultado del cambio.
  • El programa inicia cuando se solicita la usuario que introduzca un texto a formatear. Esa información se guarda en la variable t.
  • Se crea la variable t_may que recibirá el resultado de la función pasar_mayuscula, la que, más adelante, se imprime.
  • De igual forma se crea t_min recibe el resultado de pasar_minuscula, e igualmente la imprime.
Ejercicio Nro. 2: Desarrollar un programa que ejecute una de estas operaciones matemáticas: Suma, resta, división o multiplicación. Se debe crear un función que haga la operación y se le pase como parámetros los dos números y el nombre de la operación a realizar.

# -*- coding: utf-8 -*-
def operacion(num1, num2, oper = 'suma'):
    if oper.lower() == "suma":
        return num1 + num2
    elif oper.lower() == "resta":
        return num1 - num2
    elif oper.lower() == "division":
        return num1 / num2
    elif oper.lower() == "multiplicacion":
        return num1 * num2
    else:
        return "Operación inválida"

oper = input("Qué desea hacer: suma, resta, division o multiplicacion: ")
n1 = int(input("Ingrese el primer número: "))
n2 = int(input("Ingrese el segundo número: "))
result = operacion(n1, n2, oper)
print(result)

Explicación:
  • Se crea la función operacion que recibe como parámetro el num1, num2 y oper. Los dos primeros son los números con los que se hará el cálculo y el tercer recibe un string indicando la operación a realizar. La función formatea la variable oper en minúscula para pasar todo el texto en minúscula (esto se hace por si el usuario escriba la palabra de una forma diferente a la esperada, por ejemplo: Suma, SUMA, etc). A su vez, se verifica que operación indicó y se procede a retornar el cálculo. Si la opción que ingresó el usuario no coincide se retorna un texto indicando el error.
  • El programa inicia solicitando al usuario cual operación hará. Posteriormente solicitando los valores a operar. Al tener estos datos se llama la función y se pasa los datos como parámetro.
  • Al final se imprime los resultados.
Bien amigos. Con estos dos ejercicios concluimos este artículo. En el siguiente post estudiaremos la creación de Clase en Python.

lunes, 9 de septiembre de 2019

Módulo de Login y Registro en Python - Templates y menu

Con este post damos inicio al proyecto de desarrollo de un Módulo de Login y Registro de Usuario en Python. Como requerimiento es necesario que tengas instalado Python 3. Así como también recomendamos crees un entorno virtual para gestionar los paquetes que se instalarán en tu app. Si aun no sabes como crear un entorno virtual para tus proyectos, te sugerimos leas esto primero --> Creación de entorno virtual.

Hoy iniciaremos creando la estructura inicial de nuestro proyecto y desarrollando el template/menu para solicitar y mostrar la información por consola. El código presentado a continuación la puedes clonar desde mi cuenta github.

ESTRUCTURA DE LA APP

app  # paquete app
  |-> main.py  # archivo principal del app
  |-> menu.py  # funciones para los menús del app  
La estructura tiene un paquete llamado app. Dentro de él dos archivos .py, el main donde se concentrará la gestión principal de la app, y menu donde estará la clase y métodos para mostrar el menú y solicitar los datos al usuario.

DESARROLLANDO EL MENÚ

En el archivo menu.py se crea una clase para mostrar la plantillas y menú del proyecto. El código es el siguiente:
# -*- coding: utf-8 -*-
import os

class MenuTemplate():
    def __init__(self,
                 title = "Tratado Tecnologico"):
        self.title = title
        self.clean = "clear" if os.name == "posix" else "cls"
    
    def clean_screen(self):
        os.system(self.clean)

    def head(self):
        print("\t-------------------------------------------------------")
        print("\t\t\t{}".format(self.title))
        print("\t Bienvenido al Módulo de Lógin y Registro de Usuarios")
        print("\t-------------------------------------------------------")
 
    def main_menu(self):
        self.clean_screen()
        self.head()
        print("1.- Registrar")
        print("2.- Logear")
        print("3.- Salir")
        opcion = input("Seleccione una opción --> ")
        return opcion

    def register(self):
        self.clean_screen()
        self.head()
        username = input("Ingrese username: ")
        email = input("Ingrese email: ")
        password1 = input("Ingrese password: ")
        password2 = input("Repita el password: ")
        return {
            "username": username,
            "email": email,
            "password1": password1,
            "password2": password2,
        }

    def login(self):
        self.clean_screen()
        self.head()
        username = input("Ingrese username: ")
        password = input("Ingrese password: ")
        return {
            "username": username,
            "password": password,
        }

    def exit_text(self):
        self.clean_screen() 
        self.head()
        print("\n")
        print("Gracias por usar nuestro sistema")

 La clase se llama MenuTemplate cuyos métodos son los siguientes:
  • __init__: Es el constructor de la clase. Su función es inicializar el objeto; esto es, cuando se crea un objeto de esta clase se ejecutará este método de forma automática. Él recibe como argumento self y title, el primer argumento es la que permite a __init__ (y como se verá más adelante también a los demás métodos) acceder a los atributos y métodos de la clase; en sí es como si le pasáramos la clase como argumento. El argumento title es un dato que debe ser pasado cuando se instancia el clase y si no se pasa él tomará como valor por defecto el string "Tratado Tecnológico". Dentro del método se asigna el valor de title a un atributo de la clase con el mismo nombre (self.title); asimismo se le asigna a un atributo llamado clean el comando para limpiar la pantalla; para ello se usa el módulo os importado al inicio y se le pasa el comando dependiendo del sistema operativo donde se ejecute el programa (self.clean = "clear" if os.name == "posix" else "cls").
  • clean_screen: Con este método limpiamos la pantalla de la consola. Nótese que hace uso del atributo clean inicializado en __init__ y al ser llamado éste método se ejecuta un comando del sistema operativo para limpiar la consola (os.system(self.clean)).
  • head: Es un método genérico que mostrará un encabezado en los menú del proyecto. Como se puede observar él hace uso del atributo title para mostrarlo en el encabezado.
  • main_menu: Con esto se muestra el menú inicial donde se le pide al usuario que indique lo que desea hacer: 1.-Registrarse, 2.-Logear o 3.-Salir. Antes de mostrar este texto se limpia la pantalla y se imprime el encabezado. Fíjense que para acceder a los atributos y métodos de la clase se debe anteponer el prefijo self seguido de punto (.), es por ello que para usar el método clean_screen se hace self.clean_screen, lo mismo se hace para el método head. Posterioemente se captura la tecla presionada por el usuario mediante la instrucción input, la cual se asigna a la variable opcion para luego retornarla.
  • register: Este método permite al usuario ingresar los datos de registro: username, email, password1 y password2, y se retorna los datos como un diccionario.
  • login: El método login solicita los datos para logearse: username y password, para luego retornar la información en forma de diccionario.
  • exit_text: Con él se muestra un texto de despedida al salirse del programa.

CONSTRUYENDO EL MAIN
En el archivo main.py concentra la estructura del programa. Así es el código de este archivo:

# -*- coding: utf-8 -*-
import time
from menu import MenuTemplate

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

    if opcion == "1":
        data = menu.register()
        print("Los datos de registro son: ")
        print(data)
        time.sleep(2)
        main()
    elif opcion == "2":
        data = menu.login()
        print("Los datos de ingreso son: ")
        print(data)
        time.sleep(2)
        main()
    elif opcion == "3":
        menu.exit_text()
        time.sleep(2)
        menu.clean_screen()
    else: 
        print("\n Opción incorrecta. Intente de nuevo")
        time.sleep(2)
        main()

main()

Por los momentos se usará este archivo para validar la opción seleccionado por el usuario y para mostrar los datos que se han ingresado en el registro y login. Nótese que se tiene una función llamada main() (def main()), la cual se llama para que se ejecuté al final. Dentro de la función:
  • Se instancia la clase MenuTemplate(); para ello es necesario importarlo desde el archivo menu.py (from menu import MenuTemplate). Cuando se instanció la clase no se pasó el argumento title, por lo que tomará su valor por defecto. Sin embargo se pudo crear el objeto de esta forma MenuTemplate("Mi sistema") para asignarle el título al encabezado.
  • Se recibe la opción del menu inicial con opcion = menu.main_menu().
  • Se evalúa las opciones con los if. Si es una de las evaluadas se muestra los datos correspondiente; sino se vuelve a mostrar el menú inicial.
  • La función time.sleep hace que el programa se detenga por 2 segundos.
Para correr el programa habrá la consola, dirígete a path donde se encuentra el archivo main.py y ejecute python3 main.py.
Con esto concluimos esta post. Por los momentos sólo se ingresarán los datos y se mostrarán. En el siguiente post asignaremos esos datos en un arreglo y haremos algunas validaciones iniciales. 
Espero lo hayan disfrutado y sigan este tutorial. ¡Nos vemos!

sábado, 31 de agosto de 2019

Programación en Python - Control de Flujo (IF - WHILE - FOR)

Hola participantes. ¿Cómo se han sentido con el Curso? Recuerden que pueden dejar su comentarios o sugerencias al final del post. Hoy avanzaremos en un tema muy importante: Control de Flujo. Hasta los momentos hemos hecho programas lineales, donde se ejecutan instrucciones de forma secuencial. Aplicaciones desarrolladas de esta forma son de poca utilidad, ya que si deseamos tomar una decisión respecto a un dato, por los momentos, no podríamos hacerlo.

Los condicionales nos permiten dirigir el flujo del programa al evaluar una condición; haciendo que se comporte de una forma u otra, que ejecute un segmento de código o no. Así que para darle más vida a nuestros programas introduciremos condicionales en él. Qué te parece si estudiamos el primero de ellos.

IF - ELIF - ELSE

La forma más simple de evaluar una condición es con IF (Si inglés) seguido de la condición a evaluar y, para finalizar, dos puntos (:). Posteriormente debajo, en la siguiente línea e indentado, la instrucciones a ejecutar si se cumple la condición. Un ejemplo sería el siguiente: 

# -*- coding: utf-8 -*-
tu_web = "www.tratadotecnologico.com.ve"
if tu_web == "www.tratadotecnologico.com.ve":
    print("Tienes buen gusto")
Analizemos este código:
  • Primero creamos una variable llamada tu_web a la cual se le asigno el string de tu web favorita (www.tratadotecnologico.com.ve).
  • Luego se evalua el condicional de esta forma: Si tu_web es igual al texto "www.tratadotecnologico.com.ve" haz lo que sigue.
  • Lo que sigue es la impresión en pantalla del texto "Tienes buen gusto". Note que esta instrucción esta indentada (tabulada); esto se ha hecho de forma intencional y es la forma que tiene python de saber que esa instrucción pertenece a ese IF.
Te recomiendo que copies este código en un archivo .py y lo ejecutes o si utilizas Ninja IDE crea un nuevo archivo escribe el código y presiona Ctrl+F6; en el terminal que tiene integrado este IDE verás el resultado. Ahora si intentas modificar algo de texto que se asignó a la variable tu_web y ejecutas el programa no se mostrará algún texto pues no se cumple con la condición.

Ahora probemos mostrar otro texto si no se cumple la condición:

# -*- coding: utf-8 -*-
tu_web = "www.tratadotecnologico.com.ve"
if tu_web == "www.tratadotecnologico.com.ve":
    print("Tienes buen gusto")
if not tu_web == "www.tratadotecnologico.com.ve":
    print("Te recomiendo que visites www.tratadotecnologico.com.ve") 
Fíjate que ahora se agrega otro condicional en la que se evalúa si se cumple la condición: Si tu_web no es igual al texto "www.tratadotecnologico.com.ve" haz lo siguiente: Y lo siguiente es imprimir un texto de recomendación a nuestro blog.

Esto esta bien, pero cuando se evalúan dos posibles condiciones normalmente se utiliza la combinación IF - ELSE. Esto es: Si (IF) se cumple una condición haz esto Sino (ELSE) haz esto otro. Modifiquemos el código anterior para adaptarlo a esta nueva estructura:

# -*- coding: utf-8 -*-
tu_web = "www.tratadotecnologico.com.ve"
if tu_web == "www.tratadotecnologico.com.ve":
    print("Tienes buen gusto")
else:
    print("Te recomiendo que visites www.tratadotecnologico.com.ve")
Estudiemos éstas instrucciones:
  • Ya sabemos que en el if se evalúa que la variable tu_web sea igual al texto "www.tratadotecnologico.com.ve" y si esto es cierto se muestra el mensaje: "Tienes buen gusto".
  • La siguiente sentencia else indica que sino se cumple la condición anterior ejecuta las instrucciones de abajo.
Lo que hemos visto es funcional porque solo tenemos dos posibles alternativas, pero quisiéramos evaluar más de una condición. Pero si tenemos más de una condición podemos usar elif (que es una contracción de else if), esto quiere decir sino, si condición. Veamos un ejemplo para comprenderlo mejor.

# -*- coding: utf-8 -*-
numero = 0
if numero > 0:
    print("El número es positivo")
elif numero < 0:
    print("El número es negativo")
else:
    print("El número es cero") 
Nótese que ahora son tres evaluaciones:
  • Si el número es mayor a cero, se imprime el texto "El número es positivo".
  • Sino si el número es meno a cero, se imprime: "El número es negativo".
  • Sino se muestra: "El número es cero."
En este caso el else final serían las instrucciones que ejecutarán sino se cumplen las condiciones evaluadas previamente.

BUCLE WHILE

Hemos visto como lo condicionales nos permiten ejecutar cierto fragmento de código dependiendo de la evaluación de una condición; pero cómo haríamos si deseamos repetir ciertas líneas de instrucción mientras una condición se cumpla. La respuesta es: Usando instrucción de repetición (Bucles) como el WHILE y FOR.

El WHILE (mientras) nos permite ejecutar un segmento de código mientras una condición evaluada se cumpla. Como ejemplo veamos el siguiente código:

# -*- coding: utf-8 -*-
edad = 0
while edad < 18:
    edad = edad + 1
    print("Tu edad es "+str(edad)) 
Este ejemplo sencillo nos sirve para ilustrar el funcionamiento del WHILE.
  • Primero creamos una variable llamada edad la cual se inicializa con cero.
  • Posteriormente se coloca la instrucción while con la evaluación de la condición edad menor a 18. Esta condición pregunta si la variable edad es menor al 18. Si esto se cumple se ingresa al segmento de código debajo de ella.
  • Luego se introduce una razón de cambio de la variable que es evaluada. E este caso sumar uno a edad.
  • Finalmente se imprime en consola un texto indicando la edad.
  • Una vez se ejecuta la última instrucción dentro del bucle, se regresa al inicio a evaluar nuevamente la condición y si se cumplir se vuelve a correr las segmento de código interno.
  • Si ejecutamos este código mostrará el texto 18 veces, iniciando en la edad 1 hasta la 18.
  • Es importante tener en cuenta la razón de cambio dentro del bucle, ya que si no se cambia el valor de la variable a evaluar el bucle se ejecutará infinitamente. Por ejemplo, si quitamos la línea que suma uno a la variable edad, ella siempre valdría cero y cada vez que se evalúe la instrucción se cumplirá que cero es menor que dieciocho, haciendo que se corra para siempre (al menos que cierres el programa o apagues el PC).
Ahora, no siempre es un error que el bucle se ejecute infinitamente, es muy posible que esa sea nuestra intención. Entonces, si es así, bajo qué circunstancia queremos que el bucle nunca se termine. Supongamos que deseamos que se ejecute unas líneas de código hasta que el usuario teclee la palabra "salir". Creo que es mejor si lo codificamos ¿Les parece?

# -*- coding: utf-8 -*-
while True:
    palabra = input("Para terminar teclee 'salir' > ")
    if palabra == "salir":
        break 
    print(palabra)
Estudiemos este caso: El while True es una forma de decir ejecútate siempre. Entonces si es así, cómo haremos para que se termine, pues justamente después que se solicita que el usuario teclee una palabra, lo evaluamos con if con el string "salir", si esto se cumple se ejecuta la instrucción break. Es ésta instrucción la que rompe el bucle y lo fuerza a salir del while.

Otra forma de hacer esto es:

# -*- coding: utf-8 -*-
terminar = False
while not terminar:
    palabra = input("Para terminar teclee 'salir' > ")
    if palabra == "salir":
        terminar = True 
    print(palabra)
Si corremos este código obtendremos el mismo resultado, sin embargo nos ha permitido presentar la utilidad de la instrucción break dentro las sentencias de repetición. Otra instrucción que se usa dentro de los bucles es el continue; ésta no nos permite romper y salirse del bucle, pero sí pasar a la siguiente iteracción. Modifiquemos un poco el ejemplo anterior (El de mostrar las edades del 1 al 18) para mostrar solo las edades impares.

# -*- coding: utf-8 -*-
edad = 0
while edad < 18:
    edad = edad + 1
    if edad % 2 == 0:
        continue
    print("Tu edad es "+str(edad)) 
La diferencia de este código al anterior es que antes de mostrar el texto se evalúa la condición si el residuo de edad es igual a cero (en otras palabras esto verifica si el valor de edad es par), si esto se cumple entonces se ejecuta continue que no es más que indicarle a python que no siga con las instrucciones que siguen abajo y que pase a la siguiente iteracción.

BUCLE FOR

El for en Python es diferente a otros lenguajes de programación como C/C++. Acá esta pensado para iterar sobre una secuencia (listas, tuplas), y como tal intenta facilitar su uso para ese fin. La mejor forma de verlo es haciéndolo:

# -*- coding: utf-8 -*-
cuidades = ["Caracas", "Bogotá", "Quito", "Lima", "Santiago", "Buenos Aires"]
for ciudad in ciudades:
    print(ciudad) 
Como se digo anteriormente la sentencia for en python está pensado para iterar sobre secuencias. En el ejemplo anterior podríamos interpretar como para cada ciudad en ciudades hacer. En este caso lo que ocurre es que en cada iteracción se toma un dato de la lista cuidades y se asigna a la variable ciudad, para que luego dentro del for se manipule ese dato extraido. Acá simplemente se ha impreso el valor. Al ejecutar este pequeño código obtendremos la impresión de cada ciudad: Caracas, Bogotá, Quito, Lima, Santiago, Buenos Aires.

EJERCICIOS

Llegó el momento de ejercitarnos. La resolución de estos ejercicios los podrás encontrar acá.

Ejercicio Nro. 1: Crear un programa que solicite al usuario dos número e indique cual de los dos es el mayor.

# -*- coding: utf-8 -*-
#el_mayor.py
num1 = float(input("Ingrese el primer número: "))
num2 = float(input("Ingrese el segundo número: "))
if num1 > num2:
    print("El primer número es mayor que el segundo")
elif num1 < num2:
    print("El segundo número es mayor que el primero")
else:
    print("Ambos número son iguales")
Explicacion: 
  • Se crean dos variables num1 y num2 a las cuales se le asigna el valor que ingresa el usuario mediante la instrucción input(). El dato ingresado se formatea a float para tener el dato como un valor número real.
  • Con los condicionales se evalúa dos condiciones: Un primer if para saber si num1 es mayor a num2; un elif para evaluar si num2 es mayor a num1. Finalmente un else cómo condición final (sino se cumplen las dos condiciones anteriores).
Ejercicio Nro. 2: Creemos un programa que simule un juego de adivinanza en la que el usuario tenga tres intentos para adivinar un número del 1 al 100. Cada vez que usuario indique un número erróneo debemos decirle si es mayor o menor (según sea el caso) al número a adivinar. 

# -*- coding: utf-8 -*-
#juego_de_adivinanza.py
numero_adivinar = 55
intentos = 3
while True:
    num = int(input("Intente adivinar el número: "))
    if num == numero_adivinar:
        print("¡Felicitaciones lo ha logrado!")
        break
    elif num > numero_adivinar:
        print("Error: Este número es mayor al que debe adivinar")
    else:
        print("Error: Este número es menor al que debe adivinar")
    intentos = intentos - 1
    if intentos == 0:
        print("Ha usado todos sus intentos. Mejor suerte a la próxima")
        break
Explicación:
  • Iniciamos el programa creando dos variables numero_adivinar e intentos. La primero es el numero que el usuario debe adivinar y la segunda la cantidad de intentos que tiene el usuario para adivinar.
  • Dentro de un while que se ejecutará continuamente se solicita un número al usuario y se almacena en la variable num.
  • La variable se evalúa para saber si coincide con el valor de numero_adivinar; si esta condición se cumple se muestra un mensaje de felicitaciones y se rompe el bucle con break. De lo contrario se evalúa otras dos condiciones para saber si el dígito que ingreso es mayor o menor al número a adivinar.
  • Por último se disminuye en uno la variable intentos (intentos = intentos - 1) para descontar el intento actual y se consulta si la variable alcanzó el valor cero, es decir, si ya realizó tres intentos; de ser así se muestra un mensaje indicando que no lo logró y se rompe el bucle con break.
Ejercicio Nro. 3: Escribir un programa que muestre en pantalla el mensaje "Yo cursé <asignatura>  y obtuve <nota> pts.", donde asignatura y nota es una cátedra y su nota, respectivamente, de una lista creada por el programador de que contiene un diccionario con la información requerida.

# -*- coding: utf-8 -*-
#yo_estudio.py
asignaturas = [
    {"catedra":"Matemática", "nota": 18},
    {"catedra":"Física","nota": 16},
    {"catedra":"Química","nota": 19},
    {"catedra":"Inglés","nota": 17},
    {"catedra":"Historia","nota": 16},
    {"catedra":"Biología","nota": 18},
]
for asign in asignaturas:
    print("Yo cursé "+str(asign["catedra"])+" y obtuve "+str(asign["nota"])+ "pts.")
Explicación:
  • Lo primero que se hace es crear una lista cuyos datos será un diccionario que tiene la información de una cátedra y su nota. Por ejemplo la cátedra Matemática y su nota 18. Así ocurre con las otras materias.
  • Luego con un for iteramos la lista extrayendo uno a uno cada diccionario asignandolo a la variable asign
  • Posteriormente muestro el texto haciendo mención a cada clave del diccionario para obtener su valor asociado. Esto se logra haciendo asign["catedra"] para obtener la cátedra y asign["nota"] para obtener su nota.
Noten que en post anterior donde estudiamos las listas, tuplas y diccionarios no se hizo ejercicios, esto fue así porque para ver si aplicación es necesario combinarlo con alguna estructura de repetición como el for
Con estos ejemplos terminamos este capítulo. Espero les haya gustado. Si tienen alguna dudan puede hacérmelo saber mediante un comentario. Hasta pronto.