Mutations - batch creation of objects - graphql

I want to use graphene to create many people in one go.
The document only mention the way to create one person like this:
class CreatePerson(graphene.Mutation):
class Input:
name = graphene.String()
age = graphene.Int()
ok = graphene.Boolean()
person = graphene.Field(lambda: Person)
#staticmethod
def mutate(root, args, context, info):
person = Person(name=args.get('name'), age=args.get('age'), mobile=args.get('mobile'))
ok = True
return CreatePerson(person=person, ok=ok)
are there any ways to get it done?

Instead of using a mutation that creates a list of objects, you can also call a mutation that creates one objects multiple times in one GraphQL request. This is accomplished using GraphQL Aliases:
mutation {
c001: createPerson(
name: "Donald Duck"
age: 42
) {
id
}
c002: createPerson(
name: "Daisy Duck"
age: 43
) {
id
}
c003: createPerson(
name: "Mickey Mouse"
age: 44
) {
id
}
}

I can figure out a solution base on the answer of Jan Hančič
There is a type called graphene.InputObjectType to use in this case
The solution can be
class PersonInput(InputObjectType):
name = graphene.String()
age = graphene.Int()
class CreatePeople(graphene.Mutation):
class Input:
people = graphene.List(PersonInput)
people = graphene.List(lambda: Person)
#staticmethod
def mutate(root, args, context, info):
people = [Person.objects.create(name=person.name, age=person.age) for person in args.get('people')]
return CreatePeople(people=people)

