GraphQL and Python Series (Expressive Introduction)
This is part 1 of an ongoing series to introduce you to using GraphQL and Python. Hopefully, at the end of this series, you will be able to build a GraphQL Backend with Django and Graphene.
GraphQL Introductions
GraphQL is a strongly typed language and is an alternative to REpresentational Transfer(REST).
Missing explanation: Try and explain why GraphQL was created and why it's an alternative to REST. PayPal Engineering Medium article can help.
An example of a GraphQL query us as follows:
{
allFilms {
films {
title
}
}
}
GraphQL Type Schema
We'd mostly be using two types in GraphQL, the Object Type and the Scalar Type.
Object types can contain their own set of subfields, generally speaking, they have to be an object.
Scalar types are built-in GraphQL data types, and they are five in number: integers, floats, strings, booleans and ids(these are unique strings). Now, these are the standard five, but they can be extended and still be Scalar Types as we will in this article.
CRUD Operations in GraphQL
Create Read Update Delete(CRUD) functionalities are done in GraphQL using two operations: Query and Mutations. Just like we've done above, reading data(R) is done normally using queries. But to write or change data, we need a GraphQL operation called Mutation.
In summary, CUD - is covered by mutations while R - is covered by queries. The codes below are examples of using mutation to create, update and delete objects respectively
mutation {
createBaby(bodyInfo: {
name:"Edidiong",
votes: 1
}) {
name
votes
}
}
mutation {
upvote(name: "Edidiong") {
name
votes
}
}
mutation {
remove(name: "Edidiong") {
name
votes
}
}
You notice when reading data, you don't need to use query keyword but in creating, updating or deleting data, you use the mutation keyword so the engine knows you're doing a mutation.
Intro to Graphene/GraphQL In Python
By now you have seen how to do queries and mutations in Vanilla GraphQL(borrowing the word Vanilla from JavaScript), let's see how to perform GraphQL in Python.
Query Operation
To create a query, you, first of all, need a root query(this point will be explained in the code session below). Also, GraphQL uses the OOP structure to work so if you're not familiar with OOP, you may find it difficult to follow.
Code
Create a new file called schema.py
and rewrite these codes here into the file.
import uuid
import json
from datetime import datetime
import graphene
class User(graphene.ObjectType):
id = graphene.ID(default_value=str(uuid.uuid4()))
username = graphene.String()
created_at = graphene.DateTime(default_value=datetime.now())
class Query(graphene.ObjectType):
users = graphene.List(User, limit=graphene.Int())
hello = graphene.String()
def resolve_hello(self, info):
return "world"
def resolve_users(self, info, limit=None):
return [
User(id="1", username="Khalid", created_at=datetime.now()),
User(id="2", username="Ann", created_at=datetime.now()),
User(id="3", username="fred", created_at=datetime.now()),
][:limit]
# Query of users
schema = graphene.Schema(query=Query, auto_camelcase=True)
result = schema.execute(
'''
{
users {
id
username
createdAt
}
}
'''
)
dict_result = dict(result.data.items())
print(dict_result)
Let's break down the code above to grasp what was done.
User
A User
class was created that inherited from the graphene.ObjectType
. Almost all of the GraphQL types you define will be object types(only a few percentages will be Scalar Types). Object types have a name, but most importantly describe their fields.
So the User
class contains the id, username and date the account was created all. You will notice id and username are default scalar types(the .ID()
and .String()
), we are adding .DateTime()
as a scalar type(Dive deep into the Graphene source code to see all available Scalar types).
Query
Earlier, we learnt that to create a query in graphene, we first need a root query. The class Query
is that root query. We have two class attributes defined in the query: hello
and users
. More about the hello
query later.
The users
attribute takes in the User
class and a limit
argument, these two are what we would use in our actual GraphQL query. The users' details will be returned in a List
structure.
The methods we have created in this Query
class are called resolvers
. Resolvers are functions that resolves a value for a type or field in a schema. Simply put, resolvers are what return objects or scalar from our query. To name a resolver, it has to have the word resolve_
in its name(just like how Test Driven Development in Python) are. We also set a default value for the limit parameter.
Schema
To execute the code we've written, we will use the Graphene Schema
. The graphene schema can execute operations(query, mutation and subscription) against the defined types. Looking up type definitions and answering questions about the types through introspection are advance use cases of the schema.
The schema has a query
parameter which we will pass our class Query
into as an argument. This query argument is the entry point for fields to read data in our Schema.
Execute
To provide the result, we use the execute
method in the Schema class. In this method, we will provide the query in a GraphQL format but since we're using Python, it will be wrapped in a Block comment. In GraphQL, it is standard to use camelCase names. So our Fieldnames are transformed in Schema's TypeMap from snake_case to camelCase and this is made possible because of the auto_camelcase
argument and it set to True
by default.
The effect is seen in our query as created_at
is changed to createdAt
following the camelCase
convention.
Result
You should have a response like the image below
Include The Result Image.
To better format the result, we use the json.dumps
method so as to use the indent
parameter in this method. Alter the print statement above to the one below and run it to see the newly formatted result.
print(json.dumps(dict_result, indent=2))
Include The Result Image.
If you have a result similar to the image above, congratulations.
The limit parameter is by default set to None
so the output shows all the users in the resolver. If we set a limit to 1 as we do in the code below, we see it returns just a single query.
result = schema.execute(
'''
{
users(limit: 1) {
id
username
createdAt
}
}
'''
)
Note: The
(limit: 1)
is used because of theusers
attribute in the classQuery
. We had definedlimit
as a scalar type inside the users: which is what made(limit: 1)
work.
Also, we created a hello resolver. We will say this is our hello world in Graphene. To run the hello query, replace the previous query with the one below:
{
hello
}
Include The Result Image.
The image above is a result of the query.
We have been able to understand everything there is about queries. Now let's move to mutations.
Mutation Operations
Remember when we said to create queries we need a root query, in mutations, it's sorta different. The Mutation is a convenience type that helps us build a Field
which takes Arguments
and returns a mutation Output ObjectType
. This means to perform CUD operations, we need to pass arguments to a mutation. To pass in arguments to a mutation we need to pass in an inner Arguments class.
Code(mutation)
Add these new classes into the schema.py
file but arrange the classes to be on top of another.
class CreateUser(graphene.Mutation):
user = graphene.Field(User)
class Arguments:
username = graphene.String()
def mutate(self, info, username):
user = User(id="4", username=username, created_at=datetime.now())
return CreateUser(user=user)
class Mutation(graphene.ObjectType):
create_user = CreateUser.Field()
Also, comment out the previous schema and append this to the end of the file.
# Mutation
schema = graphene.Schema(query=Query, mutation=Mutation)
result = schema.execute(
'''
mutation {
createUser(username: "Edidiong") {
user {
id
username
createdAt
}
}
}
'''
)
Let's break down the code above to grasp what was done.
CreateUser
The CreateUser
class inherits from graphene's Mutation
class and creates a Field that is available on an ObjectType. The ObjectType here is our User
class.
Arguments
Arguments
inner class passes the attribute username
to the CreateUser
class and this username is gotten from the resolver. This attribute is a Scalar Type(String) and it's the arguments we use for the mutation Field.
The mutate
method is a resolver method for the Mutation class. It performs data change and returns the output which in this case the username
, id
and created_at
are the data change and a new user is returned.
Mutation
The Mutation class takes in the CreateUser
class and mounts it as a Field
. Like we know, almost all GraphQL types you define will be ObjectType
. Refer to the Mutation Operation header to understand what happens behind the engine of the Mutation
class.
Schema(mutation)
We already know about the Schema class and query
parameter. The mutation
parameter describes the entry point for fields to create, update or delete data in our API. We pass in the Mutation
class as an argument to the mutation
argument so as to perform CUD operations with it(in our case it's only Create we've done).
Execute(mutation)
The execute in mutation also does the same thing as execute in query: to read the query from a GraphQL format into a Python format so as a result can be gotten.
The only difference is the way to write the call. The mutation
keyword comes in just like we were told earlier in the article. From the previous Execute header, we understand how (username: "Edidiong")
comes about.
Result
We should have a result similar to the image below.
Include The Result Image.
Subscription Operations
Next Steps
Next, we will talk about Building GraphQL Backends with Django and Graphene. If you liked the article upvote and either drop a comment below or send me a mail
Top comments (0)