Win back lovely API - GraphQL in Python

Keith Yang, May 2018, PyCon

Win back lovely API
GraphQL in Python

Keith Yang

Web and utilities in Python since 2008

Senior developer in iCHEF F&B POS

Taipei.py Co-founder

PyCon Taiwan voluteer since 2012

Chairpersion of PyCon APAC 2015

<< Ready Player One >>

Content Structure

from future import __hope__

What these slides are and are not

iCHEF: Point of Sale platform

At the point of sale, the merchant calculates the amount owed by the customer, indicates that amount, may prepare an invoice for the customer, which may be a cash register printout, and indicates the options for the customer to make payment. -- Point of Sale, Wikipedia

iCHEF R&D Teams

iCHEF machine

Some architecture and deployment in iCHEF

Question 1
what's and where to find
current go-to API implement?

Existed APIs in varieties

Disclaimer: not all caused by RESTful if carefully crafted with OpenAPI Specification (Swagger or Stoplight)

© 2014? 2015, 2016, 2017, 2018

Question 2
what type of fields
will be in response?

No idea until response back

HTTP GET

/games/1
{
    'status': 123,
    'name': '🐲'
    },
}

Question 3
any
docuement?

Take one



  1. No document
  2. Somewhere outdated document
  3. Code is Law (like 1?)

What we looking for

  1. Consistent interface: flexible and predictable
    • Especially when it's time to open our API to third party
  2. Doable ans scalable: step by step, team by team
  3. Future promising: inspiring and fun

Trend of GraphQL vs REST API vs Python programming: the rise of GraphQL and REST API

🍰 ☕️ 🍕 🍺
LIFE EASIER
in short 🐛

GraphQL
glances of

Look of GraphQL request

HTTP GET
/graphql?query={ restaurant(id: "1") \
    { name, category { primary } } }
{
    'data': {
        'category': {
            'primary': 'Taiwanese Cuisine',
        },
        'name': 'Mazendo'
    },
    'errors': []
}

A sample of REST request

HTTP GET

/restaurants/1
{
    'category': {
        'primary': 'Taiwanese Cuisine',
        'second': 'Beef Noodle',
    },
    'name': 'Mazendo'
    },
}

GraphQL vs REST:
Similar Parts

GraphQL vs REST:
Different Parts

GraphQL vs REST:
Different Parts (cont.)

GraphQL vs REST:
Different Parts (cont.)

GET or POST
HTTP 200

Language agnostic
3-in-1 idea in a GraphQL request

For example, between ECMAScript and Python

Not for API Versioning

GraphQL easy benefits

Development with GraphQL?

  1. Schema
  2. UI and API
  3. Product

Real development with GraphQL

  1. Schema
  2. UI and API
  3. Product

Product design

UI design

query {
    restaurant {
        logoUrl: String!
    }
}

API desgin

♻️ Schema design

Naming
still hard

Naming hazard of fields

GraphQL Schema

GraphQL query schema sample

query {
    restaurant {
        settings {
            ...
        }
    }
}

Common error message format:
errors and data

{
    "errors": [{
        "message": String,
        ...
    }],
    "data": {
        "restaurant": {
            "settings": {...}
        }
    }
}

GraphQL schemas and types

Where it takes time to learn
Grow with spec evolving

GraphQL type language sample

interface Restaurant {
    id: ID!
    name: String!
    categories: [Category]!
}
type TaiwaneseRestaurant implements Restaurant {
    id: ID!
    name: String!
    categories: [Category]!
    tax_unique_identifier: String
}

pip install graphene

GroupCreated with Sketch.

GroupCreated with Sketch.

A piece of spec
implemented with Graphene

import graphene
...
class ReportObject(graphene.ObjectType):
    timestamp = graphene.Int()
    uuid = graphene.UUID()
    type = InventoryTypeGrapheneEnum()
    ...
    purchase_amount = graphene.String()
    counting_amount = graphene.Float()

Source code tree of version 2.0.1

GraphQL in iCHEF !=

GraphQL in Yelp

GraphQL in Cousera

GraphQL in IBM

GraphQL in Facebook

GraphQL in iCHEF

How to prompt the idea in the company?

Mock

Django serializer

Looks fine to be with graphene-django

Quick graphene-django example

cookbook/urls.py
from django.conf.urls import url
from django.contrib import admin

from graphene_django.views import GraphQLView

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^graphql', GraphQLView.as_view(graphiql=True)),
]

graphene-django example (cont.)

cookbook/recipes/schema.py
from cookbook.recipes.models import Recipe, RecipeIngredient
from graphene import Node
from graphene_django.types import DjangoObjectType

class RecipeNode(DjangoObjectType):
    class Meta:
        model = Recipe
        interfaces = (Node, )
        filter_fields = ['title','amounts']

graphene-django example (cont.)

cookbook/recipes/schema.py
from graphene_django.filter import DjangoFilterConnectionField
class Query(object):
    recipe = Node.Field(RecipeNode)
    all_recipes = DjangoFilterConnectionField(RecipeNode)

    recipeingredient = Node.Field(RecipeIngredientNode)
    all_recipeingredients = DjangoFilterConnectionField(
        RecipeIngredientNode)

Integration with DRF

It does provide SerializerMutation to help you with DRF. However in our casejust want to write our want serializer mutation due to the varied product spec.

from graphene_django.rest_framework.mutation import \
    SerializerMutation
class MyAwesomeMutation(SerializerMutation):
    class Meta:
        serializer_class = MySerializer

Client Authentication

Numeric detail

ichef/queryfilter

To share same query interface between Django ORM, SQLAlchemy, GraphQL backend, and raw dict

Example of queryfilter

dicts = [
    {"age": None},
    {"age": 1},
    {"age": 2},
]
query_filter = QueryFilter({
    "age": {
        "type": "number",
        "options": {"drop_none": True},
        "min": 0, "max": 1,
    }
})
assert len(query_filter.on_dicts(dicts)) == 1

Example of queryfilter

import graphene
from queryfilter import (QueryFilter,
    BirthFilterQueryType, TextFilterQueryType,
    NumberRangeFilterQueryType)
 
class UserQueryFilter(graphene.InputObjectType):
    name = TextFilterQueryType()
    birth = BirthFilterQueryType()
    total_login = NumberRangeFilterQueryType()

Summary

GraphQL is evolving
even production ready

<< New Release of GraphQL blah >>

I’ve spent 2 years trying to get Apollo Server into prod,
now I’ll no doubt have to make changes.

References

TODO

Q&A

Q&A 1:
Is it worth to migrate existed APIs?

Q&A 2:
Then HOW?

Strategies
Plans
Fun

Start small and stay finish

Motivation

Communication

Just like all other new or different technology you hope to have in the orgazination, it's more of team art and culture. Then it's not always someone's fault, in different condition it's just meant to be failed. It's just that if you didn't try, things surely won't happen. Sometimes it's just that the technology not worthy indead. For this one, I encourage you to try on.

Thank you!