6 oct. 2016

API Rest Ful con Flask y MongoDB (Flask-MongoAlchemy)

API Rest Ful con Flask y MongoDB (Flask-MongoAlchemy)

En artículo anterior se hizo un CRUD para Flask con MongoDB usando Mongo-Alchemy, en dicho artículo se manejaron varios URL para hacer el CRUD.

En este artículo se manejará el REST por medio de los URL y con Flask solamente, en próximos artículos se usará Flask-RESTful y Flask-Restless.

Métodos de HTTP 

En este caso se va a estandarizar el CRUD por medio de API Rest Ful, donde se usará el mismo URL con métodos GET, POST, PUT y DELETE.

Los métodos se usarán de la siguiente forma:

  • Agregar Empleado: Método POST, URL /empleado, sin parámetros.
  • Buscar Empleado: Método  GET, URL /empleado, parámetro <string:nombre>.
  • Listar Empleados: Método GET, URL /empleado
  • Editar/Actualizar Empleado: Método PUT, URL /empleado, parámetro <string:nombre>.
  • Borrar Empleado: Método DELETE, URL /empleado, parámetro <string:nombre>.


Como puede notarse, con un sólo URL se tiene las acciones del CRUD usando los métodos de HTTP, esto permite estándarizar y simplificar el uso de los URL, y principalmente hacer un correcto uso de HTTP:

Para más información sobre el estilo arquitectónico API REST pueden revisar en wikipedia.

Estructura de directorios y archivos

Se mantiene la misma estructura de archivos y directorios del artículo sobre CRUD:

tutorial-flask
├── app
│   └── run.py
├── docker-compose.yml
├── Dockerfile
└── README.md




Archivo Dockerfile y docker-compose.yml


El archivo Docker contiene lo siguiente:


