Table of Contents
- Python compatibility
- What's new in Graphene v3
- GraphQL-core v3
- Better Enum support
- Subscription support
- Fast ObjectType creation
- Other new features
- Backwards incompatible changes in 3.0
- Backends support removed
- Enum inputs
- ObjectType private fields
- Graphene Schema no longer subclasses GraphQLSchema type
- Field class no longer needs to set the type keyword:
- Scalars no longer support tuples for the description in favor of string type values:
- Dataloaders are no longer supported for django as they require async views
- Other breaking changes
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Welcome to Graphene v3!
This is a big release and brings Graphene up to date with the latest changes in the GraphQL spec as well as delivering some new features and bug fixes. Since this is a major version release there are some backwards incompatible changes you’ll want to be aware of when upgrading from Graphene 2.1 or earlier.
Python compatibility
Graphene 3.0 drops support for Python 2.x and now supports Python 3.6, 3.7, and 3.8.
What's new in Graphene v3
GraphQL-core v3
Graphene builds on top of the excellent GraphQL-core library which is a direct port of the reference GraphQL.js library. Graphene v3 now uses GraphQL-core v3 which is a significant update from v2 bringing lots of new language features and other improvements.
Better Enum support
Graphene v3 introduces better Enum support which should improve the developer experience of working with them in Graphene. Previously when resolving an Enum you had to return the Enum value from the field resolver. In v3 you can now return the Enum member directly:
from graphene import Enum, ObjectType, Schema
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class Query(ObjectType):
color = Color(required=True)
def resolve_color(root, info):
return Color.RED
schema = Schema(query=Query)
result = schema.execute("query { color }")
assert result.data["color"] == "RED"
(This change is completely backwards compatible so any resolvers that return the member value will still work)
Also when Enum's are used as an input to a field the resolver now receives the Enum member directly rather than the value:
from graphene import Enum, ObjectType, Schema
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class Query(ObjectType):
color = Color(required=True, color_input=Color(required=True))
def resolve_color(root, info, color_input):
assert color_input is Color.RED
return color_input
schema = Schema(query=Query)
result = schema.execute("query { color(colorInput: RED) }")
assert result.data["color"] == "RED"
⚠️ This is a breaking change. You will need to update any resolvers or mutations that accept Enum's as inputs to support this change. ⚠️
Reference: https://github.com/graphql-python/graphene/pull/1153
Subscription support
Graphene v3 add subscription support 🎉 Here is an example of it in action:
from graphene import ObjectType, Int, Schema
class Subscription(ObjectType):
count_to_ten = Int()
async def subscribe_count_to_ten(root, info):
count = 0
while count < 10:
count += 1
yield count
class Query(ObjectType):
a = String()
schema = Schema(query=Query, subscription=Subscription)
result = await schema.subscribe("subscription { countToTen }")
count = 0
async for item in result:
count = item.data["countToTen"]
assert count == 10
Note: Using subscriptions with a server like Django or Flask still relies on the server supporting websockets.
Fast ObjectType creation
In Graphene v3 optimises the ObjectType initialization resulting in a x3 speed up! It does this by leveraging the same strategy that dataclasses introduced (dynamic creation of optimal __init__
functions based on eval).
More info here: https://github.com/graphql-python/graphene/pull/1157
Other new features
- Add a new Base64 scalar type (https://github.com/graphql-python/graphene/pull/1221)
- Better error messages when relay
global_id
fails to parse (https://github.com/graphql-python/graphene/pull/1074)
Backwards incompatible changes in 3.0
Backends support removed
GraphQL-core v3 removed support for the backends feature that was available in v2 and so it is no longer possible to replace the GraphQL-core backend for another one. If you have a specific requirement that requires using a backend please raise an issue on GitHub.
Enum inputs
As outlined above: if a field takes an Enum as an input the resolver will now get passed the Enum member rather than the member value.
For example, given the following schema:
from graphene import Enum, ObjectType, String, Schema
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class Query(ObjectType):
color = String(color_input=Color(required=True))
def resolve_color(root, info, color_input):
return color_input
schema = Schema(query=Query)
Before:
result = schema.execute("query { color(colorInput: RED) }")
assert result.data["color"] == "1"
After:
result = schema.execute("query { color(colorInput: RED) }")
assert result.data["color"] == "EnumMeta.RED"
ObjectType private fields
Previously you ObjectType's could be initialized with private variables by prepending the variable name with an _
. This is no longer possible in v3 without defining a custom __init__
function on the ObjectType
.
Before:
from graphene import ObjectType, String
class User(ObjectType):
_private_state = None
name = String()
user = User(name="Leia", _private_state="Extra info")
assert user._private_state == "Extra info"
After:
from graphene import ObjectType, String
class User(ObjectType):
def __init__(self, _private_state=None, **kwargs):
self._private_state = _private_state
super().__init__(**kwargs)
_private_state = None
name = String()
user = User(name="Leia", _private_state="Extra info")
assert user._private_state == "Extra info"
Graphene Schema
no longer subclasses GraphQLSchema type
The Graphene Schema
type no longer subclasses the GraphQLSchema
type but instead references it through the graphql_schema
attribute.
Before:
from graphql import GraphQLSchema
from graphene import Schema
schema = Schema(query=Query)
assert isinstance(schema, GraphQLSchema)
After:
from graphql import GraphQLSchema
from graphene import Schema
schema = Schema(query=Query)
assert isinstance(schema.graphql_schema, GraphQLSchema)
Field
class no longer needs to set the type
keyword:
Before:
permissions = Field(
type=SubmittedContentReadPermissions,
description="Query the permissions...",
)
After:
permissions = Field(
SubmittedContentReadPermissions,
description="Query the permissions...",
)
Scalars no longer support tuples for the description in favor of string
type values:
Before:
search=String(
description=(
"Fuzzy filter of Time...",
"Worker FullName...",
),
required=False,
)
After:
search=String(
description="Fuzzy filter of Time...",
required=False,
)
Dataloaders
are no longer supported for django as they require async views
Before:
def resolve_client(root, info, **kwargs):
return info.context.loaders.client_by_client_id_loader.load(root["client_id"])
loader definition:
from promise import Promise
from promise.dataloader import DataLoader
from typing import Dict
from collections import defaultdict
from server.domain.entities import Clients
class ClientByClientIdLoader(DataLoader):
def __init__(self, *args, **kwargs):
self.current_user = kwargs.pop("current_user")
super().__init__(*args, **kwargs)
def batch_load_fn(self, client_ids):
id_client: Dict[int, Clients.Client] = defaultdict(None)
clients = Clients.by_ids(
current_user=self.current_user,
ids=client_ids,
)
for client in clients:
id_client[client["id"]] = client
return Promise.resolve(
[id_client.get(client_id, None) for client_id in client_ids]
)
After (currently this approach won't work) check this issue
from aiodataloader import DataLoader
from typing import Dict
from collections import defaultdict
from server.domain.entities import Clients
class ClientByClientIdLoader(DataLoader):
def __init__(self, *args, **kwargs):
self.current_user = kwargs.pop("current_user")
super().__init__(*args, **kwargs)
async def batch_load_fn(self, client_ids):
id_client: Dict[int, Clients.Client] = defaultdict(None)
clients = Clients.by_ids(
current_user=self.current_user,
ids=client_ids,
)
for client in clients:
id_client[client["id"]] = client
return [id_client.get(client_id, None) for client_id in client_ids]
Other breaking changes
- Relay's
ConnectionField
andField
get_resolver
function renamed towrap_resolve
Node.get_node_from_global_id
now raises an exception when failing to parse global id instead of returningNone
(https://github.com/graphql-python/graphene/pull/1074)- Relay's function
from graphene.relay.node import to_global_id
now raises errorcannot import name 'to_global_id' from 'graphene.relay.node'
and must be updated tofrom graphql_relay import to_global_id
A huge thanks to everyone involved in bringing this release together!
Full changelog https://github.com/graphql-python/graphene/compare/v2.1.8...v3.0.0