前言
很高興現在接手的項目讓我接觸到了Python Graphql,百度上對其介紹相對較少也不夠全面,幾乎沒有完整的中文文檔,所以這邊也借此機會學習一下Graphql。
什么是Graphql呢?
Graphql是一個API查詢語言,其數據由服務器上的一個Scheme提供,其查詢返回的數據依賴請求的時候用戶需要的精確數據。列如用戶只需要一個name字段,服務器也只返回name的值。
參考
英文學習文檔:https://graphene-python.org/
更多example關注:https://github.com/graphql-python/graphene/tree/master/examples
Hello Word 入門
先看下面一個例子,查詢語句為{ hello(name:"gaojiayi") } 定義了要查詢的入口,以及傳入的參數。
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。
my_schema = Schema( query=MyRootQuery, mutation=MyRootMutation, subscription=MyRootSubscription,
type=[SomeExtraObjectType,]
)
另外查詢字符串默認為駝峰命名,列如
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
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)
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")
當然,根查詢的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'])
當查詢語句存在多個的時候,可指定執行那一條語句

schema = Schema(Query) query_string = ''' query getUserWithFirstName { user { id firstName lastName } } query getUserWithFullName { user { id fullName } } ''' result = schema.execute( query_string, # 指定執行第二條語句 operation_name='getUserWithFullName' )
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'])
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}!'
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')
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')
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])
另外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)
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可以自動完成數據解析,封裝,過濾操作。