From 67ce20f0a1685dde9035604e61b955213276a8c6 Mon Sep 17 00:00:00 2001 From: ddmytruk Date: Sun, 24 Apr 2022 04:18:00 +0300 Subject: [PATCH 1/5] int nullable field --- graphene_pydantic/converters.py | 4 ++-- graphene_pydantic/fields.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 graphene_pydantic/fields.py diff --git a/graphene_pydantic/converters.py b/graphene_pydantic/converters.py index b12eab6..7c3d79f 100644 --- a/graphene_pydantic/converters.py +++ b/graphene_pydantic/converters.py @@ -15,7 +15,6 @@ Field, Float, InputField, - Int, List, String, Union, @@ -26,6 +25,7 @@ from pydantic.fields import ModelField from pydantic.typing import evaluate_forwardref +from .fields import IntNullable from .registry import Registry from .util import construct_union_class_name @@ -191,7 +191,7 @@ def find_graphene_type( elif type_ == decimal.Decimal: return GrapheneDecimal if DECIMAL_SUPPORTED else Float elif type_ == int: - return Int + return IntNullable elif type_ in (tuple, list, set): # TODO: do Sets really belong here? return List diff --git a/graphene_pydantic/fields.py b/graphene_pydantic/fields.py new file mode 100644 index 0000000..ba2c784 --- /dev/null +++ b/graphene_pydantic/fields.py @@ -0,0 +1,17 @@ +from graphene import Int +from graphql import GraphQLError +from graphql.pyutils import inspect, is_integer +from graphql.type.scalars import MAX_INT, MIN_INT + + +class IntNullable(Int): + @staticmethod + def parse_value(value): + if value is None: + return value + + if not is_integer(value): + raise GraphQLError(f'Int cannot represent non-integer value: {inspect(value)}') + if not MIN_INT <= value <= MAX_INT: + raise GraphQLError(f'Int cannot represent non 32-bit signed integer value: {inspect(value)}') + return int(value) From 0894b05b364ece9b12345888a68157092563a860 Mon Sep 17 00:00:00 2001 From: ddmytruk Date: Sun, 24 Apr 2022 04:29:22 +0300 Subject: [PATCH 2/5] empty string as None & remove unset fields from frontend in the input --- graphene_pydantic/__init__.py | 2 +- graphene_pydantic/schemas.py | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 graphene_pydantic/schemas.py diff --git a/graphene_pydantic/__init__.py b/graphene_pydantic/__init__.py index 2d696ab..ee50f1b 100644 --- a/graphene_pydantic/__init__.py +++ b/graphene_pydantic/__init__.py @@ -1,4 +1,4 @@ from .inputobjecttype import PydanticInputObjectType from .objecttype import PydanticObjectType -__all__ = ["PydanticObjectType", "PydanticInputObjectType"] +__all__ = ["PydanticObjectType", "PydanticInputObjectType", "Schema"] diff --git a/graphene_pydantic/schemas.py b/graphene_pydantic/schemas.py new file mode 100644 index 0000000..e1eed7b --- /dev/null +++ b/graphene_pydantic/schemas.py @@ -0,0 +1,48 @@ +from collections.abc import Mapping +from typing import Any + +from graphene import Schema as GraheneSchema +from graphene.types.schema import normalize_execute_kwargs +from graphql import GraphQLType, OperationDefinitionNode, graphql_sync, is_input_object_type, type_from_ast +from graphql.language import parse + + +class Schema(GraheneSchema): + def execute(self, *args, **kwargs): + kwargs = normalize_execute_kwargs(kwargs) + var_type, all_model_fields = self.override_input_fields(args, kwargs) + response = graphql_sync(self.graphql_schema, *args, **kwargs) + response.data = self.replace_empty_string_to_none(response.data) + var_type.fields = all_model_fields + return response + + def override_input_fields(self, args, kwargs) -> tuple[GraphQLType, dict]: + source = args[0] + document = parse(source) + operation_name = kwargs.get('operation_name') + operation = None + for definition in document.definitions: + if isinstance(definition, OperationDefinitionNode): + if ( + operation_name is None + and not operation + or definition.name + and definition.name.value == operation_name + ): + operation = definition + var_def_nodes = operation.variable_definitions + for var_def_node in var_def_nodes: + var_type = type_from_ast(self.graphql_schema, var_def_node.type) + if is_input_object_type(var_type): + all_model_fields = var_type.fields.copy() + fields = {} + for field_name, value in var_type.fields.items(): + if field_name in list(kwargs.get('variable_values', {}).values())[0]: + fields.update({f'{field_name}': value}) + var_type.fields = fields + return var_type, all_model_fields + + def replace_empty_string_to_none(self, obj: Any) -> Any: + if isinstance(obj, Mapping): + return {key: self.replace_empty_string_to_none(val) for key, val in obj.items()} + return None if obj == '' else obj From 3e3be59563836a0a3d1cd7957c521bf9cd34362b Mon Sep 17 00:00:00 2001 From: ddmytruk Date: Mon, 25 Apr 2022 16:01:19 +0300 Subject: [PATCH 3/5] update --- graphene_pydantic/converters.py | 4 +-- graphene_pydantic/fields.py | 17 ------------- graphene_pydantic/schemas.py | 45 ++++++++++++++++++++++----------- 3 files changed, 32 insertions(+), 34 deletions(-) delete mode 100644 graphene_pydantic/fields.py diff --git a/graphene_pydantic/converters.py b/graphene_pydantic/converters.py index 7c3d79f..b12eab6 100644 --- a/graphene_pydantic/converters.py +++ b/graphene_pydantic/converters.py @@ -15,6 +15,7 @@ Field, Float, InputField, + Int, List, String, Union, @@ -25,7 +26,6 @@ from pydantic.fields import ModelField from pydantic.typing import evaluate_forwardref -from .fields import IntNullable from .registry import Registry from .util import construct_union_class_name @@ -191,7 +191,7 @@ def find_graphene_type( elif type_ == decimal.Decimal: return GrapheneDecimal if DECIMAL_SUPPORTED else Float elif type_ == int: - return IntNullable + return Int elif type_ in (tuple, list, set): # TODO: do Sets really belong here? return List diff --git a/graphene_pydantic/fields.py b/graphene_pydantic/fields.py deleted file mode 100644 index ba2c784..0000000 --- a/graphene_pydantic/fields.py +++ /dev/null @@ -1,17 +0,0 @@ -from graphene import Int -from graphql import GraphQLError -from graphql.pyutils import inspect, is_integer -from graphql.type.scalars import MAX_INT, MIN_INT - - -class IntNullable(Int): - @staticmethod - def parse_value(value): - if value is None: - return value - - if not is_integer(value): - raise GraphQLError(f'Int cannot represent non-integer value: {inspect(value)}') - if not MIN_INT <= value <= MAX_INT: - raise GraphQLError(f'Int cannot represent non 32-bit signed integer value: {inspect(value)}') - return int(value) diff --git a/graphene_pydantic/schemas.py b/graphene_pydantic/schemas.py index e1eed7b..f26c8e0 100644 --- a/graphene_pydantic/schemas.py +++ b/graphene_pydantic/schemas.py @@ -1,9 +1,17 @@ -from collections.abc import Mapping -from typing import Any +from typing import Any, Union from graphene import Schema as GraheneSchema from graphene.types.schema import normalize_execute_kwargs -from graphql import GraphQLType, OperationDefinitionNode, graphql_sync, is_input_object_type, type_from_ast +from graphql import ( + GraphQLList, + GraphQLNamedType, + GraphQLNonNull, + GraphQLType, + OperationDefinitionNode, + graphql_sync, + is_input_object_type, + type_from_ast, +) from graphql.language import parse @@ -12,11 +20,16 @@ def execute(self, *args, **kwargs): kwargs = normalize_execute_kwargs(kwargs) var_type, all_model_fields = self.override_input_fields(args, kwargs) response = graphql_sync(self.graphql_schema, *args, **kwargs) - response.data = self.replace_empty_string_to_none(response.data) - var_type.fields = all_model_fields + if var_type and all_model_fields: + var_type.fields = all_model_fields return response - def override_input_fields(self, args, kwargs) -> tuple[GraphQLType, dict]: + def override_input_fields( + self, args, kwargs + ) -> Union[ + tuple[None, None], + tuple[Union[GraphQLType, None, GraphQLNonNull, GraphQLList, GraphQLNamedType], dict[str, Any]], + ]: source = args[0] document = parse(source) operation_name = kwargs.get('operation_name') @@ -24,13 +37,17 @@ def override_input_fields(self, args, kwargs) -> tuple[GraphQLType, dict]: for definition in document.definitions: if isinstance(definition, OperationDefinitionNode): if ( - operation_name is None - and not operation - or definition.name - and definition.name.value == operation_name + operation_name is None + and not operation + or definition.name + and definition.name.value == operation_name ): operation = definition + var_def_nodes = operation.variable_definitions + if not var_def_nodes: + return None, None + for var_def_node in var_def_nodes: var_type = type_from_ast(self.graphql_schema, var_def_node.type) if is_input_object_type(var_type): @@ -40,9 +57,7 @@ def override_input_fields(self, args, kwargs) -> tuple[GraphQLType, dict]: if field_name in list(kwargs.get('variable_values', {}).values())[0]: fields.update({f'{field_name}': value}) var_type.fields = fields - return var_type, all_model_fields + else: + return None, None - def replace_empty_string_to_none(self, obj: Any) -> Any: - if isinstance(obj, Mapping): - return {key: self.replace_empty_string_to_none(val) for key, val in obj.items()} - return None if obj == '' else obj + return var_type, all_model_fields From 0852fde20b06d17ebd9f88670af9b4187e2973e1 Mon Sep 17 00:00:00 2001 From: ddmytruk Date: Thu, 5 May 2022 23:33:59 +0300 Subject: [PATCH 4/5] fix schema for few fields --- graphene_pydantic/schemas.py | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/graphene_pydantic/schemas.py b/graphene_pydantic/schemas.py index f26c8e0..f041776 100644 --- a/graphene_pydantic/schemas.py +++ b/graphene_pydantic/schemas.py @@ -1,5 +1,3 @@ -from typing import Any, Union - from graphene import Schema as GraheneSchema from graphene.types.schema import normalize_execute_kwargs from graphql import ( @@ -18,18 +16,14 @@ class Schema(GraheneSchema): def execute(self, *args, **kwargs): kwargs = normalize_execute_kwargs(kwargs) - var_type, all_model_fields = self.override_input_fields(args, kwargs) + var_types = self.override_input_fields(args, kwargs) response = graphql_sync(self.graphql_schema, *args, **kwargs) - if var_type and all_model_fields: - var_type.fields = all_model_fields + response.data = self.replace_empty_string_to_none(response.data) + for var_type, fields in var_types.items(): + var_type.fields = fields return response - def override_input_fields( - self, args, kwargs - ) -> Union[ - tuple[None, None], - tuple[Union[GraphQLType, None, GraphQLNonNull, GraphQLList, GraphQLNamedType], dict[str, Any]], - ]: + def override_input_fields(self, args, kwargs) -> dict: source = args[0] document = parse(source) operation_name = kwargs.get('operation_name') @@ -37,27 +31,26 @@ def override_input_fields( for definition in document.definitions: if isinstance(definition, OperationDefinitionNode): if ( - operation_name is None - and not operation - or definition.name - and definition.name.value == operation_name + operation_name is None + and not operation + or definition.name + and definition.name.value == operation_name ): operation = definition - var_def_nodes = operation.variable_definitions if not var_def_nodes: - return None, None + return {} + var_types_fields = {} for var_def_node in var_def_nodes: var_type = type_from_ast(self.graphql_schema, var_def_node.type) if is_input_object_type(var_type): - all_model_fields = var_type.fields.copy() + var_types_fields[var_type] = var_type.fields.copy() fields = {} for field_name, value in var_type.fields.items(): if field_name in list(kwargs.get('variable_values', {}).values())[0]: fields.update({f'{field_name}': value}) var_type.fields = fields else: - return None, None - - return var_type, all_model_fields + continue + return var_types_fields From a521663953edfd87f5e53c9880b184b8738c626e Mon Sep 17 00:00:00 2001 From: ddmytruk Date: Thu, 5 May 2022 23:38:59 +0300 Subject: [PATCH 5/5] fix typo --- graphene_pydantic/schemas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene_pydantic/schemas.py b/graphene_pydantic/schemas.py index f041776..2941039 100644 --- a/graphene_pydantic/schemas.py +++ b/graphene_pydantic/schemas.py @@ -1,4 +1,4 @@ -from graphene import Schema as GraheneSchema +from graphene import Schema as GrapheneSchema from graphene.types.schema import normalize_execute_kwargs from graphql import ( GraphQLList, @@ -13,7 +13,7 @@ from graphql.language import parse -class Schema(GraheneSchema): +class Schema(GrapheneSchema): def execute(self, *args, **kwargs): kwargs = normalize_execute_kwargs(kwargs) var_types = self.override_input_fields(args, kwargs)