Make your mutation input a list and return a list of created people. Something like this:
class CreatePerson(graphene.Mutation):
class Input:
name = graphene.List(graphene.String)
ok = graphene.Boolean()
people = graphene.List(Person)
#staticmethod
def mutate(root, args, context, info):
people = [Person(name=name) for name in args.get('name)]
ok = True
return CreatePerson(people=people, ok=ok)

Receive a list of input, create all instances and return them all
The model node/type should be like-
class UserType(DjangoObjectType):
class Meta:
model = User
interfaces = (CustomGrapheneNode, )
filter_fields = {}
only_fields = (
'name',
'email'
)
Define Input fields
class UserInput(graphene.InputObjectType):
name = graphene.String(required=True)
password = graphene.String(required=True)
Mutation class
class CreateUser(graphene.Mutation):
users = graphene.List(UserType)
class Input:
data = graphene.List(UserInput)
Output = graphene.List(UserType)
def mutate(self, info, data):
users = []
for item in data:
user = User.objects.create(name=data['name'],
password=data['password'])
users.append(user)
return users
make this mutation callable by main schema
class Mutation():
create_user = CreateUser.Field()
the Mutation Query view will be as -
mutation{
createUser(data:[{name:"john", password:"1234"},
{name:"john", password:"1234"}]) {
user{
name
}
}
}

Related

How to get models schema (type_defs) in form of string?

Example Code
I have the following model.
class Post(Base):
__tablename__ = "post"
id = Column(Integer, primary_key=True, index=True)
title = Column(String)
author = Column(String)
content = Column(String)
time_created = Column(DateTime(timezone=True), server_default=func.now())
with this schema
class PostSchema(SQLAlchemyObjectType):
class Meta:
model = Post
Problem
How to to get the graphql schema definition in form of string like this
"""
type Post{
id: ID
title: string!
author: String!
content: String!
time_created: Int!
}
"""
I tried this, but there is not type_defs option.
graphene.Field(PostModel).type_defs
One option you can use is to introspect your schema not just the object type. Suppose you have a graphene schema called schema,
from graphene_sqlalchemy import SQLAlchemyObjectType
class schema(SQLAlchemyObjectType):
class Meta:
model = MyModel
you can do something like:
schema_dict = schema.introspect()
This will return a dict version of your schema and the types that you want can be found here:
types = schema_dict["__schema"]["types"]
Unfortunately, this returns a list of all object types so you might need to process it to narrow down what you want with something like:
list(filter(lambda types: types['name'] == 'PostSchema', types))
alternative way
def make_schemas(model):
d = """
"""
for i, key in model.__table__.columns.items():
x = str(key.type.python_type)
x = x.replace("<class '", '')
x = x.replace("'>", '')
x = x.title()
if x in ['Str', 'Datetime.Datetime']:
x = 'String'
if x == 'Bool':
x = 'Boolean'
d += f"""
{i}: {x}
"""
y = f"""
type {model.__tablename__.title()} {{
{d}
}}
"""
return y

Django GraphQL Mutation Updated, but no change in Database

I created an update mutation as follows, with django==3.1.4 and graphene==2.1.8 :
# models.py
class CustomUser(AbstractUser):
# email = models.EmailField()
firebase_id = models.CharField(max_length=50, null=True)
nickname = models.CharField(max_length=50, null=True)
name = models.CharField(max_length=20, null=True)
gender = models.IntegerField(choices=Gender, default=3)
phone = models.CharField(max_length=20, null=True)
birthday = models.DateField(default=datetime(2020,1,1))
address = models.CharField(max_length=200, null=True)
profile_image = models.ImageField(default='default-avatar.png', upload_to='users/',
null=True, blank=True)
class UpdateMember(graphene.Mutation):
class Arguments:
firebase_id = graphene.String(required=True)
nickname = graphene.String()
name = graphene.String()
gender = graphene.Int()
phone = graphene.String()
birthday = graphene.Date()
address = graphene.String()
profile_image = graphene.String()
class Meta:
exclude = ["password"]
member = graphene.Field(MemberType)
success = graphene.Boolean()
# #login_required
#staticmethod
def mutate(root, info, firebase_id, **kwargs):
success = False
member_instance = CustomUser.objects.get(firebase_id=firebase_id)
if member_instance:
print(member_instance)
success = True
for k, v in kwargs.items():
member_instance.k = v
member_instance.save()
return UpdateMember(member=member_instance, success=True)
else:
return UpdateMember(member=None, success=False)
Running GQL below:
mutation {
updateMember(
firebaseId:"777",
name:"JJJJ")
{
success
}
}
Response:
{
"data": {
"updateMember": {
"success": true
}
}
}
But I checked the database, it seems no change in it, I think .save() should have done the work persisting changes to database......
Creating Member works fine. Using PostgresQL
Could anyone figure out why?
There is several issues in your code:
You can not assign your model fields using string like that. See this thread
for k, v in kwargs.items():
member_instance.k = v
member_instance.save()
Currently your member_instance.k has nothing to do with variable k inside for loop.
firebase_id field should be unique.
Currently you call CustomUser.objects.get(firebase_id=firebase_id) which is risky because firebase_id is not unique field. This may lead Multiple objects error if you have more than one CustomUsers saved with same id. To fix it, just define:
class CustomUser(AbstractUser):
# email = models.EmailField()
firebase_id = models.CharField(max_length=50, unique=True)
...
To check if your member_instance has really updated. You can for example print out the values before saving it and run some test cases before final implementation. For example:
if member_instance:
print(member_instance)
success = True
for k, v in kwargs.items():
member_instance.k = v
print(member_instance.k)
print(k)
print(getattr(member_instance, k))
member_instance.save()

How to pass complex types as arguments in graphene Python

I'm trying to create a query that accepts a complex argument object like so:
class Pair(graphene.ObjectType):
x = graphene.Int()
y = graphene.Int()
class Pairs(graphene.ObjectType):
pairs = graphene.List(graphene.NonNull(graphene.Field(Pair, required=True)), required=True)
class Query(graphene.ObjectType):
endpoint = graphene.Field(ResultType, pairs=graphene.Argument(Pairs, required=True))
I'm invoking it as follows in testing:
client = graphene.test.Client(graphene.Schema(query=Query))
executed = client.execute(
"""query($pairs: Pairs!) {
endpoint(pairs: $pairs) {
[result type goes here]
}
}"""
Any thoughts on what may be wrong with this approach?
I was able to do with the code below
class SomeFilter(graphene.InputObjectType):
name = graphene.String()
class Query(graphene.ObjectType):
all_somes = graphene.List(Some, options=SomeFilter())
def resolve_all_somes(self, info, options=None):
if options:
if name := options.get('name'):

graphene could not convert string to float in SerializerMutation

Is the SerializerMutation suppose to convert ID! from base64 to a pk? Is there some frontend/backend helper util to assist in conversion? I haven't been able to find anything specific.
Example create thing mutation:
class CreateThingMutation(SerializerMutation):
#classmethod
def get_serializer_kwargs(cls, root, info, **input):
import pdb;pdb.set_trace()
return None
#classmethod
def mutate_and_get_payload(cls, root, info, text, id):
import pdb;pdb.set_trace()
return None
class Meta:
serializer_class = ThingListViewSerializer
Example query:
mutation TestCreate($input: CreateThingMutationInput!) {
createThing(input: $input) {
item {
id
}
}
}
Example ID!:
item.id === atob('VW5pdE5vZGU6MjA=') === "UnitNode:20"
Edit, I was failing to convert the ID all the way so i had "20", just converted the type:
Number(atob(item.id).split(':')[1])
Question still remains about if there are any utility tools to automagically convert data being submitted to a mutation.

Best way to do a delete operation with GraphQL + graphene

I'm struggling to get my Delete operation working.
My Create, Read and Update are working fine, but a Delete has nothing to return.
class DeleteEmployeeInput(graphene.InputObjectType):
"""Arguments to delete an employee."""
id = graphene.ID(required=True, description="Global Id of the employee.")
class DeleteEmployee(graphene.Mutation):
"""Delete an employee."""
employee = graphene.Field(
lambda: Employee, description="Employee deleted by this mutation.")
class Arguments:
input = DeleteEmployeeInput(required=True)
def mutate(self, info, input):
data = utils.input_to_dictionary(input)
#data['edited'] = datetime.utcnow()
employee = db_session.query(
EmployeeModel).filter_by(id=data['id'])
employee.delete(data['id'])
db_session.commit()
#employee = db_session.query(
#EmployeeModel).filter_by(id=data['id']).first()
#return DeleteEmployee(employee=employee)
What is the best way to delete an entry?
I assume I have to return an OK or an Error.
When I run my mutation:
mutation {
deleteEmployee (input: {
id: "RW1wbG95ZWU6MQ=="
})
}
I get the error Field \"deleteEmployee\" of type \"DeleteEmployee\" must have a sub selection."
Note the commented out lines
Try replacing employee = graphene.Field... with ok = graphene.Boolean() and then make the last line of your mutate method return DeleteEmployee(ok=True)
Your mutate method will then look something like:
def mutate(self, info, input):
... skipping deletion code ...
db_session.commit()
return DeleteEmployee(ok=True)

Resources