詳解Python Graphql

前言

很高興如今接手的項目讓我接觸到了Python Graphql,百度上對其介紹相對較少也不夠全面,幾乎沒有完整的中文文檔,因此這邊也藉此機會學習一下Graphql。前端

什麼是Graphql呢vue

Graphql是一個API查詢語言,其數據由服務器上的一個Scheme提供,其查詢返回的數據依賴請求的時候用戶須要的精確數據。列如用戶只須要一個name字段,服務器也只返回name的值。python

參考react

英文學習文檔:https://graphene-python.org/git

更多example關注:https://github.com/graphql-python/graphene/tree/master/examplesgithub

Hello Word 入門

先看下面一個例子,查詢語句爲{ hello(name:"gaojiayi") } 定義了要查詢的入口,以及傳入的參數。vuex

from graphene import ObjectType, String, Schema

class Query(ObjectType):
    """定義一個字符串屬性域hello 且有一個字符串參數爲name,設置name的默認""" 
    hello = String(name = String(default_value="gaojy",required=True))

    # resolve_hello定義了上面hello的實現,並返回查詢結果
    # 通常resolve須要加上固定前綴resolve_
    @staticmethod
    def resolve_hello(root,info,name):
        return f"hello word -- {name}"

schema = Schema(query=Query)

if __name__ == '__main__':
    query_string = '''{ hello(name:"gaojiayi") }'''
    result = schema.execute(query_string)
    print(result.data['hello'])

 Graphql中的Types

Scheme

下面定義了一個Scheme,其中MyRootQuery,MyRootMutation,MyRootSubscription都是繼承了graphene .objectType,可是不一樣之處在於query定義了查詢數據的入口,而mutation用來數據改變或者數據恢復,而subscription是用來實時呈現數據的變化給client。type是用來指定返回數據的精確類型,列如返回的數據是一個interface,可是有多個類型繼承了該interface,這時候須要指定一個具體的實現來返回給client。json

my_schema = Schema(
    query=MyRootQuery,
    mutation=MyRootMutation,
    subscription=MyRootSubscription,
   type=[
SomeExtraObjectType,]
)

另外查詢字符串默認爲駝峯命名,列如redux

from graphene import ObjectType, String, Schema

class Query(ObjectType): 
    other_name = String(name='_other_Name')

    @staticmethod
    def resolve_other_name(root, info):
        return "test CamelCase"


schema = Schema(query=Query)

if __name__ == '__main__':
    # 查詢數默認使用otherName,此處用了別名。
    result = schema.execute('''{_other_Name}''')
    print(result.data['_other_Name'])

若是關閉默認駝峯命名方式,則能夠在定義scheme的時候加上auto_camelcase=False後端

my_schema = Schema(  auto_camelcase=False )

scalars

scalars type能夠理解爲用來定義Field,它能夠傳入如下幾種可選參數,例如

other_name = String(name='_other_Name',required=True,description="",deprecation_reason="",defalut_value=Any)

 

常見的基本saclars type有以下幾個:

graphene.String
graphene.Int
graphene.Float
graphene.Boolean
graphene.ID
graphene.types.datetime.Date
graphene.types.datetime.DateTime
graphene.types.datetime.Time
graphene.types.json.JSONString
View Code

 

saclars type的掛載在objectType,interface,Mutation中的field域中。

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

# Is equivalent to:
class Person(graphene.ObjectType):
    name = graphene.Field(graphene.String)
View Code

 

Lists and Non-Null

Non-Null

import graphene

class Character(graphene.ObjectType):
    name = graphene.String(required=True)
#等價於 即返回的數據若是name=null,則會報錯
class Character(graphene.ObjectType):
    name = graphene.String(required=True)

Lists

import graphene

class Character(graphene.ObjectType):
    # appears_in表示爲一個非null元素的列表
    appears_in = graphene.List(graphene.NonNull(graphene.String))

 

ObjectType

