很高興如今接手的項目讓我接觸到了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(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'])
下面定義了一個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 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)
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是在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的配置
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')
顧名思義,接口,其餘的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
該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)
若是說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相似於react js中的redux,VUE中的vuex,能夠緩存server端數據,加快查詢並提供更新機制。example可參考前言中的example。
技術自己就是爲業務服務,讀者會問Graphql究竟能夠使用在哪些業務場景呢?
官方有這麼一句話ask exactly what you want.若是一個前端的接口只須要返回部分數據,而另外一個前端接口也只須要返回部分數據,這兩份數據有可能有交集,也可能沒有。傳統的作法可能須要開發兩個接口或者一個接口內不斷的if else來根據前端的具體場景去過濾某些數據。使用Graphql可以根據client指定須要哪些參數,後端scheme返回哪些參數,然後端只須要一個API能夠查詢到數據全集,Graphql能夠自動完成數據解析,封裝,過濾操做。