<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
openapi.json
和工具自動生成client讓前後端無縫對接的做法,在前後端一體的架構中維護這些查詢語句,屬於重複勞動。resolve
一下就實現。loader filters
的支援,在一些業務邏輯下可以簡化很多程式碼。如果把Dataloader 的 keys 等價視為 relationship的 join on 條件的話, 那麼 loader_filters
就類似在別處的其他過濾條件。結論:
GraphQL更適合 public API。
對前後端作為一個整體的專案,RESTful + Pydantic-resolve 才是快速靈活提供資料結構的最佳方法。
.option(subquery(Model.field))
之類的程式碼loader_filters
引數,可以提供額外的全域性過濾條件。結論
relationship 方案的靈活度低,不方便修改,預設的用法會產生外來鍵約束。對迭代頻繁的專案不友好。
Pydantic-resolve 和 ORM 層完全解耦,可以通過靈活建立Dataloader 來滿足各種需要。
如果你使用過dataloader, 不論是js還是python的,都會遇到一個問題,如何為單獨的一個請求建立獨立的dataloader?
以 python 的 strawberry
來舉例子:
@strawberry.type class User: id: strawberry.ID async def load_users(keys) -> List[User]: return [User(id=key) for key in keys] loader = DataLoader(load_fn=load_users) @strawberry.type class Query: @strawberry.field async def get_user(self, id: strawberry.ID) -> User: return await loader.load(id) schema = strawberry.Schema(query=Query)
如果單獨範例化的話,會導致所有的請求都使用同一個dataloader, 由於loader本身是有快取優化機制的,所以即使內容更新之後,依然會返回快取的歷史資料。
因此 strawberry
的處理方式是:
@strawberry.type class User: id: strawberry.ID async def load_users(keys) -> List[User]: return [User(id=key) for key in keys] class MyGraphQL(GraphQL): async def get_context( self, request: Union[Request, WebSocket], response: Optional[Response] ) -> Any: return {"user_loader": DataLoader(load_fn=load_users)} @strawberry.type class Query: @strawberry.field async def get_user(self, info: Info, id: strawberry.ID) -> User: return await info.context["user_loader"].load(id) schema = strawberry.Schema(query=Query) app = MyGraphQL(schema)
開發者需要在get_context
中去初始化loader, 然後框架會負責在每次request的時候會執行初始化。 這樣每個請求就會有獨立的loader, 解決了多次請求被快取的問題。
其中的原理是:contextvars 在 await 的時候會做一次淺拷貝,所以外層的context可以被內部讀到,因此手動在最外層(request的時候) 初始化一個參照型別(dict)之後,那麼在 request 內部自然就能獲取到參照型別內的loader。
這個方法雖然好,但存在兩個問題:
get_context
, 每當新增了一個 DataLoader, 就需要去裡面新增, 而且實際執行 .load
的地方也要從context 裡面取loader。而 graphene
就更加任性了,把loader 的活交給了 aiodataloader, 如果翻閱檔案的話,會發現處理的思路也是類似的,只是需要手動去維護建立過程。
我所期望的功能是:
其實這兩件事情說的是同一個問題,就是如何把初始化的事情依賴反轉到 resolve_field 方法中。
具體轉化為程式碼:
class CommentSchema(BaseModel): id: int task_id: int content: str feedbacks: List[FeedbackSchema] = [] def resolve_feedbacks(self, loader=LoaderDepend(FeedbackLoader)): return loader.load(self.id) class TaskSchema(BaseModel): id: int name: str comments: List[CommentSchema] = [] def resolve_comments(self, loader=LoaderDepend(CommentLoader)): return loader.load(self.id)
就是說,我只要這樣申明好loader,其他的事情就一律不用操心。那麼,這做得到麼?
得益於pydantic-resolve
存在一個手動執行resolve
的過程,於是有一個思路:
tasks: list[TaskSchema]
有n個,我希望在第一次遇到的時候把loader 初始化並快取,後續其他都使用快取的loader。總體就是一個lazy的路子,到實際執行的時候去處理初始化流程。
下圖中 1 會執行LoaderA 初始化,2,3則是讀取快取, 1.1 會執行LoaderB初始化,2.1,3.1 讀取快取
程式碼如下:
class Resolver: def __init__(self): self.ctx = contextvars.ContextVar('pydantic_resolve_internal_context', default={}) def exec_method(self, method): signature = inspect.signature(method) params = {} for k, v in signature.parameters.items(): if isinstance(v.default, Depends): cache_key = str(v.default.dependency.__name__) cache = self.ctx.get() hit = cache.get(cache_key, None) if hit: instance = hit else: instance = v.default.dependency() cache[cache_key] = instance self.ctx.set(cache) params[k] = instance return method(**params)
有些DataLoader的實現可能需要一個外部的查詢條件, 比如查詢使用者的absense資訊的時候,除了user_key 之外,還需要額外提供其他全域性filter 比如sprint_id)。 這種全域性變數從load引數走會顯得非常囉嗦。
這種時候就依然需要藉助contextvars 在外部設定變數。 以一段專案程式碼為例:
async def get_team_users_load(team_id: int, sprint_id: Optional[int], session: AsyncSession): ctx.team_id_context.set(team_id) # set global filter ctx.sprint_id_context.set(sprint_id) # set global filter res = await session.execute(select(User) .join(UserTeam, UserTeam.user_id == User.id) .filter(UserTeam.team_id == team_id)) db_users = res.scalars() users = [schema.UserLoadUser(id=u.id, employee_id=u.employee_id, name=u.name) for u in db_users] results = await Resolver().resolve(users) # resolve return results
class AbsenseLoader(DataLoader): async def batch_load_fn(self, user_keys): async with async_session() as session, session.begin(): sprint_id = ctx.sprint_id_context.get() # read global filter sprint_stmt = Sprint.status == SprintStatusEnum.ongoing if not sprint_id else Sprint.id == sprint_id res = await session.execute(select(SprintAbsence) .join(Sprint, Sprint.id == SprintAbsence.sprint_id) .join(User, User.id == SprintAbsence.user_id) .filter(sprint_stmt) .filter(SprintAbsence.user_id.in_(user_keys))) rows = res.scalars().all() dct = {} for row in rows: dct[row.user_id] = row.hours return [dct.get(k, 0) for k in user_keys]
期望的設定方式為:
loader_filters = { AbsenseLoader: {'sprint_id': 10}, OtherLoader: {field: 'value_x'} } results = await Resolver(loader_filters=loader_filters).resolve(users)
如果需要filter但是卻沒有設定, 該情況下要拋異常
以上就是pydantic-resolve巢狀資料結構生成LoaderDepend管理contextvars的詳細內容,更多關於LoaderDepend管理contextvars的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45