Pasar a producción un API en python con flask / connexion

Hacer un servicio que sirva un API en python es muy, muy sencillo usando flask o connexion. Mi método favorito de hoy (esto cambia día a día) es usar connexion que nos permite crear un servidor teniendo su definición en un archivo openapi con simplemente tres líneas de código:

app = connexion.App(__name__,specification_dir='./')
CORS(app.app)
app.add_api('openapi.yml')
app.run(port=8080)

Con estas cuatro líneas de código (además de los imports correspondientes) ya tendríamos un servidor web en el puerto 8080 de la máquina. Luego basta con tener en el openapi.yml definido el nombre de las operationId e implementarlas adecuadamente… Me tienta, pero en este momento no voy a hacer un tutorial sobre connexion o sobre cómo generar un proyecto python usable. Para esta entrada supongamos que ya tenemos el servidor implementado y corriendo… Veremos que cuando lo arrancamos nos muestra este mensaje:

 * Serving Flask app 'app'
 * Debug mode: off
2024-01-25 20:57:13,203 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8080
 * Running on http://172.18.0.20:8080
2024-01-25 20:57:13,204 - werkzeug - INFO - Press CTRL+C to quit

Si nos fijamos nos está advirtiendo de que el servidor que está usando es el propio de Flask y no es muy adecuado para entornos de producción. ¿Qué significa esto? Que si recibe muchas peticiones el tiempo de proceso de las mismas va a ser muy alto. El mismo mensaje nos recomienda usar un servidor WSGI para entornos de producción.

De hecho, si estás ejecutando una versión más moderna de connexion te aparecerá este mensaje en su lugar:

2024-01-25 22:05:31,351 - connexion.middleware.main - WARNING - `ConnexionMiddleware.run` is optimized for development. For production, run using a dedicated ASGI server.

Que es lo mismo pero con ASGI en lugar de WSGI. La receta para ejecutarlo mas en «producción» en este caso sería instalar gunicorn y lanzarlo con un tipo de worker ASGI (uvicorn en este caso) teniendo en cuenta que app está definido dentro del archivo app.py:.

pip install gunicorn
gunicorn -k uvicorn.workers.UvicornWorker --bind "0.0.0.0:8080" app:app

Con eso ya lanzamos un proceso, si queremos lanzar más lo podemos hacer con el parámetro -w poniendo, por ejemplo:

gunicorn -k uvicorn.workers.UvicornWorker -w 4 --bind "0.0.0.0:8080" app:app

Con esto ya se lanza 4 procesos para dar servicio al API… Esto debería mejorarlo.

Y una cosa más… Si tenéis algún proceso en la aplicación que no deba replicarse con cada worker (un scheduler por ejemplo), tenéis que lanzar gunicorn con el parámetro --preload para que se ejecute solo una instancia de la aplicación (de todo lo que no dependa de las peticiones)

Hacer un bot de telegram sin usar ninguna librería de telegram

Entiendo que conoces la aplicación telegram, lleva con nosotros mucho tiempo y, aunque en nuestro país es mucho más usada whatsapp, telegram tiene también sus adeptos, sobre todo para canales de distribución o para uso secundario con aquellos que ya usan esta herramienta.

Una de las características más importantes de esta plataforma es la separación entre usuarios y bots, los usuarios tienen que tener un número de teléfono registrado y no es tan sencillo hacerse con una cuenta adicional si no tienes otro teléfono, la plataforma distingue perfectamente entre personas y bots, que serían los programas a los que solo se permite interacturar con personas que hayan hablado con ellos antes o con canales de distribución a los que se hayan añadido como administradores (antes podían añadirse como miembros, pero esa posibilidad ya no existe). El caso es que llevo un tiempo desarrollando bots para esta plataforma, usando diversas librerías y después de tantear todos los límites de telegram y de las librerías me he visto obligado a ir directo al api de telegram para bots… Y resulta que no solo es sencillo, sino muy sencillo iniciarse en la creación de bots sin ninguna librería adicional… En este caso vamos a crear un bot de cero y sin librerías de por medio.

