一文读懂Graphene中的Interfaces、Union、Mutation

雍光远
2023-12-01

枚举

枚举是一种特殊的GraphQL类型,它表示绑定到唯一常数值的一组符号名称(成员)

创建一个Enum使用类

import graphene

class Episode(graphene.Enum):
    NEWHOPE = 4
    EMPIRE = 5
    JEDI = 6

也可以使用枚举实例

Episode = graphene.Enum('Episode', [('NEWHOPE', 4), ('EMPIRE', 5), ('JEDI', 6)])

Value descriptions

可以向枚举值添加描述

class Episode(graphene.Enum):
    NEWHOPE = 4
    EMPIRE = 5
    JEDI = 6

    @property
    def description(self):
        if self == Episode.NEWHOPE:
            return 'New Hope Episode'
        return 'Other episode'
      
 print(Episode().NEWHOPE.description)

如果已经定义了枚举,则可以使用Enum.from_Enum函数重用它们。

graphene.Enum.from_enum(AlreadyExistingPyEnum)

Enum.from_Enum支持描述和deposition_reason lambdas作为输入,因此可以在不更改原始描述的情况下将描述等添加到枚举中

graphene.Enum.from_enum(
    AlreadyExistingPyEnum,
    description=lambda v: return 'foo' if v == AlreadyExistingPyEnum.Foo else 'bar'
)

Interfaces

接口是一种抽象类型,它定义了一组特定的字段,类型必须包含这些字段才能实现接口

import graphene

class Character(graphene.Interface):
    id = graphene.ID(required=True)
    name = graphene.String(required=True)
    friends = graphene.List(lambda: Character)

任何实现Character的ObjectType都将具有这些精确的字段,以及这些参数和返回类型。

class Human(graphene.ObjectType):
    class Meta:
        interfaces = (Character, )

    starships = graphene.List(Starship)
    home_planet = graphene.String()

class Droid(graphene.ObjectType):
    class Meta:
        interfaces = (Character, )

    primary_function = graphene.String()

这两种类型都具有Character接口中的所有字段,但也引入了特定于该特定类型角色的额外字段home_planet、starships和primary_function。

如果查询的字段返回的是接口或者联合类型,那么你可能需要使用内联片段来取出下层具体类型的数据

# 首先实例化两个interface变量

luke = Human(
        id="20",
        name="Luke",
        friends=["1002", "1003", "2000", "2001"],
        home_planet="Human function"
)

droid = Droid(
        id="100",
        name="lu wei",
        friends=["1002", "1003", "2000", "2001"],
        primary_function="Droid function"
)

# 编写query类
class Query(graphene.ObjectType):
    hero = graphene.Field(
        Character,  # 指定当前字段类型为Character 它是一个interface类型
        required=True,
        episode=graphene.Int(required=True)
    )

    def resolve_hero(root, info, episode):
      # 返回hero值
        if episode == 1:
            return luke
        return droid
      
#  type是包含在架构中的任何类型的列表,这里是两个实例, 因为我们要同时查询两个 但是它无法通过root进行内省
schema = graphene.Schema(query=Query, types=[Human, Droid])

# 定义查询语句
query_string = """
    query HeroForEpisode($episode: Int!) {
        hero(episode: $episode) {
            name
            ... on Droid {
                primaryFunction
            }
            ... on Human {
                homePlanet
            }
        }
    }
"""

result = schema.execute(query_string, variables={"episode": 1})
print(result.data)

# episode为1时返回: {'hero': {'name': 'Luke', 'homePlanet': 'Human function'}}
# episode为2时返回: {'hero': {'name': 'lu wei', 'primaryFunction': 'Droid function'}}

当schema时,resolve通常会返回数据的对象,而不是类型的实例(例如Django或SQLAlchemy模型)。这适用于ObjectType和Scalar字段,但当您开始使用接口时,可能会遇到以下错误:

"Abstract type Character must resolve to an Object type at runtime for field Query.hero ..."

这种情况是因为没有足够的信息将数据对象转换为解析interface所需的类型。这时可以在Interface上定义resolve_type类方法,该方法将数据对象映射到graphene类型:

class Character(graphene.Interface):
    id = graphene.ID(required=True)
    name = graphene.String(required=True)
    friends = graphene.List(lambda: Character)

    @classmethod
    def resolve_type(cls, instance, info):
        if type(instance) == Human:
            return Human
        return Droid

联合类型(Union)

重点:联合类型与接口非常相似,但它们不指定类型之间的任何公共字段

每个Union都是从graphene.Union继承的Python类。

重点:联合上没有任何字段,只有指向可能的ObjectTypes

此示例定义了几个具有自己字段的ObjectType。SearchResult是此对象类型的并集的实现

import graphene

class Human(graphene.ObjectType):
    name = graphene.String()
    born_in = graphene.String()

class Droid(graphene.ObjectType):
    name = graphene.String()
    primary_function = graphene.String()

class Starship(graphene.ObjectType):
    name = graphene.String()
    length = graphene.Int()

class SearchResult(graphene.Union):
    class Meta:
        types = (Human, Droid, Starship)

