1. Introducción
Hola a todos y bienvenidos al segundo post de 2020 de nuestro querido blog!. En el anterior vimos como crear una aplicación flask
para definir un endpoint de graphql
. En esta ocasión utilizaremos graphene-sqlalchemy-filter
para definir y utilizar filtros complejos en nuestras consultas. Utilizamos sqlite como motor de db y testearemos nuestro código de través de unittest.
Esta librería permite definir expresiones del tipo not-equals
, in
o like
.
Key | Descripción | GraphQL sufijo |
---|---|---|
eq |
equal | |
ne |
not equal | Ne |
like |
like | Like |
ilike |
insensitive like | Ilike |
is_null |
is null | IsNull |
in |
in | In |
not_in |
not in | NotIn |
lt |
less than | Lt |
lte |
less than or equal | Lte |
gt |
greater than | Gt |
gte |
greater than or equal | Gte |
range |
in range | Range |
contains |
contains (PostgreSQL array) | Contains |
contained_by |
contained_by (PostgreSQL array) | ContainedBy |
overlap |
overlap (PostgreSQL array) | Overlap |
Se definen a través del argumento filters
Tecnologías empleadas:
- Python 3.6
- Flask 1.1.1
- Graphene 2.1.8
- Graphene SQLAlchemy Filter 1.10.2
- SQLAlchemy 1.3.13
- SQLite
Os podéis descargar el código de mi GitHub que se encuentra aquí.
1. Creación virtualenv
Creamos el virtualenv e instalamos las dependencias necesarias
flask flask-graphql flask-migrate flask-sqlalchemy graphene graphene-sqlalchemy graphene-sqlalchemy-filter
2. Fuentes
Creamos una instancia de la clase Flask
. Además establecemos la configuración para acceder a la base de datos a través de la variable de entorno SQLALCHEMY_DATABASE_URI
from flask import Flask app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tests/sqlite.db'
Creamos el modelo asociado a la tabla user
from flask_sqlalchemy import SQLAlchemy from app import app db = SQLAlchemy(app) class UserModel(db.Model): __tablename__ = 'user' userid = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(256)) surname = db.Column(db.String(256)) age = db.Column(db.Integer) def __repr__(self): return '<User {} {} {} {}>'.format(self.id, self.name, self.surname, self.age)
Aquí viene una de las novedades con respecto al post anterior!. Creamos una clase que extienda de FilterSet
. Nos permitirá definir los filtros que podemos aplicar a cada uno de los campos de nuestro modelo. En esta ocasión asignaremos todas las operaciones permitidas a todos los campos del modelo.
from graphene_sqlalchemy_filter import FilterSet from model import UserModel ALL_OPERATIONS = ['eq', 'ne', 'like', 'ilike', 'is_null', 'in', 'not_in', 'lt', 'lte', 'gt', 'gte', 'range'] class UserFilter(FilterSet): class Meta: model = UserModel fields = { 'userid': ALL_OPERATIONS, 'name': ALL_OPERATIONS, 'surname': ALL_OPERATIONS, 'age': ALL_OPERATIONS, }
Creamos el schema para GraphQL. Se instancia un objeto de la clase graphene.Schema
que define las consultas que se podrán realizar. La clase FilterableConnectionField
será la encargada de manejar los filtros que se ejecuten en las consultas. Además se encargará de componer las querys que se ejecutarán sobre el modelo UserModel
a partir de las consultar realizadas.
import graphene from graphene_sqlalchemy import SQLAlchemyObjectType from graphene_sqlalchemy_filter import FilterableConnectionField from filter import UserFilter from model import UserModel class User(SQLAlchemyObjectType): class Meta: model = UserModel interfaces = (graphene.relay.Node,) class Query(graphene.ObjectType): node = graphene.relay.Node.Field() user = FilterableConnectionField(connection=User, filters=UserFilter(), sort=User.sort_argument()) schema = graphene.Schema(query=Query, types=[User])
Creación de la vista a través del schema creado
from flask_graphql import GraphQLView from app import app from schema import schema app.add_url_rule( '/graphql', view_func=GraphQLView.as_view( 'graphql', schema=schema, graphiql=True ) ) if __name__ == '__main__': app.run()
3. Testeando la aplicación
Para testear la aplicación utilizamos unittest
. Creamos la base de datos a partir de nuestro modelo
Insertamos usuarios de prueba
@staticmethod def _insert_users(): db.session.add(UserModel(userid=1, name='Jorge', surname='Hernandez', age=32)) db.session.add(UserModel(userid=2, name='Jose', surname='Hernandez', age=32)) db.session.commit()
Definimos el contexto de la aplicación
Y creamos un cliente sobre el que realizar las consultas
Se muestra todo el fichero test_user.py
import json import os import unittest from flask.ctx import AppContext from graphene.test import Client from app import app from model import db, UserModel from schema import schema class UserTest(unittest.TestCase): FILTER_IN = """ query{ user(filters: {useridIn: [1, 2]}){ edges{ node{ userid name surname age } } } } """ FILTER_NE = """ query{ user(filters: {useridNe: 1}){ edges{ node{ userid name surname age } } } } """ FILTER_LIKE = """ query{ user(filters: {nameLike: "%os%"}){ edges{ node{ userid name surname age } } } } """ @staticmethod def _create_database_mock(): db.drop_all() db.create_all() @staticmethod def _insert_users(): db.session.add(UserModel(userid=1, name='Jorge', surname='Hernandez', age=32)) db.session.add(UserModel(userid=2, name='Jose', surname='Hernandez', age=32)) db.session.commit() @classmethod def setUpClass(cls) -> None: os.environ['FLASK_ENV'] = 'test' AppContext(app).push() UserTest._create_database_mock() UserTest._insert_users() def setUp(self): self.client = Client(schema) def test_should_be_not_none_client(self): self.assertIsNotNone(self.client) def test_should_validate_in_operator(self): self.assertEqual( {"data": {"user": {"edges": [{"node": {"userid": "1", "name": "Jorge", "surname": "Hernandez", "age": 32}}, {"node": {"userid": "2", "name": "Jose", "surname": "Hernandez", "age": 32}}]}}}, self.client.execute(self.FILTER_IN)) def test_should_validate_not_equal_operator(self): self.assertEqual( {"data": {"user": {"edges": [{"node": {"userid": "2", "name": "Jose", "surname": "Hernandez", "age": 32}}]}}}, self.client.execute(self.FILTER_NE)) def test_should_validate_like_operator(self): self.assertEqual( {"data": {"user": {"edges": [{"node": {"userid": "2", "name": "Jose", "surname": "Hernandez", "age": 32}}]}}}, self.client.execute(self.FILTER_LIKE)) if __name__ == '__main__': unittest.main()