Django源码分析 runserver(一)
学习django有段时间了,为了对django有更深入的了解,决定对django源码学习一番
django版本为2.2
django项目再我们开发中通常使用如下命令来启动:python manage.py runserver 8000
下面我们来分析下整个启动过程
manage.py是入口,先看下这个文件的代码
def main():
# 设置环境变量DJANGO_SETTINGS_MODULE,指定项目配置文件 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'toystore.settings')
# 导入相关模块,如果没安装django会有一些友好的提示 try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc
# 最重要的函数,下面分析下这个函数 execute_from_command_line(sys.argv) if __name__ == '__main__': main()
execute_from_command_line,实例化ManagementUtility,并调用execute方法,类ManagementUtility其他方法已省略,待分析到相关代码再贴出
def execute_from_command_line(argv=None): """Run a ManagementUtility.""" utility = ManagementUtility(argv) utility.execute() class ManagementUtility: """ Encapsulate the logic of the django-admin and manage.py utilities. """ def __init__(self, argv=None): self.argv = argv or sys.argv[:] self.prog_name = os.path.basename(self.argv[0]) if self.prog_name == '__main__.py': self.prog_name = 'python -m django' self.settings_exception = None def execute(self): """ Given the command-line arguments, figure out which subcommand is being run, create a parser appropriate to that command, and run it. """ try:
# 获取子命令,我们这里是runserver subcommand = self.argv[1] except IndexError: subcommand = 'help' # Display help if no arguments were given. # Preprocess options to extract --settings and --pythonpath. # These options could affect the commands that are available, so they # must be processed early. # 获取一些命令行参数,我们这里options结果如下: Namespace(args=['8000'], pythonpath=None, settings=None)
parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False) parser.add_argument('--settings') parser.add_argument('--pythonpath') parser.add_argument('args', nargs='*') # catch-all try: options, args = parser.parse_known_args(self.argv[2:]) handle_default_options(options) except CommandError: pass # Ignore any option errors at this point. try:
# 后续对settings详细分析,此处先略过,这里可以理解为检查settings中是否配置了INSTALLED_APPS settings.INSTALLED_APPS except ImproperlyConfigured as exc: self.settings_exception = exc except ImportError as exc: self.settings_exception = exc
# 如果settings已配置,会进入下面的语句,对命令做处理,最重要的是django.setup,下面会进行详细分析
# autoreload.check_errors是个闭包,检查一些错误,最终还是返回django.setup后执行 if settings.configured: # Start the auto-reloading dev server even if the code is broken. # The hardcoded condition is a code smell but we can't rely on a # flag on the command class because we haven't located it yet. if subcommand == 'runserver' and '--noreload' not in self.argv: try: autoreload.check_errors(django.setup)() except Exception: # The exception will be raised later in the child process # started by the autoreloader. Pretend it didn't happen by # loading an empty list of applications. apps.all_models = defaultdict(OrderedDict) apps.app_configs = OrderedDict() apps.apps_ready = apps.models_ready = apps.ready = True # Remove options not compatible with the built-in runserver # (e.g. options for the contrib.staticfiles' runserver). # Changes here require manually testing as described in # #27522. _parser = self.fetch_command('runserver').create_parser('django', 'runserver') _options, _args = _parser.parse_known_args(self.argv[2:]) for _arg in _args: self.argv.remove(_arg) # In all other cases, django.setup() is required to succeed. else: django.setup()
# 这里如果环境变量设置了DJANGO_AUTO_COMPLETE,会做响应的处理,我们这里没有设置这个环境变量,这里不会做任何事情 self.autocomplete() if subcommand == 'help': if '--commands' in args: sys.stdout.write(self.main_help_text(commands_only=True) + '\n') elif not options.args: sys.stdout.write(self.main_help_text() + '\n') else: self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0]) # Special-cases: We want 'django-admin --version' and # 'django-admin --help' to work, for backwards compatibility. elif subcommand == 'version' or self.argv[1:] == ['--version']: sys.stdout.write(django.get_version() + '\n') elif self.argv[1:] in (['--help'], ['-h']): sys.stdout.write(self.main_help_text() + '\n') else: self.fetch_command(subcommand).run_from_argv(self.argv)
django.setup源码分析
def setup(set_prefix=True): """ Configure the settings (this happens as a side effect of accessing the first setting), configure logging and populate the app registry. Set the thread-local urlresolvers script prefix if `set_prefix` is True. """ from django.apps import apps from django.conf import settings from django.urls import set_script_prefix from django.utils.log import configure_logging
# 配置日志 configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
# 设置路由前缀,这里是/ if set_prefix: set_script_prefix( '/' if settings.FORCE_SCRIPT_NAME is None else settings.FORCE_SCRIPT_NAME )
# 最重要的代码,对项目中配置的所有APP加载配置和模块 apps.populate(settings.INSTALLED_APPS) class Apps: """ A registry that stores the configuration of installed applications. It also keeps track of models, e.g. to provide reverse relations. """ def __init__(self, installed_apps=()): # installed_apps is set to None when creating the master registry # because it cannot be populated at that point. Other registries must # provide a list of installed apps and are populated immediately. if installed_apps is None and hasattr(sys.modules[__name__], 'apps'): raise RuntimeError("You must supply an installed_apps argument.") # Mapping of app labels => model names => model classes. Every time a # model is imported, ModelBase.__new__ calls apps.register_model which # creates an entry in all_models. All imported models are registered, # regardless of whether they're defined in an installed application # and whether the registry has been populated. Since it isn't possible # to reimport a module safely (it could reexecute initialization code) # all_models is never overridden or reset. self.all_models = defaultdict(OrderedDict) # Mapping of labels to AppConfig instances for installed apps. self.app_configs = OrderedDict() # Stack of app_configs. Used to store the current state in # set_available_apps and set_installed_apps. self.stored_app_configs = [] # Whether the registry is populated. self.apps_ready = self.models_ready = self.ready = False # For the autoreloader. self.ready_event = threading.Event() # Lock for thread-safe population. self._lock = threading.RLock() self.loading = False # Maps ("app_label", "modelname") tuples to lists of functions to be # called when the corresponding model is ready. Used by this class's # `lazy_model_operation()` and `do_pending_operations()` methods. self._pending_operations = defaultdict(list) # Populate apps and models, unless it's the master registry. if installed_apps is not None: self.populate(installed_apps) def populate(self, installed_apps=None): """ Load application configurations and models. Import each application module and then each model module. It is thread-safe and idempotent, but not reentrant. """
# 如果已经启动不直接返回
if self.ready: return # populate() might be called by two threads in parallel on servers # that create threads before initializing the WSGI callable. with self._lock: if self.ready: return # An RLock prevents other threads from entering this section. The # compare and set operation below is atomic.
# 防止多个线程进入到这里,执行多次
if self.loading: # Prevent reentrant calls to avoid running AppConfig.ready() # methods twice. raise RuntimeError("populate() isn't reentrant") self.loading = True # Phase 1: initialize app configs and import app modules.
# 遍历所有installed_apps,使用AppConfig.create来初始化app配置(name,module,label,verbose_name,path属性),导入app模块
for entry in installed_apps: if isinstance(entry, AppConfig): app_config = entry else: app_config = AppConfig.create(entry) if app_config.label in self.app_configs: raise ImproperlyConfigured( "Application labels aren't unique, " "duplicates: %s" % app_config.label) # 将app的label作为key,app_config作为值存入orderdict self.app_configs[app_config.label] = app_config app_config.apps = self # Check for duplicate app names. counts = Counter( app_config.name for app_config in self.app_configs.values()) duplicates = [ name for name, count in counts.most_common() if count > 1] if duplicates: raise ImproperlyConfigured( "Application names aren't unique, " "duplicates: %s" % ", ".join(duplicates)) self.apps_ready = True # Phase 2: import models modules.
# 导入app下的模型
for app_config in self.app_configs.values(): app_config.import_models() self.clear_cache() self.models_ready = True # Phase 3: run ready() methods of app configs.
# 执行AppConfig实例的ready方法
for app_config in self.get_app_configs(): app_config.ready() self.ready = True self.ready_event.set()
AppConfig,这里假设我们的配置文件中INSTALLED_APPS如下
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01',
'app02.apps.App02Config',
]
class AppConfig: """Class representing a Django application and its configuration.""" def __init__(self, app_name, app_module): # Full Python path to the application e.g. 'django.contrib.admin'. self.name = app_name # Root module for the application e.g. <module 'django.contrib.admin' # from 'django/contrib/admin/__init__.py'>. self.module = app_module # Reference to the Apps registry that holds this AppConfig. Set by the # registry when it registers the AppConfig instance. self.apps = None # The following attributes could be defined at the class level in a # subclass, hence the test-and-set pattern. # Last component of the Python path to the application e.g. 'admin'. # This value must be unique across a Django project. if not hasattr(self, 'label'): self.label = app_name.rpartition(".")[2] # Human-readable name for the application e.g. "Admin". if not hasattr(self, 'verbose_name'): self.verbose_name = self.label.title() # Filesystem path to the application directory e.g. # '/path/to/django/contrib/admin'. if not hasattr(self, 'path'): self.path = self._path_from_module(app_module) # Module containing models e.g. <module 'django.contrib.admin.models' # from 'django/contrib/admin/models.py'>. Set by import_models(). # None if the application doesn't have a models module. self.models_module = None # Mapping of lowercase model names to model classes. Initially set to # None to prevent accidental access before import_models() runs. self.models = None @classmethod def create(cls, entry): """ Factory that creates an app config from an entry in INSTALLED_APPS. """ try: # If import_module succeeds, entry is a path to an app module, # which may specify an app config class with default_app_config. # Otherwise, entry is a path to an app config class or an error. # 尝试导入entry字符串对应的模块。如果我们自定义的app(如app01)在配置中直接写的是app01,那么这边可以正常导入,接着往else走;
# 如果写的是app02.apps.App01Config,那么这边导入会抛异常,接着往except走
module = import_module(entry) except ImportError: # Track that importing as an app module failed. If importing as an # app config class fails too, we'll trigger the ImportError again. module = None # mod_path:app02.apps cls_name:App02Config
# rpartition从右边开始搜索分隔符,如果字符串包含分隔符,返回3元的元组,分别是分隔符左边,分隔符,分隔符右边;否则返回('','',str) mod_path, _, cls_name = entry.rpartition('.') # Raise the original exception when entry cannot be a path to an # app config class. if not mod_path: raise else: try: # If this works, the app module specifies an app config class. # 尝试获取app模块的default_app_config值,如果有再做进一步处理,如果没有则返回AppConfig实例;
# 以django.contrib.admin和app01为例,前者模块中定义了default_app_config='django.contrib.admin.apps.AdminConfig',所有会接着走else
# app01我们没有定义default_app_config,这里会走except,直接返回AppConfig实例
entry = module.default_app_config except AttributeError: # Otherwise, it simply uses the default app config class.
# app01的AppConfig实例self.name='app01' self.module=app01 self.label='app01'
return cls(entry, module) else:
# mod_path:django.contrib.admin.apps cls_name:AdminConfig mod_path, _, cls_name = entry.rpartition('.') # If we're reaching this point, we must attempt to load the app config # class located at <mod_path>.<cls_name> # 导入app下面的apps模块
mod = import_module(mod_path) try:
# 获取到cls_name对应的类这里是,App02Config,AdminConfig cls = getattr(mod, cls_name) except AttributeError: if module is None: # If importing as an app module failed, check if the module # contains any valid AppConfigs and show them as choices. # Otherwise, that error probably contains the most informative # traceback, so trigger it again. candidates = sorted( repr(name) for name, candidate in mod.__dict__.items() if isinstance(candidate, type) and issubclass(candidate, AppConfig) and candidate is not AppConfig ) if candidates: raise ImproperlyConfigured( "'%s' does not contain a class '%s'. Choices are: %s." % (mod_path, cls_name, ', '.join(candidates)) ) import_module(entry) else: raise # Check for obvious errors. (This check prevents duck typing, but # it could be removed if it became a problem in practice.)
# 检查刚刚从apps获取到的类是否是AppConfig的子类
if not issubclass(cls, AppConfig): raise ImproperlyConfigured( "'%s' isn't a subclass of AppConfig." % entry) # Obtain app name here rather than in AppClass.__init__ to keep # all error checking for entries in INSTALLED_APPS in one place. try: app_name = cls.name except AttributeError: raise ImproperlyConfigured( "'%s' must supply a name attribute." % entry) # Ensure app_name points to a valid module. try: app_module = import_module(app_name) except ImportError: raise ImproperlyConfigured( "Cannot import '%s'. Check that '%s.%s.name' is correct." % ( app_name, mod_path, cls_name, ) ) # Entry is a path to an app config class.
# app02: app_name字符串app02,app_module为模块app02,label为app02
# django.contrib.admin: app_name为字符串django.contrib.admin,app_module为模块django.contrib.admin,label为admin
return cls(app_name, app_module)
def import_models(self): # Dictionary of models for this app, primarily maintained in the # 'all_models' attribute of the Apps this AppConfig is attached to. self.models = self.apps.all_models[self.label]
# 导入app下的models,在导入的时候会记录到self.models中,其中原理以后再做分析 if module_has_submodule(self.module, MODELS_MODULE_NAME): models_module_name = '%s.%s' % (self.name, MODELS_MODULE_NAME) self.models_module = import_module(models_module_name)
暂时先分析到这里,做一个简单的总结,在我们执行python manage.py runserver 8000的时候
1.manage.py调用execute_from_command_line,
2.execute_from_command_line中实例化ManagementUtility,调用execute方法
3.execute方法对命令行进行解析,发现是runserver子命令,会调用django.setup,django.setup先设置日志参数,然后调用Apps实例的populate方法,主要操作如下:1.初始化INSTALLED_APPS中所有的app(app_config)并导入app模块;2.导入app下的模型即models;3.执行app_config的ready方法,至此django.setup方法结束.回到ManagementUtility的execute,下一篇继续分析。
第一次分析源码,还有许多不足之处,欢迎指正

浙公网安备 33010602011771号