Implementación de una API en Python para enviar email con Angular.

Para llevar a cabo una web o aplicación móvil, es necesario desarrollar un sistema de comunicación con el servidor con el que la aplicación se comunicará para recibir y enviar datos. La forma más habitual de comunicarse con el servidor es a través de una comunicación HTTP o HTTPS utilizando uno de los dos servicios más extendidos: SOAP y REST.

En este ejemplo realizaremos un breve análisis teórico y práctico de cómo deben implementarse una API REST en Python. Os dejo el siguiente repositorio https://github.com/al118345/envio_email_api_python con una implementación de ejemplo y el siguiente video https://www.youtube.com/watch?v=RSGQuH_gWNw:

1. ¿Primer paso ¿que es una api rest?

Seguramente, si estás en este artículo, es la primera vez que vas a implementar una aplicación API REST en python y, por lo tanto, puede llegar a ser interesante refrescar o recordar los conceptos más básicos. Para aumentar tu motivación solamente tengo que informarte de la gran cantidad de diferentes soluciones que nos podemos encontrar por internet. Twitter, YouTube, los sistemas de identificación con Facebook… hay cientos de empresas que generan negocio gracias a REST y las APIs REST, es más, puedo llegar a afirmar que actualmente no existe proyecto o aplicación que no disponga de una API REST para la creación de servicios profesionales a partir de ese software. Sin ellas, todo el crecimiento en horizontal sería prácticamente imposible. Esto es así porque REST es el estándar más lógico, eficiente y habitual en la creación de APIs para servicios de Internet.

Respecto a la etimología, REST (Representational State Transfer) es la abreviatura de Transferencia de Estado Representacional que nos servirá para definir todo aquella interfaz entre sistemas que use HTTP para obtener datos o generar operaciones sobre esos datos en todos los formatos posibles, como XML y JSON.

Volviendo a la definición, un servicio REST no tiene estado , lo que quiere decir que, entre dos llamadas, el servicio pierde todos sus datos (no los almacena en memoria). Para un desarrollador, esto supone que un servicio REST no memoriza credenciales para las siguientes peticiones. El programador debe mantener el estado y, por lo tanto, es el cliente quien debe pasar el estado en cada llamada. Eso se puede realizar con un usuario y una contraseña, un token o cualquier otro tipo de credenciales, pero insisto, debo pasarlas en cada llamada.

1 Ejemplo comunicación REST

Si, lo sé, el no tener estado es una desventaja clara. Tener que implementar el estado en cada llamada es agotador. Pero todo este trabajo nos proporciona un beneficio muy importante, la escalabilidad. Para mantener un estado se requiere memoria donde guardar todos los estados de todos los clientes. A más clientes, más memoria, hasta que al final podemos llegar a no poder admitir más clientes. Es algo parecido a lo que ocurre con la web tradicional (que también es stateless). Sea cual sea la tecnología con la que desarrolles para web, seguro que conoces que puedes crear lo que se llama una “sesión”. Los datos de la sesión se mantienen entre peticiones y son por cada usuario.

2. SOAP vs REST.

Otra pregunta importante que debes saber responder es si tu proyecto necesita una API REST o debes buscar otras opciones. Dentro de esas otras opciones, se encuentra SOAP (Simple Object Access Protocol), un protocolo basado en la tecnología Web Services pero mucho más complejo que las API REST. SOAP es un sistema pensado para dar soluciones a casi cualquier necesidad en lo que a comunicaciones se refiere, incluyendo aspectos avanzados de seguridad, transaccionalidad, mensajería asegurada y demás.

2 Ejemplo comunicación SOAP

En otro sentido totalmente opuesto está REST. Este sistema no está pensado para dar soluciones a todo y, por lo tanto, disminuye la complejidad técnica y sus necesidades.

Otra diferencia entre el servicio web SOAP y un servicio REST es que el primero está orientado a RPC, es decir, a invocar métodos sobre un servicio remoto, mientras que el segundo está orientado a recursos. Vamos a definir mejor este último concepto. En una API REST destruye la ide de “servicio”. Lo que tenemos son recursos, accesibles por identificadores (URIs). Sobre esos recursos podemos realizar acciones, generalmente diferenciadas a través de diferentes acciones HTTP.

Así, en un servicio web clásico (SOAP) tendríamos un servicio llamado BarService que tendría un método GetAll me devolvería todas los platos ofertados por el local. La idea, independientemente de la tecnología usada para consumir el servicio web, es que se llama a un método (GetAll) de un servicio remoto (BarService). De forma parecida, para obtener un plato en concreto llamaríamos al método GetById() pasándole el id del plato. De ahí que se diga que están orientados a RPC (Remote Procedure Call – Llamada a método remoto).

Por su parte en un servicio REST es opuesto. Utilizamos un “recurso”, llamémosle “Colección de platos” que tiene una URI que lo identifica, p. ej. /Plates. Así, si invoco dicha URI debo obtener una representación de dicho recurso, es decir, debo obtener el listado de todos los platos.

