diff --git a/examples/flask_sqlalchemy/schema.py b/examples/flask_sqlalchemy/schema.py index 9ed09464..b509d1f7 100644 --- a/examples/flask_sqlalchemy/schema.py +++ b/examples/flask_sqlalchemy/schema.py @@ -3,10 +3,26 @@ from models import Role as RoleModel import graphene +from graphql_relay.node.node import from_global_id from graphene import relay from graphene_sqlalchemy import SQLAlchemyConnectionField, SQLAlchemyObjectType +# Just a util function + +def input_to_dictionary(input): + """Method to convert Graphene inputs into dictionary""" + dictionary = {} + for key in input: + # Convert GraphQL global id to database id + if key[-2:] == 'id': + input[key] = from_global_id(input[key])[1] + dictionary[key] = input[key] + return dictionary + + + +# Queries Part : class Department(SQLAlchemyObjectType): class Meta: model = DepartmentModel @@ -24,7 +40,6 @@ class Meta: model = RoleModel interfaces = (relay.Node, ) - class Query(graphene.ObjectType): node = relay.Node.Field() # Allow only single column sorting @@ -35,5 +50,25 @@ class Query(graphene.ObjectType): # Disable sorting over this field all_departments = SQLAlchemyConnectionField(Department, sort=None) +# Mutation Part : +# only one exemple : employee. +class CreateEmployeeInput(SQLAlchemyInputObjectType): + class Meta: + # You have to exclude those fields to avoid conflict + exclude_fields = ('id','uuid') + model = EmployeeModel + +class CreateEmployee(graphene.Mutation): + class Arguments: + input = CreateEmployeeInput(required=True) + + def mutate(self,info,input): + data = utils.input_to_dictionary(input) + employee = EmployeeModel(**data) + + return CreateEmployee(employee=employee) -schema = graphene.Schema(query=Query, types=[Department, Employee, Role]) +class Mutation(graphene.ObjectType): + createEmplyee = CreateEmployee.Field() + +schema = graphene.Schema(query=Query,mutation=Mutation types=[Department, Employee, Role]) diff --git a/graphene_sqlalchemy/__init__.py b/graphene_sqlalchemy/__init__.py index d328304a..e9aec9f9 100644 --- a/graphene_sqlalchemy/__init__.py +++ b/graphene_sqlalchemy/__init__.py @@ -1,4 +1,4 @@ -from .types import SQLAlchemyObjectType +from .types import SQLAlchemyObjectType, SQLAlchemyInputObjectType from .fields import SQLAlchemyConnectionField from .utils import get_query, get_session @@ -7,6 +7,7 @@ __all__ = [ "__version__", "SQLAlchemyObjectType", + "SQLAlchemyInputObjectType", "SQLAlchemyConnectionField", "get_query", "get_session", diff --git a/graphene_sqlalchemy/types.py b/graphene_sqlalchemy/types.py index c20e8cfc..411104e0 100644 --- a/graphene_sqlalchemy/types.py +++ b/graphene_sqlalchemy/types.py @@ -210,3 +210,69 @@ def enum_for_field(cls, field_name): sort_enum = classmethod(sort_enum_for_object_type) sort_argument = classmethod(sort_argument_for_object_type) + +def construct_fields_for_input( + obj_type, model, registry, connection_field_factory,only_fields, exclude_fields +): + inspected_model = sqlalchemyinspect(model) + + fields = OrderedDict() + + for name, column in inspected_model.columns.items(): + is_not_in_only = only_fields and name not in only_fields + # is_already_created = name in options.fields + is_excluded = name in exclude_fields # or is_already_created + if is_not_in_only or is_excluded: + # We skip this field if we specify only_fields and is not + # in there. Or when we exclude this field in exclude_fields + continue + converted_column = convert_sqlalchemy_column(column, registry) + fields[name] = converted_column + + for name, composite in inspected_model.composites.items(): + is_not_in_only = only_fields and name not in only_fields + # is_already_created = name in options.fields + is_excluded = name in exclude_fields # or is_already_created + if is_not_in_only or is_excluded: + # We skip this field if we specify only_fields and is not + # in there. Or when we exclude this field in exclude_fields + continue + converted_composite = convert_sqlalchemy_composite(composite, registry) + fields[name] = converted_composite + + for hybrid_item in inspected_model.all_orm_descriptors: + + if type(hybrid_item) == hybrid_property: + name = hybrid_item.__name__ + is_not_in_only = only_fields and name not in only_fields + # is_already_created = name in options.fields + is_excluded = name in exclude_fields # or is_already_created + if is_not_in_only or is_excluded: + # We skip this field if we specify only_fields and is not + # in there. Or when we exclude this field in exclude_fields + continue + converted_hybrid_property = convert_sqlalchemy_hybrid_method(hybrid_item) + + fields[name] = converted_hybrid_property + + + + return fields + + + +class SQLAlchemyInputObjectType(graphene.InputObjectType): + @classmethod + def __init_subclass_with_meta__(cls, model=None, registry=None, skip_registry=False,only_fields=(), exclude_fields=(), + connection=None,_meta=None,connection_field_factory=default_connection_field_factory, + use_connection=None, interfaces=(), id=None, **options): + + if not registry: + registry = get_global_registry() + + + sqla_fields = yank_fields_from_attrs( + construct_fields_for_input(model=model, registry=registry,only_fields=only_fields, exclude_fields=exclude_fields, + connection_field_factory=connection_field_factory,obj_type=cls), + _as=graphene.Field, + )