question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Dicussion: Support mutations in graphene-sqlalchemy

See original GitHub issue

This is going to be a discussion thread to debate whether it is good to implement mutations in graphene-sqlalchemy. There is definitely a scope for this feature and I think it’s useful, so the real question is more towards how to implement it.

We can probably start with creating objects and then later expand to updating the various attributes.

There are discussions around this topic in https://github.com/graphql-python/graphene-sqlalchemy/pull/213 as well as https://github.com/graphql-python/graphene-sqlalchemy/issues/29. I’ll copy the relevant items here so that we won’t have to repeat them again.

Points to note:

  • Have a class named SQLAlchemyInputObjectType and have model and exclude_fields as meta properties.
class CreateUser(SQLAlchemyInputObjectType):
    class Meta:
        exclude_fields = ('id', )
        model = UserModel
  • No need to worry about hybrid or composite columns as they are basically created from other columns. So, we just need a mechanism to just accept the fields. So, a function named construct_fields_for_input.

  • How to handle relationships ? Since we are creating row entries for the table, we can probably accept id of the related table entry and convert it to the database id.

@jnak thoughts ?

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:7
  • Comments:7 (1 by maintainers)

github_iconTop GitHub Comments

7reactions
maquino1985commented, Oct 2, 2020

It’s beyond obvious that this is useful, it’s basically required for anyone to seriously use graphene-sqlalchemy in a complex project. I’ve been using this in my fork for about 2 years now. It’s not pretty because I mostly copy pasted stuff from the library code for creating these objects with a few changes, but it gets the job done.

class SQLAlchemyMutation(Mutation):
    @classmethod
    def __init_subclass_with_meta__(
            cls,
            model=None,
            create=False,
            delete=False,
            registry=None,
            arguments=None,
            only_fields=(),
            structure: Type[Structure] = None,
            exclude_fields=(),
            **options,
    ):
        meta = SQLAlchemyMutationOptions(cls)
        meta.create = create
        meta.model = model
        meta.delete = delete

        if arguments is None and not hasattr(cls, "Arguments"):
            arguments = {}
            # don't include id argument on create
            if not meta.create:
                arguments["id"] = ID(required=True)

            # don't include input argument on delete
            if not meta.delete:
                inputMeta = type(
                    "Meta",
                    (object,),
                    {
                        "model": model,
                        "exclude_fields": exclude_fields,
                        "only_fields": only_fields,
                    },
                )
                inputType = type(
                    cls.__name__ + "Input",
                    (SQLAlchemyInputObjectType,),
                    {"Meta": inputMeta},
                )
                arguments = {"input": inputType(required=True)}
        if not registry:
            registry = get_global_registry()
        output_type: ObjectType = registry.get_type_for_model(model)
        if structure:
            output_type = structure(output_type)
        super(SQLAlchemyMutation, cls).__init_subclass_with_meta__(
            _meta=meta, output=output_type, arguments=arguments, **options
        )

    @classmethod
    def mutate(cls, root, info, **kwargs):
        session = get_session(info.context)
        with session.no_autoflush:
            meta = cls._meta

            if meta.create:
                model = meta.model(**kwargs["input"])
                session.add(model)
            else:
                model = (
                    session.query(meta.model)
                        .filter(meta.model.id == kwargs["id"])
                        .first()
                )
            if meta.delete:
                session.delete(model)
            else:

                def setModelAttributes(model, attrs):
                    relationships = model.__mapper__.relationships
                    for key, value in attrs.items():
                        if key in relationships:
                            if getattr(model, key) is None:
                                # instantiate class of the same type as
                                # the relationship target
                                setattr(model, key, relationships[key].mapper.entity())
                            setModelAttributes(getattr(model, key), value)
                        else:
                            setattr(model, key, value)

                setModelAttributes(model, kwargs["input"])
            session.flush()  # session.commit() now throws session state exception: 'already committed'

            return model

    @classmethod
    def Field(cls, *args, **kwargs):
        return Field(
            cls._meta.output, args=cls._meta.arguments, resolver=cls._meta.resolver
        )

You can use it like this, very easy: (in the Meta class set create=True for creation, delete=True for deletion. Setting neither will update)

class CreateWorkflow(SQLAlchemyMutation):
    """CreateWorkflow"""

    class Arguments:
        """Arguments"""

        input = graphene.Argument(WorkflowInput, required=True)

    class Meta:
        """Meta"""

        create = True
        model = WorkflowModel

    @classmethod
    def mutate(cls, root: Any, info: ResolveInfo, input) -> WorkflowModel:
        """mutate

        Args:
            root (Any):
            info (ResolveInfo):
            **kwargs (Any):

        Returns:
            model (WorkflowModel)

        """

        workflow = WorkflowFactory.generate(workflow_input=input)
        return workflow
2reactions
jbvsmocommented, Jun 14, 2021

Any news on this?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Developers - Dicussion: Support mutations in graphene-sqlalchemy -
This is going to be a discussion thread to debate whether it is good to implement mutations in graphene-sqlalchemy. There is definitely a...
Read more >
GraphQL Mutations with Graphene-Python And SQLAlchemy
This video shows how to use GraphQL mutations to allow clients to create data. This is an example using Graphene - Python and...
Read more >
Generate Graphene Mutation Inputs from SQLAlchemy Class ...
I solved creating this class: from graphene.types.utils import yank_fields_from_attrs from graphene.utils.subclass_with_meta import ...
Read more >
Building Models in Flask and GraphQL
Let's jump right in by opening up our mutations.py file. Findr / backend / graphql / mutations.py. import graphene from backend import ...
Read more >
GraphQL Authorization with Graphene, SQLAlchemy and oso
We'll use SQLAlchemy as our ORM, and Graphene — a popular Python GraphQL ... than just adding the simple decorator we discussed earlier....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found