Pero, como decía Jack el destripador: vayamos por partes…

Lo primero es crear un bot, telegram pone a nuestra disposición el bot llamado botfather que nos permite crear bots asociados a nuestra cuenta (un máximo de 20 por si os sentís tentados de crear armadas de bots) y se crean simplemente con el comando /newbot y responder unas pocas preguntas… Como podemos ver en la imagen:

Lo importante, y fundamental, es el token que nos genera botfather, esto será todo lo que necesitamos para acceder al api, que como bien nos indican está en https://core.telegram.org/bots/api

Lo segundo será decidir en qué lenguaje y para qué plataforma vamos a crear nuestro bot, en nuestro caso y para simplificar las cosas vamos a hacerlo en python que puede correr en cualquier sitio (linux, mac, windows o incluso serverless), en mi caso vamos a crear un bot que, cuando se lo pedimos, nos genere una nueva contraseña (luego ya veremos si queremos mejorarlo). Por cierto, todo el código que generemos aquí estará en el repositorio https://github.com/yoprogramo/nomorepassbot para que podáis usarlo y modificarlo a voluntad.

Hemos dicho que no tendría dependencias de ninguna librería telegram, pero no hemos dicho que no la tuviese de alguna otra, así que es momento de decidir si queremos usar alguna librería http, en mi caso, por simplicidad he elegido la librería httpx, como tampoco quiero ir instalando cosas de más voy a utilizar pipenv para crear mi entorno de trabajo (vosotros podéis hacerlo como queráis)

La lógica del programa será sencilla, simplemente preguntaremos periódicamente a telegram si tenemos updates, y si los tenemos, miraremos a ver si alguna petición contiene la cadena «password» y si la contiene devolveremos una nueva contraseña generada por nosotros en ese momento. Para recuperar los updates haremos un bucle infinito en donde llamaremos a la función getUpdates y si recibimos elementos los recorreremos uno a uno para tratarlos.

La función para generar una contraseña aleatoria será esta:

def generatePassword(num=10):
    # Generamos una contraseña aleatoria de num caracteres
    letras = string.ascii_letters + string.digits
    password = ''.join(random.choice(letras) for i in range(num))
    return password

Lo primero que tenemos que hacer es dar un valor a nuestro token y establecer la url del api de telegram:

token = 'TOKEN'
url_base = f'https://api.telegram.org/bot{token}/'

Y la función que recupera las actualizaciones será, por tanto esta:

r = httpx.get(url_base + 'getUpdates')
    updates = r.json()['result']

La función para enviar un mensaje a un usuario (o a un chat) se llama sendMessage y recibe como parámetros (entre otros) chat_id y text, así que poniendo todo junto nos quedaría esta función:

while True:
    r = httpx.get(url_base + 'getUpdates')
    updates = r.json()['result']
    for update in updates:
        if 'message' in update:
            message = update['message']
            if 'text' in message:
                text = message['text']
                if 'from' in update['message']:
                    user = update['message']['from']
                    if 'password' in text:
                        respuesta = 'La contraseña es ' + generatePassword(10)
                        r = httpx.post(url_base + 'sendMessage', data={'chat_id': user['id'], 'text': respuesta})
                else:
                    continue 
    time.sleep(1) 

Si ejecutamos esto (y tenemos el token que nos ha dado godfather) ya podremos pedir la contraseña y recibiremos respuesta (al hablar con nuestro bot, claro):

Claro que si dejamos el código así el bot se empeñará en darnos contraseñas una detrás de otra sin parar, y es que telegram no sabe si hemos tratado o no el update que nos ha enviado, para ello hay que usar el parámetro offset para indicar cual será la siguiente actualización a tratar y cambiando el código un poco nos quedaría:

