From f13e54b4a4e963b3bf2a6d44d19d27f99b9da8fd Mon Sep 17 00:00:00 2001 From: Dan <3498629+dan98765@users.noreply.github.com> Date: Wed, 30 May 2018 04:53:44 -0700 Subject: [PATCH 1/7] Update contributing docs about using tox and sync tox pytest cmd with travis (#744) * Update pytest command run by tox to match the command used by travis. Updated README contributing section with info about using tox to run tests. * Uppercase 'Graphene' --- README.md | 18 ++++++++++++++++-- docs/conf.py | 1 + tox.ini | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2288f341..3d972fdc 100644 --- a/README.md +++ b/README.md @@ -79,18 +79,32 @@ After cloning this repo, ensure dependencies are installed by running: 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 -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: ```sh 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 diff --git a/docs/conf.py b/docs/conf.py index ccb5305d..b25a36dc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,5 @@ import os + import sphinx_graphene_theme on_rtd = os.environ.get('READTHEDOCS', None) == 'True' diff --git a/tox.ini b/tox.ini index cf538bb7..3c2860c9 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ deps = .[test] setenv = PYTHONPATH = .:{envdir} commands= - py.test + py.test --cov=graphene graphene examples [testenv:pre-commit] basepython=python3.6 From 4e59cf3ea6c3cbf6bab589301b8be8e2600d44ca Mon Sep 17 00:00:00 2001 From: Dan Palmer Date: Wed, 30 May 2018 14:50:22 +0100 Subject: [PATCH 2/7] Fix warning output Warning filtering is the responsibility of the application, not a library, and this current use causes all warnings from an application (at least those after this function is evaluated the first time) to print their contents. This makes the library a better citizen in the Python ecosystem, and more closely matches what developers would expect. (For what it's worth, we also can't start using this library without this patch because the logging is too verbose and may obscure more important warnings. We depend on being able to accurately control warning and logging output) --- graphene/utils/deprecated.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/graphene/utils/deprecated.py b/graphene/utils/deprecated.py index f70b2e7d..d00642f8 100644 --- a/graphene/utils/deprecated.py +++ b/graphene/utils/deprecated.py @@ -6,13 +6,11 @@ string_types = (type(b''), type(u'')) def warn_deprecation(text): - warnings.simplefilter('always', DeprecationWarning) warnings.warn( text, category=DeprecationWarning, stacklevel=2 ) - warnings.simplefilter('default', DeprecationWarning) def deprecated(reason): From aa0c401cb517afc09f6dee7477005ed1badb35c8 Mon Sep 17 00:00:00 2001 From: Kurtis Jantzen Date: Wed, 30 May 2018 16:56:42 -0600 Subject: [PATCH 3/7] Resolve #750 by editing assert message --- graphene/relay/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene/relay/node.py b/graphene/relay/node.py index 6596757c..5c787ffb 100644 --- a/graphene/relay/node.py +++ b/graphene/relay/node.py @@ -101,8 +101,8 @@ class Node(AbstractNode): if only_type: assert graphene_type == only_type, ( - 'Must receive an {} id.' - ).format(graphene_type._meta.name) + 'Must receive a {} id.' + ).format(only_type._meta.name) # We make sure the ObjectType implements the "Node" interface if cls not in graphene_type._meta.interfaces: From d6a81ee7ff7a9bb406478cecc3d78ed68da451a1 Mon Sep 17 00:00:00 2001 From: Kurtis Jantzen Date: Wed, 30 May 2018 17:06:43 -0600 Subject: [PATCH 4/7] Update tests to reflect changes --- graphene/relay/tests/test_node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene/relay/tests/test_node.py b/graphene/relay/tests/test_node.py index 10dc5d94..df44fcb5 100644 --- a/graphene/relay/tests/test_node.py +++ b/graphene/relay/tests/test_node.py @@ -115,7 +115,7 @@ def test_node_field_only_type_wrong(): '{ onlyNode(id:"%s") { __typename, name } } ' % Node.to_global_id("MyOtherNode", 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} @@ -132,7 +132,7 @@ def test_node_field_only_lazy_type_wrong(): '{ onlyNodeLazy(id:"%s") { __typename, name } } ' % Node.to_global_id("MyOtherNode", 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} From 00ccc2056bd6ac3af484459cd9825f38f9006106 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin Date: Thu, 31 May 2018 21:52:35 -0400 Subject: [PATCH 5/7] Don't overwrite fields on InputObject - closes #720 --- graphene/tests/issues/test_720.py | 44 +++++++++++++++++++++++++++++++ graphene/types/inputobjecttype.py | 5 +++- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 graphene/tests/issues/test_720.py diff --git a/graphene/tests/issues/test_720.py b/graphene/tests/issues/test_720.py new file mode 100644 index 00000000..8cd99bdd --- /dev/null +++ b/graphene/tests/issues/test_720.py @@ -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 diff --git a/graphene/types/inputobjecttype.py b/graphene/types/inputobjecttype.py index dbfccc46..b84fc0fd 100644 --- a/graphene/types/inputobjecttype.py +++ b/graphene/types/inputobjecttype.py @@ -50,7 +50,10 @@ class InputObjectType(UnmountedType, BaseType): yank_fields_from_attrs(base.__dict__, _as=InputField) ) - _meta.fields = fields + if _meta.fields: + _meta.fields.update(fields) + else: + _meta.fields = fields if container is None: container = type(cls.__name__, (InputObjectTypeContainer, cls), {}) _meta.container = container From 2fbd2c1cb6ad4b6ff19896d267b9f8a4af567107 Mon Sep 17 00:00:00 2001 From: Felipe Mesquita Date: Fri, 1 Jun 2018 17:15:34 -0300 Subject: [PATCH 6/7] Fix parameter order for Relay's Root-field --- UPGRADE-v2.0.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/UPGRADE-v2.0.md b/UPGRADE-v2.0.md index 5dc76154..32b28d8b 100644 --- a/UPGRADE-v2.0.md +++ b/UPGRADE-v2.0.md @@ -198,6 +198,32 @@ class MyObject(ObjectType): 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 Now only receives (`self`, `info`, `**args`) and is not a @classmethod From 1b3e7f3b9600f5d2c20a17c9cc0a07f559b76b92 Mon Sep 17 00:00:00 2001 From: Dan <3498629+dan98765@users.noreply.github.com> Date: Tue, 5 Jun 2018 13:47:07 -0700 Subject: [PATCH 7/7] Add flake8 pre-commit hook and manually edit files to pass flake8 validation (#746) Add flake8 pre-commit hook and manually edit files to pass flake8 validation --- .pre-commit-config.yaml | 3 ++- graphene/relay/tests/test_connection_query.py | 7 +++---- graphene/tests/issues/test_313.py | 1 - graphene/tests/issues/test_356.py | 13 ++++++++----- graphene/tests/issues/test_425.py | 3 +-- graphene/types/tests/test_abstracttype.py | 2 -- graphene/types/tests/test_argument.py | 10 ++++++++-- graphene/types/tests/test_base.py | 2 -- graphene/types/tests/test_datetime.py | 6 +++--- graphene/types/tests/test_definition.py | 9 ++++++++- graphene/types/tests/test_enum.py | 4 ++-- graphene/types/tests/test_query.py | 2 -- graphene/types/tests/test_typemap.py | 3 +-- graphene/utils/tests/test_annotate.py | 2 +- graphene/utils/tests/test_deprecated.py | 2 +- graphene/utils/tests/test_resolve_only_args.py | 2 -- 16 files changed, 38 insertions(+), 33 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 085f7fa9..156d038a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: git://github.com/pre-commit/pre-commit-hooks - rev: v1.2.3 + rev: v1.3.0 hooks: - id: autopep8-wrapper args: @@ -16,6 +16,7 @@ repos: - id: pretty-format-json args: - --autofix + - id: flake8 - repo: https://github.com/asottile/seed-isort-config rev: v1.0.0 hooks: diff --git a/graphene/relay/tests/test_connection_query.py b/graphene/relay/tests/test_connection_query.py index b8150e64..0f266a55 100644 --- a/graphene/relay/tests/test_connection_query.py +++ b/graphene/relay/tests/test_connection_query.py @@ -56,8 +56,7 @@ schema = Schema(Query) letters = OrderedDict() for i, letter in enumerate(letter_chars): - l = Letter(id=i, letter=letter) - letters[letter] = l + letters[letter] = Letter(id=i, letter=letter) def edges(selected_letters): @@ -74,8 +73,8 @@ def edges(selected_letters): def cursor_for(ltr): - l = letters[ltr] - return base64('arrayconnection:%s' % l.id) + letter = letters[ltr] + return base64('arrayconnection:%s' % letter.id) def execute(args=''): diff --git a/graphene/tests/issues/test_313.py b/graphene/tests/issues/test_313.py index 9df6c17b..e524a85b 100644 --- a/graphene/tests/issues/test_313.py +++ b/graphene/tests/issues/test_313.py @@ -1,7 +1,6 @@ # https://github.com/graphql-python/graphene/issues/313 import graphene -from graphene import resolve_only_args class Query(graphene.ObjectType): diff --git a/graphene/tests/issues/test_356.py b/graphene/tests/issues/test_356.py index 8eeaed10..9d8fc77b 100644 --- a/graphene/tests/issues/test_356.py +++ b/graphene/tests/issues/test_356.py @@ -21,10 +21,13 @@ class MyUnion(graphene.Union): def test_issue(): + class Query(graphene.ObjectType): + things = relay.ConnectionField(MyUnion) + with pytest.raises(Exception) as exc_info: - class Query(graphene.ObjectType): - things = relay.ConnectionField(MyUnion) + graphene.Schema(query=Query) - schema = 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".' + ) diff --git a/graphene/tests/issues/test_425.py b/graphene/tests/issues/test_425.py index cc77e730..525e0b76 100644 --- a/graphene/tests/issues/test_425.py +++ b/graphene/tests/issues/test_425.py @@ -2,8 +2,7 @@ # Adapted for Graphene 2.0 from graphene.types.enum import Enum, EnumOptions -from graphene.types.inputobjecttype import (InputObjectType, - InputObjectTypeOptions) +from graphene.types.inputobjecttype import InputObjectType from graphene.types.objecttype import ObjectType, ObjectTypeOptions diff --git a/graphene/types/tests/test_abstracttype.py b/graphene/types/tests/test_abstracttype.py index 73144bec..6ad35b75 100644 --- a/graphene/types/tests/test_abstracttype.py +++ b/graphene/types/tests/test_abstracttype.py @@ -1,5 +1,3 @@ -import pytest - from .. import abstracttype from ..abstracttype import AbstractType from ..field import Field diff --git a/graphene/types/tests/test_argument.py b/graphene/types/tests/test_argument.py index 744c9e29..bc7b6e1c 100644 --- a/graphene/types/tests/test_argument.py +++ b/graphene/types/tests/test_argument.py @@ -51,7 +51,10 @@ def test_to_arguments_raises_if_field(): with pytest.raises(ValueError) as exc_info: 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(): @@ -62,7 +65,10 @@ def test_to_arguments_raises_if_inputfield(): with pytest.raises(ValueError) as exc_info: 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(): diff --git a/graphene/types/tests/test_base.py b/graphene/types/tests/test_base.py index 6399f69d..cd555ad5 100644 --- a/graphene/types/tests/test_base.py +++ b/graphene/types/tests/test_base.py @@ -1,5 +1,3 @@ -import pytest - from ..base import BaseOptions, BaseType diff --git a/graphene/types/tests/test_datetime.py b/graphene/types/tests/test_datetime.py index 80c1d3c1..35e8c785 100644 --- a/graphene/types/tests/test_datetime.py +++ b/graphene/types/tests/test_datetime.py @@ -62,7 +62,7 @@ def test_bad_datetime_query(): assert len(result.errors) == 1 assert isinstance(result.errors[0], GraphQLError) - assert result.data == None + assert result.data is None def test_bad_date_query(): @@ -72,7 +72,7 @@ def test_bad_date_query(): assert len(result.errors) == 1 assert isinstance(result.errors[0], GraphQLError) - assert result.data == None + assert result.data is None def test_bad_time_query(): @@ -82,7 +82,7 @@ def test_bad_time_query(): assert len(result.errors) == 1 assert isinstance(result.errors[0], GraphQLError) - assert result.data == None + assert result.data is None def test_datetime_query_variable(): diff --git a/graphene/types/tests/test_definition.py b/graphene/types/tests/test_definition.py index af9168c9..d3cd2c38 100644 --- a/graphene/types/tests/test_definition.py +++ b/graphene/types/tests/test_definition.py @@ -288,7 +288,14 @@ def test_stringifies_simple_types(): # ] # for x in bad_union_types: # 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) + '.' \ # == str(excinfo.value) diff --git a/graphene/types/tests/test_enum.py b/graphene/types/tests/test_enum.py index d35d555f..231abba6 100644 --- a/graphene/types/tests/test_enum.py +++ b/graphene/types/tests/test_enum.py @@ -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[0].name == 'NEWHOPE' and GraphQLPyEpisode[0].deprecation_reason == 'meh' - assert GraphQLPyEpisode[1].name == 'EMPIRE' and GraphQLPyEpisode[1].deprecation_reason == None - assert GraphQLPyEpisode[2].name == 'JEDI' and GraphQLPyEpisode[2].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 is None def test_enum_from_python3_enum_uses_enum_doc(): diff --git a/graphene/types/tests/test_query.py b/graphene/types/tests/test_query.py index c4133970..7bdde001 100644 --- a/graphene/types/tests/test_query.py +++ b/graphene/types/tests/test_query.py @@ -442,8 +442,6 @@ def test_big_list_of_containers_multiple_fields_custom_resolvers_query_benchmark def test_query_annotated_resolvers(): - import json - context = Context(key="context") class Query(ObjectType): diff --git a/graphene/types/tests/test_typemap.py b/graphene/types/tests/test_typemap.py index 94af960c..7d441bfb 100644 --- a/graphene/types/tests/test_typemap.py +++ b/graphene/types/tests/test_typemap.py @@ -67,8 +67,7 @@ def test_objecttype(): foo_field = fields['foo'] assert isinstance(foo_field, GraphQLField) assert foo_field.description == 'Field description' - f = MyObjectType.resolve_foo - # assert foo_field.resolver == getattr(f, '__func__', f) + assert foo_field.args == { 'bar': GraphQLArgument(GraphQLString, description='Argument description', default_value='x', out_name='bar') } diff --git a/graphene/utils/tests/test_annotate.py b/graphene/utils/tests/test_annotate.py index 446fbbfe..259df3cb 100644 --- a/graphene/utils/tests/test_annotate.py +++ b/graphene/utils/tests/test_annotate.py @@ -34,6 +34,6 @@ def test_annotate_with_params(): def test_annotate_with_wront_params(): 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".' diff --git a/graphene/utils/tests/test_deprecated.py b/graphene/utils/tests/test_deprecated.py index 0af06763..16fca03b 100644 --- a/graphene/utils/tests/test_deprecated.py +++ b/graphene/utils/tests/test_deprecated.py @@ -63,5 +63,5 @@ def test_deprecated_class_text(mocker): def test_deprecated_other_object(mocker): mocker.patch.object(deprecated, 'warn_deprecation') - with pytest.raises(TypeError) as exc_info: + with pytest.raises(TypeError): deprecated_decorator({}) diff --git a/graphene/utils/tests/test_resolve_only_args.py b/graphene/utils/tests/test_resolve_only_args.py index 11a6bbdb..1dfd4789 100644 --- a/graphene/utils/tests/test_resolve_only_args.py +++ b/graphene/utils/tests/test_resolve_only_args.py @@ -8,8 +8,6 @@ def test_resolve_only_args(mocker): def resolver(root, **args): return root, args - my_data = {'one': 1, 'two': 2} - wrapped_resolver = resolve_only_args(resolver) assert deprecated.warn_deprecation.called result = wrapped_resolver(1, 2, a=3)