mirror of
https://github.com/graphql-python/graphene.git
synced 2024-12-01 14:03:58 +03:00
60a9609b9a
* Updated all str.format(…) to f-strings This revamps the PR #984 * Pass black * Fix flake8 * Updated objecttype * Fix black version
386 lines
8.0 KiB
Markdown
386 lines
8.0 KiB
Markdown
# v2.0 Upgrade Guide
|
||
|
||
`ObjectType`, `Interface`, `InputObjectType`, `Scalar` and `Enum` implementations
|
||
have been quite simplified, without the need to define a explicit Metaclass for each subtype.
|
||
|
||
It also improves the field resolvers, [simplifying the code](#simpler-resolvers) the
|
||
developer has to write to use them.
|
||
|
||
**Deprecations:**
|
||
|
||
- [`AbstractType`](#abstracttype-deprecated)
|
||
- [`resolve_only_args`](#resolve_only_args)
|
||
- [`Mutation.Input`](#mutationinput)
|
||
|
||
**Breaking changes:**
|
||
|
||
- [`Simpler Resolvers`](#simpler-resolvers)
|
||
- [`Node Connections`](#node-connections)
|
||
|
||
**New Features!**
|
||
|
||
- [`InputObjectType`](#inputobjecttype)
|
||
- [`Meta as Class arguments`](#meta-as-class-arguments) (_only available for Python 3_)
|
||
|
||
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending
|
||
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/v2.0.0/graphene/tests/issues/test_425.py).
|
||
|
||
## Deprecations
|
||
|
||
### AbstractType deprecated
|
||
|
||
AbstractType is deprecated in graphene 2.0, you can now use normal inheritance instead.
|
||
|
||
Before:
|
||
|
||
```python
|
||
class CommonFields(AbstractType):
|
||
name = String()
|
||
|
||
class Pet(CommonFields, Interface):
|
||
pass
|
||
```
|
||
|
||
With 2.0:
|
||
|
||
```python
|
||
class CommonFields(object):
|
||
name = String()
|
||
|
||
class Pet(CommonFields, Interface):
|
||
pass
|
||
```
|
||
|
||
### resolve_only_args
|
||
|
||
`resolve_only_args` is now deprecated as the resolver API has been simplified.
|
||
|
||
Before:
|
||
|
||
```python
|
||
class User(ObjectType):
|
||
name = String()
|
||
|
||
@resolve_only_args
|
||
def resolve_name(root):
|
||
return root.name
|
||
```
|
||
|
||
With 2.0:
|
||
|
||
```python
|
||
class User(ObjectType):
|
||
name = String()
|
||
|
||
def resolve_name(root, info):
|
||
return root.name
|
||
```
|
||
|
||
### Mutation.Input
|
||
|
||
`Mutation.Input` is now deprecated in favor of using `Mutation.Arguments` (`ClientIDMutation` still uses `Input`).
|
||
|
||
Before:
|
||
|
||
```python
|
||
class User(Mutation):
|
||
class Input:
|
||
name = String()
|
||
```
|
||
|
||
With 2.0:
|
||
|
||
```python
|
||
class User(Mutation):
|
||
class Arguments:
|
||
name = String()
|
||
```
|
||
|
||
## Breaking Changes
|
||
|
||
### Simpler resolvers
|
||
|
||
All the resolvers in graphene have been simplified.
|
||
Prior to Graphene `2.0`, all resolvers required four arguments: `(root, args, context, info)`.
|
||
Now, resolver `args` are passed as keyword arguments to the function, and `context` argument dissapeared in favor of `info.context`.
|
||
|
||
Before:
|
||
|
||
```python
|
||
my_field = graphene.String(my_arg=graphene.String())
|
||
|
||
def resolve_my_field(root, args, context, info):
|
||
my_arg = args.get('my_arg')
|
||
return ...
|
||
```
|
||
|
||
With 2.0:
|
||
|
||
```python
|
||
my_field = graphene.String(my_arg=graphene.String())
|
||
|
||
def resolve_my_field(root, info, my_arg):
|
||
return ...
|
||
```
|
||
|
||
**PS.: Take care with receiving args like `my_arg` as above. This doesn't work for optional (non-required) arguments as stantard `Connection`'s arguments (first, before, after, before).**
|
||
You may need something like this:
|
||
|
||
```python
|
||
def resolve_my_field(root, info, known_field1, known_field2, **args): ## get other args with: args.get('arg_key')
|
||
```
|
||
|
||
And, if you need the context in the resolver, you can use `info.context`:
|
||
|
||
```python
|
||
my_field = graphene.String(my_arg=graphene.String())
|
||
|
||
def resolve_my_field(root, info, my_arg):
|
||
context = info.context
|
||
return ...
|
||
```
|
||
|
||
### Node Connections
|
||
|
||
Node types no longer have a `Connection` by default.
|
||
In 2.0 and onwards `Connection`s should be defined explicitly.
|
||
|
||
Before:
|
||
|
||
```python
|
||
class User(ObjectType):
|
||
class Meta:
|
||
interfaces = [relay.Node]
|
||
name = String()
|
||
|
||
class Query(ObjectType):
|
||
user_connection = relay.ConnectionField(User)
|
||
```
|
||
|
||
With 2.0:
|
||
|
||
```python
|
||
class User(ObjectType):
|
||
class Meta:
|
||
interfaces = [relay.Node]
|
||
name = String()
|
||
|
||
class UserConnection(relay.Connection):
|
||
class Meta:
|
||
node = User
|
||
|
||
class Query(ObjectType):
|
||
user_connection = relay.ConnectionField(UserConnection)
|
||
```
|
||
|
||
## Node.get_node
|
||
|
||
The method `get_node` in `ObjectTypes` that have `Node` as interface, changes its API.
|
||
From `def get_node(cls, id, context, info)` to `def get_node(cls, info, id)`.
|
||
|
||
```python
|
||
class MyObject(ObjectType):
|
||
class Meta:
|
||
interfaces = (Node, )
|
||
|
||
@classmethod
|
||
def get_node(cls, id, context, info):
|
||
return ...
|
||
```
|
||
|
||
To:
|
||
|
||
```python
|
||
class MyObject(ObjectType):
|
||
class Meta:
|
||
interfaces = (Node, )
|
||
|
||
@classmethod
|
||
def get_node(cls, info, id):
|
||
return ...
|
||
```
|
||
|
||
## Node.get_node_from_global_id
|
||
|
||
The parameters' order of `get_node_from_global_id` method has changed. You may need to adjust your [Node Root Field](http://docs.graphene-python.org/en/latest/relay/nodes/#node-root-field) and maybe other places that uses this method to obtain an object.
|
||
|
||
Before:
|
||
|
||
```python
|
||
class RootQuery(object):
|
||
...
|
||
node = Field(relay.Node, id=ID(required=True))
|
||
|
||
def resolve_node(root, args, context, info):
|
||
node = relay.Node.get_node_from_global_id(args['id'], context, info)
|
||
return node
|
||
```
|
||
|
||
Now:
|
||
|
||
```python
|
||
class RootQuery(object):
|
||
...
|
||
node = Field(relay.Node, id=ID(required=True))
|
||
|
||
def resolve_node(root, info, id):
|
||
node = relay.Node.get_node_from_global_id(info, id)
|
||
return node
|
||
```
|
||
|
||
## Mutation.mutate
|
||
|
||
Now only receives (`root`, `info`, `**kwargs`) and is not a @classmethod
|
||
|
||
Before:
|
||
|
||
```python
|
||
class SomeMutation(Mutation):
|
||
...
|
||
|
||
@classmethod
|
||
def mutate(cls, instance, args, context, info):
|
||
...
|
||
```
|
||
|
||
With 2.0:
|
||
|
||
```python
|
||
class SomeMutation(Mutation):
|
||
...
|
||
|
||
def mutate(root, info, **args):
|
||
...
|
||
```
|
||
|
||
With 2.0 you can also get your declared (as above) `args` this way:
|
||
|
||
```python
|
||
class SomeMutation(Mutation):
|
||
class Arguments:
|
||
first_name = String(required=True)
|
||
last_name = String(required=True)
|
||
...
|
||
|
||
def mutate(root, info, first_name, last_name):
|
||
...
|
||
```
|
||
|
||
## ClientIDMutation.mutate_and_get_payload
|
||
|
||
Now only receives (`root`, `info`, `**input`)
|
||
|
||
### Middlewares
|
||
|
||
If you are using Middelwares, you need to some adjustments:
|
||
|
||
Before:
|
||
|
||
```python
|
||
class MyGrapheneMiddleware(object):
|
||
def resolve(self, next_mw, root, args, context, info):
|
||
|
||
## Middleware code
|
||
|
||
return next_mw(root, args, context, info)
|
||
```
|
||
|
||
With 2.0:
|
||
|
||
```python
|
||
class MyGrapheneMiddleware(object):
|
||
def resolve(self, next_mw, root, info, **args):
|
||
context = info.context
|
||
|
||
## Middleware code
|
||
|
||
info.context = context
|
||
return next_mw(root, info, **args)
|
||
```
|
||
|
||
## New Features
|
||
|
||
### InputObjectType
|
||
|
||
If you are using `InputObjectType`, you now can access
|
||
its fields via `getattr` (`my_input.myattr`) when resolving, instead of
|
||
the classic way `my_input['myattr']`.
|
||
|
||
And also use custom defined properties on your input class.
|
||
|
||
Example. Before:
|
||
|
||
```python
|
||
class UserInput(InputObjectType):
|
||
id = ID(required=True)
|
||
|
||
def is_valid_input(input):
|
||
return input.get('id').startswith('userid_')
|
||
|
||
class Query(ObjectType):
|
||
user = graphene.Field(User, input=UserInput())
|
||
|
||
@resolve_only_args
|
||
def resolve_user(root, input):
|
||
user_id = input.get('id')
|
||
if is_valid_input(user_id):
|
||
return get_user(user_id)
|
||
```
|
||
|
||
With 2.0:
|
||
|
||
```python
|
||
class UserInput(InputObjectType):
|
||
id = ID(required=True)
|
||
|
||
@property
|
||
def is_valid(root):
|
||
return root.id.startswith('userid_')
|
||
|
||
class Query(ObjectType):
|
||
user = graphene.Field(User, input=UserInput())
|
||
|
||
def resolve_user(root, info, input):
|
||
if input.is_valid:
|
||
return get_user(input.id)
|
||
```
|
||
|
||
### Meta as Class arguments
|
||
|
||
Now you can use the meta options as class arguments (**ONLY PYTHON 3**).
|
||
|
||
Before:
|
||
|
||
```python
|
||
class Dog(ObjectType):
|
||
class Meta:
|
||
interfaces = [Pet]
|
||
name = String()
|
||
```
|
||
|
||
With 2.0:
|
||
|
||
```python
|
||
class Dog(ObjectType, interfaces=[Pet]):
|
||
name = String()
|
||
```
|
||
|
||
### Abstract types
|
||
|
||
Now you can create abstact types super easily, without the need of subclassing the meta.
|
||
|
||
```python
|
||
class Base(ObjectType):
|
||
class Meta:
|
||
abstract = True
|
||
|
||
id = ID()
|
||
|
||
def resolve_id(root, info):
|
||
return f"{root.__class__.__name__}_{root.id}"
|
||
```
|
||
|
||
### UUID Scalar
|
||
|
||
In Graphene 2.0 there is a new dedicated scalar for UUIDs, `UUID`.
|