offset = 0
while True:
    r = httpx.get(url_base + 'getUpdates', params={'offset': offset+1})
    updates = r.json()['result']
    for update in updates:
        offset = update['update_id']
        if 'message' in update:
            message = update['message']
            if 'text' in message:
                text = message['text']
                if 'from' in update['message']:
                    user = update['message']['from']
                    if 'password' in text:
                        respuesta = 'La contraseña es ' + generatePassword(10)
                        r = httpx.post(url_base + 'sendMessage', data={'chat_id': user['id'], 'text': respuesta})
                else:
                    continue
    time.sleep(1)

Obviamente nos falta tratar las respuestas incorrectas y los timeouts y demás (y alguna respuesta adicional que nos puede dar telegram si hacemos demasiadas peticiones), pero eso lo dejo para que lo mejoréis en el repositorio https://github.com/yoprogramo/nomorepassbot

La deuda técnica

La deuda técnica es un tema importante a considerar en el desarrollo de software. Se refiere a la acumulación de tareas pendientes, como la documentación, el mantenimiento, el refactorizado y la eliminación de código obsoleto. A medida que la deuda técnica aumenta, se vuelve cada vez más difícil y costoso mantener y mejorar el software.

Robot arreglando código

En mi experiencia, hay varias razones por las que la deuda técnica puede acumularse. Una de las principales es la falta de tiempo para dedicar a tareas no relacionadas con el desarrollo de nuevas funcionalidades. A menudo, se priorizan las tareas que producen resultados visibles a corto plazo en lugar de las que tienen un impacto a largo plazo.

Otra razón es la falta de disciplina en el desarrollo de software. A veces, los desarrolladores se sienten presionados por plazos ajustados y optan por escribir código rápido y sucio en lugar de tomarse el tiempo para refactorizar y documentar adecuadamente.

La solución para reducir la deuda técnica es simple pero no es fácil. Es necesario dedicar tiempo y recursos para abordar las tareas pendientes. Esto puede incluir dedicar tiempo cada semana para el mantenimiento y la documentación, o incluso asignar un equipo específico para abordar la deuda técnica.

Además, es importante fomentar una cultura de disciplina y responsabilidad en el equipo de desarrollo. Esto puede incluir establecer estándares de calidad y proporcionar retroalimentación continua sobre el cumplimiento de estos estándares.

En resumen, la deuda técnica es un problema común en el desarrollo de software, pero puede ser manejado si se toman medidas proactivas para abordarlo. Dedicar tiempo y recursos, y fomentar una cultura de disciplina y responsabilidad, son fundamentales para reducir la deuda técnica y mejorar la calidad del software.

Cómo usar raspberry pi pico con PlatformIO

Hace tiempo que trasteo con varias placas de desarrollo que intento integrar en distintos proyectos de IoT, la mayoría relacionados con nomorekeys, el caso es que generalmente he utilizado arduino (el pro micro es mi favorito) o el ESP32 cuando necesito wifi (antes el ESP8266). Hay muchos otros por ahi, como el seeeduino xiao y muchos de Nordic.

Raspberry pi pico pinout

El caso es que tengo mucho código escrito en C/C++ para el entorno Arduino y, resulta, que puedo reutilizarlo en distintos procesadores utilizando un plugin para visual studio code llamado PlatformIO, lo que he ido haciendo para los distintos arduino, ESP y Seeeduino, Hace relativamente poco tiempo me decidí a probar el nuevo MCU de Raspberry, el Raspberry Pi Pico, pero me centre en usarlo con CircuitPython, teniendo unos resultados excelentes y que os recomiendo probar.

Llegado el caso necesité más memoria para un proyecto que inicialmente estaba codificado para arduino pro micro ya que este solo posee 2,5k de memoria RAM y se quedaba muy corta. Revisando lo que tenía por casa me di cuenta que tenía un par de rpi pico por ahí de las pruebas con python y me di cuenta que tenían 264k de memoria (x100 lo que tiene un arduino), tampoco andan mal de precio y tienen todos los pines de entrada salida que necesitaba, así que, manos a la obra… Vamos a ver si podemos adaptar el código de arduino a la pico… Usando PlatformIO.

