Django源码分析-辅助层源码解读(七)

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)
    
    
    
    
  • 49
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值