以下为个人学习笔记整理
# 基于 watchdog 实现自动化更新
# 背景:
每次修改 Python 后为了不重启服务器进行调试,需要手动执行一个指令来指定需要热更的文件,有时候会比较麻烦。为此写了一个自动识别文件修改的工具,可以实时监听文件的修改并自动执行热更操作。
# 原理:
- 基于 watchdog 监听某个文件下的文件
 - 发现文件修改后触发相应的 Event。
 - 根据 Event 内容生成热更指令,写入热更文件。
 - 热更程序定时读取文件,执行热更指令,并清空文件。
 
# 核心代码:
# -*- coding: utf8 -*- | |
# DATE: 2020/10/16 Fri | |
import sys | |
import time | |
import logging | |
from watchdog.observers import Observer  | |
from watchdog.events import LoggingEventHandler  | |
class EventHandler(LoggingEventHandler):  | |
def on_modified(self, event):  | |
print(f"{event.src_path}")  | |
...  | |
if __name__ == "__main__":  | |
event_handler = EventHandler()  | |
observer = Observer()  | |
observer.schedule(event_handler, path="./", recursive=True)  | |
observer.start()  | |
try:  | |
while True:  | |
time.sleep(1)  | |
except KeyboardInterrupt:  | |
observer.stop()  | |
observer.join()  | 
# 代码实现:
# -*- coding: utf8 -*- | |
# DATE: 2020/10/16 Fri | |
import os | |
import time | |
import enum | |
import json | |
from watchdog.observers import Observer  | |
from watchdog.events import FileSystemEventHandler  | |
class EventType(enum.IntEnum):  | |
UPDATE = 1  | |
class FilterPath:  | |
def __init__(self):  | |
self._paths = WatchDog.get_json_info("listen_path")  | |
def __call__(self, path: str):  | |
for _path in self._paths:  | |
if path.startswith(_path):  | |
return True  | |
return False  | |
class FilterFile:  | |
def __init__(self):  | |
self._files = WatchDog.get_json_info("listen_file_ext")  | |
def __call__(self, path: str):  | |
_, ext = os.path.splitext(path)  | |
if ext not in self._files:  | |
return False  | |
return True  | |
class CmdConverter:  | |
def __init__(self):  | |
self.root_path = WatchDog.get_json_info("converter_root")  | |
self._replaces = WatchDog.get_json_info("replace_char")  | |
self._cmd_format = WatchDog.get_json_info("cmd_format")  | |
def __call__(self, event_type: EventType, path: str):  | |
if not path.startswith(self.root_path):  | |
return ""  | |
path = path[len(self.root_path):]  | |
for _replace in self._replaces:  | |
path = path.replace(_replace, ".")  | |
if event_type == EventType.UPDATE:  | |
return self._cmd_format.format("update", path)  | |
return ""  | |
class FileWriter:  | |
def __init__(self):  | |
self.write_path = WatchDog.get_json_info("update_path")  | |
def write_to_file(self, content):  | |
try:  | |
with open(self.write_path, "a") as f:  | |
f.writelines(content)  | |
except Exception as e:  | |
print(e)  | |
class AutoUpdateHandler(FileSystemEventHandler):  | |
def __init__(self):  | |
self._filters = [FilterPath(), FilterFile()]  | |
self._converter = CmdConverter()  | |
self._writer = FileWriter()  | |
def on_modified(self, event):  | |
for _filter in self._filters:  | |
if not _filter(event.src_path):  | |
				return | |
cmd = self._converter(EventType.UPDATE, event.src_path)  | |
self._writer.write_to_file(cmd)  | |
print(f'cmd:{cmd}', end='')  | |
class WatchDog:  | |
json_info = {}  | |
valid_init = False  | |
	@classmethod | |
def init(cls):  | |
if cls.valid_init:  | |
			return | |
try:  | |
with open("./config.json", "r") as f:  | |
cls.json_info = json.load(f)  | |
cls.valid_init = True  | |
except Exception:  | |
			pass | |
	@classmethod | |
def get_json_info(cls, key):  | |
if not cls.valid_init:  | |
cls.init()  | |
return cls.json_info.get(key)  | |
	@classmethod | |
def listen_change(cls):  | |
observer = Observer()  | |
observer.schedule(AutoUpdateHandler(), path=cls.get_json_info("root_path"), recursive=True)  | |
observer.start()  | |
try:  | |
while True:  | |
time.sleep(1)  | |
except KeyboardInterrupt:  | |
observer.stop()  | |
observer.join()  | |
if __name__ == "__main__":  | |
WatchDog.init()  | |
WatchDog.listen_change()  | 
部分路径参数抽离到了 config.json 文件内:
{ | |
"root_path":"/root",  | |
"listen_path":[  | |
        "/root/xxxx" | |
        // 需要监听的文件目录,有些文件可以忽略修改,在这里进行定制 | |
],  | |
"listen_file_ext":[".py"], // 监听文件后缀  | |
"update_path":"需要写入的 update 文件,该文件会被热更程序定时读取",  | |
"converter_root":"用于转换更新指令的路径 例如监听路径是 /root/ 修改文件是 /root/code/Python/test.py 而热更指令可能是 update Python.test.py, 所以这里应该填 /root/code/ 用于进行路径转换",  | |
"cmd_format":"{0} {1}\n", // 热更指令,例如:update Python.test.py  | |
"replace_char":[  | |
"/",  | |
        "\\" | |
    ] | |
} | 
参考内容:
- watchdog github 源码