Linkero – Creación de APIs RESTful en Python (parte 1)

Según vas cogiendo experiencia como desarrollador de software te vas dando cuenta del verdadero significado de "No reinventar la rueda". Debido muchas veces a la falta de tiempo, es casi obligatorio buscar soluciones desde las que partir, o buscar integraciones de software que permitan alcanzar el objetivo marcado.

No obstante la mayoría se suele quedarse ahí y se contentan con reutilizar "piezas". El siguiente paso lógico y racional para ahorrar trabajo, pasa por diseñar. Por pensar y configurar un diseño que sea lo más generalista posible para que abarque el mayor número de casos de uso. Esta parte de diseño (y preferiblemente buen diseño) no suele ser frecuente entre desarrolladores, ya que suele implicar el gasto de más tiempo para ahorrarlo en un futuro. Debido a eso suele ser complicado trasladar a la dirección o al jefe inmediatamente superior y responsable del desarrollo, la necesidad de invertir más tiempo en un desarrollo para ahorrarlo en un futuro.

Hoy os traigo un caso de esos, en los que se juntan "piezas" y se refuerzan con un diseño para conformar una librería que ayude a un desarrollo más ágil, sujeto a un diseño flexible. Su nombre es Linkero:

https://github.com/RDCH106/linkero

link_slash

Linkero es un framework que permite la creación de manera sencilla de APIs RESTful para peticiones externas. Esta desarollado en Python y su diseño hace hincapié en los siguientes puntos:

  • Propósito general --> Se puede usar para cualquier tipo de API RESTful
  • Modular --> Se pueden agregar las API como si de módulos se tratasen quedando separadas del core de Linkero
  • Escalable --> Se pueden añadir nuevas APIs
  • Seguro --> Permite autenticar las peticiones por usuario y contraseña o mediante token de seguridad
  • Sencillo --> La codificación de las APIS y su securización es fácil de implementar.

La verdad es que originalmente el nombre de Linkero venía de link (unión en inglés) y el sufijo ero (que viene a significar "el  que"), es decir, el que une. Con el tiempo le vi una segunda connotación que tenía relación con el personaje Link de "The Lengedn of Zelda" y el juego de palabras link + hero (unión + héroe).

Como desde hace un tiempo, pienso que el Open Source es una vertiente totalmente Win&Win, decidí liberar el código fuente cuando consideré que el código estaba lo suficientemente maduro para empezar a recoger aportes. Uno de los mejores puntos que tiene el Open Source es su escrutinio público que ayuda a revelar fallos y la aportación de mejoras por otros usuario o empresas que usen dicho código y hayan implementado mejoras. Por contra, existe muchos usuario y sobre todo empresas que usan Open Source, pero no devuelven absolutamente NADA, si la licencia que acompaña el software lo permite. Es lícito hacerlo, no obstante la contrapartida real, es que tras cada mejora en el proyecto principal que se quiera integrar, resulta exponencialmente más difícil, ya que la mejoras introducidas no liberadas, pueden provocar divergencias con las actualizaciones y el diseño oficial.

Desde hace tiempo tengo la convicción de que en software, no se debería vender software tal cual, sino servicio, vender soluciones en vez de productos. Un mismo software o producto puede aplicarse para proveer diversidad de soluciones a problemas distintos e integrarse con otras tantas soluciones diferentes para solventar retos mayores. Esto es una visión totalmente personal ya que como Ingeniero Informático, lo he podido comprobar. El software es una herramienta. Yo proporciono soluciones a través de mi herramienta principal que es la creación de software y a su vez también uso software de terceros para proveer soluciones. Aclarada la visión personal y profesional de mi campo, volvamos al tema principal de la entrada 😉 .

Linkero cuenta con el siguiente diseño general:

linkero_general-scheme_800x600

Y hace uso de las siguientes librerías:

El esquema conceptual de dependencias refleja como únicas dependencias para crear una aplicación, el uso de Linkero y el uso de todas las APIs desarrolladas para Linkero que se quieran incluir. Linkero a su vez hace uso de las anteriormente citadas librerías. A su vez hace falta un "MAIN" que cargue las APIs y las ponga en funcionamiento con Linkero

Teniendo en mente el esquema puedes instalarte Linkero siguiendo las indicaciones de la Wiki:

https://github.com/RDCH106/linkero/wiki/instalacion

Si has seguido todos los pasos de la sección de instalación deberías poder probar Linkero usando el ejemplo testBasicAPI ejecutando el "testBasicAPI_main.py" desde Python.  Este ejemplo es una versión simplificada que a efectos ilustrativos no incluye temas de autenticación. Si analizamos el ejemplo, obtendremos la estructura mínima para desarrollar una API con Linkero que no requiera de autenticación.

Primero se importa el core de Linkero:

import core.linkero as linkero

A efectos demostrativos se crea una estructura de tareas pendientes:

