GRAPH QL === ## 基礎 ### selectionSet 若沒有寫任何的方式或是名稱,這樣的描述就是selectionSet ``` { players { playerName playerHeight } } ``` 但實際上要做成query要寫成其他樣子 ### query 這樣的寫法就是一般grapQL query ``` query getSomePlayerName { players { playerName playerHeight } } ``` ### argument 他也可以支援各式各樣的argument limit就是只給三個 skip就是跳過前兩個 random可以隨機出現 ``` query getSomePlayerDetails { players(limit:3, skip:2,random:true) { playerName playerHeight } } ``` 可以下其他condition來找 query getSomePlayerDetails { players(teamAbbreviation: "TOR", season: "2018-19", limit:20) { playerName playerHeight } } ### Query ``` query getSomePlayerDetails($team: String, $season: String, $limit: Int) { players(teamAbbreviation: $team, season: $season, limit: $limit) { playerName playerHeight playerWeight teamAbbreviation draftYear draftRound } } ``` variable ``` { "team": "TOR", "season": "2018-19", "limit": 3 } ``` 最終會出來的就是符合如下的 ``` { "data": { "players": [ { "playerName": "Pascal Siakam", "playerHeight": 205.74, "playerWeight": 104.32616, "teamAbbreviation": "TOR", "draftYear": "2016", "draftRound": "1" }, { "playerName": "Patrick McCaw", "playerHeight": 200.66, "playerWeight": 83.91452, "teamAbbreviation": "TOR", "draftYear": "2016", "draftRound": "2" }, { "playerName": "Serge Ibaka", "playerHeight": 208.28, "playerWeight": 106.59412, "teamAbbreviation": "TOR", "draftYear": "2008", "draftRound": "1" } ] } } ``` 若是後面放!但沒有那個variable就會報錯 ``` query getSomePlayerDetails($team: String!, $season: String, $limit: Int) { players(teamAbbreviation: $team, season: $season, limit: $limit) { playerName playerHeight playerWeight teamAbbreviation draftYear draftRound } } ``` 報錯資訊 ``` { "data": null, "errors": [ { "message": "Variable '$team' of required type 'String!' was not provided.", "locations": [ { "line": 1, "column": 28 } ] } ] } ``` ### Fragment 也可以把一個區塊摘出來變成Fragment 這樣就可以跑到不同的地方來做使用或是節省你的寫法 定義一次就能在很多地方用了 ``` query getSomePlayerDetails($team: String, $season: String, $limit: Int) { players(teamAbbreviation: $team, season: $season, limit:$limit){ ...PlayerFields } } fragment PlayerFields on Player { playerName playerHeight pts usgPct } ``` ### Directives 可以使用skip來做處理,或是include 這樣可以更結構化的顯現你的資料 有時候你可以判斷是否有sessioon登入然後才顯示一些該顯示給登入者的資料 ``` query getSomePlayerDetails($noWeight:Boolean!) { players{ ...PlayerFields } } fragment PlayerFields on Player { playerName playerHeight teamAbbreviation draftYear draftRound playerWeight @skip(if:$noWeight) } ``` 記得要寫上variables ``` { "noWeight": false } ``` ### 實作 一個小於180公分且在2021-22打球的球員 ``` query RandomPlayerShorterThan( $Height: Float!, $Season:String! ) { players(limit:10, random:true,season:$Season, playerHeight: {lt:$Height}) { ...PlayerFields } } fragment PlayerFields on Player { playerHeight teamAbbreviation draftYear } ``` 使用variable ``` { "Height": 180, "Season":"2021-22"} ``` ## Code-First or Schema-First - Schema-First 可以讓前端先來處理串接 但可能需要維護code跟Schema同時 比較麻煩 框架有 ariadne, tratiflette - Code-First 邊寫code邊寫Schema code跟Schema得狀況會是相同 但前端就要等待後端才來做最後處理 框架有 Graphene, Strawberry 若小型團隊可以使用Code-First這樣先行來做開發 但大型團隊可能要先用Schema-First來保持其他團隊可以同步的一起並行處理 ## Coding - Graphene workflow 目前最多人在使用的python GraphQL Schema - Schema - ObjectType - Fields - Type [string, int, float, boolean] - arguments - resolver - QueryOperation ### Field Field拿來定義接受的資料跟會回傳的資料類型 下面的code就是Field定義他會回傳UserType這種資料,然後他的接受user_id作為查詢輸入 resolve那邊就是定義他要怎樣解析出來他的所有符合項目的資料然後回傳 ``` class UserType(ObjectType): id = Int() name = String() age = Int() class Query(ObjectType): user = Field(UserType, user_id=Int()) users = [ {"id": 1, "name": "John Doe", "age": 23}, {"id": 2, "name": "Jane Jones", "age": 35}, {"id": 3, "name": "Jim Smith", "age": 34}, {"id": 4, "name": "Jill Johnson", "age": 23}, {"id": 5, "name": "Jack Williams", "age": 39} ] def resolve_user(self, info, user_id): matched_users = [user for user in Query.users if user["id"] == user_id] return matched_users[0] if matched_users else None ``` ### PlayGround 可以用一些基礎的方式做debug可以使用以下兩種 graphiql跟playground都提供很不錯的UI介面來做測試跟debug ``` from starlette_graphene3 import GraphQLApp, make_graphiql_handler, make_playground_handler app.mount("/graphql", GraphQLApp( schema=schema, on_get=make_graphiql_handler() )) app.mount("/graphql-p", GraphQLApp( schema=schema, on_get=make_playground_handler() )) ``` ## lambda 用法 有時候定義這種互相崁套的結構 可以使用lambda讓他被使用的時候才去找裡面的東西 ``` class EmployerObject(ObjectType): id = Int() name = String() contact_email = String() industry = String() jobs = List(lambda: JobObject) @staticmethod def resolve_jobs(root, info): return [job for job in jobs_data if job["employer_id"]==root.id] class JobObject(ObjectType): id = Int() title = String() description = String() employer_id = Int() employer = Field(lambda: EmployerObject) @staticmethod def resolve_employer(root, info): return next((employer for employer in employers_data if employer["id"] == root["employer_id"]) ,None) ``` ### DB 自動增加 記得DB若有p key或是數字,記得那個欄位後面要寫autoincrement ``` class Employer(Base): __tablename__ = "employers" id = Column(Integer, primary_key=True, autoincrement= True) name = Column(String) contact_email = Column(String) industry = Column(String) jobs = relationship("Job", back_populates="employer") ``` ### DB 除錯 有關DB有時候下orm可能會有未知的錯誤 可以使用create_engine的時候加上echo 就可以在log中看到他的狀況了 ``` engine =create_engine(DB_URL, echo=True) Session = sessionmaker(bind=engine) ```