Merge branch 'master' into daniellg_create_makefile

This commit is contained in:
Dan 2018-06-08 22:58:03 -07:00 committed by GitHub
commit 8065eeae26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 133 additions and 45 deletions

View File

@ -1,6 +1,6 @@
repos: repos:
- repo: git://github.com/pre-commit/pre-commit-hooks - repo: git://github.com/pre-commit/pre-commit-hooks
rev: v1.2.3 rev: v1.3.0
hooks: hooks:
- id: autopep8-wrapper - id: autopep8-wrapper
args: args:
@ -16,6 +16,7 @@ repos:
- id: pretty-format-json - id: pretty-format-json
args: args:
- --autofix - --autofix
- id: flake8
- repo: https://github.com/asottile/seed-isort-config - repo: https://github.com/asottile/seed-isort-config
rev: v1.0.0 rev: v1.0.0
hooks: hooks:

View File

@ -79,18 +79,32 @@ After cloning this repo, ensure dependencies are installed by running:
pip install -e ".[test]" pip install -e ".[test]"
``` ```
After developing, the full test suite can be evaluated by running: Well-written tests and maintaining good test coverage is important to this project. While developing, run new and existing tests with:
```sh ```sh
py.test graphene --cov=graphene --benchmark-skip # Use -v -s for verbose mode py.test PATH/TO/MY/DIR/test_test.py # Single file
py.test PATH/TO/MY/DIR/ # All tests in directory
``` ```
Add the `-s` flag if you have introduced breakpoints into the code for debugging.
Add the `-v` ("verbose") flag to get more detailed test output. For even more detailed output, use `-vv`.
Check out the [pytest documentation](https://docs.pytest.org/en/latest/) for more options and test running controls.
You can also run the benchmarks with: You can also run the benchmarks with:
```sh ```sh
py.test graphene --benchmark-only py.test graphene --benchmark-only
``` ```
Graphene supports several versions of Python. To make sure that changes do not break compatibility with any of those versions, we use `tox` to create virtualenvs for each python version and run tests with that version. To run against all python versions defined in the `tox.ini` config file, just run:
```sh
tox
```
If you wish to run against a specific version defined in the `tox.ini` file:
```sh
tox -e py36
```
Tox can only use whatever versions of python are installed on your system. When you create a pull request, Travis will also be running the same tests and report the results, so there is no need for potential contributors to try to install every single version of python on their own system ahead of time. We appreciate opening issues and pull requests to make graphene even more stable & useful!
### Documentation ### Documentation

View File

@ -198,6 +198,32 @@ class MyObject(ObjectType):
return ... 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(self, 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(self, info, id):
node = relay.Node.get_node_from_global_id(info, id)
return node
```
## Mutation.mutate ## Mutation.mutate
Now only receives (`self`, `info`, `**args`) and is not a @classmethod Now only receives (`self`, `info`, `**args`) and is not a @classmethod

View File

@ -101,8 +101,8 @@ class Node(AbstractNode):
if only_type: if only_type:
assert graphene_type == only_type, ( assert graphene_type == only_type, (
'Must receive an {} id.' 'Must receive a {} id.'
).format(graphene_type._meta.name) ).format(only_type._meta.name)
# We make sure the ObjectType implements the "Node" interface # We make sure the ObjectType implements the "Node" interface
if cls not in graphene_type._meta.interfaces: if cls not in graphene_type._meta.interfaces:

View File

@ -56,8 +56,7 @@ schema = Schema(Query)
letters = OrderedDict() letters = OrderedDict()
for i, letter in enumerate(letter_chars): for i, letter in enumerate(letter_chars):
l = Letter(id=i, letter=letter) letters[letter] = Letter(id=i, letter=letter)
letters[letter] = l
def edges(selected_letters): def edges(selected_letters):
@ -74,8 +73,8 @@ def edges(selected_letters):
def cursor_for(ltr): def cursor_for(ltr):
l = letters[ltr] letter = letters[ltr]
return base64('arrayconnection:%s' % l.id) return base64('arrayconnection:%s' % letter.id)
def execute(args=''): def execute(args=''):

View File

@ -115,7 +115,7 @@ def test_node_field_only_type_wrong():
'{ onlyNode(id:"%s") { __typename, name } } ' % Node.to_global_id("MyOtherNode", 1) '{ onlyNode(id:"%s") { __typename, name } } ' % Node.to_global_id("MyOtherNode", 1)
) )
assert len(executed.errors) == 1 assert len(executed.errors) == 1
assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.' assert str(executed.errors[0]) == 'Must receive a MyNode id.'
assert executed.data == {'onlyNode': None} assert executed.data == {'onlyNode': None}
@ -132,7 +132,7 @@ def test_node_field_only_lazy_type_wrong():
'{ onlyNodeLazy(id:"%s") { __typename, name } } ' % Node.to_global_id("MyOtherNode", 1) '{ onlyNodeLazy(id:"%s") { __typename, name } } ' % Node.to_global_id("MyOtherNode", 1)
) )
assert len(executed.errors) == 1 assert len(executed.errors) == 1
assert str(executed.errors[0]) == 'Must receive an MyOtherNode id.' assert str(executed.errors[0]) == 'Must receive a MyNode id.'
assert executed.data == {'onlyNodeLazy': None} assert executed.data == {'onlyNodeLazy': None}

View File

@ -1,7 +1,6 @@
# https://github.com/graphql-python/graphene/issues/313 # https://github.com/graphql-python/graphene/issues/313
import graphene import graphene
from graphene import resolve_only_args
class Query(graphene.ObjectType): class Query(graphene.ObjectType):

View File

@ -21,10 +21,13 @@ class MyUnion(graphene.Union):
def test_issue(): def test_issue():
with pytest.raises(Exception) as exc_info:
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
things = relay.ConnectionField(MyUnion) things = relay.ConnectionField(MyUnion)
schema = graphene.Schema(query=Query) with pytest.raises(Exception) as exc_info:
graphene.Schema(query=Query)
assert str(exc_info.value) == 'IterableConnectionField type have to be a subclass of Connection. Received "MyUnion".' assert str(exc_info.value) == (
'IterableConnectionField type have to be a subclass of Connection. '
'Received "MyUnion".'
)

View File

@ -2,8 +2,7 @@
# Adapted for Graphene 2.0 # Adapted for Graphene 2.0
from graphene.types.enum import Enum, EnumOptions from graphene.types.enum import Enum, EnumOptions
from graphene.types.inputobjecttype import (InputObjectType, from graphene.types.inputobjecttype import InputObjectType
InputObjectTypeOptions)
from graphene.types.objecttype import ObjectType, ObjectTypeOptions from graphene.types.objecttype import ObjectType, ObjectTypeOptions

View File

@ -0,0 +1,44 @@
# https://github.com/graphql-python/graphene/issues/720
# InputObjectTypes overwrite the "fields" attribute of the provided
# _meta object, so even if dynamic fields are provided with a standard
# InputObjectTypeOptions, they are ignored.
import graphene
class MyInputClass(graphene.InputObjectType):
@classmethod
def __init_subclass_with_meta__(
cls, container=None, _meta=None, fields=None, **options):
if _meta is None:
_meta = graphene.types.inputobjecttype.InputObjectTypeOptions(cls)
_meta.fields = fields
super(MyInputClass, cls).__init_subclass_with_meta__(
container=container, _meta=_meta, **options)
class MyInput(MyInputClass):
class Meta:
fields = dict(x=graphene.Field(graphene.Int))
class Query(graphene.ObjectType):
myField = graphene.Field(graphene.String, input=graphene.Argument(MyInput))
def resolve_myField(parent, info, input):
return 'ok'
def test_issue():
query_string = '''
query myQuery {
myField(input: {x: 1})
}
'''
schema = graphene.Schema(query=Query)
result = schema.execute(query_string)
assert not result.errors

View File

@ -50,6 +50,9 @@ class InputObjectType(UnmountedType, BaseType):
yank_fields_from_attrs(base.__dict__, _as=InputField) yank_fields_from_attrs(base.__dict__, _as=InputField)
) )
if _meta.fields:
_meta.fields.update(fields)
else:
_meta.fields = fields _meta.fields = fields
if container is None: if container is None:
container = type(cls.__name__, (InputObjectTypeContainer, cls), {}) container = type(cls.__name__, (InputObjectTypeContainer, cls), {})

View File

@ -1,5 +1,3 @@
import pytest
from .. import abstracttype from .. import abstracttype
from ..abstracttype import AbstractType from ..abstracttype import AbstractType
from ..field import Field from ..field import Field

View File

@ -51,7 +51,10 @@ def test_to_arguments_raises_if_field():
with pytest.raises(ValueError) as exc_info: with pytest.raises(ValueError) as exc_info:
to_arguments(args) to_arguments(args)
assert str(exc_info.value) == 'Expected arg_string to be Argument, but received Field. Try using Argument(String).' assert str(exc_info.value) == (
'Expected arg_string to be Argument, but received Field. Try using '
'Argument(String).'
)
def test_to_arguments_raises_if_inputfield(): def test_to_arguments_raises_if_inputfield():
@ -62,7 +65,10 @@ def test_to_arguments_raises_if_inputfield():
with pytest.raises(ValueError) as exc_info: with pytest.raises(ValueError) as exc_info:
to_arguments(args) to_arguments(args)
assert str(exc_info.value) == 'Expected arg_string to be Argument, but received InputField. Try using Argument(String).' assert str(exc_info.value) == (
'Expected arg_string to be Argument, but received InputField. Try '
'using Argument(String).'
)
def test_argument_with_lazy_type(): def test_argument_with_lazy_type():

View File

@ -1,5 +1,3 @@
import pytest
from ..base import BaseOptions, BaseType from ..base import BaseOptions, BaseType

View File

@ -62,7 +62,7 @@ def test_bad_datetime_query():
assert len(result.errors) == 1 assert len(result.errors) == 1
assert isinstance(result.errors[0], GraphQLError) assert isinstance(result.errors[0], GraphQLError)
assert result.data == None assert result.data is None
def test_bad_date_query(): def test_bad_date_query():
@ -72,7 +72,7 @@ def test_bad_date_query():
assert len(result.errors) == 1 assert len(result.errors) == 1
assert isinstance(result.errors[0], GraphQLError) assert isinstance(result.errors[0], GraphQLError)
assert result.data == None assert result.data is None
def test_bad_time_query(): def test_bad_time_query():
@ -82,7 +82,7 @@ def test_bad_time_query():
assert len(result.errors) == 1 assert len(result.errors) == 1
assert isinstance(result.errors[0], GraphQLError) assert isinstance(result.errors[0], GraphQLError)
assert result.data == None assert result.data is None
def test_datetime_query_variable(): def test_datetime_query_variable():

View File

@ -288,7 +288,14 @@ def test_stringifies_simple_types():
# ] # ]
# for x in bad_union_types: # for x in bad_union_types:
# with raises(Exception) as excinfo: # with raises(Exception) as excinfo:
# GraphQLSchema(GraphQLObjectType('Root', fields={'union': GraphQLField(GraphQLUnionType('BadUnion', [x]))})) # GraphQLSchema(
# GraphQLObjectType(
# 'Root',
# fields={
# 'union': GraphQLField(GraphQLUnionType('BadUnion', [x]))
# }
# )
# )
# assert 'BadUnion may only contain Object types, it cannot contain: ' + str(x) + '.' \ # assert 'BadUnion may only contain Object types, it cannot contain: ' + str(x) + '.' \
# == str(excinfo.value) # == str(excinfo.value)

View File

@ -96,8 +96,8 @@ def test_enum_from_builtin_enum_accepts_lambda_description():
assert GraphQLPyEpisode[2].name == 'JEDI' and GraphQLPyEpisode[2].description == 'Other' assert GraphQLPyEpisode[2].name == 'JEDI' and GraphQLPyEpisode[2].description == 'Other'
assert GraphQLPyEpisode[0].name == 'NEWHOPE' and GraphQLPyEpisode[0].deprecation_reason == 'meh' assert GraphQLPyEpisode[0].name == 'NEWHOPE' and GraphQLPyEpisode[0].deprecation_reason == 'meh'
assert GraphQLPyEpisode[1].name == 'EMPIRE' and GraphQLPyEpisode[1].deprecation_reason == None assert GraphQLPyEpisode[1].name == 'EMPIRE' and GraphQLPyEpisode[1].deprecation_reason is None
assert GraphQLPyEpisode[2].name == 'JEDI' and GraphQLPyEpisode[2].deprecation_reason == None assert GraphQLPyEpisode[2].name == 'JEDI' and GraphQLPyEpisode[2].deprecation_reason is None
def test_enum_from_python3_enum_uses_enum_doc(): def test_enum_from_python3_enum_uses_enum_doc():

View File

@ -442,8 +442,6 @@ def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark
def test_query_annotated_resolvers(): def test_query_annotated_resolvers():
import json
context = Context(key="context") context = Context(key="context")
class Query(ObjectType): class Query(ObjectType):

View File

@ -67,8 +67,7 @@ def test_objecttype():
foo_field = fields['foo'] foo_field = fields['foo']
assert isinstance(foo_field, GraphQLField) assert isinstance(foo_field, GraphQLField)
assert foo_field.description == 'Field description' assert foo_field.description == 'Field description'
f = MyObjectType.resolve_foo
# assert foo_field.resolver == getattr(f, '__func__', f)
assert foo_field.args == { assert foo_field.args == {
'bar': GraphQLArgument(GraphQLString, description='Argument description', default_value='x', out_name='bar') 'bar': GraphQLArgument(GraphQLString, description='Argument description', default_value='x', out_name='bar')
} }

View File

@ -6,13 +6,11 @@ string_types = (type(b''), type(u''))
def warn_deprecation(text): def warn_deprecation(text):
warnings.simplefilter('always', DeprecationWarning)
warnings.warn( warnings.warn(
text, text,
category=DeprecationWarning, category=DeprecationWarning,
stacklevel=2 stacklevel=2
) )
warnings.simplefilter('default', DeprecationWarning)
def deprecated(reason): def deprecated(reason):

View File

@ -34,6 +34,6 @@ def test_annotate_with_params():
def test_annotate_with_wront_params(): def test_annotate_with_wront_params():
with pytest.raises(Exception) as exc_info: with pytest.raises(Exception) as exc_info:
annotated_func = annotate(p=int, _trigger_warning=False)(func) annotate(p=int, _trigger_warning=False)(func)
assert str(exc_info.value) == 'The key p is not a function parameter in the function "func".' assert str(exc_info.value) == 'The key p is not a function parameter in the function "func".'

View File

@ -63,5 +63,5 @@ def test_deprecated_class_text(mocker):
def test_deprecated_other_object(mocker): def test_deprecated_other_object(mocker):
mocker.patch.object(deprecated, 'warn_deprecation') mocker.patch.object(deprecated, 'warn_deprecation')
with pytest.raises(TypeError) as exc_info: with pytest.raises(TypeError):
deprecated_decorator({}) deprecated_decorator({})

View File

@ -8,8 +8,6 @@ def test_resolve_only_args(mocker):
def resolver(root, **args): def resolver(root, **args):
return root, args return root, args
my_data = {'one': 1, 'two': 2}
wrapped_resolver = resolve_only_args(resolver) wrapped_resolver = resolve_only_args(resolver)
assert deprecated.warn_deprecation.called assert deprecated.warn_deprecation.called
result = wrapped_resolver(1, 2, a=3) result = wrapped_resolver(1, 2, a=3)

View File

@ -9,9 +9,7 @@ deps=
setenv = setenv =
PYTHONPATH = .:{envdir} PYTHONPATH = .:{envdir}
commands= commands=
py.test py.test --cov=graphene graphene examples
pre-commit install -f --install-hooks
pre-commit run --all-files
[testenv:pre-commit] [testenv:pre-commit]
basepython=python3.6 basepython=python3.6