Skip to content
Alexis ROLLAND edited this page Feb 3, 2018 · 67 revisions

Flask Graphene SQLAlchemy Tutorial

Introduction

The objective of this tutorial is to describe step by step how to create a GraphQL API using Python and more specifically the following packages:

The API will expose data coming from an SQLite database stored locally on your machine. The end result will follow the files architecture described in the schema below. This files architecture has been reworked compared to previous tutorials I have followed on the topic with the intention to make it more scalable. It also contains complete examples of GraphQL mutations which I was not able to find in existing tutorials:

Architecture

  • The example folder contains the files used by Flask to start the API and the schema files used by Graphene to define GraphQL queries and mutations. It also contains the database sub folder.
  • The database sub folder contains files used to create and interact with the SQLite database. It contains in particular one model file per database table which are mapping the corresponding tables to their SQLAlchemy classes.

Project set-up

Open a terminal window to create your Python virtual environment. Keep the environment name short for convenience. I named it gql in the example below.

$ sudo apt-get install python3-venv
$ python3 -m venv /mypath/gql
$ source /mypath/gql/bin/activate

The last command above is used to activate your virtual environment. Once activated, install the required third party Python packages. I have specifically indicated the versions I used for this tutorial.

$ pip3 install sqlalchemy==1.1.14
$ pip3 install graphene==2.0.0
$ pip3 install graphene-sqlalchemy==2.0.0
$ pip3 install flask==0.11.1
$ pip3 install flask-graphql==1.4.1

Create your project folders and change directory to it.

$ mkdir example
$ mkdir example/database
$ cd example/

Create database

The database will contain some Planet and People data from Star Wars stored in 2 different tables (data source: The Star Wars API):

  • Planet: List of planets appearing in Star Wars movies
  • People: List of people appearing in Star Wars movies

Note there is a relationship between planets and people as one planet is the homeworld of one or several persons in the people table. It is a "one-to-many" relationship (one planet, many people). The homeworld column in the people table will be used as a foreign key of the planet table and as such it will contain the planet id.

Data model

In the database sub folder create a file named base.py. This file will be used by SQLALchemy to perform the following:

  • Define the database engine used to generate and interact with the SQLite file database.db.
  • Create the Base class used to produce database tables and map them to their respective SQLAlchemy class.
  • Manage the database sessions in order to execute queries against the database.

base.py

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
import os

# Create database engine
db_name = 'database.db'
db_path = os.path.join(os.path.dirname(__file__), db_name)
db_uri = 'sqlite:///{}'.format(db_path)
engine = create_engine(db_uri, convert_unicode=True)

# Declarative base model to create database tables and classes
Base = declarative_base()
Base.metadata.bind = engine  # Bind engine to metadata of the base class

# Create database session object
db_session = scoped_session(sessionmaker(bind=engine, expire_on_commit=False))
Base.query = db_session.query_property()  # Used by graphql to execute queries

In the database sub folder create two files named model_people.py and model_planet.py which will contain the data model information required by SQLAlchemy to create the tables and relationships.

model_people.py

from .base import Base
from sqlalchemy import Column, ForeignKey, Integer, String


class ModelPeople(Base):
    """People model."""

    __tablename__ = 'people'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String)
    height = Column('height', Integer)
    mass = Column('mass', Integer)
    hair_color = Column('hair_color', String)
    skin_color = Column('skin_color', String)
    eye_color = Column('eye_color', String)
    birth_year = Column('birth_year', String)
    gender = Column('gender', String)
    homeworld = Column('homeworld', Integer, ForeignKey('planet.id'))
    created = Column('created', String)
    edited = Column('edited', String)
    url = Column('url', String)

model_planet.py

from .base import Base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship


class ModelPlanet(Base):
    """Planet model."""

    __tablename__ = 'planet'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String)
    rotation_period = Column('rotation_period', Integer)
    orbital_period = Column('orbital_period', Integer)
    diameter = Column('diameter', Integer)
    climate = Column('climate', String)
    gravity = Column('gravity', String)
    terrain = Column('terrain', String)
    surface_water = Column('surface_water', Integer)
    population = Column('population', Integer)
    created = Column('created', String)
    edited = Column('edited', String)
    url = Column('url', String)

    peoples = relationship('People', backref='planet')

The next file setup.py will be used to create the database file database.db and load data into it. Data is available in the project repository in the shape of JSON files:

In the database folder create another data sub folder and place the JSON files in it. Your path should be something like:

  • example/database/data/people.json
  • example/database/data/planet.json

Copy the following code and create the setup.py file in the example folder.

setup.py

from ast import literal_eval
from database.model_people import ModelPeople
from database.model_planet import ModelPlanet
from database import base
import logging
import sys

# Load logging configuration
log = logging.getLogger(__name__)
logging.basicConfig(
    stream=sys.stdout,
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')


if __name__ == '__main__':
    log.info('Create database {}'.format(base.db_name))
    base.Base.metadata.create_all(base.engine)

    log.info('Insert Planet data in database')
    with open('database/data/planet.json', 'r') as file:
        data = literal_eval(file.read())
        for record in data:
            planet = ModelPlanet(**record)
            base.db_session.add(planet)
        base.db_session.commit()

    log.info('Insert People data in database')
    with open('database/data/people.json', 'r') as file:
        data = literal_eval(file.read())
        for record in data:
            planet = ModelPeople(**record)
            base.db_session.add(planet)
        base.db_session.commit()

Execute setup.py. You should see a new file database.db appearing in the database folder.

$ python3 setup.py
2018-02-03 15:36:37,561 - __main__ - INFO - Create database database.db
2018-02-03 15:36:37,589 - __main__ - INFO - Insert Planet data in database
2018-02-03 15:36:37,610 - __main__ - INFO - Insert People data in database

Create GraphQL queries

The database has been created and contains our data. We can start to create the API. Change directory to the example folder and create two files named schema_people.py and schema_planet.py. These two files use the Graphene-SQLAlchemy package to map our ModelPeople and ModelPlanet classes to Graphene schema objects. This allows us to reuse SQLAlchemy classes definition instead of redefining Graphene schemas manually.

schema_people.py

from graphene_sqlalchemy import SQLAlchemyObjectType
from database.model_people import ModelPeople
import graphene


class People(SQLAlchemyObjectType):
    """People."""

    class Meta:
        model = ModelPeople
        interfaces = (graphene.relay.Node,)

schema_planet.py

from graphene_sqlalchemy import SQLAlchemyObjectType
from database.model_planet import ModelPlanet
import graphene


class Planet(SQLAlchemyObjectType):
    """Planet."""

    class Meta:
        model = ModelPlanet
        interfaces = (graphene.relay.Node,)

The file schema.py is used to declare the different objects on which the API can perform queries. It will contain 4 objects for the moment:

  • people: to perform a query that returns a single person
  • peopleList: to perform a query that returns a list of persons
  • planet: to perform a query that returns a single planet
  • planetList: to perform a query that returns a list of planets

schema.py

from graphene_sqlalchemy import SQLAlchemyConnectionField
import graphene
import schema_people
import schema_planet


class Query(graphene.ObjectType):
    """Query objects for GraphQL API."""

    node = graphene.relay.Node.Field()
    people = graphene.relay.Node.Field(schema_people.People)
    peopleList = SQLAlchemyConnectionField(schema_people.People)
    planet = graphene.relay.Node.Field(schema_planet.Planet)
    planetList = SQLAlchemyConnectionField(schema_planet.Planet)


schema = graphene.Schema(query=Query)

Create Flask application

api.py