Nombre: | Tutorial #2 - FLASK API REST |
Duración: | 60 minutos |
Profesor responsable | Harold Castro, Mario Villamizar |
Pre-Requisitos: | Programación en Python |
Al finalizar el tutorial el estudiante estará en capacidad de:
En particular se utilizarán los siguientes recursos:
Al desarrollar y especialmente al probar cosas nuevas, se cometen errores y se rompen cosas. Aislar el ambiente de desarrollo, facilita instalar paquetes, probar librerías y ejecutar tareas de modo que cuando inevitablemente se rompa algo, el daño quede aislado. Esta es una breve explicación de los entornos virtuales que utilizaremos para desarrollar aplicaciones Python y cómo configurarlo.
Los entornos virtuales facilitan mantener las dependencias de un proyecto aisladas y bien administradas cuando desarrolla aplicaciones en Python. Hay muchas herramientas diferentes para hacer esto. Los entornos virtuales crean una carpeta para la instalación y las dependencias de Python y modifican la variable de entorno PATH para apuntar a la instalación deseada.
Utilizaremos Python 3.8 o superior para nuestros proyectos, utilizaremos el paquete venv para administrar entornos virtuales. Está incluido en la biblioteca estándar de Python desde la versión 3.3, esto significa que no tiene que instalar ninguna herramienta adicional en el caso de sistemas Microsoft Windows, sin embargo, para el caso de Ubuntu/Debian es necesario instalar el paquete.
Para administrar un nuevo proyecto usando Python 3.8 o superior, debe crear una nueva carpeta ejecutando:
$ python3 -m venv mi_nuevo_ambiente
Una instalación de Python 3.x es creada de inmediato en la carpeta ambiente. Para activar en nuevo entorno virtual, utilice el comando source:
$ source mi_nuevo_ambiente/bin/activate
Para cerrar el ambiente de ejecución, ejecute el comando deactivate:
$ deactivate
Flask es un micro-framework de Python que le permite crear aplicaciones web rápidamente con Python. En este tutorial vamos a desarrollar una API RESTful, almacenando y consultando información en un motor de base de datos SQL por medio del ORM SQLAlchemy.
Al final de este tutorial, podrá crear una API REST que puede administrar las publicaciones de blog con funciones de creación, lectura, actualización y eliminación de entradas o posts.
Para el tutorial necesitamos instalar las siguientes dependencias que están disponibles en el Python Package Index (PyPI), y para instalarlas basta con utilizar pip:
# Windows
$ pip install flask
$ pip install flask-sqlalchemy
$ pip install flask-restful
$ pip install flask-marshmallow
# Ubuntu
$ pip3 install flask
$ pip3 install flask-sqlalchemy
$ pip3 install flask-restful
$ pip3 install flask-marshmallow
Dado que Flask es un micro-framework, no cuenta con muchos componentes para gestionar cierto tipo de tareas como en un framework de las características de Django. Esto hace que Flask sea simple y rápido de aprender.
Sin embargo, al momento de conectar Flask a una base de datos o implementar un sistema de autenticación se requiere integrar a un proyecto librerías adicionales para gestionar dichas tareas.
En la sección anterior instalamos algunos paquetes adicionales para el desarrollo de nuestra API:
Cree una nueva carpeta para este tutorial, llamada API y en esta crea un nuevo archivo Python llamado app.py. Iniciaremos con un breve esqueleto:
from flask import Flask app = Flask(__name__) if __name__ == '__main__': app.run(debug=True)
La primera línea del programa importa Flask a nuestra aplicación en Python. La segunda línea app = Flask (__name__) crea un objeto de tipo Flask llamado app. Finalmente, el bloque condicional nos permite lanzar nuestra aplicación Flask cuando se carga el archivo app.py en un intérprete de Python.
Desde la consola de comandos puede ejecutar su aplicación ejecutando la instrucción:
$ python app.py
Podrá observar en pantalla el siguiente mensaje de consola, abra un navegador web (el de su preferencia) e ingrese a la URL y el puerto donde se ejecuta el servidor de Flask con nuestra aplicación.
Al ingresar a la URL http://127.0.0.1:5000, se encontrará una página de error 404, esto se debe a que nuestra aplicación aún no entrega algún tipo de recurso.
Antes de iniciar con el desarrollo de nuestra API REST, vamos a crear una base de datos con para almacenar los datos de las publicaciones del blog.
Durante la primera parte del tutorial se utilizará SQLite, pero recuerde que un ambiente de producción debe utilizar un motor robusto como PostgreSQL o MySQL. PostgreSQL y MySQL son soportados por SQLAlchemy por lo que migrar la base de datos no representa un cambio complejo.
Ajuste su esqueleto con los cambios presentados a continuación:
from flask import Flask from flask_sqlalchemy import SQLAlchemy # Líneas nuevas app = Flask(__name__) # Líneas nuevas app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' db = SQLAlchemy(app) if __name__ == '__main__': app.run(debug=True)
La línea app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' crea un diccionario de configuración para nuestra aplicación en Flask. En este caso particular estamos creando la llave que representa la conexión SQLAlchemy con la base de datos que vamos a utilizar. En este caso su valor corresponde a la URI de la base de datos SQLite llamada test.db.
La línea db = SQLAlchemy(app) crea un objeto de tipo SQLAlchemy llamado db, que en nuestro caso representa la base de datos en el programa. Este objeto se inicializa con la configuración creada en la línea anterior.
Un modelo es más una representación de una entidad en base de datos, donde puede almacenar, recuperar, manipular sus datos y generalmente representa una sola tabla.
SQLAlchemy permite hacer todo en esa tabla (el modelo) directamente desde el código Python, por medio de operaciones con objetos y no con consultas SQL.
Para crear un modelo en Python solo es necesario definir una clase de Python con sus respectivos atributos. En este tutorial vamos a crear un modelo que represente los datos de las publicaciones de un blog, particularmente un identificador de la publicación, un título y su contenido. Ajuste su esqueleto con los cambios presentados a continuación:
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' db = SQLAlchemy(app) # Líneas nuevas class Publicacion(db.Model): id = db.Column(db.Integer, primary_key = True) titulo = db.Column( db.String(50) ) contenido = db.Column( db.String(255) ) if __name__ == '__main__': app.run(debug=True)
En el bloque de nuevas líneas, tenemos un atributo de identificación que es un campo que representa la llave principal de la tabla y generado automáticamente. Luego, también tenemos un campo de título y contenido, solo un campo de tipo String con una longitud máxima definida.
Es necesario configurar el esquema para el nuevo modelo. Esto es necesario para manipular los objetos de publicación en una respuesta JSON. Para esto es necesario usar el paquete flask_marshmallow. Ajuste su esqueleto con los cambios presentados a continuación:
from flask import Flask from flask_sqlalchemy import SQLAlchemy # Líneas nuevas from flask_marshmallow import Marshmallow app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' db = SQLAlchemy(app) # Líneas nuevas ma = Marshmallow(app) class Publicacion(db.Model): id = db.Column(db.Integer, primary_key = True) titulo = db.Column( db.String(50) ) contenido = db.Column( db.String(255) ) # Líneas nuevas class Publicacion_Schema(ma.Schema): class Meta: fields = ("id", "titulo", "contenido") post_schema = Publicacion_Schema() posts_schema = Publicacion_Schema(many = True) if __name__ == '__main__': app.run(debug=True)
En este esquema, podemos elegir qué campos exponer a nuestros usuarios. Si el modelo tiene algunos datos confidenciales, es posible que desee excluirlos aquí. Luego, se instancia posts_schema y post_schema. Se usa posts_schema para serializar una serie de publicaciones. Si solo se desea serializar una publicación se utilizará post_schema.
La siguiente parte importante del tutorial es inicializar el esquema de base de datos invocando la función db.create_all desde la instancia SQLAchemy dentro de un shell interactivo de Python (REPL). La razón por la que se realiza esto en un REPL es porque solo se requiere inicializar el esquema una vez.
Desde la consola de comandos puede ejecutar su aplicación ejecutando la instrucción:
$ python
>>> from app import db
>>> db.create_all()
>>> exit()
En esta sección vamos a crear los endpoints RESTful. Se hace uso del paquete Flask-RESTful, un conjunto de herramientas que permiten construir rutas RESTful con diseño orientado a objetos.
Necesitamos configurar la extensión Flask-RESTful para comenzar a funcionar en nuestro servidor Flask. Ajuste su esqueleto con los cambios presentados a continuación:
from flask import Flask, request # Línea Nueva, se importa request from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow # Líneas nuevas from flask_restful import Api, Resource app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' db = SQLAlchemy(app) ma = Marshmallow(app) # Líneas nuevas api = Api(app) ### ... ### ... if __name__ == '__main__': app.run(debug=True)
El contenido que un cliente web envía al servidor siempre va almacenado en un Request. En Flask el Request se representa mediante el objeto request. Este objeto lo utilizamos para saber el tipo de petición que nos hace el cliente: GET, POST, DELETE, PUT, etc y el contenido del request.
Para crear un nuevo recurso RESTful es necesario crear una nueva clase, de esa manera, se define la lógica de negocios para obtener datos de la base de datos, cómo, por ejemplo, realizar procesos de autenticación, etc.
from flask import Flask, request from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow from flask_restful import Api, Resource ### ... ### ... # Líneas nuevas class RecursoListarPublicaciones(Resource): def get(self): publicaciones = Publicacion.query.all() return posts_schema.dump(publicaciones) api.add_resource(RecursoListarPublicaciones, '/publicaciones') if __name__ == '__main__': app.run(debug=True)
Con el recurso anterior, el API acepta una solicitud GET y hacemos una consulta para obtener todas las publicaciones con el modelo de publicación. Luego, aprovechamos posts_schema para serializar los datos de la base de datos y devolverlos como respuesta al cliente.
Finalmente, registramos nuestro recurso usando el método api.add_resource y definimos el punto final de la ruta.
Inicie el servidor, envíe una solicitud y obtendrá una lista vacía. Si no tiene en ejecución Flask porque cerró la consola utilizada previamente, puede ejecutar su aplicación ejecutando la instrucción:
$ python app.py
Desde un navegador web puede ingresar al recurso creado, en este caso, como no hay elementos en base de datos el API nos retornara una JSON (una lista) vacía:
El siguiente paso es desarrollar la operación de crear publicación, utilizando una operación POST. Creamos una nueva función en la clase RecursoListarPublicaciones, es necesario instanciar un nuevo objeto de tipo publicación con los datos de la solicitud y guardar el registro en la base de datos. Finalmente, en este caso vamos a devolver los datos de la publicación como respuesta al cliente.
from flask import Flask, request from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow from flask_restful import Api, Resource ### ... ### ... # Líneas nuevas class RecursoListarPublicaciones(Resource): def get(self): publicaciones = Publicacion.query.all() return posts_schema.dump(publicaciones) def post(self): nueva_publicacion = Publicacion( titulo = request.json['titulo'], contenido=request.json['contenido'] ) db.session.add(nueva_publicacion) db.session.commit() return post_schema.dump(nueva_publicacion) ### ...
Utilice una herramienta como CURL o POSTMAN para enviar datos via POST al API. A continuación se presenta un ejemplo utilizando POSTMAN.
La primera imagen presenta la solicitud POST:
La segunda imagen presenta la respuesta a la solicitud POST:
Finalmente, la tercera imagen corresponde nuevamente a una solicitud GET desde cualquier navegador:
Rutas REST - PUT y DELETE
En esta sección vamos a listar una publicación específica, vamos a actualizar su valor y a eliminar las publicaciones que no sean de nuestro interés utilizando su identificador.
Creemos un nuevo recurso (una nueva clase) para buscar publicaciones individuales. Ajuste su esqueleto con los cambios presentados a continuación:
from flask import Flask, request from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow from flask_restful import Api, Resource ### ... ### ... # Líneas nuevas class RecursoUnaPublicacion(Resource): def get(self, id_publicacion): publicacion = Publicacion.query.get_or_404(id_publicacion) return post_schema.dump(publicacion) # Líneas nuevas, se agrega una nueva ruta api.add_resource(RecursoListarPublicaciones, '/publicaciones') api.add_resource(RecursoUnaPublicacion,'/publicaciones/<int:id_publicacion>') ### ...
Como puede verificar se creó una nueva clase. La clase RecursoUnaPublicación, esta clase implementa las operaciones GET, PUT y DELETE sobre único recurso, es decir, sobre una única publicación del blog.
Inicialmente, se implementa la operación GET, ésta permite traer de la base de datos una única publicación especificando el id de la entrada del blog.
En la linea api.add_resource(RecursoUnaPublicacion,'/publicaciones/<int:id_publicacion>') se agrega una nueva ruta, que especifica de forma genérica la URL para las solicitudes sobre un único recurso a través de su id.
Ingrese a su navegador de preferencia, o a través de POSTMAN especifique la URL hacia una publicación de interés:
Ajuste su esqueleto con los cambios presentados a continuación:
from flask import Flask, request from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow from flask_restful import Api, Resource ### ... ### ... # Líneas nuevas class RecursoUnaPublicacion(Resource): def get(self, id_publicacion): publicacion = Publicacion.query.get_or_404(id_publicacion) return post_schema.dump(publicacion) def put(self, id_publicacion): publicacion = Publicacion.query.get_or_404(id_publicacion) if 'titulo' in request.json: publicacion.titulo = request.json['titulo'] if 'contenido' in request.json: publicacion.contenido = request.json['contenido'] db.session.commit() return post_schema.dump(publicacion) def delete(self, id_publicacion): publicacion = Publicacion.query.get_or_404(id_publicacion) db.session.delete(publicacion) db.session.commit() return '', 204 ### ...
En operación PUT, se obtiene un objeto de publicación ya existente, se actualizan los atributos que se definieron en el cuerpo de la solicitud (request.json). Los cambios se guardan en la base de datos utilizando db.session.commit() y envíe los datos de actualización al cliente.
En operación DELETE, también se obtiene el objeto de una publicación. Pero esta vez, solo se elimina el objeto con el método de eliminación del objeto de publicación. Guarde los cambios y no retorna nada al cliente (porque no hay nada que mostrar).
La primera imagen presenta la solicitud PUT:
La segunda imagen presenta el resultado de la solicitud PUT:
La tercera imagen presenta la solicitud DELETE:
La última imagen presenta el resultado de la solicitud DELETE:
El código de la aplicación lo encuentra en el siguiente repositorio de GitHub:
[1] https://flask.palletsprojects.com/en/1.1.x/
[2] https://flask-restful.readthedocs.io/en/latest/
[3] https://docs.sqlalchemy.org/en/13/