objectType是在scheme中用來定義Fields之間聯繫以及數據流轉的python類,每個obejctType屬性表示一個Field,每一個Field定義一個resolve方法用來獲取數據,若是沒有定義,則使用一個默認的resolver。

 接下來看一個例子。

from graphene import ObjectType, String, Schema

class Query(ObjectType):
    @staticmethod
    def resolve_hello(parent,info,name):
        return f"hello word -- {name}"

上面的resolve_hello有三個參數,分別是parent,info,name

1 parent一般用來獲取objectType內的其餘field的值,而在根query中默認爲None,看下面的事例,當OjectType的Field爲saclar type,則parent不會再向下傳遞。

class Person(ObjectType):
    full_name = String()

    def resolve_full_name(parent, info):
        return f"{parent.first_name} {parent.last_name}"

class Query(ObjectType):
    me = Field(Person)

    def resolve_me(parent, info):
        # returns an object that represents a Person
        # 這裏的parent爲None
        return get_human(name="Luke Skywalker")
View Code

固然,根查詢的parent也能夠初始化值,就是在execute的時候添加root變量

    @staticmethod
    def resolve_hello(parent, info, name):
        # 打印結果 man ,parent默認爲root的值
        print(parent['sex'])
        return f"hello word -- {name}"


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

if __name__ == '__main__':
    query_string = '''{ hello(name:"gaojiayi") }'''
    # 指定root的值  
    result = schema.execute(query_string, root={'sex': 'man'})
    print(result.data['hello'])
View Code

當查詢語句存在多個的時候,可指定執行那一條語句

schema = Schema(Query)
query_string = '''
    query getUserWithFirstName {
        user {
            id
            firstName
            lastName
        }
    }
    query getUserWithFullName {
        user {
            id
            fullName
        }
    }
'''
result = schema.execute(
    query_string,
    # 指定執行第二條語句
    operation_name='getUserWithFullName'
)
View Code

2 info表示請求的上下文,能夠在查詢語中添加context,列如

class Query(ObjectType):
     hello = String(name=String(default_value="gaojy", required=True))
     @staticmethod
     def resolve_hello(root, info, name):
        # 經過info可獲取上下文內容
        print(info.context.get('company'))
        return f"hello word -- {name}"


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

if __name__ == '__main__':
    query_string = '''{ hello(name:"gaojiayi") }'''
    # 1 execute中添加context
    result = schema.execute(query_string, context={'company': 'baidu'})
    print(result.data['hello'])
View Code

3 name表示請求時帶的參數,能夠參考hello word事例,若有多個參數可形參**kwargs

from graphene import ObjectType, String

class Query(ObjectType):
    hello = String(required=True, name=String())

    def resolve_hello(parent, info, **kwargs):
        # name 爲None 則name = World
        name = kwargs.get('name', 'World')
        return f'Hello, {name}!'
View Code

4 默認resolver:列如一個objectType的field都沒有指定隊友的resolve,那麼對象默認會序列化一個字典。

PersonValueObject = namedtuple('Person', 'first_name', 'last_name')

class Person(ObjectType):
    first_name = String()
    last_name = String()

class Query(ObjectType):
    me = Field(Person)
    my_best_friend = Field(Person)

    def resolve_me(parent, info):
        # always pass an object for `me` field
        # {"firstName": "Luke", "lastName": "Skywalker"}
        return PersonValueObject(first_name='Luke', last_name='Skywalker')
View Code

 5 meta 類:用於objectType的配置

Enum

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'

class Query(ObjectType):
         desc1 = String(
        v=Argument(Episode, default_value=Episode.NEWHOPE.value),
        description='default value in schema is `4`, which is not valid. Also, awkward to write.')

 @staticmethod
    def resolve_desc1(parent, info,v):
        return f'argument: {v!r}'

# 使用下面的方式能夠將python類型的enum轉化成saclars類型
graphene.Enum.from_enum(
    AlreadyExistingPyEnum,
    description=lambda v: return 'foo' if v == AlreadyExistingPyEnum.Foo else 'bar')
View Code

Interfaces

顧名思義,接口,其餘的obectType能夠繼承接口,示例以下

