iT邦幫忙

2023 iThome 鐵人賽

DAY 6
0

在這次內容中將使用一個特別的範例,來示範 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]

上面建立了四個使用者詳細資訊的型態:

  • 第一個是普通使用者的詳細資訊(NormalUserDetail),它僅包含共通欄位。
  • 第二個是員工的詳細資訊(StaffUserDetail),除了共通欄位,還增加部門(department)欄位。
  • 第三個是管理者的詳細資訊(ManagerUserDetail),除了員工有的欄位以外,還有下屬(subordinates)的使用者列表欄位,User Type 使用字串包起來是因為它在程式碼下方才被實作出來。
  • 最後是系統管理員的詳細資訊(AdminUserDetail),只有系統權限(system_permissions)的字串列表欄位。

再來就是使用聯合型別(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型態。

https://ithelp.ithome.com.tw/upload/images/20230921/20161957WCgCUv4hTv.png

可以看到 User Typedetail: 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}"

參考資料


上一篇
Day 5:使用 Strawberry 學習 GraphQL 型別 – 2
下一篇
Day 7:使用 Strawberry 學習 GraphQL 查詢
系列文
Django 與 Strawberry GraphQL:探索現代 API 開發之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言