mirror of
				https://github.com/graphql-python/graphene.git
				synced 2025-10-31 07:57:26 +03:00 
			
		
		
		
	feat: Enable use of Undefined in InputObjectTypes (#1506)
* Changed InputObjectType's default builder-from-dict argument to be `Undefined` instead of `None`, removing ambiguity of undefined optional inputs using dot notation access syntax. * Move `set_default_input_object_type_to_undefined()` fixture into conftest.py for sharing it between multiple test files.
This commit is contained in:
		
							parent
							
								
									8ede21e063
								
							
						
					
					
						commit
						2da8e9db5c
					
				|  | @ -14,6 +14,31 @@ class InputObjectTypeOptions(BaseOptions): | |||
|     container = None  # type: InputObjectTypeContainer | ||||
| 
 | ||||
| 
 | ||||
| # Currently in Graphene, we get a `None` whenever we access an (optional) field that was not set in an InputObjectType | ||||
| # using the InputObjectType.<attribute> dot access syntax. This is ambiguous, because in this current (Graphene | ||||
| # historical) arrangement, we cannot distinguish between a field not being set and a field being set to None. | ||||
| # At the same time, we shouldn't break existing code that expects a `None` when accessing a field that was not set. | ||||
| _INPUT_OBJECT_TYPE_DEFAULT_VALUE = None | ||||
| 
 | ||||
| # To mitigate this, we provide the function `set_input_object_type_default_value` to allow users to change the default | ||||
| # value returned in non-specified fields in InputObjectType to another meaningful sentinel value (e.g. Undefined) | ||||
| # if they want to. This way, we can keep code that expects a `None` working while we figure out a better solution (or | ||||
| # a well-documented breaking change) for this issue. | ||||
| 
 | ||||
| 
 | ||||
| def set_input_object_type_default_value(default_value): | ||||
|     """ | ||||
|     Change the sentinel value returned by non-specified fields in an InputObjectType | ||||
|     Useful to differentiate between a field not being set and a field being set to None by using a sentinel value | ||||
|     (e.g. Undefined is a good sentinel value for this purpose) | ||||
| 
 | ||||
|     This function should be called at the beginning of the app or in some other place where it is guaranteed to | ||||
|     be called before any InputObjectType is defined. | ||||
|     """ | ||||
|     global _INPUT_OBJECT_TYPE_DEFAULT_VALUE | ||||
|     _INPUT_OBJECT_TYPE_DEFAULT_VALUE = default_value | ||||
| 
 | ||||
| 
 | ||||
| class InputObjectTypeContainer(dict, BaseType):  # type: ignore | ||||
|     class Meta: | ||||
|         abstract = True | ||||
|  | @ -21,7 +46,7 @@ class InputObjectTypeContainer(dict, BaseType):  # type: ignore | |||
|     def __init__(self, *args, **kwargs): | ||||
|         dict.__init__(self, *args, **kwargs) | ||||
|         for key in self._meta.fields: | ||||
|             setattr(self, key, self.get(key, None)) | ||||
|             setattr(self, key, self.get(key, _INPUT_OBJECT_TYPE_DEFAULT_VALUE)) | ||||
| 
 | ||||
|     def __init_subclass__(cls, *args, **kwargs): | ||||
|         pass | ||||
|  |  | |||
							
								
								
									
										12
									
								
								graphene/types/tests/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								graphene/types/tests/conftest.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| import pytest | ||||
| from graphql import Undefined | ||||
| 
 | ||||
| from graphene.types.inputobjecttype import set_input_object_type_default_value | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture() | ||||
| def set_default_input_object_type_to_undefined(): | ||||
|     """This fixture is used to change the default value of optional inputs in InputObjectTypes for specific tests""" | ||||
|     set_input_object_type_default_value(Undefined) | ||||
|     yield | ||||
|     set_input_object_type_default_value(None) | ||||
|  | @ -1,3 +1,5 @@ | |||
| from graphql import Undefined | ||||
| 
 | ||||
| from ..argument import Argument | ||||
| from ..field import Field | ||||
| from ..inputfield import InputField | ||||
|  | @ -6,6 +8,7 @@ from ..objecttype import ObjectType | |||
| from ..scalars import Boolean, String | ||||
| from ..schema import Schema | ||||
| from ..unmountedtype import UnmountedType | ||||
| from ... import NonNull | ||||
| 
 | ||||
| 
 | ||||
| class MyType: | ||||
|  | @ -136,3 +139,31 @@ def test_inputobjecttype_of_input(): | |||
| 
 | ||||
|     assert not result.errors | ||||
|     assert result.data == {"isChild": True} | ||||
| 
 | ||||
| 
 | ||||
| def test_inputobjecttype_default_input_as_undefined( | ||||
|     set_default_input_object_type_to_undefined, | ||||
| ): | ||||
|     class TestUndefinedInput(InputObjectType): | ||||
|         required_field = String(required=True) | ||||
|         optional_field = String() | ||||
| 
 | ||||
|     class Query(ObjectType): | ||||
|         undefined_optionals_work = Field(NonNull(Boolean), input=TestUndefinedInput()) | ||||
| 
 | ||||
|         def resolve_undefined_optionals_work(self, info, input: TestUndefinedInput): | ||||
|             # Confirm that optional_field comes as Undefined | ||||
|             return ( | ||||
|                 input.required_field == "required" and input.optional_field is Undefined | ||||
|             ) | ||||
| 
 | ||||
|     schema = Schema(query=Query) | ||||
|     result = schema.execute( | ||||
|         """query basequery { | ||||
|         undefinedOptionalsWork(input: {requiredField: "required"}) | ||||
|     } | ||||
|     """ | ||||
|     ) | ||||
| 
 | ||||
|     assert not result.errors | ||||
|     assert result.data == {"undefinedOptionalsWork": True} | ||||
|  |  | |||
|  | @ -20,8 +20,8 @@ from ..inputobjecttype import InputObjectType | |||
| from ..interface import Interface | ||||
| from ..objecttype import ObjectType | ||||
| from ..scalars import Int, String | ||||
| from ..structures import List, NonNull | ||||
| from ..schema import Schema | ||||
| from ..structures import List, NonNull | ||||
| 
 | ||||
| 
 | ||||
| def create_type_map(types, auto_camelcase=True): | ||||
|  | @ -227,6 +227,18 @@ def test_inputobject(): | |||
|     assert foo_field.description == "Field description" | ||||
| 
 | ||||
| 
 | ||||
| def test_inputobject_undefined(set_default_input_object_type_to_undefined): | ||||
|     class OtherObjectType(InputObjectType): | ||||
|         optional_field = String() | ||||
| 
 | ||||
|     type_map = create_type_map([OtherObjectType]) | ||||
|     assert "OtherObjectType" in type_map | ||||
|     graphql_type = type_map["OtherObjectType"] | ||||
| 
 | ||||
|     container = graphql_type.out_type({}) | ||||
|     assert container.optional_field is Undefined | ||||
| 
 | ||||
| 
 | ||||
| def test_objecttype_camelcase(): | ||||
|     class MyObjectType(ObjectType): | ||||
|         """Description""" | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ try: | |||
| except ImportError: | ||||
|     # backwards compatibility for v3.6 | ||||
|     from typing import Pattern | ||||
| from typing import Callable, Dict, List, Optional, Union | ||||
| from typing import Callable, Dict, List, Optional, Union, Tuple | ||||
| 
 | ||||
| from graphql import GraphQLError | ||||
| from graphql.validation import ValidationContext, ValidationRule | ||||
|  | @ -82,7 +82,7 @@ def depth_limit_validator( | |||
| 
 | ||||
| 
 | ||||
| def get_fragments( | ||||
|     definitions: List[DefinitionNode], | ||||
|     definitions: Tuple[DefinitionNode, ...], | ||||
| ) -> Dict[str, FragmentDefinitionNode]: | ||||
|     fragments = {} | ||||
|     for definition in definitions: | ||||
|  | @ -94,7 +94,7 @@ def get_fragments( | |||
| # This will actually get both queries and mutations. | ||||
| # We can basically treat those the same | ||||
| def get_queries_and_mutations( | ||||
|     definitions: List[DefinitionNode], | ||||
|     definitions: Tuple[DefinitionNode, ...], | ||||
| ) -> Dict[str, OperationDefinitionNode]: | ||||
|     operations = {} | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user