import graphene

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

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

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

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

    primary_function = graphene.String()

class Query(graphene.ObjectType):
    # 返回的類型是Character
    hero = graphene.Field(
        Character,
        required=True,
        episode=graphene.Int(required=True)
    )

    def resolve_hero(root, info, episode):
        # Luke is the hero of Episode V
        if episode == 5:
            return get_human(name='Luke Skywalker')
        return get_droid(name='R2-D2')

#對於返回數據具體類型,能夠在type屬性中列舉
schema = graphene.Schema(query=Query, types=[Human, Droid])
View Code

另外scheme中若是沒有指定type,會報錯

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

能夠在interface中重寫resolve_type方法

class Character(graphene.Interface):
    id = graphene.ID(required=True)
    name = graphene.String(required=True)
    #返回數據的時候,能夠轉換成具體的數據類型
    @classmethod
    def resolve_type(cls, instance, info):
        if instance.type == 'DROID':
            return Droid
        return Human

Union

該scalars type用來組合多個ObjectType,列如

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()
# SearchResult組合了Human Droid Starship全部的Fields
class SearchResult(graphene.Union):
    class Meta:
        types = (Human, Droid, Starship)
View Code

Mutations

若是說query是一個http get請求,那麼Mutations能夠看作是一個http post put請求。

def Mutate做爲一個特殊的resover,當被調用的時候意在改變Mutation內的數據。

看下面一個操做示例

#具體的操做類
class CreatePerson(graphene.Mutation):
    # 請求提交的參數,一樣須要傳遞到mutate中
    class Arguments:
        name = graphene.String()

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

    def mutate(root, info, name):
        person = Person(name=name)
        ok = True
        #可執行具體的業務邏輯 包括寫表 發消息等等
        return CreatePerson(person=person, ok=ok)


# Mutation
class MyMutations(graphene.ObjectType):
    create_person = CreatePerson.Field()
#指定mutation  MyMutations
schema = Schema(query=Query,mutation=MyMutations)

執行結果以下:

 

Mutation下可申明InputFields 和InputObjectTypes類型的出入參,其中InputFields能夠定義複合型入參,Output可指定複合型出參。

例1:InputFields 

class DataInput(graphene.InputObjectType):
    user_name = String()
    basic_age = Int()

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

# 具體的操做類
class CreatePerson(graphene.Mutation):
    # 請求提交的參數,一樣須要傳遞到mutate中
    class Arguments:
        data = DataInput(required=True)

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

    def mutate(root, info, data):
        person = Person(name=data.user_name, age=data.basic_age * 10)
        ok = True
        return CreatePerson(person=person, ok=ok)

執行結果:

 

 例2:InputObjectTypes

class DataInput(graphene.InputObjectType):
    user_name = String()
    basic_age = Int()


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

# 具體的操做類
class CreatePerson(graphene.Mutation):
    # 請求提交的參數,一樣須要傳遞到mutate中
    class Arguments:
        data = DataInput(required=True)
    # 定義一個Output 且指定class ,在mutate方法中返回實例
    Output = Person
    def mutate(root, info, data):
        person = Person(name=data.user_name, age=data.basic_age * 10)
        return person

運行結果:

 relay

relay相似於react js中的redux,VUE中的vuex,能夠緩存server端數據,加快查詢並提供更新機制。example可參考前言中的example。

 

小結

  技術自己就是爲業務服務,讀者會問Graphql究竟能夠使用在哪些業務場景呢?

  官方有這麼一句話ask exactly what you want.若是一個前端的接口只須要返回部分數據,而另外一個前端接口也只須要返回部分數據,這兩份數據有可能有交集,也可能沒有。傳統的作法可能須要開發兩個接口或者一個接口內不斷的if else來根據前端的具體場景去過濾某些數據。使用Graphql可以根據client指定須要哪些參數,後端scheme返回哪些參數,然後端只須要一個API能夠查詢到數據全集,Graphql能夠自動完成數據解析,封裝,過濾操做。

相關文章
相關標籤/搜索