No voy a entrar ahora mismo ni en cómo instalar platformio ni en como crear un proyecto, eso os lo dejo para vosotros o si me lo pedís lo esccribo más adelante, por ahora partiremos de que eso ya lo has hecho.

Si ya tienes un proyecto hecho con platformio, enhorabuena, todos los cambios que tendrás que hacer es incluir esto en tu platformio.ini:

[env:pico]
platform = raspberrypi
board = pico
framework = arduino

Recuerda poner el #include <Arduino.h> si estás importando un sketch del ide de arduino y ya estaría…

La primera vez que quieras subir el código a la placa tendrás que copiar el archivo firmware.uf2 después de haber puesto en modo boot la placa (enchufala al ordenador pulsando el botón de la misma), yo lo hago con este comando (uso linux)

cp .pio/build/pico/firmware.uf2 /media/$USER/RPI-RP2/

Las siguientes veces ya no hará falta, puedes dar al botón upload directamente y el código compilado se subirá a la placa.

Happy coding!

Gestión de versiones de Flutter

Flutter es un framework que me está gustando bastante, es muy consistente en cuanto a las distintas versiones, se ve muy similar en todas las plataformas y Dart como lenguaje es bastante interesante y nada esotérico (por eso de que usa cosas que ya manejamos y no se dedica a reinventar la rueda).

Flutter Version Management

Tanto es así que empezamos hace unos meses un proyecto de aplicación móvil con Flutter, coincidiendo con la llegada de la versión 2.0 y las cosas empezaron a ir «demasiado deprisa». La comunidad y los mantenedores del sdk parece que se han puesto las pilas y han decidido incluir mejoras a un ritmo trepidante, cuando escribo estas líneas ya vamos por la versión 2.8.1.

Nosotros estabilizamos la app en un contenedor docker usando la versión 2.5.2 de Flutter y nos encontramos ahora que no hay manera de instalar desde cero esa versión con los sistemas que proporciona Flutter. Todos aquellos sistemas en los que hicimos un upgrade ya no los podemos utilizar para desarrollo porque, entre otras cosas, han cambiado librerías y ya no son compatibles algunas partes de nuestro código.

Pero no soy el único con ese problema. Varios de los desarrolladores de Flutter se han encontrado con la misma situación y, afortunadamente, han desarrollado una manera de poder disponer de una versión propia de flutter para cada proyecto… Mediante FVM.

Os cuento los pasos básicos para tener FVM funcionando (partiendo de que has instalado flutter en tu sistema) y cómo usarlo para cada proyecto:

1. Instalar FVM

dart pub global activate fvm

Esto te instala el paquete, pero tendrás que cambiar el entorno para poder acceder al comando fvm cada vez, en mi caso es algo como:

export PATH="$PATH":"$HOME/.pub-cache/bin"

Añadiendo esta línea al final del ~/.bashrc conseguiréis meter en el path el comando FVM

2. Instalar una versión de flutter para su uso posterior

En mi caso quería instalar la versión 2.5.2 y tuve que ejecutar esto:

fvm install 2.5.2

Puedes instalar tantas como quieras (o necesites), puedes conseguir la lista de todas las instaladas con el comando fvm list

3. Usar una versión concreta en tu proyecto

Dentro del directorio del proyecto en el que quieras usar esta versión puedes sustituir el uso del comando flutter por el comando fvm flutter que te ejecutará la versión correspondiente, para indicar qué versión quieres usar debes escribir:

fvm use 2.5.2

Esto te genera un directorio .fvm en donde se almacenarán los enlaces correspondientes, no olvides incluir en tu .gitignore el directorio .fvm/flutter_sdk

Con esto y algunas cosillas más (os dejo consultar la documentación) ya podréis desarrollar y depurar con la versión de flutter que queráis.