En esto pienso cuando escucho la palabra “microservicios”. Huyendo del antiguo monolito, los microservicios llegaron para quedarse y Python tiene también presencia dentro de este campo y lo hace por medio del Framework de Flask. En este post veremos el “cómo” (how-to) de una API básica con acceso a base de datos.

Por aclarar, lo implementado en este artículo es una API sencilla; los microservicios deben llevar, entre otras cosas, despliegue, métricas, y suelen ser ejecutados con Kubernetes.

Primera API Sencilla

Para comenzar hay que instalar Flask en nuestro equipo por medio del uso de pip:

pip install flask

Ahora comencemos con un pequeño ejemplo (sin base de datos) de “Hello World”:

from flask import Flask

app = Flask(__name__)

@app.route(‘/’)
def hello():
    return ‘Hello World’

¿Qué pasó aquí?...

  1. Importamos el módulo de Flask.
  2. Creamos una instancia de WSGI de la clase de Flask, dentro de la variable app.
  3. Utilizamos el decorador @app.route para crear la ruta de acceso al API.
  4. Por último, definimos una función que retorna el mensaje ‘Hello World’.

Ahora para ejecutar este script primero debemos crear una variable de ambiente llamada FLASK_APP que contenga como valor el nombre de nuestro fichero .py:

export FLASK_APP=main.py

Y, por último, ejecutaremos flask run para publicar nuestra primera API:

flask run

Este nos devuelve un mensaje donde nos indica la dirección localhost y puerto TCP para acceder a la API:

 * Serving Flask app "main.py"
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Para ver el resultado en un navegador, debemos ir a http://127.0.0.1:5000/:

¡Listo! Primera API de microservicios con Python y utilizando Flask.

Pero, vamos a cambiarlo un poco para que acepte un saludo “personalizado”. Para ello vamos a cambiar el @app.route y la función:

from flask import Flask

app = Flask(__name__)

@app.route(‘/’)
def hello():
    person = request.args.get(‘person’)
    if person is None:
        person = ‘World’
    return ‘Hello {}’.format(person)

Hemos agregado el método request.args.get que sirve para tomar los argumentos del querystring y poder con GET tomar la variable pasada. Hemos también colocado una variable person con un valor por defecto y además estamos evaluando si existe o no dicha variable en la URL de la API.

El resultado en el navegador sería el siguiente:

Integrar con Base de Datos

Ahora vamos a crear una API que nos permita hacer CRUD contra una base de datos, pero veamos cómo se puede relacionar SQL con el acceso a la API utilizando los métodos HTTP:

Tarea Acción SQL Acción HTTP
Crear Registro INSERT POST
Eliminar Registro DELETE DELETE
Actualizar Registro UPDATE PUT
Consultar Registro SELECT GET

Lo primero es instalar la dependencia del código flask_sqlalchemy, el cual es un ORM para crear e interactuar con la base de datos y flask_marshmallow para convertir objetos ORM en JSON:

pip install flask_sqlalchemy
pip install flask_marshmallow

El código completo del CRUD es el siguiente:

import os
import json
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow

app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'hello.sqlite')
db = SQLAlchemy(app)
ma = Marshmallow(app)

class Person(db.Model):
   id = db.Column(db.Integer, primary_key=True)
   person = db.Column(db.String(80), unique=True)

   def __init__(self, person):
     self.person = person

class PersonSchema(ma.Schema):
   class Meta:
     fields = ['person']

persons_schema = PersonSchema(many=True)

db.create_all()

@app.route("/hello/", methods=["GET"])
def get():
  query = Person.query.all()
  result = persons_schema.dump(query)

  return jsonify(result.data)

@app.route("/hello/", methods=["POST"])
def post():
  person = request.json['person']
  new_person = Person(person)
  db.session.add(new_person)
  db.session.commit()

  return json.dumps({'success':True}), 201, {'ContentType':'application/json'}

@app.route("/hello/<id>", methods=["PUT"])
def put(id):
  query = Person.query.get(id)
  person = request.json['person']
  query.person = person
  db.session.commit()
   return json.dumps({'success':True}), 201, {'ContentType':'application/json'}

@app.route("/hello/<id>", methods=["DELETE"])
def delete(id):
  query = Person.query.get(id)
  db.session.delete(query)
  db.session.commit()

  return json.dumps({'success':True}), 200, {'ContentType':'application/json'}

Veamos en detalle cada parte del código:

import os
import json
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow

Lo primero es importar los módulos necesarios para la aplicación. Importamos Flask para la creación de la instancia web, luego request para poder recibir los datos enviados y jsonify para convertir la respuesta en formato JSON. Después, importamos SQLAlchemy para el modelo y Marshmallow para serialización.

app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'hello.sqlite')
db = SQLAlchemy(app)
ma = Marshmallow(app)

