Graphene: Enum argument doesn't seem to work - python

Im currently having hard time on mutation enum Argument.
Below are my code for Mutation:
class CreatePerson(graphene.Mutation):
foo = graphene.String()
def mutate(self, info, **kwargs):
return CreatePerson(foo='foo')
class Arguments:
enum_arg = graphene.Argument(graphene.Enum.from_enum(EnumArg))
Enum class:
from enum import Enum
class EnumArg(Enum):
Baz = 0
Bar = 1
Spam = 2
Egg = 3
Command using POSTMAN:
{
"query": "mutation": {createPerson(enumArg=1) { foo }}
}
But I end up this error message:
"message": "Argument \"enumArg\" has invalid value 1.
Expected type \"EnumArg\", found 1.",
I also tried giving enumArg=\"Bar\" on the createPerson mutation and the error still persists.

When defining an enum, we can assign an arbitrary value to each enum value in the enum. However, this value is only used internally by the GraphQL service itself. For example, if the type of a field's argument is the enum, this value will be passed to the field's resolver as the argument value. However, when composing a GraphQL document, the enum value must always be referred to by it's name, not it's value.
mutation {
createPerson(enumArg: Bar) {
foo
}
}

enum defined in backend is:
enum Gender {
MALE
FEMALE
}
I am using Vue for frontend so passing data to the mutation from Vue can be done like this.
I have defined gender as a string in my local state of the component as:
data(){
return {
gender: ''
}
}
The method from Vue is:
async handleEditProfile () {
const response = await this.$apollo.mutate({
query: EDIT_PROFILE,
variables: {
nameAsInPan: this.nameAsInPan,
gender: this.gender,
dateOfBirth: this.dateOfBirth
}
})
}
mutation used above EDIT_PROFILE:
gql`mutation editProfile($name: String!, $email: String!,$phone: String!, $gender: Gender!, $dateOfBirth: String!) {
editProfile (profileInput:{name: $name, email: $email, phone: $phone, gender: $gender, dateOfBirth: $dateOfBirth}){
id
email
phone
firstName
lastName
nameAsInPan
gender
dateOfBirth
}
}
`
use the enum variable name as defined in the mutation and send it to Graphql, like I have used gender As
$gender: Gender! in gql mutation. You don't have to worry about sending data as enum, just send it as String otherwise you will have to face JSON error, Graphql will take care of the value you send as a string (like 'MALE' or 'FEMALE') just don't forget to mention that gender is type of Gender(which is enum) in gql mutation as I did above.
Please read my answer on this link Link for reference

Related

FastAPI/Pydantic alias existing ORM field