FROM python
WORKDIR /code/
RUN pip3 install --upgrade pip
RUN pip3 install  pymongo
RUN pip3 install Flask
RUN pip3 install Flask-PyMongo
RUN pip3 install Flask-MongoAlchemy
EXPOSE 5000
ADD ./app/* /code/
COPY ./app/* /code/
CMD python run.py

El archivo docker-compose.yml contiene lo siguiente:


flask-rest1:

  build: .

  ports:

    - "5000:5000"

  volumes:

    - "./app/:/code"

  links:

    - mongo

mongo:

  image: mongo

  ports:

    - "27017:27017"

  volumes:

    - "/srv/data/db:/data/db:rw"



Codigo de la aplicación


El código de run.py se muestra a continuación:



#!/usr/bin/env python
#Se importa Flask, reqest y jsonify
from flask import Flask, request,jsonify
#Se importa MongoAlchemy
from flask_mongoalchemy import MongoAlchemy
#Se importa dumps
from bson.json_util import dumps
#Se instancia la clase de Flask, se configura el acceso
#a la base de datos mongodb a empleados
app = Flask(__name__)
app.config['MONGOALCHEMY_DATABASE'] = 'empleados'
app.config['MONGOALCHEMY_CONNECTION_STRING'] = 'mongodb://mongo:27017/empleados'
#Se instancia mongoalchemy pasando la app.
db = MongoAlchemy(app)
#Se crea la clase empleados la cual manejara los documentos.
class empleados(db.Document):
    nombre = db.StringField()
    sexo = db.StringField()
    edad = db.IntField()
    dni = db.IntField()
#Se define la funcion agregar con metodo get
@app.route('/empleado',methods=['POST'])
def agregar():
    #Se crea la instancia empleado de la clase empleados donde se
    #logra hacer la inserción de un empleado con el metodo save.
    nombre = str(request.json['nombre'])
    sexo = str(request.json['sexo'])
    edad = int(request.json['edad'])
    dni = int(request.json['dni'])
    empleado = empleados(nombre=nombre,sexo=sexo,edad=edad,dni=dni)
    empleado.save()
    #Se retorna que el usuario fue agregado.
    ###
    consulta = empleados.query.all()
    listado = []
    for i in consulta:
        listado.append(i.wrap())
    return dumps(listado)
#Se crea la funcion buscar con metodo get.
@app.route('/empleado/<string:nombre>',methods=['GET'])
def buscar(nombre):
    #Se realiza la busqueda y se devuelve el resultado, si existe un error de atributo (que el empleado no existe)
    #Se devuelve empleado no encontrado.
    try:
        resultado = empleados.query.filter(empleados.nombre == nombre).first()
        return dumps({'resultado':{'nombre':resultado.nombre,'sexo':resultado.sexo,'edad':resultado.edad,'dni':resultado.dni}})
    except (AttributeError):
        return dumps({'resultado': 'Empleado no encontrado'})
@app.route('/empleado',methods=['GET'])
def listar():
    #Se realiza la busqueda y se devuelve el resultado, si existe un error de atributo (que el empleado no existe)
    #Se devuelve empleado no encontrado.
    try:
        consulta = empleados.query.all()
        resultado = []
        for i in consulta:
            resultado.append(i.wrap())
        return dumps(resultado)
    except (AttributeError):
        return dumps({'resultado': 'Empleado no encontrado'})
#Se crea la funcion actualizar que tiene metodo put, con
#url /empleado y se le pasa el nombre a buscar
@app.route('/empleado/<string:nombre>',methods=['PUT'])
def actualizar(nombre):
    #Se intenta buscar al empleado en la base de datos, si no esta devuelve error
    try:
        #Se consulta en la base de datos, donde devuelve el primer elemento encontrado
        resultado = empleados.query.filter(empleados.nombre == nombre).first()
        #Se toma los datos de un json y se guardan en sus variables, salvando luego
        #en la base de datos.
        resultado.sexo = str(request.json['sexo'])
        resulado.edad = int(request.json['edad'])
        resultado.dni = int(request.json['dni'])
        resultado.save()
        #Se realiza la consulta desplegando los empleados
        consulta = empleados.query.all()
        listado = []
        for i in consulta:
            listado.append(i.wrap())
        #Se devuelve la nueva lista de empleados en un json.
        return dumps(listado)
    except (AttributeError):
        return dumps({'resultado': 'Empleado no encontrado'})
#Borrar un empleado de la base de datos, se pasa el url /empleado con el
#string nombre usando el metodo delete.
@app.route('/empleado/<string:nombre>',methods=['DELETE'])
def borrar(nombre):
    #Se busca el empleado, si existe se borra de la base de datos y se devuelve
    #mensaje de empleado borrado, si no, se devuelve el mensaje de empleado no
    #encontrado.
    try:
        resultado = empleados.query.filter(empleados.nombre == nombre).first()
        resultado.remove()
        ###
        consulta = empleados.query.all()
        listado = []
        for i in consulta:
            listado.append(i.wrap())
        return dumps(listado)
    except (AttributeError):
        return dumps({'resultado':'Empleado no encontrado'})
if __name__ == "__main__":
    #Se corre la aplicacion en modo debug
    app.run(host="0.0.0.0",debug=True)



Construcción de la imagen y ejecución del contenedor


Construcción de la imagen Docker:

docker-compose build


Ejecución del contenedor:

docker-compose up



Pruebas del API REST FUL


Para este caso se usará POSTMAN, una aplicación para google chrome.

Listar Empleados


Se usa postman colocando 127.0.0.1:5000/empleados con método GET, en la siguiente figura se muestra el listado:


El JSON que devuelve es el siguiente:

[{"edad": 29, "dni": 8, "sexo": "Femenino", "nombre": "Jane Doe", "_id": {"$oid": "57ebbce45fd2bbeffc51330b"}}, {"edad": 39, "dni": 7, "sexo": "Masculino", "nombre": "John Doe", "_id": {"$oid": "57ebbd195fd2bbeffc51330c"}}, {"edad": 55, "dni": 6, "sexo": "Masculino", "nombre": "Pedro Perez", "_id": {"$oid": "57ebbd505fd2bbeffc51330d"}}, {"edad": 65, "dni": 5, "sexo": "Femenino", "nombre": "Petra", "_id": {"$oid": "57ebbd6b5fd2bbeffc51330e"}}, {"edad": 18, "dni": 4, "sexo": "Masculino", "nombre": "Luis Gonzalez", "_id": {"$oid": "57ebc34d5fd2bbeffc51330f"}}, {"edad": 34, "dni": 2, "sexo": "Femenino", "nombre": "Luissana", "_id": {"$oid": "57ebc3935fd2bbeffc513311"}}, {"edad": 43, "dni": 1, "sexo": "Masculino", "nombre": "Neg", "_id": {"$oid": "57ebc4b85fd2bbeffc513312"}}, {"edad": 29, "dni": 1050, "sexo": "Femenino", "nombre": "Dayana", "_id": {"$oid": "57f64d7d557e3f00086651e8"}}]

Buscar un Empleado

En el postman se coloca el siguiente url con método GET 127.0.0.1/empleado/Neg , la siguiente figura muestra el resultado:


EL JSON que devuelve es el siguiente:

{"resultado": {"edad": 43, "dni": 1, "sexo": "Masculino", "nombre": "Neg"}}


Agregar Empleado

En postman se agrega el url con método POST 127.0.0.1:5000/empleado y se pasa en formato JSON lo siguiente:

{
"nombre": "Fatima",
"sexo": "Femenino",
"edad": 24,
"dni": 1052
}

La siguiente figura muestra la captura de la información que se coloca en postman:



Esto devuelve el siguiente JSON:

[{"edad": 29, "dni": 8, "sexo": "Femenino", "nombre": "Jane Doe", "_id": {"$oid": "57ebbce45fd2bbeffc51330b"}}, {"edad": 39, "dni": 7, "sexo": "Masculino", "nombre": "John Doe", "_id": {"$oid": "57ebbd195fd2bbeffc51330c"}}, {"edad": 55, "dni": 6, "sexo": "Masculino", "nombre": "Pedro Perez", "_id": {"$oid": "57ebbd505fd2bbeffc51330d"}}, {"edad": 65, "dni": 5, "sexo": "Femenino", "nombre": "Petra", "_id": {"$oid": "57ebbd6b5fd2bbeffc51330e"}}, {"edad": 18, "dni": 4, "sexo": "Masculino", "nombre": "Luis Gonzalez", "_id": {"$oid": "57ebc34d5fd2bbeffc51330f"}}, {"edad": 34, "dni": 2, "sexo": "Femenino", "nombre": "Luissana", "_id": {"$oid": "57ebc3935fd2bbeffc513311"}}, {"edad": 43, "dni": 1, "sexo": "Masculino", "nombre": "Neg", "_id": {"$oid": "57ebc4b85fd2bbeffc513312"}}, {"edad": 29, "dni": 1050, "sexo": "Femenino", "nombre": "Dayana", "_id": {"$oid": "57f64d7d557e3f00086651e8"}}, {"edad": 24, "dni": 1052, "sexo": "Femenino", "nombre": "Fatima", "_id": {"$oid": "57f68505557e3f000e1aa766"}}]

Como se nota el empleado Fátima aparece en la base de datos al final.

Borrar Empleado

Para este caso se buscará borrar al empleado Fátima, se coloca el siguiente url en postman con método DELETE: 127.0.0.1:5000/empleado/Fatima .

La siguiente imagen muestra la ejecución en postman:



Esto devuelve el siguiente JSON:

[{"edad": 29, "dni": 8, "sexo": "Femenino", "nombre": "Jane Doe", "_id": {"$oid": "57ebbce45fd2bbeffc51330b"}}, {"edad": 39, "dni": 7, "sexo": "Masculino", "nombre": "John Doe", "_id": {"$oid": "57ebbd195fd2bbeffc51330c"}}, {"edad": 55, "dni": 6, "sexo": "Masculino", "nombre": "Pedro Perez", "_id": {"$oid": "57ebbd505fd2bbeffc51330d"}}, {"edad": 65, "dni": 5, "sexo": "Femenino", "nombre": "Petra", "_id": {"$oid": "57ebbd6b5fd2bbeffc51330e"}}, {"edad": 18, "dni": 4, "sexo": "Masculino", "nombre": "Luis Gonzalez", "_id": {"$oid": "57ebc34d5fd2bbeffc51330f"}}, {"edad": 34, "dni": 2, "sexo": "Femenino", "nombre": "Luissana", "_id": {"$oid": "57ebc3935fd2bbeffc513311"}}, {"edad": 43, "dni": 1, "sexo": "Masculino", "nombre": "Neg", "_id": {"$oid": "57ebc4b85fd2bbeffc513312"}}, {"edad": 29, "dni": 1050, "sexo": "Femenino", "nombre": "Dayana", "_id": {"$oid": "57f64d7d557e3f00086651e8"}}]

Modificar empleado

Para este caso se modificará el empleado Neg, cambiando su edad.

Se abre el postman colocando el url 127.0.0.1:5000/empleado/Neg con método PUT. Adicional se agrega el JSON:

{
"sexo": "Masculino",
"edad": 41,
"dni": 1
}

La siguiente imagen muestra el postman:




Esto devuelve el siguiente JSON:
[{"sexo": "Femenino", "_id": {"$oid": "57ebbce45fd2bbeffc51330b"}, "nombre": "Jane Doe", "dni": 8, "edad": 29}, {"sexo": "Masculino", "_id": {"$oid": "57ebbd195fd2bbeffc51330c"}, "nombre": "John Doe", "dni": 7, "edad": 39}, {"sexo": "Masculino", "_id": {"$oid": "57ebbd505fd2bbeffc51330d"}, "nombre": "Pedro Perez", "dni": 6, "edad": 55}, {"sexo": "Femenino", "_id": {"$oid": "57ebbd6b5fd2bbeffc51330e"}, "nombre": "Petra", "dni": 5, "edad": 65}, {"sexo": "Masculino", "_id": {"$oid": "57ebc34d5fd2bbeffc51330f"}, "nombre": "Luis Gonzalez", "dni": 4, "edad": 18}, {"sexo": "Femenino", "_id": {"$oid": "57ebc3935fd2bbeffc513311"}, "nombre": "Luissana", "dni": 2, "edad": 34}, {"sexo": "Masculino", "_id": {"$oid": "57ebc4b85fd2bbeffc513312"}, "nombre": "Neg", "dni": 1, "edad": 41}, {"sexo": "Femenino", "_id": {"$oid": "57f64d7d557e3f00086651e8"}, "nombre": "Dayana", "dni": 1050, "edad": 29}]


Se muestra en subrayado el empleado Neg que cambió su edad en la base de datos.

Para terminar, ahora se muestra la salida del servidor:

flask-rest1_1 | 172.17.0.1 - - [06/Oct/2016 15:59:33] "GET /empleado HTTP/1.1" 200 -
flask-rest1_1 | 172.17.0.1 - - [06/Oct/2016 16:03:48] "GET /empleado/Neg HTTP/1.1" 200 -
flask-rest1_1 | 172.17.0.1 - - [06/Oct/2016 17:08:22] "POST /empleado HTTP/1.1" 200 -
flask-rest1_1 | 172.17.0.1 - - [06/Oct/2016 17:39:24] "DELETE /empleado/Fatima HTTP/1.1" 200 -
flask-rest1_1 | 172.17.0.1 - - [06/Oct/2016 17:47:59] "PUT /empleado/Neg HTTP/1.1" 200 -


Como se ve, la salida devuelve el URL que se usa y los métodos utilizados.

De esta manera se muestra el uso del estilo arquitectónico API REST y el uso de HTTP con sus diferentes métodos. 

El código fuente de este artículo se encuentra en el repositorio tutorial-flask de gitlab en la rama mongo-restful.
Publicar un comentario en la entrada

AddThis