Acá inicializamos la aplicación web y configuramos el URI del SQLIte3 e inicializamos SQLAlchemy y Marshmallow en la aplicación Flask.

class Person(db.Model):
   id = db.Column(db.Integer, primary_key=True)
   person = db.Column(db.String(80), unique=True)

   def __init__(self, person):
     self.person = person

Creamos un modelo sencillo de dos campos, un id para la llave primaria y un campo de tipo string para Person, por ser un ejemplo sencillo no nos extendemos en más campos.

class PersonSchema(ma.Schema):
   class Meta:
     fields = ['person']

persons_schema = PersonSchema(many=True)

Definimos la estructura de respuesta de la serialización que hagamos con Marshmallow, la opción many=True nos permite mostrar más de un registro.

db.create_all()

Crearemos el modelo de base de datos.

@app.route("/hello/", methods=["GET"])
def get():
  query = Person.query.all()
  result = persons_schema.dump(query)

  return jsonify(result.data)

Crearemos una ruta llamada “/hello/”, el cual va ser nuestro punto de entrada a la API, y limitamos que el único método a aceptar sea GET, definimos una función llamada get, la misma consulta todos los registro de la base de datos, los serializa según el esquema anteriormente declarado y los muestra en formato JSON.

@app.route("/hello/", methods=["POST"])
def post():
  person = request.json['person']
  new_person = Person(person)
  db.session.add(new_person)
  db.session.commit()

  return json.dumps({'success':True}), 201, {'ContentType':'application/json'}

Para el caso de insertar datos a través de la API, tenemos una ruta que solamente permite el método POST, la función hace uso de request y así obtiene el valor del JSON enviado en la petición y este valor será insertado en la base de datos, y retornamos una respuesta con el código 201.

@app.route("/hello/<id>", methods=["PUT"])
def put(id):
  query = Person.query.get(id)
  person = request.json['person']
  query.person = person
  db.session.commit()
   return json.dumps({'success':True}), 201, {'ContentType':'application/json'}

Podemos actualizar datos basado en el id que tenga el registro. Este id es enviado al final de la ruta de acceso de la API con el método PUT y es almacenado en la variable con el mismo nombre (id), basado en ello hacemos una consulta a la base de datos con el valor obtenido y nos devuelve un único registro. Como vamos a actualizar es necesario pasar un JSON con la nueva información a aplicar.

@app.route("/hello/<id>", methods=["DELETE"])
def delete(id):
  query = Person.query.get(id)
  db.session.delete(query)
  db.session.commit()

  return json.dumps({'success':True}), 200, {'ContentType':'application/json'}

La última función realizada sirve para eliminar un registro, bastante parecida a la de actualizar, lo que cambia es el método a acceder a la API que es DELETE y la opción de la base de datos (delete).

Ahora bien, ¿cómo interactuamos con el script? Lo hacemos por medio del comando CURL.

Para poder consultar todos los registro a través de la API ejecutamos el siguiente comando, que nos muestra un JSON con todos los registro de la base de datos:

curl -X GET http://127.0.0.1:5000/hello

Para insertar un registro a través de la API, ejecutamos el siguiente comando. Se puede observar que con -d pasamos el JSON con la información, el header y el método POST:

curl -d {“person”: “Chamo”} -H “Content-Type: application/json” -X POST http://127.0.0.1:5000/hello

Para actualizar un registro a través de la API, ejecutamos el siguiente comando, se puede ver que al final del URL pasamos el id del registro a actualizar, además del JSON con la información y el método PUT:

curl -d {“person”: “Alvaro”} -H “Content-Type: application/json” -X PUT http://127.0.0.1:5000/hello/1

Para eliminar un registro a través de la API, ejecutamos el siguiente comando, se pasa al final del URL el id a eliminar y utilizamos el método DELETE:

curl -X DELETE http://127.0.0.1:5000/hello/1

Se puede hacer git clone del código de este post de la siguiente manera:

git clone https://github.com/alvarolinarescabre/paradigma_post_divide_y_venceras.git

Un detalle a tener en cuenta al momento de trabajar con ficheros mayores a 200 líneas en Flask es utilizar los Blueprint que nos dará la posibilidad de hacer no solamente de hacer nuestro código más legible y más manejable, sino también hacer nuestra aplicación Flask modular.

Mi uso del día a día

En Paradigma utilizamos el framework basado en Flask llamado Py-MS, creado por compañeros de trabajo. Nos hace la vida mucho más fácil, ya que nos permite de forma rápida hacer implementaciones de microservicios con Python.

Este se compone de:

Utiliza una configuración como el configmap de k8s o Kubernetes, además de integrar el soporte de manera sencilla de Swagger.

Pero de este Framework hablaré en otro post más adelante y con ejemplo de implementarlo.

Hasta el próximo post :D

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.