Hug framework

en Curso Python, Programación, Python

Crear APIs REST con Python y Hug

Crear APIs REST con Python y Hug

Hola a todos, han sido unas semanas muy divertidas, en las cuales he ido aprendiendo a ser padre, por lo que deje un poco olvidado el blog; pero creí conveniente dedicarle un tiempo. En esta entrada, vamos a aprender a crear APIs REST con Python y Hug, un framework que llevo un tiempo utilizando en mis proyectos, y que me ha dejado un buen sabor de boca, debido a su rendimiento y facilidad para obtener productos finales.

Mucho bla, bla, bla pero…¿Qué es Hug?

Sus desarrolladores lo explican claramente en su sitio web.

Embrace the APIs of the future

Drastically simplify API development over multiple interfaces. With hug, design and develop your API once, then expose it however your clients need to consume it. Be it locally, over HTTP, or through the command line – hug is the fastest and most modern way to create APIs on Python3.

Debo decir que Hug tiene algunos puntos a resaltar frente a otras opciones para realizar la tarea de creación de APIs con Python; los cuales son:

  • Genera código obvio, limpio y radicalmente simple.
  • Tiene un rendimiento sin igual. (Esto ya que se compila utilizando Cython, obteniendo todas las ventajas que esto conlleva)
  • Incorpora una sencilla y eficiente gestión de versiones para nuestras APIs. (Podemos exponer múltiples versiones de un API de forma sencilla)
  • Genera documentación automáticamente. (Gracias a que se vale de las cadenas de documentación y anotaciones de tipo que incluyamos en nuestro código)
  • Realiza validaciones utilizando las anotaciones de tipo.
  • Sigue la ideología de “Escribe una vez, usa en todas partes”, ya que separa el API y la lógica de negocio que la expone, lo que significa que puede exponerse utilizando HTTP, línea de comandos y el propio intérprete de Python de una sola vez.

Este último punto es el que vamos a explotar en esta entrada.

Bien, manos al teclado

Para instalar Hug, necesitamos una versión de Python3.3+ instalada y funcionando (que imagino ya tienes) y ejecutar el siguiente comando:

pip install hug -U

Ya que se encuentra instalada, vamos a crear un API que únicamente tendrá acceso local.  Esto lo realizamos utilizando el decorador de ruta @hug.local que Hug nos provee. Con esto vamos a poder observar un par de caracterísitcas de Hug; la validación basada en anotaciones y directivas. Esta primer API va a retornar un mensaje de saludo al usuario y calculará su año de nacimiento basado en su edad, así como el tiempo que le tomó a Hug generar el mensaje.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Primer método: acceso local"""
import datetime
import hug


@hug.local()
def say_hello(name: hug.types.text, age: hug.types.number, hug_timer=3):
    """Decimos hola al usuario y calculamos su año de nacimiento"""
    year_of_birth = datetime.datetime.now().year - age
    return {
        'message': "Hola {0}, naciste el año {1}".format(name, year_of_birth),
        'took': float(hug_timer)
    }


if __name__ == '__main__':
    print(say_hello("Gabriel", 23))

Basta con ejecutar nuestro archivo para tener algo parecido a esto:

{'message': 'Hola Gabriel, naciste el año 1994', 'took': 0.0}

Lo sé, el calculo del año de nacimiento deja mucho que desear, pero recuerda el objetivo del post, ver las bondades de hug 😀

También podemos utilizar este archivo como un módulo, con las ventajas que trabajar de esta manera nos proporciona, entonces desde otro archivo podríamos llamar a nuestro método say_hello de la siguiente manera:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from example_local import say_hello

print(say_hello("Mushr00m_Dev", 23))

En mi caso, mi archivo se llama example_local, con lo que desde mi archivo app importé el método y pude utilizarlo sin problema. Aún no te convence Hug, bien, ahora veamos otro de sus puntos fuertes, la validación basada en tipos.

Si fuiste observador en la definición de nuestro método say_hello, establecimos que iba a recibir 2 parámetros; name que definimos que debe ser de tipo texto (hug.types.text) y age que debe ser un número (hug.types.number), el parámetro hug_timer, es interno de hug y solo es necesario cuando se desea obtener el tiempo que tarda la respuesta en procesarse.

Entonces, ¿qué ocurre si me olvidó de llamar mi método sin, digamos, el parámetro age? Sencillamente obtendremos un mensaje como el siguiente.

{'errors': {'age': "Required parameter 'age' not supplied"}}

Increíble lo sencillo que es, ¿cierto? Hug realiza esto en el fondo, transformando sus anotaciones integradas de tipos y reemplaza hug_timer con un objeto HugTimer.

Exponiendo nuestra API como un servicio HTTP

Para lograr esto, debemos aplicar un decorador de ruta HTTP que Hug integra a nuestra función say_hello además del decorador local. Hug incluye de manera conveniente, decoradores para cada una de las métodos HTTP (GET, POST, PUT, etc). Para el ejemplo, voy a añadir un decorador de tipo GET. Además, voy a añadir una muestra de parámetros, esto nos sirve para mostrarle el camino correcto a nuestros usuarios.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Segundo método: acceso http"""
import datetime
import hug