Para obtener datos de un plato, habrá otro recurso (plate) con una URI asociada. Además, entre los recursos hay relaciones: está claro que un plato forma parte de la “Colección de platos” así que parece lógico que a partir de la colección entera (/Plates) pueda acceder a uno de sus elementos con una URI tipo /Plates/123, siendo "123" el ID del plato.

Volvamos a nuestro servicio SOAP: para dar de alta un plato llamaríamos a un método "AddPlate" de nuestro "BarService", y le pasaremos el plato a añadir. En cambio, en nuestra API REST añadir un plato consiste en usar la URI de dicho plato , (p. ej. /Plates/456 si estamos añadiendo el plato con ID 456) usando POST o PUT como verbo HTTP y colocando los datos del plato en el cuerpo de la petición. La URI para acceder al plato con ID 456 es siempre /Plates/456 y es el verbo HTTP (GET, POST, PUT, DELETE,...) el que indica cual es la operación que deseamos hacer con esa cerveza.

3 Implementación API REST Python

3.1 ¿Por qué desarrollar microservicios en Python?

Una vez escogido el estilo de API que vamos a implementar, es necesario escoger el lenguaje y las herramientas que más nos convenga desde el punto de vista de la programación.

El primer punto a analizar es el rendimiento de la aplicación. Realmente la API no es un aspecto crítico, es decir, no es necesario escoger lenguajes de bajo nivel que nos permitan afinar más este aspecto como C, C++ o Go. Es más, nos interesará escoger lenguajes de alto o nivel o que implementen ciertas características particulares, como Python, Ruby, Groovy o Scala, que nos permitan avanzar más rápido o se acomoden mejor al servicio que deben proporcionar.

Y, dentro de estas opciones, podemos usar un lenguaje de alto nivel como Python, dónde es posible aprovechar muchas de sus ventajas para nuestro objetivo. Este lenguaje es interpretado y combina múltiples paradigmas de programación como el orientado a objetos o el funcional. Además de todo ello, es uno de los lenguajes más extendido entre los programadores, por lo que tiene una gran base de código disponible en github u otros repositorios.

Por último, su sintaxis está orientada escribir menos líneas de código, siendo mucho más claro que otros lenguajes y, por lo tanto, más fácil de mantener.

3.2 Estructura de la aplicación

Para la aplicación vamos a seguir una estructura muy similar de un proyecto Flask.

Comenzaremos creando un directorio para el proyecto llamado proyecto_api_envio_email. Accede al directorio, crea un entorno virtual y actívalo.

Para hacerlo más facil, dispones del proyecto comentado en la url https://github.com/al118345/envio_email_api_python con el siguiente código:

  git clone https://github.com/al118345/envio_email_api_python
  pip install -r requirements.txt

El resultado será la siguiente estructura:

proyecto_api_envio_email
  |_ envio_email_api
  ├── app.py
  ├── babel.cfg
  ├── envio_email_api
  │     ├── api
  │     │     └── __init__.py
  │     ├── config.py
  │     ├── __init__.py
  │     ├── login
  │     │     └── __init__.py
  │     ├── metrics
  │     │     └── __init__.py
  │     ├── clave.json
  │     └── utils.py
  ├── LICENSE
  ├── README.md
  ├── requirements.txt
  ├── setup.py
  ├── test.py
  └── var.sh

No te preocupes, iremos dando contenido y explicando cada uno de los módulos a lo largo del tutorial.

3.3 Los recursos

Como hemos recordado en la introducción, toda API REST se basa en acceder, manipular, eliminar y/o crear recursos. Para ello, las APIs utilizan el protocolo HTTP, es decir, utiliza los siguientes protocolos:

  • GET: Para obtener un recurso o colección.
  • POST: Para crear un recurso (y/o añadirlo a una colección).
  • PUT/PATCH: Para modificar un recurso.
  • DELETE: Para eliminar un recurso.

En nuestro ejemplo, tenemos una web dónde nuestro objetivo será enviar un email a un correo recibido como parámetro. Analizado el fin, una propuesta para resolver el enunciado sería el siguiente métodos:

  class Envio_Sencillo_Emial (SecuredResource):
    def get(self,email_destino):
        enviar_email = SendEMail()
        enviar_email.send_email_test(email_destino)
        response = jsonify({'status': 'ok'})
        response.status_code = 200
        return response


  class SendEMail():
    def send_email_test(self, para):
        import smtplib
        import datetime

        gmail_user = '1938web@gmail.com'
        gmail_password = ''

        from_address = gmail_user
        to_address = para
        today = datetime.date.today()
        today = today.strftime('%Y-%m-%d')
        asunto = "Test Envio Email"
        mensaje=  'Envio de un correo'
        message = """From: %s\nTo: %s\nSubject: %s\n\n%s
        	    """ % (from_address, ", ".join(to_address), asunto, mensaje)
        server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
        server.ehlo()
        server.login(gmail_user, gmail_password)
        server.sendmail(from_address, to_address, message)
        server.close()

