[Design Discussion] Support mutations using an InputObjectType #346
Replies: 7 comments
-
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: 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 |
Beta Was this translation helpful? Give feedback.
-
@maquino1985 This a good example and good start. Maybe we can work together on a PR. |
Beta Was this translation helpful? Give feedback.
-
Any news on this? |
Beta Was this translation helpful? Give feedback.
-
maybe tagging @jnak will help to get some feedback. |
Beta Was this translation helpful? Give feedback.
-
Hello. Just switched from Django to FastAPI for lightweighting a project I'm working on. |
Beta Was this translation helpful? Give feedback.
-
Another blueprint could be the way Dgraph autogenerates mutations: https://dgraph.io/docs/graphql/mutations/mutations-overview/ |
Beta Was this translation helpful? Give feedback.
-
There's definitely a use case for this!
I think there's a more differentiated view to this. In some cases, only the composite/hybrid field is relevant to the API. When users want to create/modify an object, they might only know about the hybrid property but not the underlying columns/data structures. Sqlalchemy provides setters for hybrid, tackling this problem. Optimally, our solution should provide similar functionality. However, we can leave that to the developer implementing the mutation for now.
This is also a great idea, but I'd see that as a separate feature since it drastically extends the scope of the input objects. A first step could be to design the input objects to be compatible with future extensions like auto-generation. InputObjects would already be helpful in common mutations aimed at modifying an object. Some other points that come to my mind are the following:
|
Beta Was this translation helpful? Give feedback.
-
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 #213 as well as #29. I'll copy the relevant items here so that we won't have to repeat them again.
Points to note:
SQLAlchemyInputObjectType
and have model and exclude_fields as meta properties.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 ?
Beta Was this translation helpful? Give feedback.
All reactions