text: #!/usr/bin/env python
  # -*- encoding: utf-8 -*-
  '''
  @Author: 思文伟
  @Date: 2022/03/30 17:50:18
  '''
  import os
  import inspect
  from ..utils.import_helper import ImportHelper
  from ..core.errors import ImproperlyConfigured
  from . import global_settings
  from ..utils.lazy_obj import empty, LazyObject
  
  __all__ = ['settings', 'SettingsFileFinder']
  
  
  class SettingsFileFinder(object):
      def __init__(self, settings_file_name='settings.py'):
  
          self.__settings_file_name = settings_file_name
  
      def is_py_package(self, dirpath):
  
          return os.path.isdir(dirpath) and os.path.isfile(os.path.join(dirpath, '__init__.py'))
  
      @property
      def settings_file_name(self):
          return self.__settings_file_name
  
      def __issuite(self, tc):
          "A crude way to tell apart testcases and suites with duck-typing"
          try:
              iter(tc)
          except TypeError:
              return False
          return True
  
      def __all_in_one(self, tc):
  
          alltests = []
          if self.__issuite(tc):
              for one in tc:
                  if not self.__issuite(one):
                      alltests.append(one)
                  else:
                      alltests.extend(self.__all_in_one(one))
          else:
              alltests.append(tc)
          return alltests
  
      def __is_exists_file(self, path):
  
          return os.path.exists(path) and os.path.isfile(path)
  
      def find_settings_file_from_start_dir(self, dirpath):
          """ 从给定的目录开始查找配置文件，如果在该目录能找到配置文件，则不会遍历其子目录，否则会一直遍历。
          遍历完其下所有子孙目录后，仍找不到则返回None，找到则返回完整配置文件路径
  
          Args:
              dirpath: 开始查找的目录
          """
          if not os.path.isdir(dirpath):
              return None
          filepath = os.path.join(dirpath, self.settings_file_name)
          if not self.__is_exists_file(filepath):
              filepath = self.__find_settings_file_from_subdir(dirpath)
          return filepath
  
      def __find_settings_file_from_subdir(self, dirpath):
  
          names = os.listdir(dirpath)
          settings_file_path = None
  
          for name in names:
              dpath = os.path.join(dirpath, name)
              if os.path.isdir(dpath):
                  filepath = os.path.join(dpath, self.settings_file_name)
                  if self.__is_exists_file(filepath):
                      settings_file_path = filepath
                      break
                  else:
                      result = self.__find_settings_file_from_subdir(dpath)
                      if result:
                          settings_file_path = result
                          break
          return settings_file_path
  
      def set_non_py_package_dir_as_start_dir(self, abspath):
          """ 设置开始查找配置文件的目录为第一个非python包的目录，
          如果给定的路径中的目录没有非python包目录，则设置当前目录为开始查找配置文件的目录
  
          Args:
              abspath: 绝对路径
          """
  
          if not os.path.exists(abspath):
              return (None, None)
  
          if os.path.isfile(abspath):
              dirpath = os.path.dirname(abspath)
          else:
              dirpath = abspath
          current_dirpath = dirpath
          paths = []
          start_dirpath = os.path.dirname(current_dirpath)  # 从哪个路径开始查找配置文件 sconfig.py
          while True:
              if not self.is_py_package(dirpath):
                  start_dirpath = dirpath
                  break
              if os.path.basename(dirpath):
                  paths.append(dirpath)
                  dirpath = os.path.dirname(dirpath)
              else:
                  break
  
          return start_dirpath
  
      def find_settings_file_by_test(self, test):
          used = set()
          start_path = None
          config_path = None
          for t in self.__all_in_one(test):
              mod = inspect.getmodule(t)
              if mod in used:
                  continue
              else:
                  used.add(mod)
                  file_path = os.path.abspath(mod.__file__)
                  start_path = self.set_non_py_package_dir_as_start_dir(file_path)
                  config_path = self.find_settings_file_from_start_dir(start_path)
                  if config_path:
                      break
          return (start_path, config_path)
  
      def find_settings_file_by_testcase_class(self, testclass):
  
          start_path = None
          config_path = None
          mod = inspect.getmodule(testclass)
          file_path = os.path.abspath(mod.__file__)
          start_path = self.set_non_py_package_dir_as_start_dir(file_path)
          config_path = self.find_settings_file_from_start_dir(start_path)
          return (start_path, config_path)
  
  
  class LazySettings(LazyObject):
      """
      框架会在运行测试时去查找项目配置文件，并调用load_configure_from_file()方法载入配置文件中的配置
      """
      def _setup(self):
  
          self._wrapped = Settings()
  
      def __repr__(self):
          # Hardcode the class name as otherwise it yields 'Settings'.
          if self._wrapped is empty:
              return '<LazySettings [Unevaluated]>'
          return '<LazySettings "%(settings_module_file_path)s">' % {
              'settings_module_file_path': self._wrapped.SETTINGS_MODULE_FILE_PATH,
          }
  
      def __getattr__(self, name):
  
          if self._wrapped is empty:
              self._setup()
          val = getattr(self._wrapped, name)
          self.__dict__[name] = val
          return val
  
      def __setattr__(self, name, value):
          if name == '_wrapped':
              self.__dict__.clear()
          else:
              self.__dict__.pop(name, None)
          super().__setattr__(name, value)
  
      def __delattr__(self, name):
  
          super().__delattr__(name)
          self.__dict__.pop(name, None)
  
      def load_configure_from_file(self, settings_module_filepath, force=False):
  
          if force or (not self.is_loaded):
              if self._wrapped is empty:
                  self._wrapped = Settings()
              self._wrapped.load(settings_module_filepath)
  
      @property
      def configured(self):
          return self._wrapped is not empty
  
      @property
      def is_loaded(self):
          return self.configured and self._wrapped.is_loaded
  
  
  class Settings(object):
      def __init__(self):
  
          for setting in dir(global_settings):
              if setting.isupper():
                  setattr(self, setting, getattr(global_settings, setting))
  
          self._explicit_settings = set()
          self._is_loaded = False
          self.SETTINGS_MODULE_FILE_PATH = None
  
      def load(self, settings_module_filepath):
  
          self.SETTINGS_MODULE_FILE_PATH = settings_module_filepath
          mod = ImportHelper().load_module(self.SETTINGS_MODULE_FILE_PATH)
          tuple_settings = ()
          for setting in dir(mod):
              if setting.isupper():
                  setting_value = getattr(mod, setting)
  
                  if (setting in tuple_settings and not isinstance(setting_value, (list, tuple))):
                      raise ImproperlyConfigured("The %s setting must be a list or a tuple. " % setting)
                  setattr(self, setting, setting_value)
                  self._explicit_settings.add(setting)
          self._is_loaded = True
  
      def is_overridden(self, setting):
          return setting in self._explicit_settings
  
      @property
      def is_loaded(self):
          return self._is_loaded
  
      def __repr__(self):
          return '<%(cls)s "%(settings_module_file_path)s">' % {
              'cls': self.__class__.__name__,
              'settings_module_file_path': self.SETTINGS_MODULE_FILE_PATH,
          }
  
  
  settings = LazySettings()
  