I need to point Pydantic to a different attribute when serializing an ORM model. alias= doesn't seem to work as expected. In the example below I have an ORM object with both id and uuid attributes. I want to serialize uuid as id.
The API response should be:
{
"id": "12345678-1234-5678-1234-567812345678",
"foo": "bar"
}
Full example:
from uuid import UUID
from fastapi import FastAPI
from pydantic import BaseModel, Field
from dataclasses import dataclass
class ApiSchema(BaseModel):
class Config:
orm_mode = True
uuid: UUID = Field(alias='id')
foo: str | None = None
#dataclass
class ORMModel:
id: int
uuid: UUID
foo: str = 'bar'
app = FastAPI()
#app.get("/")
def endpoint() -> ApiSchema:
t = ORMModel(id=1, uuid=UUID('12345678123456781234567812345678'), foo='bar')
return t
This raises
File fastapi/routing.py", line 141, in serialize_response
raise ValidationError(errors, field.type_)
pydantic.error_wrappers.ValidationError: 1 validation error for ApiSchema
response -> id
value is not a valid uuid (type=type_error.uuid)
The marshmallow equivalent of what I'm trying to achieve would be this:
import marshmallow as ma
class ApiSchema(ma.Schema):
id = ma.fields.UUID(attribute='uuid')
foo = ma.fields.Str()
You misunderstand how aliases work. An alias on a field takes priority (over the actual field name) when the fields are populated. That means, during initialization, the class will look for the alias of a field in the data it is supposed to parse.
The way you defined ApiSchema, the field uuid has the alias id. Therefore, when you are parsing an instance of ORMModel (happens in FastAPI behind the scenes via ApiSchema.from_orm), the ApiSchema class will look for an attribute named id on that ORMModel object to populate the uuid field.
Since your ORMModel actually has an attribute named id (with the value 1 in your example), its value is taken to be assigned to the uuid field of ApiSchema.
Obviously, the integer 1 is not a UUID object and can not be coerced into one, so you get that validation error telling you that the value it found for id is not a valid UUID.
Here is the problem boiled down to the essentials:
from uuid import UUID
from pydantic import BaseModel, Field, ValidationError
class ApiSchema(BaseModel):
uuid: UUID = Field(alias='id')
foo: str | None = None
try:
ApiSchema.parse_obj({"uuid": "this is ignored", "foo": "bar"})
except ValidationError as exc:
print(exc.json(indent=2))
try:
ApiSchema.parse_obj({"id": 1, "foo": "bar"})
except ValidationError as exc:
print(exc.json(indent=2))
The output of the first attempt:
[
{
"loc": [
"id"
],
"msg": "field required",
"type": "value_error.missing"
}
]
The second:
[
{
"loc": [
"id"
],
"msg": "value is not a valid uuid",
"type": "type_error.uuid"
}
]
I think you want it the other way around. I assume that your actual goal is to have a field named id on your ApiSchema model (and have that appear in your API endpoint) and alias it with uuid, so that it takes the value of the ORMModel.uuid attribute during initialization:
from uuid import UUID
from pydantic import BaseModel, Field
class ApiSchema(BaseModel):
id: UUID = Field(alias="uuid")
foo: str | None = None
obj = ApiSchema.parse_obj(
{
"id": "this is ignored",
"uuid": UUID("12345678123456781234567812345678"),
"foo": "bar",
}
)
print(obj.json(indent=2))
The output:
{
"id": "12345678-1234-5678-1234-567812345678",
"foo": "bar"
}
To fix your FastAPI example, you would therefore probably do this:
from dataclasses import dataclass
from uuid import UUID
from fastapi import FastAPI
from pydantic import BaseModel, Field
class ApiSchema(BaseModel):
id: UUID = Field(alias="uuid")
foo: str | None = None
class Config:
orm_mode = True
#dataclass
class ORMModel:
id: int
uuid: UUID
foo: str = "bar"
app = FastAPI()
#app.get("/", response_model=ApiSchema, response_model_by_alias=False)
def endpoint() -> ORMModel:
t = ORMModel(id=1, uuid=UUID("12345678123456781234567812345678"), foo="bar")
return t
Side note: Yes, the actual return type of endpoint is ORMModel. The wrapper returned by the decorator then takes that and turns it into an instance of ApiSchema via from_orm.
PS
Forgot the last part to actually get the response you want. You need to set response_model_by_alias=False in the route decorator (it is True by default) for the response to actually use the regular field name instead of the alias. I fixed the last code snipped accordingly. Now the response will be:
{"id":"12345678-1234-5678-1234-567812345678","foo":"bar"}
In the Pydantic BaseModel.json method the by_alias parameter has the value False by default. FastAPI does this differently.

zeep.exceptions.Fault - Invalid Enum Value | Python Zeep Enum Attribute Error

def generate_waybill(shipper, consignee, services, profile):
success = True
request = {
"Shipper": shipper,
"Consignee": consignee,
"Services": services
}
client = Client(url)
res = client.service.GenerateWayBill(Request=request, Profile=profile)
print("RESPONSE GENERATE WAYBILL: \n\n", res, res.json())
if not res:
success = False
return res, success
While calling the generate_waybill function i get an execption from zeep >>> 'Invalid enum value 'ProductType.Dutiables'
In the services object it has a key named as ProductType and its Data Type ProductType [Enumerator] and the allowed values for this fields are ProductType.Docs, ProductType.Dutiables.
The services object
services = {
"ProductCode": shipping_options['bluedart']['product_code'],
"ProductType": shipping_options['bluedart']['product_type'],
"PieceCount": return_request_line_items.all().count(),
"ActualWeight": weight,
"CreditReferenceNo": reference_id,
"PickupDate": timezone.now() + timezone.timedelta(days=2),
"PickupTime": '1052',
"RegisterPickup": True,
"IsReversePickup": True
}
In the "ProductType": field I have passed hard coded string value as 'ProductType.Dutiables'
I have also tried creating a calss with Enum type like
from enum import Enum
class ProductType(Enum):
Dutiables = "Dutiables"
and used this as ProductType.Dutiables in the 'ProductType' field.
But nothing seems to work for me. Please help!
zeep.exceptions.Fault: The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:Request. The InnerException message was 'Invalid enum value 'ProductType.Dutiables' cannot be deserialized into type 'SAPI.Entities.Enums.AWBGeneration.ProductType'. Ensure that the necessary enum values are present and are marked with EnumMemberAttribute attribute if the type has DataContractAttribute attribute.'. Please see InnerException for more details.

How can I get object field descriptions to ouput with a Graphene GraphQL schema?

I have my graphql schema implemented in graphene, with the a test object and schema object being:
class Test(ObjectType):
id = ID(description='Test ID')
test_string = String(description='Test String')
schema = Schema(query=Query, subscription=Subscription, mutation=Mutation)
For testing and front end development purposes, I print out the schema using:
from server.graphql_handler.schema import schema
print(schema)
This gives me something like this:
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
type Test {
id: ID
test_string: String
}
My question being, is is possible to output the Test object fields' descriptions as well? Maybe something like:
type Test {
"""
Test ID
""""
id: ID
"""
Test String
"""
test_string: String
}