# TODOS Data
TODOS = {
    'todo1': {'task': 'build an API'},
    'todo2': {'task': '?????'},
    'todo3': {'task': 'profit!'},
}

Se define una función que maneje las peticiones sobre objetos no existentes y se genera un objeto "parser" para las peticiones, al que se le añade el argumento "task":

def abort_if_todo_doesnt_exist(todo_id):
    if todo_id not in TODOS:
        linkero.abort(404, message="Todo {} doesn't exist".format(todo_id))

parser = linkero.reqparse.RequestParser()
parser.add_argument('task')

Posteriormente definimos los métodos que nos permitan manejar los items de las tareas por separado (métodos GET, DELETE, PUT). Los métodos GET y DELETE hacen  uso del método "abort_if_todo_doesnt_exist" para comprobar que se puede realizar la acción sobre el item específico y PUT hace uso del objeto "parser" para obtener el valor a introducir de la petición:

# Todo
# shows a single todo item and lets you delete a todo item
class Todo(linkero.Resource):
    def get(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        return TODOS[todo_id]

    def delete(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        del TODOS[todo_id]
        return '', 204

    def put(self, todo_id):
        args = parser.parse_args()
        task = {'task': args['task']}
        TODOS[todo_id] = task
        return task, 201

Es habitual ofrecer métodos que nos permitan manejar conjuntos de items. Por ello se definen métodos como pueden ser GET (obtener todas las tareas) o POST (incluir un grupo de tareas):

# TodoList
# shows a list of all todos, and lets you POST to add new tasks
class TodoList(linkero.Resource):
    def get(self):
        return TODOS

    def post(self):
        args = parser.parse_args()
        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
        todo_id = 'todo%i' % todo_id
        TODOS[todo_id] = {'task': args['task']}
        return TODOS[todo_id], 201

Una vez definida los métodos que permiten operar con la API, se define un método "loadTestBasicAPI" que asocia un patrón de URL a los métodos definidos:

##
## Actually setup the Api resource routing here
##
def loadTestBasicAPI():
    linkero.api.add_resource(TodoList, '/todos')
    linkero.api.add_resource(Todo, '/todos/<todo_id>')

Con la API completamente definida sólo queda cargarla y ejecutarla con Linkero mediante un "main.py" (para nuestro ejemplo es el "testBasicAPI_main.py"). Para ello se importa el core de Linkero y nuestra API (paso 1 y 2), se carga la API con el método "loadTestBasicAPI" (paso 3) y se invoca el método "run" de Linkero (paso 4):

# 1) Linkero Core
import core.linkero as linkero

# 2) APIs developed to use with Linkero
import examples.testBasicAPI

# 3) Load desired APIs
examples.testBasicAPI.loadTestBasicAPI()

# 4) Run Linkero
linkero.run()

Con estos simples pasos es posible realizar una API RESTful, definiendo la interfaz de la misma y su lógica en cómodos ficheros que pueden ser cargados y lanzados por Linkero. En la segunda parte de esta entrada se ahondará en la securización de la API, así como los aspectos que no se han podido tratar en esta primera parte.

Corregir colores ANSI en la consola de Windows 10 tras la actualización Windows Anniversary

La actualización de Windows Aniversario por fin llegó a mi equipo y aunque en general parece ser más robusto y seguro, era imposible no encontrarse con nuevos fallos introducidos a causa de esta enorme actualización. No obstante por la curva de fallos real del software, en cada actualización se introducen nuevos fallos no existentes anteriormente o mal solventados.

curva_fallos_software

Obviamente en esta actualización no iba a ser menos, dado el calado tan ambicioso de la actualización. El fallo que he detectado se corresponde a la interpretación de los colores ANSI de la consola de Windows. Concretamente el problema reside en que no se procesan los códigos ANSI de escape, que antes estaba activado por defecto gracias al flag de la consola ENABLE_VIRTUAL_TERMINAL_PROCESSING.

Esto supone un problema para todas las librerías y códigos de Python que usaban los colores ANSI para cambiar de color la letra y fondo de la consola de manera trasnaprente en todos los sistemas.

Ejemplo de la problemática que se empieza a notar en algunas librerías:

https://github.com/pyreadline/pyreadline/issues/46

Ante este problema se me ocurrieron 2 formas de arreglarlo.

 


Solución 1


cmder-main

La primera pasa por añadir un frontend a la maltrecha consola de comandos de Windows y pensé en Cmder por ser un proyecto que además es portable. De esta forma con un lanzador .BAT/.CMD es posible lanzar sobre Cmder lo que queramos y dejar a un lado la problemática de los colores.

La solución consiste en descargar Cmder en su versión portable y añadir el siguiente .BAT que nos permita lanzar Cmder y ejecutar comandos en su inicio:

@echo off
set CMDER_ROOT=%~dp0
start %CMDER_ROOT%\vendor\conemu-maximus5\ConEmu.exe /icon "%CMDER_ROOT%\cmder.exe" /title Cmder /loadcfgfile "%CMDER_ROOT%\config\ConEmu.xml" /cmd cmd /k "%CMDER_ROOT%\vendor\init.bat cd %CD% && cd .. && %~1"

Este archivo que puedes llamar "cmder.bat" debe estar al mismo nivel de directorio que el ejecutable "Cmder.exe". En este caso además se asume que Cmder está en una carpeta en la raíz del proyecto y la consola se posiciona en el nivel inmediatamente superior al que se encuentra "cmder.bat" y "Cmder.exe".

La forma de usar "cmder.bat" asumiendo que está en la carpeta "cmder" sería la siguiente:

start /B cmder.bat "python main.py" ^&^& timeout 5 ^&^& exit

En este caso se lanza un programa en Python, cuyo archivo de ejecución principal es "main.py". Con el parámetro "/B" se lanza la aplicación sin crear una nueva ventana. Adicionalmente se le indica con "^&^& timeout 5" que la consola espere 5 segundos antes de procesar la salida de la consola gracias a "^&^& exit". El resultado sería algo similar a esto:

cmder-launch

 


Solución 2


La segunda solución pasa por añadir un parche en la librería de Python que usa colores ANSI o en su defecto en el "main" de la aplicación. Las líneas de Python que debes añadir para corregir el problema específico de la interpretación de los colores ANSI de la consola de Windows tras la actualización de Windows Aniversario, es la siguiente:

import os
import platform

if os.name == 'nt' and platform.release() == '10' and platform.version() >= '10.0.14393':
    # Fix ANSI color in Windows 10 version 10.0.14393 (Windows Anniversary Update)
    import ctypes
    kernel32 = ctypes.windll.kernel32
    kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)

Gracias a la librería ctype que proporciona tipos de datos compatibles con C, se pueden hacer llamadas a DLLs o librerías compartidas. Esencialmente lo que se hace es habilitar la flag ENABLE_VIRTUAL_TERMINAL_PROCESSING.

De esta forma los colores de la consola pasan de verse así:

ansi_win_before

A verse así:

ansi_win_after

¡Mucho mejor!, ¿no?

Dejo a continuación la referencia al Fix completo publicado en Gist de GitHub:

ANSIColorFix.py

 

Ambas soluciones resuelven la problemática del color ANSI en la consola de Windows tras la actualización Aniversario del sistema. No obstante la primera solución puede ser menos invasiva al no requerir de cambios en el código, pero la segunda es una solución de enfoque más nativo, ofreciendo una solución transparente a los desarrolladores que hacen uso de las librerías afectadas por este problema.

Añadir fecha y hora al nombre de un archivo en la consola de comandos de Windows

Habitualmente suelo trabajar con la consola de comandos de Windows y con scripts .BAT. Una de las tareas mas habituales es generar outputs en forma de fichero y una buena práctica suele ser añadir al nombre del fichero la fecha y la hora.

Una forma sencilla de mostrar la fecha en la consola de comandos de Windows es poner el siguiente comando:

echo %date%

Y la forma de mostrar la hora es:

echo %time%

Sabiendo esto podemos pasar al siguiente paso para poder añadirlo al nombre de un fichero. Como has podido observar al obtener la fecha, la obtenemos con el signo "/" intercalado entre el día, mes y año. Del mismo modo la hora aparece separada por el signo ":" , para separar las horas, los minutos y los segundos. Además la parte de los segundo viene con "," para marcar los decimales para las décimas de segundo. Los tres símbolos citados, no están permitidos a la hora de incluirlos en un nombre de un fichero en Windows, por lo que procedemos a quitarlos de la siguiente manera:

echo MyFile_%date:~-4,4%-%date:~-7,2%-%date:~-10,2%_%time:~0,2%-%time:~3,2%-%time:~6,2%.txt

Si analizamos el código, lo que se hace es una concatenación de valores que se imprimen como nombre. La fecha se le da la vuelta porque a la hora de ordenar por nombre, a nombres iguales se puede ordenar por años con una simple ordenación por orden alfabético. En el caso de la hora no es necesario darle la vuelta.

Para cada variable date o time se cogen los caracteres que nos interesan de la siguiente forma:

%variable:~startIndex,numChars%

startIndex determina la posición desde la que se empiezan a coger los caracteres dentro de la variable. El índice empieza en 0 y también puede ser negativo el índice, indicando la posición de inicio desde el final de la variable.

numChars determina el número de caracteres que se van a coger desde el startIndex fijado.

cmd-win-append-date-time

De esta forma y extrapolándolo  a otros comando, es posible añadir fecha y hora, sólo fecha o solo hora al nombre de un fichero de un output de un comando.

Frase Memorable 6


Edward Snowden dijo,  “Decir que uno no se preocupa por el derecho a la privacidad, ya que no tiene nada que ocultar, no es diferente a decir que no importa la libertad de expresión porque uno no tiene nada que decir".