Coming into the final stretch, I need to build a schema so my ObjectType, resolver, and Field can be made available on the GraphQL engine.
In GraphQL, a schema is a root level type that is meant to collect and expose all the functionality available through that node. It’s also at this point that I begin to slide away from needing to think strictly about Python and one type and start thinking in terms of graphs.
Implementing my first, very boring graph
I’ve been hard at work, building out my ObjectType
of Bucket
, a resolver named resolve_bucket
, and a Field
of _bucket
. Now I need to be thinking about where I want that to exist on my graph.
I could insert _bucket
as a root-level implementation, but now that I’m trying to think about my graph, I have to ask if that is the best design decision. _bucket
does a single operation – and while that is all I have implemented here, that is probably not all I will want to build long term.
It probably makes sense for me to declare a true top level type that will represent all the capabilities I might want to query for this graph. So to keep it simple, for now I will just name that node Query
.
from graphene import Field, ObjectType, Schema, String class Bucket(ObjectType): bucket_name = String() region = String() def resolve_bucket( parent, info, bucket_name, region ): record = record_lookup(bucket_name, region) if record is None: return Bucket() else: return Bucket( bucket_name = record.bucket_name, region = record.region ) _bucket = Field( type = Bucket, resolver = resolve_bucket, bucket_name = String(default_value=None) region = String(default_value=None) ) class Query(ObjectType): bucket = _bucket schema = Schema(query = Query)
There are two things that are worth noting here.
The first is how I am using _bucket
. Think back to how I implemented Bucket
, giving it two fields whose types were Scalars
. That meant these were termination points on the node – their values will be returned to the query response.
I had anticipated I might have multiple occasions to use Bucket
, and had also decided it probably wouldn’t function well as a root-level node in my graph. So I loaded my ObjectType
of Bucket
into a Field
of _bucket
, along with it’s resolver resolve_bucket
.
Now that I had a bound definition, I could nest it onto another ObjectType
, such as Query
.
This is a central tenant of GraphQL – I make a single type definition in a single location, and then I reuse it everywhere, occasionally changing the resolver based on the context.
The second point is that I attached my new Query
type to the query
keyword in Schema
. Each GraphQL implementation has at least query node as an entry level operation (this gets into some of the opinionated nature of the spec itself). It’s a small thing on the code level, but an important concept in the GraphQL ecosystem.
Executing GraphQL queries against a schema object
When I implemented my first resolver, it was a static method that would only ever return an ObjectType
for the my_bucket
resource. I’ve since moved on from that design, enabling the use of arguments both for my resolver and also for my Field
. But I haven’t forgotten about that bucket. In fact, I now need to look it up using the schema
class I instantiated earlier.
query_string = '{ bucket(bucketName: "my_bucket") { bucketName region } }' schema.execute(query_string) Result: { bucket_name: "my_bucket", region: "us_east_1" }
To review some of the internals – we’ve requested
Just a reminder: I started this series saying I was not going to spend a lot of time explaining how GraphQL worked, in preference for specifics around implementing Graphene. That being said, if you are unfamiliar with the query structure itself, I strongly suggest you review the GraphQL documentation about the query language and all the flexibility it provides.
Wrapping a brief demo
While this Graphene implementation does not have much to show for itself, I believe it covers the main and major tenants of getting started with GraphQL in Python.
If you are at all familiar with GraphQL, however, you will probably recognize there are still a lot of concepts that would be worth covering from a Graphene perspective, such as:
- How to implement parent -> child nodes?
- How to privately pass data through those parent -> child nodes?
- How to handle lists of types?
- How to handle circular dependencies?
- How to handle recursive nodes?
- How to handle “whole field” resolvers with optional single field overrides?
- How to handle subscriptions/websockets?
Stay tuned.
Leave a Reply