1.辅助层介绍
辅助层通常是指django.utils
模块,其包含一系列用于处理各种任务的实用工具。在 django/utils
目录下,有多个子模块,每个子模块都提供不同类型的实用函数。
2.archive.py文件源码解读
-
BaseArchive
类:archive.py
文件提供了用于创建和解压档案文件的一些实用函数,BaseArchive
类是一个基类,提供了一些归档操作的通用方法和属性。class BaseArchive: # 用于复制归档文件中文件的权限 @staticmethod def _copy_permissions(mode, filename): if mode & stat.S_IROTH: os.chmod(filename, mode) # 用于分割路径,判断是否有主目录 def split_leading_dir(self, path): path = str(path) path = path.lstrip('/').lstrip('\\') if '/' in path and (('\\' in path and path.find('/') < path.find('\\')) or '\\' not in path): return path.split('/', 1) elif '\\' in path: return path.split('\\', 1) else: return path, '' # 用于判断所有路径是否有相同的主目录 def has_leading_dir(self, paths): """ Return True if all the paths have the same leading path name (i.e., everything is in one subdirectory in an archive). """ common_prefix = None for path in paths: prefix, rest = self.split_leading_dir(path) if not prefix: return False elif common_prefix is None: common_prefix = prefix elif prefix != common_prefix: return False return True # 用于获取目标文件名 def target_filename(self, to_path, name): target_path = os.path.abspath(to_path) filename = os.path.abspath(os.path.join(target_path, name)) if not filename.startswith(target_path): raise SuspiciousOperation("Archive contains invalid path: '%s'" % name) return filename def extract(self): raise NotImplementedError('subclasses of BaseArchive must provide an extract() method') def list(self): raise NotImplementedError('subclasses of BaseArchive must provide a list() method')
-
TarArchive
类:TarArchive
类继承自BaseArchive
类,提供了处理 tar 格式的归档文件的具体实现。class TarArchive(BaseArchive): # 构造函数打开 tar 归档文件 def __init__(self, file): self._archive = tarfile.open(file) # 列出 tar 归档文件中的内容 def list(self, *args, **kwargs): self._archive.list(*args, **kwargs) # 实现解压缩 tar 归档文件的具体逻辑 def extract(self, to_path): members = self._archive.getmembers() leading = self.has_leading_dir(x.name for x in members) for member in members: name = member.name if leading: name = self.split_leading_dir(name)[1] filename = self.target_filename(to_path, name) if member.isdir(): if filename: os.makedirs(filename, exist_ok=True) else: try: extracted = self._archive.extractfile(member) except (KeyError, AttributeError) as exc: # Some corrupt tar files seem to produce this # (specifically bad symlinks) print("In the tar file %s the member %s is invalid: %s" % (name, member.name, exc)) else: dirname = os.path.dirname(filename) if dirname: os.makedirs(dirname, exist_ok=True) with open(filename, 'wb') as outfile: shutil.copyfileobj(extracted, outfile) self._copy_permissions(member.mode, filename) finally: if extracted: extracted.close() # 关闭 tar 归档文件 def close(self): self._archive.close()
-
ZipArchive
类:ZipArchive
类继承自BaseArchive
类,提供了处理 zip 格式的归档文件的具体实现。class ZipArchive(BaseArchive): # 构造函数打开 zip 归档文件 def __init__(self, file): self._archive = zipfile.ZipFile(file) # 列出 zip 归档文件中的内容 def list(self, *args, **kwargs): self._archive.printdir(*args, **kwargs) # 实现解压缩 zip 归档文件的具体逻辑 def extract(self, to_path): namelist = self._archive.namelist() leading = self.has_leading_dir(namelist) for name in namelist: data = self._archive.read(name) info = self._archive.getinfo(name) if leading: name = self.split_leading_dir(name)[1] if not name: continue filename = self.target_filename(to_path, name) if name.endswith(('/', '\\')): # A directory os.makedirs(filename, exist_ok=True) else: dirname = os.path.dirname(filename) if dirname: os.makedirs(dirname, exist_ok=True) with open(filename, 'wb') as outfile: outfile.write(data) # Convert ZipInfo.external_attr to mode mode = info.external_attr >> 16 self._copy_permissions(mode, filename) # 关闭 zip 归档文件 def close(self): self._archive.close()
3.Django项目调试时自动重装源码解读
-
调试时自动重装
当你用runserver命令启动开发服务器,你修改了任一的代码文件并保存后,此时,开发服务器检测到代码变更,并自动重新加载应用。你会看到类似于以下的输出:Watching for file changes with StatReloader Performing system checks... System check identified some issues: WARNINGS: ?: (urls.W001) Your URL pattern ('',) is invalid. Ensure that urlpatterns is a list of path() and/or re_path() instances. System check identified 1 issue (0 silenced). January 31, 2024 - 12:35:01 Django version 3.2, using settings 'your_project.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. Code change detected, restarting server...
-
自动重装源码分析
1.在 Django 中,runserver 是用于运行开发服务器的命令,代码位django/core/management/commands/runserver.py
文件中# django/core/management/commands/runserver.py def inner_run(self, *args, **options): # ... try: handler = self.get_handler(*args, **options) # 通过 run 方法启动开发服务器 run(self.addr, int(self.port), handler, ipv6=self.use_ipv6, threading=threading) except socket.error as e: # ... def run(self, *args, **options): # 这是与自动重装相关的代码 autoreload.raise_last_exception()
2.Django 的 autoreload 模块负责检测代码变更并触发自动重装,代码位于
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 get_reloader(): try: WatchmanReloader.check_availability() except WatchmanUnavailable: return StatReloader() return WatchmanReloader()
启动Django服务并监听文件变更:
def start_django(reloader, main_func, *args, **kwargs): ensure_echo_on() main_func = check_errors(main_func) # 通过threading.Thread创建一个名为django-main-thread的后台线程,该线程运行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() # 主线程在一个循环中,只要reloader的should_stop属性为False,就持续监听文件变更。 while not reloader.should_stop: try: # 通过reloader.run()触发自动重载过程 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__)
其中,loader.run()函数是用于运行自动重载过程的方法。这个方法的实现依赖于具体的重载器实例。具体实现逻辑如下:
class BaseReloader: # run方法是自动重载的入口 def run(self, django_main_thread): logger.debug('Waiting for apps ready_event.') # 等待Django应用准备好 self.wait_for_apps_ready(apps, django_main_thread) from django.urls import get_resolver # 访问Django URL解析器,防止在重载器启动时URL模块未加载的竞争条件。 try: get_resolver().urlconf_module except Exception: # Loading the urlconf can result in errors during development. # If this occurs then swallow the error and continue. pass logger.debug('Apps ready_event triggered. Sending autoreload_started signal.') # 触发autoreload_started信号,表示自动重载过程已启动 autoreload_started.send(sender=self) # 调用run_loop方法 self.run_loop() def run_loop(self): ticker = self.tick() while not self.should_stop: try: # 通过迭代ticker执行必要的代码变更检查 next(ticker) except StopIteration: break # 调用stop方法执行任何清理或最终化任务 self.stop() # tick方法是一个生成器方法,应该由子类实现 def tick(self): """ This generator is called in a loop from run_loop. It's important that the method takes care of pausing or otherwise waiting for a period of time. This split between run_loop() and tick() is to improve the testability of the reloader implementations by decoupling the work they do from the loop. """ raise NotImplementedError('subclasses must implement tick().') class WatchmanReloader(BaseReloader): # tick方法实现了持续监听Watchman变更的逻辑 def tick(self): # 连接到Django的request_finished信号,以处理请求完成时的逻辑 request_finished.connect(self.request_processed) # 更新监视器(watches) self.update_watches() # 进入无限循环 while True: # 如果已处理的请求标志被设置 if self.processed_request.is_set(): # 更新监视器 self.update_watches() # 清除已处理的请求标志 self.processed_request.clear() try: # 接收来自Watchman客户端的信息 self.client.receive() except pywatchman.SocketTimeout: # 处理Watchman的SocketTimeout异常 # 这是一个超时异常,通常在等待Watchman响应时发生 pass except pywatchman.WatchmanError as ex: # 处理Watchman的通用错误 logger.debug('Watchman error: %s, checking server status.', ex) # 检查Watchman服务的状态 self.check_server_status(ex) else: # 处理Watchman客户端订阅的事件 for sub in list(self.client.subs.keys()): self._check_subscription(sub) # 产生一个值,用于在主循环中暂停一段时间 yield # 防止进入繁忙的循环,添加一个短暂的休眠 time.sleep(0.1)