by, Adam Hopkins

the brew·mas·ter | /bro͞o ˈmastər

A lover of beer, a programmer, a lawyer. A father. A husband. Some dude on the Internet.


My new development stack using Python, GraphQL and Neo4j

old computer.jpg

Okay, let’s sit down to create a new game.

Step 1. Open text editor. Done. This is easy.

Hmm …​ Geez, maybe I should break out of my shell and try something new.

I have developed MANY applications of all sorts of shapes and sizes using the Django Framework. I am a huge supporter of it from the early days, and strongly believe it still has a long future.

But, what about all these new things out there? Sure, I can jam stuff into Django, or even replace the ORM with something else. I even had started working on a package to bring Elasticsearch into the Django model manager.

This time, I want something different. Let’s start by adding GraphQL to the stack.

My Stack
========
[X] GraphQL

How am I going to interface with the specification inside Python? Ahh, Graphene. What an amazing package!

Basically, you create a schema that looks very much like any ORM: a series of classes with fields that map to attributes. This raises a problem though.

You see, when developing my elasticmanager package, I initially grabbed the existing Django ORM models and looped over them to generate a schema that fit inside elasticsearch_dsl. The problem was that this did not give me enough flexibility. So, I took that out and created two sets of schemas: Django models, and elasticsearch datatypes. Once I added Django REST Framework into the mix, I now had a third schema! I want to avoid this problem on this project.

So, I need to be able to have one defined schema, and that will be graphene.

My Stack
========
[X] GraphQL
[X] Graphene

Well, what about database you say? I thought about Postgres (my typical go to). Seeing how this is a side project and I am already taking on new technologies, maybe I should see what else is out there that I don’t know.

Enter Neo4j.

Neo4j is a relational graph database that comes with its own query language called cypher. Probably a topic for another post. Suffice it to say that cypher is both extremely comfortable and natural to write, and hugely powerful.

Back to my stack.

How am I going to interact with Neo4j? There is a really neat package that looks EXACTLY like Django ORM: neomodel. That certainly would be an easy transition; I can already tell how I would interact with it. It looks like the code I have been writing for a long time. It does, however, seem like a little bit of a cop out though since it generates all the cypher queries for you. Do I abstract that away with neomodel? Or, take it head on and learn it.

Again, this is a side project, what do you think the answer is?

My Stack
========
[X] GraphQL
[X] Graphene
[X] Neo4j, without an ORM layer

Now my backend is starting to shape up. I am still missing, however, the key component to bind it altogether. I already decided to NOT build with Django. I suppose Flask, Bottle, or Pyramid.

Hmm …​ thinking about the frontend though, I REALLY want to have a tightly coupled chat application and live streaming data. It really seems like something meant for WebSockets.

Since Django is out, that means the awesome Django Channels (my typical WebSockets interface) is out. What next?

Tom Christie of DRF fame has been working on a new API project called apistar. It does not currently handle the sort of connections I am looking for, but he also worked on a project called uvicorn. That looks promising.

And, there are a few frameworks I hear about utilizing asyncio, namely and Sanic.

Ooo, someone made a connection for Sanic with GraphQL. With one line of code I can hook up GraphQL and Grapehne.

app.add_route(GraphQLView.as_view(schema=Schema, graphiql=True), '/graphql')

And it handles both HTTP and WebSockets. I’m sold.

My Stack
========
[X] GraphQL
[X] Graphene
[X] Neo4j, without an ORM layer
[X] Sanic
    - HTTP
    - WebSocket

Now …​ I just need to hook this all up. That was surprisingly easy. Here is how I did it.

from sanic import Sanic
from sanic.response import text
from graphene import ObjectType, List, Schema, String, Field, AbstractType
from sanic_graphql import GraphQLView
from neo4j.v1 import GraphDatabase

import copy

uri = "bolt://localhost:7687"
driver = GraphDatabase.driver(uri, auth=("neo4j", "<PASSWORD>"))


def query(cypher, **kwargs):
    with driver.session() as session:
        with session.begin_transaction() as tx:
            return tx.run(cypher, **kwargs)


league_attributes = {
    'abbreviation': String(),
    'name': String(),
}


class LeagueAbstract(AbstractType):
    def resolve_name(self, args, context, info):
        return self['l'].get('name')

    def resolve_abbreviation(self, args, context, info):
        return self['l'].get('abbreviation')


League = type('League', (LeagueAbstract, ObjectType), copy.deepcopy(league_attributes))


class Query(ObjectType):
    leagues = List(League, copy.deepcopy(league_attributes))
    league = Field(League, copy.deepcopy(league_attributes))

    def resolve_leagues(self, args, context, info):
        abbreviation = args.get('abbreviation', None)
        abbr = 'WHERE l.abbreviation={abbreviation}' if abbreviation is not None else ''
        cypher = f'MATCH (l:League) {abbr} RETURN l;'
        records = query(cypher, abbreviation=abbreviation)
        return records

    def resolve_league(self, args, context, info):
        abbreviation = args.get('abbreviation')
        cypher = 'MATCH (l:League) WHERE l.abbreviation={abbreviation} RETURN l;'
        records = query(cypher, abbreviation=abbreviation)
        return records.single()


schema = Schema(query=Query)

app = Sanic(__name__)

app.add_route(GraphQLView.as_view(schema=schema, graphiql=True), '/')

app.run(host="127.0.0.1", port=8000, debug=True)

This is my proof of concept. Clearly this script needs to be cleaned up and abstracted away into modules.

But, the point of the matter is that when taking on a side project. Step out of the comfort zone and grab a whole bunch of new tools. Because, in doing so, I have been inspired to to build several new tools. All three of those mini side projects have been fun. And I would have built none of them if I had stayed inside the lines.

Go ahead. Build something new.

                 ▄▄▄▄▄
        ▀▀▀██████▄▄▄       _______________
      ▄▄▄▄▄  █████████▄  /                 \
     ▀▀▀▀█████▌ ▀▐▄ ▀▐█ |   Gotta go fast!  |
   ▀▀█████▄▄ ▀██████▄██ | _________________/
   ▀▄▄▄▄▄  ▀▀█▄▀█════█▀ |/
        ▀▀▀▄  ▀▀███ ▀       ▄▄
     ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌
   ██▀▄▄▄██▀▄███▀ ▀▀████      ▄██
▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███     ▌▄▄▀
▌    ▐▀████▐███▒▒▒▒▒▐██▌
▀▄▄▄▄▀   ▀▀████▒▒▒▒▄██▀
          ▀▀█████████▀
        ▄▄██▀██████▀█
      ▄██▀     ▀▀▀  █
     ▄█             ▐▌
 ▄▄▄▄█▌              ▀█▄▄▄▄▀▀▄
▌     ▐                ▀▀▄▄▄▀
 ▀▀▄▄▀

photo credit: wizzer2801 Classic IBM PC Full via photopin license

comments powered by Disqus