Integrate Graphene with Sqlalchemy and Flask#

Overview#

这篇文章介绍了如何用 graphene + flask + flask_graphql + sqlalchemy + graphene_sqlalchemy 实现一个极简但跟生产环境的代码很类似的的 GraphQL API. 这个例子中我们用 sqlite 做数据库

Code#

models 模块定义了 sqlalchemy ORM 的模型.

models.py
 1# -*- coding: utf-8 -*-
 2
 3import sqlalchemy as sa
 4import sqlalchemy.orm as orm
 5
 6engine = sa.create_engine("sqlite:///database.sqlite3", convert_unicode=True)
 7db_session = orm.scoped_session(
 8    orm.sessionmaker(autocommit=False, autoflush=False, bind=engine)
 9)
10
11Base = orm.declarative_base()
12# We will need this for querying
13Base.query = db_session.query_property()
14
15
16class Department(Base):
17    __tablename__ = "department"
18
19    id = sa.Column(sa.Integer, primary_key=True)
20    name = sa.Column(sa.String)
21
22
23class Employee(Base):
24    __tablename__ = "employee"
25
26    id = sa.Column(sa.Integer, primary_key=True)
27    name = sa.Column(sa.String)
28    hired_on = sa.Column(sa.DateTime, default=sa.func.now())
29    department_id = sa.Column(sa.Integer, sa.ForeignKey("department.id"))
30
31    department = orm.relationship(
32        Department,
33        backref=orm.backref("employees", uselist=True, cascade="delete,all"),
34    )

运行 add_some_data.py 脚本可以往数据库中添加一些测试用的数据.

models.py
 1# -*- coding: utf-8 -*-
 2
 3import sqlalchemy as sa
 4import sqlalchemy.orm as orm
 5
 6engine = sa.create_engine("sqlite:///database.sqlite3", convert_unicode=True)
 7db_session = orm.scoped_session(
 8    orm.sessionmaker(autocommit=False, autoflush=False, bind=engine)
 9)
10
11Base = orm.declarative_base()
12# We will need this for querying
13Base.query = db_session.query_property()
14
15
16class Department(Base):
17    __tablename__ = "department"
18
19    id = sa.Column(sa.Integer, primary_key=True)
20    name = sa.Column(sa.String)
21
22
23class Employee(Base):
24    __tablename__ = "employee"
25
26    id = sa.Column(sa.Integer, primary_key=True)
27    name = sa.Column(sa.String)
28    hired_on = sa.Column(sa.DateTime, default=sa.func.now())
29    department_id = sa.Column(sa.Integer, sa.ForeignKey("department.id"))
30
31    department = orm.relationship(
32        Department,
33        backref=orm.backref("employees", uselist=True, cascade="delete,all"),
34    )

schema 模块定义了 GraphQL 的 schema. graphene_sqlalchemy 库能够自动将 sqlalchemy ORM 对象转化为 graphql 对象, 并且自动实现了对应的 resolver, 而无需你手动实现.

schema.py
 1# -*- coding: utf-8 -*-
 2
 3import graphene
 4from graphene import relay
 5from graphene_sqlalchemy import SQLAlchemyObjectType, SQLAlchemyConnectionField
 6from models import db_session, Department as DepartmentModel, Employee as EmployeeModel
 7
 8
 9class Department(SQLAlchemyObjectType):
10    class Meta:
11        model = DepartmentModel
12        interfaces = (relay.Node,)
13
14
15class Employee(SQLAlchemyObjectType):
16    class Meta:
17        model = EmployeeModel
18        interfaces = (relay.Node,)
19
20
21class Query(graphene.ObjectType):
22    node = relay.Node.Field()
23    # Allows sorting over multiple columns, by default over the primary key
24    all_employees = SQLAlchemyConnectionField(Employee.connection)
25    # Disable sorting over this field
26    all_departments = SQLAlchemyConnectionField(Department.connection, sort=None)
27
28
29schema = graphene.Schema(query=Query)

app 模块将 graphql 的部分和 flask 的部分结合在一起.

app.py
 1# -*- coding: utf-8 -*-
 2
 3import graphene
 4from graphene import relay
 5from graphene_sqlalchemy import SQLAlchemyObjectType, SQLAlchemyConnectionField
 6from models import db_session, Department as DepartmentModel, Employee as EmployeeModel
 7
 8
 9class Department(SQLAlchemyObjectType):
10    class Meta:
11        model = DepartmentModel
12        interfaces = (relay.Node,)
13
14
15class Employee(SQLAlchemyObjectType):
16    class Meta:
17        model = EmployeeModel
18        interfaces = (relay.Node,)
19
20
21class Query(graphene.ObjectType):
22    node = relay.Node.Field()
23    # Allows sorting over multiple columns, by default over the primary key
24    all_employees = SQLAlchemyConnectionField(Employee.connection)
25    # Disable sorting over this field
26    all_departments = SQLAlchemyConnectionField(Department.connection, sort=None)
27
28
29schema = graphene.Schema(query=Query)

Test#

我们写了个简单的脚本, 用于测试在 localhost 运行的 GraphQL API Server.

test.py
 1# -*- coding: utf-8 -*-
 2
 3import requests
 4from rich import print as rprint
 5
 6
 7def run_query(query: str) -> dict:
 8    res = requests.post(
 9        "http://127.0.0.1:5000/graphql",
10        headers={
11            "Content-Type": "application/json",
12        },
13        json={"query": query.strip()},
14    )
15    return res.json()
16
17
18query = """
19{
20  allEmployees {
21    edges {
22      node {
23        id
24        name
25        department {
26          name
27        }
28      }
29    }
30  }
31}
32""".strip()
33rprint(run_query(query))
34"""
35This will print:
36
37{
38    'data': {
39        'allEmployees': {
40            'edges': [
41                {
42                    'node': {
43                        'id': 'RW1wbG95ZWU6MQ==',
44                        'name': 'Peter',
45                        'department': {'name': 'Engineering'}
46                    }
47                },
48                {
49                    'node': {
50                        'id': 'RW1wbG95ZWU6Mg==',
51                        'name': 'Roy',
52                        'department': {'name': 'Engineering'}
53                    }
54                },
55                {
56                    'node': {
57                        'id': 'RW1wbG95ZWU6Mw==',
58                        'name': 'Tracy',
59                        'department': {'name': 'Human Resources'}
60                    }
61                }
62            ]
63        }
64    }
65}
66"""

Reference#