之前分析了一波Flask的源码,其实DEBUG模式下,也有自动重启的功能,不过没有深究。最近在研究Django框架,同样也有自动重启的功能,这次我们就来研究一下吧。
Ps:Python看源码有个小技巧,可以随时修改源码文件用print来辅助我们边运行边看代码。
当我们用 python manage.py runserver 启动Django项目之后,每次改动保存都会自动进行服务器的重启。
先看下manage.py的代码
def main():
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
def execute_from_command_line(argv=None):
utility = ManagementUtility(argv)
utility.execute()
class ManagementUtility:
def execute(self):
try:
subcommand = self.argv[1]
except IndexError:
subcommand = "help"
if subcommand == "help":
pass
else:
self.fetch_command(subcommand).run_from_argv(self.argv)
def fetch_command(self, subcommand):
commands = get_commands()
try:
app_name = commands[subcommand]
except KeyError:
pass
if isinstance(app_name, BaseCommand):
klass = app_name
else:
klass = load_command_class(app_name, subcommand)
return klass
下面先研究下 get_commands 方法
@functools.lru_cache(maxsize=None)
def get_commands():
commands = {name: "django.core" for name in find_commands(__path__[0])}
if not settings.configured:
return commands
def find_commands(management_dir):
command_dir = os.path.join(management_dir, "commands")
return [
name
for _, name, is_pkg in pkgutil.iter_modules([command_dir])
if not is_pkg and not name.startswith("_")
]
def load_command_class(app_name, name):
module = import_module("%s.management.commands.%s" % (app_name, name))
return module.Command()
由上可见,python manage.py runserver 根据 runserver命令,去寻找并构造了 特定的实例来执行具体逻辑。
runserver 对应的实例就是 django.core.management.commands.runserver.Command 实例,下面分析
class Command(BaseCommand):
def run(self, **options):
use_reloader = options["use_reloader"]
if use_reloader:
autoreload.run_with_reloader(self.inner_run, **options)
else:
self.inner_run(None, **options)
发现监测文件改动并自动重启的逻辑在 django/utils/autoreload.py 中,下面重点研究
def run_with_reloader(main_func, *args, **kwargs):
signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
try:
if os.environ.get(DJANGO_AUTORELOAD_ENV) == "true":
reloader = get_reloader()
logger.info(
"Watching for file changes with %s", reloader.__class__.__name__
)
start_django(reloader, main_func, *args, **kwargs)
else:
exit_code = restart_with_reloader()
sys.exit(exit_code)
except KeyboardInterrupt:
pass
def restart_with_reloader():
new_environ = {**os.environ, DJANGO_AUTORELOAD_ENV: "true"}
args = get_child_arguments()
while True:
p = subprocess.run(args, env=new_environ, close_fds=False)
if p.returncode != 3:
return p.returncode
def get_reloader():
try:
WatchmanReloader.check_availability()
except WatchmanUnavailable:
return StatReloader()
return WatchmanReloader()
def start_django(reloader, main_func, *args, **kwargs):
ensure_echo_on()
main_func = check_errors(main_func)
django_main_thread = threading.Thread(
target=main_func, args=args, kwargs=kwargs, name="django-main-thread"
)
django_main_thread.daemon = True
django_main_thread.start()
while not reloader.should_stop:
try:
reloader.run(django_main_thread)
except WatchmanUnavailable as ex:
reloader = StatReloader()
logger.error("Error connecting to Watchman: %s", ex)
logger.info(
"Watching for file changes with %s", reloader.__class__.__name__
)
可以看到 子进程检测文件改动以及自动退出的逻辑在 reloader 实例中,下面我们来看 StatReloader类
class StatReloader(BaseReloader):
SLEEP_TIME = 1
def run(self, django_main_thread):
self.run_loop()
def run_loop(self):
ticker = self.tick()
while not self.should_stop
try:
next(ticker)
except StopIteration:
break
self.stop()
def tick(self):
mtimes = {}
while True:
for filepath, mtime in self.snapshot_files():
old_time = mtimes.get(filepath)
mtimes[filepath] = mtime
if old_time is None:
logger.debug("File %s first seen with mtime %s", filepath, mtime)
continue
elif mtime > old_time:
logger.debug(
"File %s previous mtime: %s, current mtime: %s",
filepath,
old_time,
mtime,
)
self.notify_file_changed(filepath)
time.sleep(self.SLEEP_TIME)
yield
def notify_file_changed(self, path):
results = file_changed.send(sender=self, file_path=path)
logger.debug("%s notified as changed. Signal results: %s.", path, results)
if not any(res[1] for res in results):
trigger_reload(path)
def trigger_reload(filename):
logger.info("%s changed, reloading.", filename)
sys.exit(3)
总结一下:
PS: 如果我们也想在其他地方实现检测到代码改动自动重启的功能,可以直接把django.utils.autoreload.py 复制出去,然后用autoreload.run_with_reloader() 执行目标函数即可。
Original: https://blog.csdn.net/weixin_37882382/article/details/125874302
Author: 某工程师$
Title: Django 4.0.6源码分析:自动重启机制
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/736821/
转载文章受原作者版权保护。转载请注明原作者出处!