Repository: graphql-python/graphene
Pull request #725 (2018-05-11 23:19:28)

Added partial support to Dynamic type

nikordaris: ok @jkimbo I've added a test for partial. let me know if there is anything else i should change
syrusakbary: It goods look to merge for me @jkimbo, feel free to merge once you review it :)
jkimbo: @syrusakbary I can't merge, I need write access to do that.

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..b6bb6ee
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,17 @@
+-   repo: git://github.com/pre-commit/pre-commit-hooks
+    sha: v0.8.0
+    hooks:
+    -   id: autopep8-wrapper
+        args:
+        - -i
+        - --ignore=E128,E309,E501
+        exclude: ^docs/.*$
+    -   id: check-json
+    -   id: check-yaml
+    -   id: debug-statements
+    -   id: end-of-file-fixer
+        exclude: ^docs/.*$
+    -   id: trailing-whitespace
+    -   id: pretty-format-json
+        args:
+        - --autofix
diff --git a/UPGRADE-v2.0.md b/UPGRADE-v2.0.md
index 8ef0934..96aac14 100644
--- a/UPGRADE-v2.0.md
+++ b/UPGRADE-v2.0.md
@@ -122,6 +122,13 @@ def resolve_my_field(self, 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(self, 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
@@ -193,7 +200,42 @@ class MyObject(ObjectType):
 
 ## Mutation.mutate
 
-Now only receives (`root`, `info`, `**args`)
+Now only receives (`self`, `info`, `**args`) 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(self, 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(self, info, first_name, last_name):
+        ...
+```
+
 
 
 ## ClientIDMutation.mutate_and_get_payload
@@ -201,6 +243,35 @@ Now only receives (`root`, `info`, `**args`)
 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
@@ -278,7 +349,7 @@ Now you can create abstact types super easily, without the need of subclassing t
 class Base(ObjectType):
     class Meta:
         abstract = True
-    
+
     id = ID()
 
     def resolve_id(self, info):
diff --git a/examples/complex_example.py b/examples/complex_example.py
index f9c8dbc..c9a4bb7 100644
--- a/examples/complex_example.py
+++ b/examples/complex_example.py
@@ -22,7 +22,7 @@ class Query(graphene.ObjectType):
 
 
 class CreateAddress(graphene.Mutation):
-    
+
     class Arguments:
         geo = GeoInput(required=True)
 
diff --git a/graphene/pyutils/enum.py b/graphene/pyutils/enum.py
index 928d0be..076b6a9 100644
--- a/graphene/pyutils/enum.py
+++ b/graphene/pyutils/enum.py
@@ -662,6 +662,8 @@ def __new__(cls, value):
             if member.value == value:
                 return member
     raise ValueError("%s is not a valid %s" % (value, cls.__name__))
+
+
 temp_enum_dict['__new__'] = __new__
 del __new__
 
@@ -669,12 +671,16 @@ del __new__
 def __repr__(self):
     return "<%s.%s: %r>" % (
         self.__class__.__name__, self._name_, self._value_)
+
+
 temp_enum_dict['__repr__'] = __repr__
 del __repr__
 
 
 def __str__(self):
     return "%s.%s" % (self.__class__.__name__, self._name_)
+
+
 temp_enum_dict['__str__'] = __str__
 del __str__
 
@@ -705,6 +711,8 @@ def __format__(self, format_spec):
         cls = self._member_type_
         val = self.value
     return cls.__format__(val, format_spec)
+
+
 temp_enum_dict['__format__'] = __format__
 del __format__
 
@@ -751,6 +759,8 @@ def __eq__(self, other):
     if isinstance(other, self.__class__):
         return self is other
     return NotImplemented
+
+
 temp_enum_dict['__eq__'] = __eq__
 del __eq__
 
@@ -759,18 +769,24 @@ def __ne__(self, other):
     if isinstance(other, self.__class__):
         return self is not other
     return NotImplemented
+
+
 temp_enum_dict['__ne__'] = __ne__
 del __ne__
 
 
 def __hash__(self):
     return hash(self._name_)
+
+
 temp_enum_dict['__hash__'] = __hash__
 del __hash__
 
 
 def __reduce_ex__(self, proto):
     return self.__class__, (self._value_, )
+
+
 temp_enum_dict['__reduce_ex__'] = __reduce_ex__
 del __reduce_ex__
 
@@ -785,6 +801,8 @@ del __reduce_ex__
 @_RouteClassAttributeToGetattr
 def name(self):
     return self._name_
+
+
 temp_enum_dict['name'] = name
 del name
 
@@ -792,6 +810,8 @@ del name
 @_RouteClassAttributeToGetattr
 def value(self):
     return self._value_
+
+
 temp_enum_dict['value'] = value
 del value
 
@@ -817,6 +837,8 @@ def _convert(cls, name, module, filter, source=None):
     module_globals.update(cls.__members__)
     module_globals[name] = cls
     return cls
+
+
 temp_enum_dict['_convert'] = _convert
 del _convert
 
diff --git a/graphene/pyutils/signature.py b/graphene/pyutils/signature.py
index 1308ded..9d94a6e 100644
--- a/graphene/pyutils/signature.py
+++ b/graphene/pyutils/signature.py
@@ -28,7 +28,7 @@ def formatannotation(annotation, base_module=None):
     if isinstance(annotation, type):
         if annotation.__module__ in ('builtins', '__builtin__', base_module):
             return annotation.__name__
-        return annotation.__module__+'.'+annotation.__name__
+        return annotation.__module__ + '.' + annotation.__name__
     return repr(annotation)
 
 
@@ -126,7 +126,7 @@ def signature(obj):
                                                      _partial_kwarg=True)
 
             elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and
-                            not param._partial_kwarg):
+                  not param._partial_kwarg):
                 new_params.pop(arg_name)
 
         return sig.replace(parameters=new_params.values())
@@ -193,11 +193,11 @@ class _ParameterKind(int):
         return '<_ParameterKind: {0!r}>'.format(self._name)
 
 
-_POSITIONAL_ONLY        = _ParameterKind(0, name='POSITIONAL_ONLY')
-_POSITIONAL_OR_KEYWORD  = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD')
-_VAR_POSITIONAL         = _ParameterKind(2, name='VAR_POSITIONAL')
-_KEYWORD_ONLY           = _ParameterKind(3, name='KEYWORD_ONLY')
-_VAR_KEYWORD            = _ParameterKind(4, name='VAR_KEYWORD')
+_POSITIONAL_ONLY = _ParameterKind(0, name='POSITIONAL_ONLY')
+_POSITIONAL_OR_KEYWORD = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD')
+_VAR_POSITIONAL = _ParameterKind(2, name='VAR_POSITIONAL')
+_KEYWORD_ONLY = _ParameterKind(3, name='KEYWORD_ONLY')
+_VAR_KEYWORD = _ParameterKind(4, name='VAR_KEYWORD')
 
 
 class Parameter(object):
@@ -220,11 +220,11 @@ class Parameter(object):
 
     __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg')
 
-    POSITIONAL_ONLY         = _POSITIONAL_ONLY
-    POSITIONAL_OR_KEYWORD   = _POSITIONAL_OR_KEYWORD
-    VAR_POSITIONAL          = _VAR_POSITIONAL
-    KEYWORD_ONLY            = _KEYWORD_ONLY
-    VAR_KEYWORD             = _VAR_KEYWORD
+    POSITIONAL_ONLY = _POSITIONAL_ONLY
+    POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD
+    VAR_POSITIONAL = _VAR_POSITIONAL
+    KEYWORD_ONLY = _KEYWORD_ONLY
+    VAR_KEYWORD = _VAR_KEYWORD
 
     empty = _empty
 
@@ -366,7 +366,7 @@ class BoundArguments(object):
         args = []
         for param_name, param in self._signature.parameters.items():
             if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
-                                                    param._partial_kwarg):
+                    param._partial_kwarg):
                 # Keyword arguments mapped by 'functools.partial'
                 # (Parameter._partial_kwarg is True) are mapped
                 # in 'BoundArguments.kwargs', along with VAR_KEYWORD &
@@ -396,7 +396,7 @@ class BoundArguments(object):
         for param_name, param in self._signature.parameters.items():
             if not kwargs_started:
                 if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
-                                                param._partial_kwarg):
+                        param._partial_kwarg):
                     kwargs_started = True
                 else:
                     if param_name not in self.arguments:
@@ -494,7 +494,7 @@ class Signature(object):
                     params[name] = param
             else:
                 params = OrderedDict(((param.name, param)
-                                                for param in parameters))
+                                      for param in parameters))
 
         self._parameters = params
         self._return_annotation = return_annotation
@@ -604,8 +604,8 @@ class Signature(object):
 
     def __eq__(self, other):
         if (not issubclass(type(other), Signature) or
-                    self.return_annotation != other.return_annotation or
-                    len(self.parameters) != len(other.parameters)):
+            self.return_annotation != other.return_annotation or
+                len(self.parameters) != len(other.parameters)):
             return False
 
         other_positions = dict((param, idx)
@@ -627,7 +627,7 @@ class Signature(object):
                     return False
                 else:
                     if (idx != other_idx or
-                                    param != other.parameters[param_name]):
+                            param != other.parameters[param_name]):
                         return False
 
         return True
@@ -680,7 +680,7 @@ class Signature(object):
                         parameters_ex = (param,)
                         break
                     elif (param.kind == _VAR_KEYWORD or
-                                                param.default is not _empty):
+                          param.default is not _empty):
                         # That's fine too - we have a default value for this
                         # parameter.  So, lets start parsing `kwargs`, starting
                         # with the current parameter
@@ -730,7 +730,7 @@ class Signature(object):
                 # Signature object (but let's have this check here
                 # to ensure correct behaviour just in case)
                 raise TypeError('{arg!r} parameter is positional only, '
-                                'but was passed as a keyword'. \
+                                'but was passed as a keyword'.
                                 format(arg=param.name))
 
             if param.kind == _VAR_KEYWORD:
@@ -747,8 +747,8 @@ class Signature(object):
                 # parameter, left alone by the processing of positional
                 # arguments.
                 if (not partial and param.kind != _VAR_POSITIONAL and
-                                                    param.default is _empty):
-                    raise TypeError('{arg!r} parameter lacking default value'. \
+                        param.default is _empty):
+                    raise TypeError('{arg!r} parameter lacking default value'.
                                     format(arg=param_name))
 
             else:
diff --git a/graphene/types/dynamic.py b/graphene/types/dynamic.py
index 6c4092f..0efbd08 100644
--- a/graphene/types/dynamic.py
+++ b/graphene/types/dynamic.py
@@ -1,4 +1,5 @@
 import inspect
+from functools import partial
 
 from .mountedtype import MountedType
 
@@ -11,7 +12,7 @@ class Dynamic(MountedType):
 
     def __init__(self, type, with_schema=False, _creation_counter=None):
         super(Dynamic, self).__init__(_creation_counter=_creation_counter)
-        assert inspect.isfunction(type)
+        assert inspect.isfunction(type) or isinstance(type, partial)
         self.type = type
         self.with_schema = with_schema
 
diff --git a/tox.ini b/tox.ini
index f2eccef..849f581 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = flake8,py27,py33,py34,py35,py36,pypy
+envlist = flake8,py27,py33,py34,py35,py36,pre-commit,pypy
 skipsdist = true
 
 [testenv]
@@ -20,6 +20,15 @@ setenv =
 commands=
     py.test
 
+[testenv:pre-commit]
+basepython=python3.6
+deps =
+    pre-commit>0.12.0
+setenv =
+    LC_CTYPE=en_US.UTF-8
+commands =
+    pre-commit {posargs:run --all-files}
+
 [testenv:flake8]
 deps = flake8
 commands =


Test patch:
diff --git a/graphene/relay/tests/test_node_custom.py b/graphene/relay/tests/test_node_custom.py
index cc4e910..80124c0 100644
--- a/graphene/relay/tests/test_node_custom.py
+++ b/graphene/relay/tests/test_node_custom.py
@@ -54,6 +54,7 @@ photo_data = {
 class RootQuery(ObjectType):
     node = CustomNode.Field()
 
+
 schema = Schema(query=RootQuery, types=[User, Photo])
 
 
diff --git a/graphene/types/tests/test_base.py b/graphene/types/tests/test_base.py
index 2ea3dcc..18ba677 100644
--- a/graphene/types/tests/test_base.py
+++ b/graphene/types/tests/test_base.py
@@ -17,7 +17,7 @@ class CustomType(BaseType):
 def test_basetype():
     class MyBaseType(CustomType):
         pass
-    
+
     assert isinstance(MyBaseType._meta, CustomOptions)
     assert MyBaseType._meta.name == "MyBaseType"
     assert MyBaseType._meta.description is None
diff --git a/graphene/types/tests/test_datetime.py b/graphene/types/tests/test_datetime.py
index b516e49..2d79d9f 100644
--- a/graphene/types/tests/test_datetime.py
+++ b/graphene/types/tests/test_datetime.py
@@ -55,24 +55,27 @@ def test_time_query():
     assert not result.errors
     assert result.data == {'time': isoformat}
 
+
 def test_bad_datetime_query():
     not_a_date = "Some string that's not a date"
 
     result = schema.execute('''{ datetime(in: "%s") }''' % not_a_date)
-    
+
     assert len(result.errors) == 1
     assert isinstance(result.errors[0], GraphQLError)
     assert result.data == None
 
+
 def test_bad_date_query():
     not_a_date = "Some string that's not a date"
-    
+
     result = schema.execute('''{ date(in: "%s") }''' % not_a_date)
-    
+
     assert len(result.errors) == 1
     assert isinstance(result.errors[0], GraphQLError)
     assert result.data == None
 
+
 def test_bad_time_query():
     not_a_date = "Some string that's not a date"
 
@@ -82,6 +85,7 @@ def test_bad_time_query():
     assert isinstance(result.errors[0], GraphQLError)
     assert result.data == None
 
+
 def test_datetime_query_variable():
     now = datetime.datetime.now().replace(tzinfo=pytz.utc)
     isoformat = now.isoformat()
diff --git a/graphene/types/tests/test_dynamic.py b/graphene/types/tests/test_dynamic.py
index 4e72395..b31ccf4 100644
--- a/graphene/types/tests/test_dynamic.py
+++ b/graphene/types/tests/test_dynamic.py
@@ -1,3 +1,4 @@
+from functools import partial
 from ..dynamic import Dynamic
 from ..scalars import String
 from ..structures import List, NonNull
@@ -25,3 +26,11 @@ def test_list_non_null():
     dynamic = Dynamic(lambda: List(NonNull(String)))
     assert dynamic.get_type().of_type.of_type == String
     assert str(dynamic.get_type()) == '[String!]'
+
+
+def test_partial():
+    def __type(_type):
+        return _type
+    dynamic = Dynamic(partial(__type, String))
+    assert dynamic.get_type() == String
+    assert str(dynamic.get_type()) == 'String'
diff --git a/graphene/types/tests/test_json.py b/graphene/types/tests/test_json.py
index cadc729..c33bd69 100644
--- a/graphene/types/tests/test_json.py
+++ b/graphene/types/tests/test_json.py
@@ -10,6 +10,7 @@ class Query(ObjectType):
     def resolve_json(self, info, input):
         return input
 
+
 schema = Schema(query=Query)
 
 
diff --git a/graphene/types/tests/test_uuid.py b/graphene/types/tests/test_uuid.py
index c141975..f1499e3 100644
--- a/graphene/types/tests/test_uuid.py
+++ b/graphene/types/tests/test_uuid.py
@@ -9,6 +9,7 @@ class Query(ObjectType):
     def resolve_uuid(self, info, input):
         return input
 
+
 schema = Schema(query=Query)
 
 
diff --git a/graphene/utils/tests/test_annotate.py b/graphene/utils/tests/test_annotate.py
index 760a8bf..e4930b6 100644
--- a/graphene/utils/tests/test_annotate.py
+++ b/graphene/utils/tests/test_annotate.py
@@ -1,9 +1,11 @@
 import pytest
 from ..annotate import annotate
 
+
 def func(a, b, *c, **d):
     pass
 
+
 annotations = {
     'a': int,
     'b': str,
@@ -11,8 +13,11 @@ annotations = {
     'd': dict
 }
 
+
 def func_with_annotations(a, b, *c, **d):
     pass
+
+
 func_with_annotations.__annotations__ = annotations
 
 
diff --git a/graphene/utils/tests/test_deprecated.py b/graphene/utils/tests/test_deprecated.py
index d196744..cd0e2dc 100644
--- a/graphene/utils/tests/test_deprecated.py
+++ b/graphene/utils/tests/test_deprecated.py
@@ -5,14 +5,14 @@ from ..deprecated import deprecated as deprecated_decorator, warn_deprecation
 
 def test_warn_deprecation(mocker):
     mocker.patch.object(deprecated.warnings, 'warn')
-    
+
     warn_deprecation("OH!")
     deprecated.warnings.warn.assert_called_with('OH!', stacklevel=2, category=DeprecationWarning)
 
 
 def test_deprecated_decorator(mocker):
     mocker.patch.object(deprecated, 'warn_deprecation')
-    
+
     @deprecated_decorator
     def my_func():
         return True
@@ -24,7 +24,7 @@ def test_deprecated_decorator(mocker):
 
 def test_deprecated_class(mocker):
     mocker.patch.object(deprecated, 'warn_deprecation')
-    
+
     @deprecated_decorator
     class X:
         pass
@@ -36,7 +36,7 @@ def test_deprecated_class(mocker):
 
 def test_deprecated_decorator_text(mocker):
     mocker.patch.object(deprecated, 'warn_deprecation')
-    
+
     @deprecated_decorator("Deprecation text")
     def my_func():
         return True
@@ -48,7 +48,7 @@ def test_deprecated_decorator_text(mocker):
 
 def test_deprecated_class_text(mocker):
     mocker.patch.object(deprecated, 'warn_deprecation')
-    
+
     @deprecated_decorator("Deprecation text")
     class X:
         pass
@@ -60,6 +60,6 @@ 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:
         deprecated_decorator({})
diff --git a/graphene/utils/tests/test_resolve_only_args.py b/graphene/utils/tests/test_resolve_only_args.py
index f5b77de..35554e9 100644
--- a/graphene/utils/tests/test_resolve_only_args.py
+++ b/graphene/utils/tests/test_resolve_only_args.py
@@ -4,6 +4,7 @@ from .. import deprecated
 
 def test_resolve_only_args(mocker):
     mocker.patch.object(deprecated, 'warn_deprecation')
+
     def resolver(root, **args):
         return root, args