El código es muy simple, únicamente enviaremos un email al correo recibido por parámetro. Para enviar el correo hemos utilizado una cuenta de gmail.

Lo siguiente, y tal como lo he indicado en la teoría, es identificar la ruta al recurso. En este caso las rutas son:

  local_resources = [
    (ApiCatchall, '/<path:path>/'),
    (UserToken, '/get_token'),
    (UserTokenValid, '/is_token_valid'),
    (Envio_Sencillo_Emial, '/Envio_Email/<string:email_destino>/')]
  resources = (local_resources)

En este punto, como programador, ya tendrás una duda ¿cómo bloquear el acceso a la API por terceros?

Fácil. Fijaos en un detalle, la clase Envio_Sencillo_Email hereda SecuredResource. Esta clase que la podréis encontrar en el proyecto y ha sido implementada para otorgar al recurso cierta seguridad, es decir, únicamente podrá acceder aquella persona que proporcione un token de acceso autorizado.

Un cliente tendrá de nuestra api deberá acceder al recurso gettoken para obtener un token válido. Una vez obtenido, ya podrá utilizar el resto de funcionalidades

  class UserToken(Resource):
    def post(self):
        g.login_via_header = True
        token = check_login_user(**request.json)
        return jsonify({
            'token': token
        })
  

En el carpeta proyecto_api_envio_email está la carpeta login dónde el fichero __init__py existe un documento que contiene el siguiente código:

  @classmethod
  def login(cls, user, password):
    """
    Check if the user and password are correct
        :param user: User name
        :param password: Password
        :return: an instance of the current object user
        :rtype: Envio Email User
    """
    if user == "Subasta" and password == "Sub123Ruben":
      kwargs = {
        'id': 1,
        'user_id': str(1),
        'login': 'Subasta',
        'email': 'rubenpeib@gmail.com',
      }
      kwargs['locale'] = 'ES'
      return cls(**kwargs)
    return None

Lo que estás observando es la credencial de acceso. En resumen, este códiga implementar la siguiente petición

  URL : 'http://localhost:5000/api/v1/get_token'
  Header: {Content -Type : application/json}

  Además necesito cumplir los requisitos de la comunicación OAuth, comunicándome con el siguiente usuario:
  {"user": "Subasta", "password": "Sub123Ruben"}

  Resultado:
  {"token": "eyJhbGciOiJIUzUxMiIsImlhdCI6MTU2MDMzMTQ0NSwiZXhwIjoxNTYwNDE3ODQ1fQ.eyJpZCI6MSwidXNlcl9pZCI6I"}

Por último, la petición final para enviar correo seria:

  URL : 'http://localhost:5000/api/v1/envio_email/1938web@gmail.com/'
  Header: {Content -Type : application/json}
  {"Authorization": "eyJhbGciOiJIUzUxMiIsImlhdCI6MTU2MDMzMTQ0NSwiZXhwIjoxNTYwNDE3ODQ1fQ.eyJpZCI6MSwidXNlcl9pZCI6I"}
  Resultado:
    {
    "status": "ok"
    }

  

4 Capturas de pantalla de postman

Postman es una herramienta que principalmente nos permite crear peticiones sobre APIs de una forma muy sencilla y poder, de esta manera, probar las APIs. El usuario de Postman puede ser un desarrollador que esté comprobando el funcionamiento de una API para desarrollar sobre ella o un operador el cual esté realizando tareas de monitorización sobre un API.

Alrededor de la idea de testear las APIs, Postman nos ofrece un conjunto de utilidades adicionales para poder gestionar las APIs de una forma más sencilla. Es por ello que nos va a proporcionar herramientas para documentar los APIs, realizar una monitorización sobre las APIs, crear equipos sobre un API para que trabajen de forma colaborativa,… convirtiendo a Postman plataforma de desarrollo de APIs que se basa por un modelo de desarrollo API First.

A día de hoy Postman nos ofrece aplicaciones Windows, Linux y Mac, así como un módulo colaborativo para equipos basado en Cloud. Para instalarte Postman tienes que descargarte el software desde el área de descargas de Postman.

El uso de Postman es gratuito, si bien nos ofrece un par de planes adicionales que serían el Postman Pro que nos ofrece más ancho de banda para las pruebas y Postman Enterprise que nos permite, entre otras cosas, poder integrar la herramienta en los sistemas de SSO de nuestra empresa.

Ejemplo de ejecución

El primer paso, conectarnos con la API y autentificarse. Para ello tendremos que modificar el header y adaptarlo para una API REST

2 Ejemplo de header a una aplicación REST

Segundo paso, incluir el usuario y la contraseña y enviar la petición para obtener el token

2 Resultado de Get Token

Una vez hemos obtenido el token, lo deberemos incluir en el header de la comunicación. Es decir, si cogemos como ejemplo el recurso Envio_Email debería ser de la siguiente forma:

1 Ejemplo de uso del recurso Envio Email