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()