@hug.get(examples="name=Jhon Doe&age=30")
@hug.local()
def say_hello(name: hug.types.text, age: hug.types.number, hug_timer=3):
    """Decimos hola al usuario y calculamos su año de nacimiento"""
    year_of_birth = datetime.datetime.now().year - age
    return {
        'message': "Hola {0}, naciste el año {1}".format(name, year_of_birth),
        'took': float(hug_timer)
    }


if __name__ == '__main__':
    print(say_hello("Gabriel", 23))

Entonces, lo que debemos hacer es ejecutar el siguiente comando para que nuestra APi quede expuesta:

hug -f example_http.py

Y veremos un resultado en nuestra línea de comandos similar a esto:

Crear APIs REST con Python y HugComo se aprecia en la imagen, Hug levantó un servidor por nosotros y se encuentra a la escucha en el puerto :8000. Si visualizamos el resultado en nuestro navegador, debe aparecer algo como lo siguiente (estoy utilizando Firefox):

http resultHug, como comentamos anteriormente, genera está información basado en las docs strings y las anotaciones de tipo de nuestro código. Un punto importante aquí, es que podemos apreciar los endpoints (handlers) que hemos creado, en nuestro caso únicamente tenemos el endpoint /say_hello. Si analizamos esta información, apreciamos que nos señala el método utilizado para realizar una petición, una breve descripción de su uso, y el ejemplo que colocamos en nuestro código, entonces accedamos al ejemplo para ver que nos devuelve Hug.

http result 2¿Y si olvidarámos poner el parámetro age ?

http result 3Bien, pero…¿si el valor de age no fuera un número? (hackers everywhere xD)

http result 4Utilizando nuestra API desde la línea de comandos

Nuevamente, esto se logra de forma sencilla, sólo debemos añadir a nuestro código el decorador @hug.cli() que Hug nos proporciona:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Tercer método: acceso cli"""
import datetime
import hug


@hug.cli()
@hug.get(examples="name=Jhon Doe&age=30")
@hug.local()
def say_hello(name: hug.types.text, age: hug.types.number, hug_timer=3):
    """Decimos hola al usuario y calculamos su año de nacimiento"""
    year_of_birth = datetime.datetime.now().year - age
    return {
        'message': "Hola {0}, naciste el año {1}".format(name, year_of_birth),
        'took': float(hug_timer)
    }


if __name__ == '__main__':
    print(say_hello("Gabriel", 23))

 

Y es tan sencillo de probar. Ejecuta este comando en tu cli:

hug -f example_cli.py -c say_hello "Grabriel Cueto" 23

 

Dónde tienen que substituir say_hello por el nombre del método (endpoint) al que desean realizar la petición, seguido de los parámetros a enviar. Y debemos apreciar la misma respuesta que en las ocasiones anteriores.

Si por alguna razón, no podemos acceder al código y queremos saber los endpoints con los que cuenta nuestro código, lo podemos llevar a cabo desde la línea de comandos, sólo necesitamos ejecutar lo siguiente:

hug -f example_cli.py -c help 

 

Donde es necesario substituir example_cli.py por el nombre de tu script, y obtendremos una salida como la siguiente, la cual representa la lista de métodos disponibles:cli_method

Nota: Puesta en producción del método HTTP

Es importante que tengas en cuenta utilizar servidores de desarrollo (como el provisto por Hug, Flask, etc) directamente en producción. Ya que no están pensados para este tipo de ambientes y pueden no tener el comportamiento deseado. En su lugar, un servidor WSGI-compatible (como gunicorn o uwsgi) son las opciones recomendadas, en mi caso utilizo gunicorn.

Cada API desarrollada con Hug, que cuenta con un endpoint HTTP expone de forma automática un API __hug_wsgi__ compatible con WSGI. Haciendo que nuestro ejemplo sea sencillo de exponer al mundo. Basta ejecutar el siguiente comando:


# Usando uwsgi

uwsgi --http 0.0.0.0:8000 --wsgi-file example_http.py --callable __hug_wsgi__

# O bien, utilizando gunicorn

gunicorn example_http:__hug_wsgi__

Creo que es todo por ahora, si gustan saber más al respecto de Hug, pueden visitar su documentación, que siendo honestos es bastante clara. Recuerda que todo el código fuente puedes encontrarlo en el siguiente repositorio.

Espero la explicación haya sido clara y cualquier comentario no dudes en dejarlo aquí abajo, o con confianza mencionalo por Twitter o en la Fanpage de Facebook, y aprovecho a invitarte para que me sigas en las redes y compartas el contenido del blog, con lo cual me harías un gran favor.

Saludos.

Deja un comentario