graphql-python / graphene-sqlalchemy

Graphene SQLAlchemy integration
http://docs.graphene-python.org/projects/sqlalchemy/en/latest/
MIT License
975 stars 225 forks source link

Nested inputs in mutations #133

Open NathanBWaters opened 6 years ago

NathanBWaters commented 6 years ago

I'm failing to convert nested inputs into sqlalchemy models in mutations. Here's my example:

Let's say I want to create a quiz. For that I have the following code:

'''
GraphQL Models
'''
class Quiz(SQLAlchemyObjectType):
    '''
    GraphQL representation of a Quiz
    '''
    class Meta:
        model = QuizModel

class Question(SQLAlchemyObjectType):
    '''
    GraphQL representation of a Question
    '''
    class Meta:
        model = QuestionModel

'''
Inputs
'''
class QuestionInput(graphene.InputObjectType):
    text = graphene.String()
    media = graphene.String()

class QuizInput(graphene.InputObjectType):
    name = graphene.String()
    creator_id = graphene.Int()
    description = graphene.String()
    media = graphene.String()
    id = graphene.Int()
    debugging_questions = graphene.InputField(graphene.List(QuestionInput))
    questions = graphene.InputField(graphene.List(QuestionInput))

'''
Mutation
'''
class CreateQuiz(graphene.Mutation):
    class Arguments:
        quiz = QuizInput(required=True)

    quiz = graphene.Field(lambda: Quiz)

    def mutate(self, info, **kwargs):
        quiz_attributes = dict(kwargs['quiz'])
        if quiz_attributes.get('id'):
            quiz = db_session.query(QuizModel).filter((QuizModel.id == quiz_attributes.get('id'))).one()
            quiz_attributes.pop('id')
            for key, value in quiz_attributes.items():
                setattr(quiz, key, value)
        else:
            quiz = QuizModel(**quiz_attributes)

        db_session.add(quiz)
        db_session.commit()

        return CreateQuiz(quiz=quiz)

My GraphQL query is the following:

mutation creatingQuiz($quiz: QuizInput!) {
    createQuiz(quiz: $quiz) {
        quiz {
            id,
            name,
            description,
            media,
            creatorId,
            questions {
                id,
                text,
                media,
            }
        }
    }
}

Note the relation between the global variables and the response: Example A - returns a response, obviously doesn't add any quizzes because it uses debuggingQuestions.

Global variables:
{
    "quiz": {
        "id": 136,
        "name": "fake name",
        "description": "simple desc",
        "creatorId": 1,
        "media": "img.jpg",
        "debuggingQuestions": [{
            "media": "media",
            "text": "text"
        }]
    }
}

Response:
{
    "data": {
        "createQuiz": {
            "quiz": {
                "id": "136",
                "name": "fake name",
                "description": "simple desc",
                "media": "img.jpg",
                "creatorId": 1,
                "questions": []
            }
        }
    }
}

Now if I try and pass question data in the questions field instead of debuggingQuestions:

Global variables:
{
    "quiz": {
        "id": 136,
        "name": "fake name",
        "description": "simple desc",
        "creatorId": 1,
        "media": "img.jpg",
        "questions": [{
            "media": "media",
            "text": "text"
        }]
    }
}

Response:
{
    "errors": [{
        "message": "unhashable type: 'QuestionInput'",
        "locations": [{
            "line": 2,
            "column": 3
        }]
    }],
    "data": {
        "createQuiz": null
    }
}

What step am I missing so that QuestionInput is automatically converted into a Question sqlalchemy model?

NathanBWaters commented 6 years ago

Also, all feedback welcome on how to improve the code.

NathanBWaters commented 6 years ago

Hmm I'm confused. It seems you can have nested inputs but not nested mutations. https://github.com/graphql/graphql-js/issues/672