无论在模式中什么地方返回SearchResult类型,我们都可能得到一个Human、Droid或Starship。注意,联合类型的成员需要是具体的对象类型;不能从接口或其他联合中创建联合类型

上述类型在架构中具有以下表示形式:

type Droid {
  name: String
  primaryFunction: String
}

type Human {
  name: String
  bornIn: String
}

type Ship {
  name: String
  length: Int
}

union SearchResult = Human | Droid | Starship

执行联合类型查询

human = Human(
    name="atman",
    born_in="M78"
)
droid = Droid(
    name="aida",
    primary_function="Mars"
)
ship = Starship(
    name="Noah's Ark",
    length=998
)


class Query(graphene.ObjectType):
    search_result = graphene.Field(
        SearchResult,
        required=True,
        search=graphene.String(required=True)
    )

    def resolve_search_result(root, info, search):
        if search == "A":
            return human
        if search == "B":
            return droid
        return ship


schema = graphene.Schema(query=Query)

query_string = """
    query HeroSearch($search: String!) {
        searchResult(search: $search) {
            ... on Droid {
                name
                primaryFunction
            }
            ... on Human {
                name
                bornIn
            }
            ... on Starship {
                name
                length
            }
        }
    }
"""


result = schema.execute(query_string, variables={"search": "A"})
print(result.data)

Mutation

Mutation 是一种特殊的 ObjectType,还定义了一个 Input

class Person(graphene.ObjectType):
    name = graphene.String()
    age = graphene.Int()

class CreatePerson(graphene.Mutation):
    class Arguments:
        name = graphene.String()

    ok = graphene.Boolean()
    person = graphene.Field(lambda: Person)

    def mutate(root, info, name):
        person = Person(name=name)
        ok = True
        return CreatePerson(person=person, ok=ok)

personok是解决突变时的输出字段。

Arguments属性是突变创建者需要解析的参数,在这种情况下,name将是突变的唯一参数。

mutate是在调用Mutation后将应用的函数。这个方法只是一个特殊的解析器,可以在其中更改数据。它采用与标准查询Resolver相同的参数

因此,我们可以这样完成我们的模式:

class MyMutations(graphene.ObjectType):
    create_person = CreatePerson.Field()

# 必须为模式定义一个查询
class Query(graphene.ObjectType):
    person = graphene.Field(Person)

schema = graphene.Schema(query=Query, mutation=MyMutations)

query_str = """
    mutation myFirstMutation($name: String!) {
    createPerson(name:$name) {
        person {
            name
        }
        ok
    }
	}
"""
result = schema.execute(query_str)
print(result.data)

# {'createPerson': {'person': {'name': 'Peter'}, 'ok': True}}

InputFields 和 InputObjectTypes

InputFields用于Mutation,以允许嵌套的Mutation输入数据

要使用InputField,需要定义一个InputObjectType来指定输入数据的结构

import graphene

class PersonInput(graphene.InputObjectType):
    name = graphene.String(required=True)
    age = graphene.Int(required=True)

class CreatePerson(graphene.Mutation):
    class Arguments:
        person_data = PersonInput(required=True)  # 输入的参数类型指定为PersonInput

    person = graphene.Field(Person)

    def mutate(root, info, person_data=None):
        person = Person(
            name=person_data.name,
            age=person_data.age
        )
        return CreatePerson(person=person)

name和age现在是person_data的一部分

使用上述Mutation,新查询如下:

class InputMutations(graphene.ObjectType):
    input_create_person = CreatePersonInput.Field()

# 这里的查询语句需要传入PersonInput类型的参数
input_query_str = """
    mutation myFirstMutation {
    inputCreatePerson(personData: {name:"lu wei", age: 25}) {
        person {
            name,
            age
        }
    }
	}
"""
schema = graphene.Schema(query=Query, mutation=InputMutations)
result = schema.execute(input_query_str)
print(result.data)

InputObjectType也可以是InputObjectTypes类型的字段,允许根据需要拥有任意复杂的输入数据

import graphene

class LatLngInput(graphene.InputObjectType):
    lat = graphene.Float()
    lng = graphene.Float()

# 将InputObjectType类型作为字段  用法跟普通ObjectType基本一样
class LocationInput(graphene.InputObjectType):
    name = graphene.String()
    latlng = graphene.InputField(LatLngInput)

Output

要返回现有的ObjectType而不是Mutation类型,需要将Output属性设置为需要返回的ObjectType:

# output类型
class OutputCreatePerson(graphene.Mutation):
    class Arguments:
        name = graphene.String()

    Output = Person  # 其实这里跟 person=graphene.Field(Person) 作用一样

    def mutate(root, info, name):
        return Person(name=name)


class OutMutations(graphene.ObjectType):
    output_create_person = OutputCreatePerson.Field()

查询结果

output_query_str = """
    mutation myFirstMutation {
    outputCreatePerson(name:"lu wei") {
        name
        __typename
    }
	}
"""

schema = graphene.Schema(query=Query, mutation=OutMutations)
result = schema.execute(output_query_str)
print(result.data)
 类似资料: