Tortoise-orm信號(hào)實(shí)現(xiàn)及使用場(chǎng)景源碼詳解
場(chǎng)景
在使用Tortoise操作數(shù)據(jù)庫(kù)的時(shí)候發(fā)現(xiàn),通過(guò)對(duì)操作數(shù)據(jù)庫(kù)模型加以裝飾器,如@pre_save(Model)
,可以實(shí)現(xiàn)對(duì)這個(gè)模型在savue
時(shí),自動(dòng)調(diào)用被裝飾的方法,從而實(shí)現(xiàn)對(duì)模型的一些操作。
在此先從官方文檔入手,看一下官方的對(duì)于模型信號(hào)的Example
# -*- coding: utf-8 -*- """ This example demonstrates model signals usage """ from typing import List, Optional, Type from tortoise import BaseDBAsyncClient, Tortoise, fields, run_async from tortoise.models import Model from tortoise.signals import post_delete, post_save, pre_delete, pre_save class Signal(Model): id = fields.IntField(pk=True) name = fields.TextField() class Meta: table = "signal" def __str__(self): return self.name @pre_save(Signal) async def signal_pre_save( sender: "Type[Signal]", instance: Signal, using_db, update_fields ) -> None: print('signal_pre_save', sender, instance, using_db, update_fields) @post_save(Signal) async def signal_post_save( sender: "Type[Signal]", instance: Signal, created: bool, using_db: "Optional[BaseDBAsyncClient]", update_fields: List[str], ) -> None: print('post_save', sender, instance, using_db, created, update_fields) @pre_delete(Signal) async def signal_pre_delete( sender: "Type[Signal]", instance: Signal, using_db: "Optional[BaseDBAsyncClient]" ) -> None: print('pre_delete', sender, instance, using_db) @post_delete(Signal) async def signal_post_delete( sender: "Type[Signal]", instance: Signal, using_db: "Optional[BaseDBAsyncClient]" ) -> None: print('post_delete', sender, instance, using_db) async def run(): await Tortoise.init(db_url="sqlite://:memory:", modules={"models": ["__main__"]}) await Tortoise.generate_schemas() # pre_save,post_save will be send signal = await Signal.create(name="Signal") signal.name = "Signal_Save" # pre_save,post_save will be send await signal.save(update_fields=["name"]) # pre_delete,post_delete will be send await signal.delete() if __name__ == "__main__": run_async(run())
以上代碼可直接復(fù)制后運(yùn)行,運(yùn)行后的結(jié)果:
signal_pre_save <class '__main__.Signal'> Signal <tortoise.backends.sqlite.client.SqliteClient object at 0x7f8518319400> None
post_save <class '__main__.Signal'> Signal <tortoise.backends.sqlite.client.SqliteClient object at 0x7f8518319400> True None
signal_pre_save <class '__main__.Signal'> Signal_Save <tortoise.backends.sqlite.client.SqliteClient object at 0x7f8518319400> ['name']
post_save <class '__main__.Signal'> Signal_Save <tortoise.backends.sqlite.client.SqliteClient object at 0x7f8518319400> False ['name']
pre_delete <class '__main__.Signal'> Signal_Save <tortoise.backends.sqlite.client.SqliteClient object at 0x7f8518319400>
post_delete <class '__main__.Signal'> Signal_Save <tortoise.backends.sqlite.client.SqliteClient object at 0x7f8518319400>
可以發(fā)現(xiàn),對(duì)模型進(jìn)行保存和刪除時(shí)候,都會(huì)調(diào)用對(duì)應(yīng)的信號(hào)方法。
源碼
從導(dǎo)包可以得知,tortoise的所有信號(hào)方法都在tortoise.signals
中。
from enum import Enum from typing import Callable Signals = Enum("Signals", ["pre_save", "post_save", "pre_delete", "post_delete"]) def post_save(*senders) -> Callable: """ Register given models post_save signal. :param senders: Model class """ def decorator(f): for sender in senders: sender.register_listener(Signals.post_save, f) return f return decorator def pre_save(*senders) -> Callable: ... def pre_delete(*senders) -> Callable: ... def post_delete(*senders) -> Callable: ...
其內(nèi)部實(shí)現(xiàn)的四個(gè)信號(hào)方法分別是模型的保存后,保存前,刪除前,刪除后。
其內(nèi)部裝飾器代碼也十分簡(jiǎn)單,就是對(duì)裝飾器中的參數(shù)(也就是模型),注冊(cè)一個(gè)監(jiān)聽(tīng)者,而這個(gè)監(jiān)聽(tīng)者,其實(shí)就是被裝飾的方法。
如上面的官方示例中:
# 給模型Signal注冊(cè)一個(gè)監(jiān)聽(tīng)者,它是方法signal_pre_save @pre_save(Signal) async def signal_pre_save( sender: "Type[Signal]", instance: Signal, using_db, update_fields ) -> None: print('signal_pre_save', sender, instance, using_db, update_fields)
而到了Model類中,自然就有一個(gè)register_listener方法,定睛一看,上面示例Signal中并沒(méi)有register_listener方法,所以自然就想到了,這個(gè)方法必定在父類Model中。
class Model: ... @classmethod def register_listener(cls, signal: Signals, listener: Callable): ... if not callable(listener): raise ConfigurationError("Signal listener must be callable!") # 檢測(cè)是否已經(jīng)注冊(cè)過(guò) cls_listeners = cls._listeners.get(signal).setdefault(cls, []) # type:ignore if listener not in cls_listeners: # 注冊(cè)監(jiān)聽(tīng)者 cls_listeners.append(listener)
接下來(lái)注冊(cè)后,這個(gè)listeners就會(huì)一直跟著這個(gè)Signal類。只需要在需要操作關(guān)鍵代碼的地方,進(jìn)行調(diào)用即可。
看看在模型save的時(shí)候,都干了什么?
async def save( self, using_db: Optional[BaseDBAsyncClient] = None, update_fields: Optional[Iterable[str]] = None, force_create: bool = False, force_update: bool = False, ) -> None: ... # 執(zhí)行保存前的信號(hào) await self._pre_save(db, update_fields) if force_create: await executor.execute_insert(self) created = True elif force_update: rows = await executor.execute_update(self, update_fields) if rows == 0: raise IntegrityError(f"Can't update object that doesn't exist. PK: {self.pk}") created = False else: if self._saved_in_db or update_fields: if self.pk is None: await executor.execute_insert(self) created = True else: await executor.execute_update(self, update_fields) created = False else: # TODO: Do a merge/upsert operation here instead. Let the executor determine an optimal strategy for each DB engine. await executor.execute_insert(self) created = True self._saved_in_db = True # 執(zhí)行保存后的信號(hào) await self._post_save(db, created, update_fields)
拋開(kāi)其他代碼,可以看到,在模型save的時(shí)候,其實(shí)是先執(zhí)行保存前的信號(hào),然后執(zhí)行保存后的信號(hào)。
自己實(shí)現(xiàn)一個(gè)信號(hào)
有了以上的經(jīng)驗(yàn),可以自己實(shí)現(xiàn)一個(gè)信號(hào),比如我打算做個(gè)數(shù)據(jù)處理器的類,我想在這個(gè)處理器工作中,監(jiān)聽(tīng)處理前/后的信號(hào)。
# -*- coding: utf-8 -*- from enum import Enum from typing import Callable, Dict # 聲明枚舉信號(hào)量 Signals = Enum("Signals", ["before_process", "after_process"]) # 處理前的裝飾器 def before_process(*senders): def decorator(f): for sender in senders: sender.register_listener(Signals.before_process, f) return f return decorator # 處理后的裝飾器 def after_process(*senders): def decorator(f): for sender in senders: sender.register_listener(Signals.after_process, f) return f return decorator class Model(object): _listeners: Dict = { Signals.before_process: {}, Signals.after_process: {} } @classmethod def register_listener(cls, signal: Signals, listener: Callable): """注冊(cè)監(jiān)聽(tīng)者""" # 判斷是否已經(jīng)存在監(jiān)聽(tīng)者 cls_listeners = cls._listeners.get(signal).setdefault(cls, []) if listener not in cls_listeners: # 如果不存在,則添加監(jiān)聽(tīng)者 cls_listeners.append(listener) def _before_process(self): # 取出before_process監(jiān)聽(tīng)者 cls_listeners = self._listeners.get(Signals.before_process, {}).get(self.__class__, []) for listener in cls_listeners: # 調(diào)用監(jiān)聽(tīng)者 listener(self.__class__, self) def _after_process(self): # 取出after_process監(jiān)聽(tīng)者 cls_listeners = self._listeners.get(Signals.after_process, {}).get(self.__class__, []) for listener in cls_listeners: # 調(diào)用監(jiān)聽(tīng)者 listener(self.__class__, self) class SignalModel(Model): def process(self): """真正的調(diào)用端""" self._before_process() print("Processing") self._after_process() # 注冊(cè)before_process信號(hào) @before_process(SignalModel) def before_process_listener(*args, **kwargs): print("before_process_listener1", args, kwargs) # 注冊(cè)before_process信號(hào) @before_process(SignalModel) def before_process_listener(*args, **kwargs): print("before_process_listener2", args, kwargs) # 注冊(cè)after_process信號(hào) @after_process(SignalModel) def before_process_listener(*args, **kwargs): print("after_process_listener", args, kwargs) if __name__ == '__main__': sm = SignalModel() sm.process()
輸出結(jié)果:
before_process_listener1 (<class '__main__.SignalModel'>, <__main__.SignalModel object at 0x7ff700116e50>) {}
before_process_listener2 (<class '__main__.SignalModel'>, <__main__.SignalModel object at 0x7ff700116e50>) {}
Processing
after_process_listener (<class '__main__.SignalModel'>, <__main__.SignalModel object at 0x7ff700116e50>) {}
總結(jié)
筆者通過(guò)對(duì)`tortoise-orm`源碼的學(xué)習(xí),抽絲剝繭,提取了信號(hào)實(shí)現(xiàn)的方式。其核心就是通過(guò)一個(gè)字典存儲(chǔ)調(diào)用方自定義的process方法,然后分別在真正的調(diào)用端的前/后觸發(fā)這些自定義方法即可。
以上就是Tortoise-orm信號(hào)實(shí)現(xiàn)及使用場(chǎng)景源碼詳解的詳細(xì)內(nèi)容,更多關(guān)于Tortoise orm信號(hào)場(chǎng)景的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
PyQt5+Caffe+Opencv搭建人臉識(shí)別登錄界面
這篇文章主要為大家詳細(xì)介紹了PyQt5+Caffe+Opencv搭建人臉識(shí)別登錄界面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08Django 拆分model和view的實(shí)現(xiàn)方法
今天小編就為大家分享一篇Django 拆分model和view的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08利用Python?Matplotlib繪圖并輸出圖像到文件中的方式
這篇文章主要介紹了利用Python?Matplotlib繪圖并輸出圖像到文件中的方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09Python利用watchdog模塊監(jiān)控文件變化
這篇文章主要為大家介紹一個(gè)Python中的模塊:watchdog模塊,它可以實(shí)現(xiàn)監(jiān)控文件的變化。文中通過(guò)示例詳細(xì)介紹了watchdog模塊的使用,需要的可以參考一下2022-06-06解決Pyinstaller打包為可執(zhí)行文件編碼錯(cuò)誤的問(wèn)題
這篇文章主要介紹了解決Pyinstaller打包為可執(zhí)行文件編碼錯(cuò)誤的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03Flask框架運(yùn)用Ajax實(shí)現(xiàn)數(shù)據(jù)交互的示例代碼
使用Ajax技術(shù)網(wǎng)頁(yè)應(yīng)用能夠快速地將增量更新呈現(xiàn)在用戶界面上,而不需要重載刷新整個(gè)頁(yè)面,這使得程序能夠更快地回應(yīng)用戶的操作,本文將簡(jiǎn)單介紹使用AJAX如何實(shí)現(xiàn)前后端數(shù)據(jù)通信2022-11-11