在這次內容中將使用一個特別的範例,來示範 GraphQL 中的 Interface 與 Union Type。
我們假設取得使用者資料的時候,依照不同的使用者角色,顯示不同的使用者詳細資訊。
首先假設使用者詳細資訊都會有幾個共通欄位。
# app/types.py
import strawberry
@strawberry.interface
class UserProfile:
phone: str
birthdate: datetime.date
address: typing.Optional[str]
這裡我們先省略一些程式碼,專注在要說明的內容上。
在這邊我建立一個 UserProfile 的介面(interface)來表示使用者詳細資訊的共通欄位,這些欄位有電話(phone)、生日(birthdate)與地址(address),地址是一個可選的欄位(可以 Null)。
後面建立個別的使用者角色的使用者詳細資訊的型態,透過繼承介面的方式宣告共通欄位。
@strawberry.type
class NormalUserDetail(UserProfile):
pass
@strawberry.type
class StaffUserDetail(UserProfile):
department: str
@strawberry.type
class ManagerUserDetail(StaffUserDetail):
subordinates: typing.List["User"]
@strawberry.type
class AdminUserDetail:
system_permissions: typing.List[str]
上面建立了四個使用者詳細資訊的型態:
再來就是使用聯合型別(Union Types)幫 User Type 增加使用者詳細資訊的欄位:
@strawberry.type
class User:
id: strawberry.ID
username: str
email: str
first_name: str
last_name: str
password: str
last_login: typing.Optional[datetime.datetime]
is_active: bool = strawberry.field(
default=True,
description="Is the user active?",
)
role: UserRole
+ detail: typing.Union[
+ NormalUserDetail,
+ StaffUserDetail,
+ ManagerUserDetail,
+ AdminUserDetail,
+ ]
@strawberry.field
def full_name(self) -> str:
return f"{self.first_name} {self.last_name}"
當使用 typing.Union
後會發現 Schema 上會顯示一個非常長的型態名稱 NormalUserDetailStaffUserDetailManagerUserDetailAdminUserDetail
則我們可以修改成下面的程式碼,就可以自行定義typing.Union
的型態名稱。
- detail: typing.Union[
- NormalUserDetail,
- StaffUserDetail,
- ManagerUserDetail,
- AdminUserDetail,
- ]
+ detail: typing.Annotated[
+ typing.Union[
+ NormalUserDetail,
+ StaffUserDetail,
+ ManagerUserDetail,
+ AdminUserDetail,
+ ],
+ strawberry.union("UserDetail"),
+ ]
使用typing.Annotated
進一步對型態標註額外資訊,第ㄧ個參數是型態本身,第二個參數是型態的額外資訊,我們在這邊使用strawberry.union
來標記型態名稱,這樣就可以在 Schema 上看到UserDetail
型態。
可以看到 User Type 有 detail: UserDetail!
欄位,點下去會顯示四種可能出現的型態。
到目前為止的修改,雖然可以看到型態,但是無法做查詢的操作。
以下是這次的完整程式碼:
import datetime
import enum
import typing
import strawberry
@strawberry.enum
class UserRole(enum.Enum):
NORMAL = strawberry.enum_value(
"normal",
description="Normal user",
)
STAFF = "staff"
MANAGER = "manager"
ADMIN = "admin"
@strawberry.interface
class UserProfile:
phone: str
birthdate: datetime.date
address: typing.Optional[str]
@strawberry.type
class NormalUserDetail(UserProfile):
pass
@strawberry.type
class StaffUserDetail(UserProfile):
department: str
@strawberry.type
class ManagerUserDetail(StaffUserDetail):
subordinates: typing.List["User"]
@strawberry.type
class AdminUserDetail:
system_permissions: typing.List[str]
@strawberry.type
class User:
id: strawberry.ID
username: str
email: str
first_name: str
last_name: str
password: str
last_login: typing.Optional[datetime.datetime]
is_active: bool = strawberry.field(
default=True,
description="Is the user active?",
)
role: UserRole
detail: typing.Annotated[
typing.Union[
NormalUserDetail,
StaffUserDetail,
ManagerUserDetail,
AdminUserDetail,
],
strawberry.union("UserDetail"),
]
@strawberry.field
def full_name(self) -> str:
return f"{self.first_name} {self.last_name}"