Aplicación Web con Python y Flet
Tutorial - Consumiendo una API Rest con Python & Flet
Table of contents
- Introducción
- Paso 1 - Descargar y ejecutar la API REST en Flask
- Paso 2 - Descargar el código base del cliente con Python y Flet
- Paso 3 - Agregar un dropdown y otros controles a la interfaz
- Paso 4 - Implementar la función dropdown_changed
- Paso 5 - Banner de Error
- Paso 6 - Cambie la aplicación de escritorio a Web
- Referencias
Introducción
En este tutorial, vamos a utilizar una API Rest que ofrece dos endpoints para cifrar y descifrar mensajes mediante el algoritmo RC4, conocido por su simplicidad. La API ya está desarrollada con Python y Flask, por lo que simplemente necesitamos descargar el código y ejecutarlo para comenzar.
El primer paso de este tutorial consiste en descargar el repositorio, ejecutar y probar el código del API. Este paso es fundamental para el resto del tutorial.
El objetivo principal de este tutorial es desarrollar una interfaz web que consuma esta API Rest con Python y el framework Flet. A través de esto, aprenderemos a consumir los endpoints del API de manera sencilla desde una aplicación web.
Es importante mencionar que esta API no requiere autenticación, lo que permite a nuestra aplicación acceder directamente a sus recursos. La aplicación en Flet se comunica con el backend en Flask mediante solicitudes HTTP.
La interfaz se verá similar a lo que se muestra en la siguiente imagen:
Paso 1 - Descargar y ejecutar la API REST en Flask
Por favor, descargue la API Rest desde el repositorio indicado a continuación:
git clone https://gitlab.com/jpadillaa/sasaki-python.git
Ejecute el comando pip install -r requirements.txt
para instalar las dependencias del proyecto (de preferencia utilice un entorno virtual):
pip install -r requirements.txt
Para ejecutar la aplicación en Windows sobre Powershell, utilice los siguientes comandos:
$env:FLASK_APP="app"
$env:FLASK_DEBUG=1
flask run --host=0.0.0.0
Si está trabajando en una terminal Linux con Bash, utilice los siguientes comandos:
export FLASK_APP=app
export FLASK_DEBUG=1
flask run --host=0.0.0.0
Para probar la API REST utilicé Curl o Postman (o una herramienta equivalente) para realizar solicitudes post a los endpoints disponibles.
Ruta Endpoint 1 [POST]:
http://ip_servidor:5000/cipher
Ruta Endpoint 2 [POST]:
http://ip_servidor:5000/decipher
Ambos endpoints requieren un JSON con el siguiente formato:
{"message" : "Texto a Cifrar/Descifrar", "key": "llave para Cifrar/Descifrar"}
A continuación, se presentan los endpoints disponibles en la aplicación:
Paso 2 - Descargar el código base del cliente con Python y Flet
Primero, descargaremos la plantilla del tutorial, una base de código con algunas funciones desarrolladas y un esquema base, para enfocarnos completamente en el desarrollo de la interfaz.
Descargue el siguiente repositorio:
git clone https://gitlab.com/jpadillaa/cipher-flet-interface
El repositorio incluye los siguientes archivos:
cipher.py
: Contiene dos funciones que consumen los servicios del API Rest utilizando la libreríarequests
de Python. Una de ellas realiza una solicitud POST al endpoint /cipher para cifrar un mensaje, mientras que la otra realiza una solicitud POST al endpoint /decipher para descifrar un mensaje.main.py
: Es un esqueleto que contiene elementos básicos en la funciónmain
para construir la interfaz con Flet.solucion.py
: Es un archivo que contiene la solución completa del tutorial, mostrando la interfaz desarrollada con Flet
Realice la instalación de Flet ejecutando el comando a continuación:
pip install flet
El esqueleto de la función main
tiene la siguiente estructura:
import flet as ft
import cipher as cp
def main(page: ft.Page):
page.title = "Basic Cipher/Decipher App"
page.window_width = 640
page.window_height = 480
page.window_resizable = False
page.padding = 48
page.margin = 48
message = ft.TextField(label="Message", autofocus = True)
passkey = ft.TextField(label="Passkey", color = "RED")
page.add(
message,
passkey,
)
ft.app(
target = main,
#view = ft.WEB_BROWSER,
)
Este bloque de código solo muestra la estructura básica de la interfaz. Faltan detalles como la implementación del cifrado y descifrado, la gestión de errores y la respuesta a las interacciones del usuario.
La interfaz de usuario está compuesta por Controles (widgets). Para que los controles sean visibles para un usuario, deben agregarse a una Page
o dentro de otros controles. El Page
es el control más alto. Anidar controles unos dentro de otros se puede representar como un árbol con el Page
como raíz.
En las líneas siguientes, se explica brevemente el código base del proyecto. Sin embargo, debido a la naturaleza simple de los controles y atributos agregados, esta explicación es sencilla. Para un entendimiento más detallado, se recomienda revisar el tutorial anterior sobre Flet.
Librerías importadas:
flet
: Librería para crear interfaces de usuario (IU).cipher
: Librería para cifrado y descifrado de datos utilizandorequets
.
Función principal:
main(page: ft.Page)
: Esta función define la estructura básica de la IU. page.title
: Establece el título de la ventana de la aplicación.page.window_width
ypage.window_height
: Define el tamaño de la ventana.page.window_resizable
: Indica si la ventana se puede redimensionar (fijo).page.padding
: Aumenta el espacio entre el borde de la ventana y los elementos de la IU.page.margin
: Añade margen entre los elementos de la IU.message
: Crea un campo de texto con la etiqueta Message y establece el foco de entrada.passkey
: Crea un campo de texto con la etiqueta Passkey y establece el color del texto en rojo.page.add
: Añade los elementosmessage
ypasskey
al objetoPage
.ft.app
: Lanza la aplicación de Flet, especificando la funciónmain
como punto de entrada.
Comentarios:
La línea #view = ft.WEB_BROWSER
está comentada, lo que significa que la aplicación se ejecutará de forma predeterminada en el escritorio y al final del tutorial cambiaremos esto para probarla en el navegador.
Si ejecutamos el programa, el resultado será similar a lo siguiente:
Paso 3 - Agregar un dropdown y otros controles a la interfaz
Este bloque de código dibuja nuevos controles en la ventana con Python y Flet. Ajuste el bloque de código de la función main
.
import flet as ft
import cipher as cp
def main(page: ft.Page):
page.title = "Basic Cipher/Decipher App"
page.window_width = 640
page.window_height = 480
page.window_resizable = False
page.padding = 48
page.margin = 48
def dropdown_changed(e):
pass
message = ft.TextField(label="Message", autofocus = True)
passkey = ft.TextField(label="Passkey", color = "RED")
encrypted_message = ft.TextField(label="Encrypted Message", autofocus = False, disabled = True, visible = False)
dropdown_chipher = ft.Dropdown(
on_change = dropdown_changed,
label = "Action Selector",
width = 320,
options=[
ft.dropdown.Option("Cipher"),
ft.dropdown.Option("Decipher"),
],
)
columns = ft.Column()
page.add(
message,
passkey,
dropdown_chipher,
columns,
encrypted_message
)
ft.app(
target = main,
#view = ft.WEB_BROWSER,
)
Analicemos línea por línea:
La función dropdown_changed
:
Esta función se define, pero por ahora no tiene ninguna implementación.
Esta función se ejecuta cuando se cambia la selección en el menú desplegable en la interfaz.
La función se encarga de manejar la selección del usuario para cifrar o descifrar un mensaje y ejecutar la acción correspondiente.
Declaración de nuevos controles:
encrypted_message
: Un campo de texto con la etiqueta "Mensaje cifrado", desactivado y oculto por defecto. Este campo se activará cuando un usuario cifre o descifre un mensaje en la interfaz.dropdown_chipher
: Es un objeto de tipodropdown
, un menú desplegable permite al usuario seleccionar entre varios elementos. En este caso eldropdown
muestra dos opciones: Cipher y Decipher.Es importante resaltar que para el
dropdown_chipher
se define un ancho de 320 píxeles.columns
: Un contenedor de tipo columna (vacío en este momento).page.add
: Añade todos estos nuevos elementos y los declarados previamente a la página en el orden indicado.
Comportamiento esperado del usuario con esta interfaz:
El usuario introduce un mensaje en el campo
message
.El usuario introduce una clave en el campo
passkey
.El usuario selecciona entre las opciones Cipher o Decipher en el menú desplegable.
La función
dropdown_changed
se ejecuta cuando se cambia la selección.El código dentro de la función
dropdown_changed
(aún no implementado) se encarga de:Utilizar la biblioteca
cipher
para invocar los endpoints del API para cifrar o descifrar el mensaje según la selección.Mostrar el resultado del cifrado/descifrado en el campo
encrypted_message
.
Si ejecutamos el programa, el resultado será similar a lo siguiente:
Paso 4 - Implementar la función dropdown_changed
La función dropdown_changed
se ejecuta cuando el usuario cambia la selección en el menú desplegable dropdown_chipher
. La función recibe un objeto e
como argumento, que contiene información sobre el evento que se ha producido.
El código dentro de la función dropdown_changed
debe realizar las siguientes tareas:
Obtener la selección actual del menú desplegable.
Determinar si la selección es Cipher o Decipher.
Utilizar la biblioteca
cipher
para cifrar o descifrar el mensaje según la selección.Mostrar el resultado del cifrado/descifrado en el campo
encrypted_message
.
Reemplace el bloque de código de la función dropdown_changed
por el siguiente:
def dropdown_changed(e):
if dropdown_chipher.value == "Cipher" and message.value != "" and passkey.value != "":
answ = cp.cipher_endpoint("http://127.0.0.1:5000", message.value, passkey.value)
encrypted_message.value = dict(answ)["ciphered"]
encrypted_message.disabled = False
encrypted_message.visible = True
encrypted_message.label="Encrypted Message"
encrypted_message.color = "PURPLE"
elif dropdown_chipher.value == "Decipher" and message.value != "" and passkey.value != "":
answ = cp.decipher_endpoint("http://127.0.0.1:5000", message.value, passkey.value)
encrypted_message.value = dict(answ)["deciphered"]
encrypted_message.disabled = False
encrypted_message.visible = True
encrypted_message.label="Decrypted Message"
encrypted_message.color = "BLUE"
else:
#page.banner.open = True
page.update()
message.value = ""
passkey.value = ""
page.update()
message.focus()
Los condicionales dentro de la función verifican la selección del menú entre las opciones y los valores de los campos Cipher y Decipher del dropdown creado previamente, llamado dropdown_cipher
. Además, válida si message
y passkey
no son campos vacíos, asegurando que dispongamos de la información suficiente para cifrar o descifrar un mensaje.
Si la selección es Cipher y los campos de mensaje y clave no están vacíos:
Realiza una llamada a la función
cipher_endpoint
de la libreríacipher
para cifrar el mensaje con la clave proporcionada.La función
cipher_endpoint
se comunica con un servidor externo en la direcciónhttp://127.0.0.1:5000
.El resultado del cifrado se almacena en el campo
encrypted_message
.El campo
encrypted_message
se habilita, se muestra y se le asigna la etiqueta Encrypted Message con color púrpura.
Si la selección es Decipher y los campos de mensaje y clave no están vacíos:
Realiza una llamada a la función
decipher_endpoint
de la libreríacipher
para descifrar el mensaje con la clave proporcionada.La función
decipher_endpoint
también se comunica con el servidor externo en la misma dirección.El resultado del descifrado se almacena en el campo
encrypted_message
.El campo
encrypted_message
se habilita, se muestra y se le asigna la etiqueta Decrypted Message con color azul.
Si la selección del menú es diferente o alguno de los campos de texto está vacío:
Se muestra una alerta en un banner (
page.banner.open = True
) en la página. Esta línea por el momento está comentada para que no nos genere ningún error, en el siguiente paso la vamos a habilitar.Se borran los valores de los campos de mensaje y clave (
message.value
ypasskey.value
), esto asignándoles una cadena de texto vacía.Se refresca la página con la instrucción
page.update()
.Se vuelve a establecer el foco en el campo de mensaje para volver a ingresar el texto con la instrucción
message.focus()
.
Analicemos algunos atributos que estamos modificando dentro de cada condicional:
encrypted_message.value = dict(answ)["ciphered"]
: Esta línea asigna el valor del diccionarioansw
con la claveciphered
al campo de textoencrypted_message
. En otras palabras, el texto cifrado obtenido de la respuesta del servidor se almacena en el campoencrypted_message
para ser visualizado en la interfaz al actualizar elPage
.encrypted_message.disabled = False
: Esta línea habilita el campo de textoencrypted_message
. Anteriormente, este campo podría estar deshabilitado para evitar la edición del texto cifrado.encrypted_message.visible = True
: Esta línea hace visible el campo de textoencrypted_message
. Inicialmente, este campo podría estar oculto hasta que el cifrado esté completo.encrypted_message.label="Encrypted Message"
: Esta línea cambia la etiqueta del campo de texto a Encrypted Message. Previamente, podría tener una etiqueta diferente (Decrypted Message) o ninguna.encrypted_message.color = "PURPLE"
: Esta línea cambia el color del texto en el campoencrypted_message
a púrpura. Esto podría servir para resaltar el texto cifrado y diferenciarlo del texto normal.
Si ejecutamos el programa, el resultado será similar a lo siguiente:
Como puede observar en este punto, la aplicación consume la API y es capaz de cifrar y descifrar mensajes.
Paso 5 - Banner de Error
El else
restante en las condiciones de la función dropdown_changed
debe gestionar el caso en el que el usuario seleccione una acción, ya sea cifrar o descifrar, sin haber ingresado la información respectiva para realizar la tarea: un mensaje y una llave.
Agregue este bloque de código antes de la función dropdown_changed
para crear un banner que notifique al usuario que antes de realizar una acción debe ingresar un mensaje y una llave.
def close_banner(e):
page.banner.open = False
page.update()
page.banner = ft.Banner(
bgcolor = ft.colors.RED,
leading = ft.Icon(ft.icons.WARNING_AMBER_ROUNDED, color=ft.colors.AMBER, size = 40),
content = ft.Text(
"Oops, there were some errors while trying to encrypt or decrypt a message without the complete data (message and passkey)"
),
actions = [
ft.TextButton("Cancel", on_click = close_banner),
],
)
Este bloque de código define un banner de error que se muestra cuando ocurre un error al seleccionar una acción en el dropdown_cipher
sin haber ingresado un mensaje y una llave en la interfaz previamente. El banner contiene un icono de advertencia, un mensaje de error y un botón para cerrarlo. Al hacer clic en el botón, se oculta el banner y se actualiza la página.
La funciónclose_banner
:
def close_banner(e):
: Define una función llamadaclose_banner
que recibe un argumentoe
(el evento).page.banner.open = False
: Esta línea establece la propiedadopen
del banner de la página aFalse
, lo que lo oculta.page.update()
: Esta línea actualiza la página para reflejar los cambios realizados, es decir, ocultar el banner.
Creación del banner:
page.banner = ft.Banner(...):
: Esta línea crea un objeto de tipoBanner
y lo asigna a la propiedadbanner
de la página o elPage
.bgcolor = ft.colors.RED
: Establece el color de fondo del banner a rojo.leading = ft.Icon(ft.icons.WARNING_AMBER_ROUNDED, color=ft.colors.AMBER, size = 40)
: Define un icono de advertencia de color ámbar y un tamaño de 40 pixeles.content = ft.Text(...):
: Define el contenido del banner como un texto que indica que hubo un error al intentar realizar una operación sin indicar un mensaje y una llave en la interfaz.actions = [ft.TextButton("Cancel", on_click = close_banner)]
: Define un botón de texto con la etiqueta Cancel que al ser presionado ejecuta la funciónclose_banner
para ocultar el banner.
Ahora, regrese a la funcion dropdown_changed
y en el bloque else, elimine el comentario a la instrucción page.banner.open = True
.
else:
page.banner.open = True
page.update()
Si ejecutamos el programa, el resultado será similar a lo siguiente:
Paso 6 - Cambie la aplicación de escritorio a Web
Modifique este bloque de código al final del archivo main.py
.
ft.app(
target = main,
view = ft.WEB_BROWSER,
)
Si ejecutamos el programa, el resultado será similar a lo siguiente: