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)
```