How can I ignore unknown arguments passed as inputs to a mutation?

As per the GraphQL spec https://graphql.github.io/graphql-spec/draft/#sec-Input-Objects
the input object literal or unordered map must not contain any entries with names not defined by a field of this input object type, otherwise an error must be thrown.
Let's say I have a project which has a user called buyer, the schema for which is as follows
type Buyer {
id: ID!
name: String!
address: String!
email: String!
}
Now I can write a graphene schema for it
class BuyerType(DjangoObjectType):
class Meta:
model = Buyer
and make a mutation for this
class BuyerInput(graphene.InputObjectType):
name = graphene.String(required=False, default_value='')
address = graphene.String(required=False, default_value='')
email = graphene.String(required=False, default_value='')
class BuyerMutation(graphene.Mutation):
"""
API to create applicant
"""
class Arguments:
buyer_data = BuyerInput(required=True)
buyer = graphene.Field(BuyerType)
ok = graphene.Boolean()
def mutate(self, info, buyer_data=None):
buyer = Buyer(name=buyer.name, address=buyer.address, email=buyer.email)
buyer.save()
return BuyerMutation(buyer=buyer, ok=True)
and write the resolvers functions and query and mutation class. Pretty basic stuff so far.
And now to create a new buyer, I just call the mutation
mutation {
createBuyer(
name: "New Buyer"
address: "Earth"
email: "abc#example.com"
) {
ok
}
}
But if I pass an additional field called phone
mutation {
createBuyer(
name: "New Buyer"
address: "Earth"
email: "abc#example.com"
phone: 8541345474
) {
ok
}
}
an Unknown field 'phone' error comes up, which is understood.
But I want to make process for frontend devs easier. Rather than mentioning each field in the argument of mutation, they can just pass a dict which contains these three fields and also other arguments that might originate from a form submission, and then read the dict in the backend and only extract the fields that I need, much like it is done with REST APIs.
If this is possible, then how can I implement this?
We could get the class attributes of BuyerInput using the inspect module, and then assemble the arguments dictionary so that it ignores the keys which are not such an attribute. Say, we have the dict containing the parameters of createBuyer stored in the variable kwargs, this could look like the following:
import inspect
members = inspect.getmembers(BuyerInput, lambda a: not(inspect.isroutine(a)))
attributes = [
tup[0]
for tup in members
if not tup[0].startswith("_")
]
kwargs = {
key: value
for key, value in kwargs.items()
if key in attributes
}

Retrieve the object ID in GraphQL

I'd like to know whether it is possible to get the "original id" of an object as the result of the query. Whenever I make a request to the server, it returns the node "global identifier", something like U29saWNpdGFjYW9UeXBlOjEzNTkxOA== .
The query is similar to this one:
{
allPatients(active: true) {
edges {
cursor
node {
id
state
name
}
}
}
and the return is:
{
"data": {
"edges": [
{
"cursor": "YXJyYXljb25uZWN0aW9uOjA=",
"node": {
"id": "U29saWNpdGFjYW9UeXBlOjEzNTkxOA==",
"state": "ARI",
"name": "Brad"
}
}
]
}
}
How can I get the "original" id of the object at the database level (e.g. '112') instead of that node unique identifier?
ps.: I am using graphene-python and Relay on the server side.
Overriding default to_global_id method in Node object worked out for me:
class CustomNode(graphene.Node):
class Meta:
name = 'Node'
#staticmethod
def to_global_id(type, id):
return id
class ExampleType(DjangoObjectType):
class Meta:
model = Example
interfaces = (CustomNode,)
First option, remove relay.Node as interface of your objectNode declaration.
Second option, use custom resolve_id fonction to return id original value.
Example
class objectNode(djangoObjectType):
.... Meta ....
id = graphene.Int(source="id")
def resolve_id("commons args ...."):
return self.id
Hope it helps
To expand on the top answer and for those using SQLAlchemy Object Types, this worked for me:
class CustomNode(graphene.Node):
class Meta:
name = 'myNode'
#staticmethod
def to_global_id(type, id):
return id
class ExampleType(SQLAlchemyObjectType):
class Meta:
model = Example
interfaces = (CustomNode, )
If you have other ObjectTypes using relay.Node as the interface, you will need to use a unique name under your CustomNode. Otherwise you will get and assertion error.
With this you can retrive the real id in database:
def get_real_id(node_id: str):
_, product_id_real = relay.Node.from_global_id(global_id=node_id)
return product_id_real

Categories

Resources