django启动过程剖析
在manage.py文件中
1 if __name__ == '__main__': 2 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test11.settings') 3 try: 4 from django.core.management import execute_from_command_line 5 except ImportError as exc: 6 raise ImportError( 7 "Couldn't import Django. Are you sure it's installed and " 8 "available on your PYTHONPATH environment variable? Did you " 9 "forget to activate a virtual environment?" 10 ) from exc 11 execute_from_command_line(sys.argv) #在这里获取命令行参数
在execute_from_command_line函数中实现一系列操作:
def execute_from_command_line(argv=None): """Run a ManagementUtility.""" utility = ManagementUtility(argv) #实例化ManagementUtility对象 utility.execute()
在ManagementUtility对象的execute方法中:
1 def execute(self): 2 """ 3 Given the command-line arguments, figure out which subcommand is being 4 run, create a parser appropriate to that command, and run it. 5 """ 6 try: 7 subcommand = self.argv[1] #这里获取第一个参数(命令)如runserver 8 except IndexError: 9 subcommand = 'help' # Display help if no arguments were given. 10 11 # Preprocess options to extract --settings and --pythonpath. 12 # These options could affect the commands that are available, so they 13 # must be processed early. 14 parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False) 15 parser.add_argument('--settings') 16 parser.add_argument('--pythonpath') 17 parser.add_argument('args', nargs='*') # catch-all 18 try: 19 options, args = parser.parse_known_args(self.argv[2:]) #这里把后面的参数装入options中如['127.0.0.1:8000'] 20 handle_default_options(options) 21 except CommandError: 22 pass # Ignore any option errors at this point. 23 24 try: 25 settings.INSTALLED_APPS 26 except ImproperlyConfigured as exc: 27 self.settings_exception = exc 28 except ImportError as exc: 29 self.settings_exception = exc 30 31 if settings.configured: 32 # Start the auto-reloading dev server even if the code is broken. 33 # The hardcoded condition is a code smell but we can't rely on a 34 # flag on the command class because we haven't located it yet. 35 if subcommand == 'runserver' and '--noreload' not in self.argv: 36 try: 37 autoreload.check_errors(django.setup)() 38 except Exception: 39 # The exception will be raised later in the child process 40 # started by the autoreloader. Pretend it didn't happen by 41 # loading an empty list of applications. 42 apps.all_models = defaultdict(OrderedDict) 43 apps.app_configs = OrderedDict() 44 apps.apps_ready = apps.models_ready = apps.ready = True 45 46 # Remove options not compatible with the built-in runserver 47 # (e.g. options for the contrib.staticfiles' runserver). 48 # Changes here require manually testing as described in 49 # #27522. 50 _parser = self.fetch_command('runserver').create_parser('django', 'runserver') 51 _options, _args = _parser.parse_known_args(self.argv[2:]) 52 for _arg in _args: 53 self.argv.remove(_arg) 54 55 # In all other cases, django.setup() is required to succeed. 56 else: 57 django.setup() #加载APP 58 59 self.autocomplete() 60 61 if subcommand == 'help': 62 if '--commands' in args: 63 sys.stdout.write(self.main_help_text(commands_only=True) + '\n') 64 elif not options.args: 65 sys.stdout.write(self.main_help_text() + '\n') 66 else: 67 self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0]) 68 # Special-cases: We want 'django-admin --version' and 69 # 'django-admin --help' to work, for backwards compatibility. 70 elif subcommand == 'version' or self.argv[1:] == ['--version']: 71 sys.stdout.write(django.get_version() + '\n') 72 elif self.argv[1:] in (['--help'], ['-h']): 73 sys.stdout.write(self.main_help_text() + '\n') 74 else: 75 self.fetch_command(subcommand).run_from_argv(self.argv)
在django.setup中调用apps.populate方法:
1 def populate(self, installed_apps=None): 2 """ 3 Load application configurations and models. 4 5 Import each application module and then each model module. 6 7 It is thread-safe and idempotent, but not reentrant. 8 """ 9 if self.ready: 10 return 11 12 # populate() might be called by two threads in parallel on servers 13 # that create threads before initializing the WSGI callable. 14 with self._lock: 15 if self.ready: 16 return 17 18 # An RLock prevents other threads from entering this section. The 19 # compare and set operation below is atomic. 20 if self.loading: 21 # Prevent reentrant calls to avoid running AppConfig.ready() 22 # methods twice. 23 raise RuntimeError("populate() isn't reentrant") 24 self.loading = True 25 26 # Phase 1: initialize app configs and import app modules. 27 for entry in installed_apps: 28 if isinstance(entry, AppConfig): 29 app_config = entry 30 else: 31 app_config = AppConfig.create(entry) #创建AppConfig对象 32 if app_config.label in self.app_configs: 33 raise ImproperlyConfigured( 34 "Application labels aren't unique, " 35 "duplicates: %s" % app_config.label) 36 37 self.app_configs[app_config.label] = app_config 38 app_config.apps = self 39 40 # Check for duplicate app names. 41 counts = Counter( 42 app_config.name for app_config in self.app_configs.values()) #判断是否有重复的AppConfig对象 43 duplicates = [ 44 name for name, count in counts.most_common() if count > 1] 45 if duplicates: 46 raise ImproperlyConfigured( 47 "Application names aren't unique, " 48 "duplicates: %s" % ", ".join(duplicates)) 49 50 self.apps_ready = True 51 52 # Phase 2: import models modules. 53 for app_config in self.app_configs.values(): #导入models 54 app_config.import_models() 55 56 self.clear_cache() 57 58 self.models_ready = True 59 60 # Phase 3: run ready() methods of app configs. 61 for app_config in self.get_app_configs(): #注册AppConfig对象 62 app_config.ready() 63 64 self.ready = True
在autocomplete中:
1 def autocomplete(self): 2 """ 3 Output completion suggestions for BASH. 4 5 The output of this function is passed to BASH's `COMREPLY` variable and 6 treated as completion suggestions. `COMREPLY` expects a space 7 separated string as the result. 8 9 The `COMP_WORDS` and `COMP_CWORD` BASH environment variables are used 10 to get information about the cli input. Please refer to the BASH 11 man-page for more information about this variables. 12 13 Subcommand options are saved as pairs. A pair consists of 14 the long option string (e.g. '--exclude') and a boolean 15 value indicating if the option requires arguments. When printing to 16 stdout, an equal sign is appended to options which require arguments. 17 18 Note: If debugging this function, it is recommended to write the debug 19 output in a separate file. Otherwise the debug output will be treated 20 and formatted as potential completion suggestions. 21 """ 22 # Don't complete if user hasn't sourced bash_completion file. 23 if 'DJANGO_AUTO_COMPLETE' not in os.environ: 24 return 25 26 cwords = os.environ['COMP_WORDS'].split()[1:] 27 cword = int(os.environ['COMP_CWORD']) 28 29 try: 30 curr = cwords[cword - 1] 31 except IndexError: 32 curr = '' 33 34 subcommands = list(get_commands()) + ['help'] 35 options = [('--help', False)] 36 37 # subcommand 38 if cword == 1: 39 print(' '.join(sorted(filter(lambda x: x.startswith(curr), subcommands)))) 40 # subcommand options 41 # special case: the 'help' subcommand has no options 42 elif cwords[0] in subcommands and cwords[0] != 'help': 43 subcommand_cls = self.fetch_command(cwords[0]) #在这里获取命令模块 44 # special case: add the names of installed apps to options 45 if cwords[0] in ('dumpdata', 'sqlmigrate', 'sqlsequencereset', 'test'): 46 try: 47 app_configs = apps.get_app_configs()#获取所有AppConfig对象 48 # Get the last part of the dotted path as the app name. 49 options.extend((app_config.label, 0) for app_config in app_configs)#把所有AppConfig对象加载到options中 50 except ImportError: 51 # Fail silently if DJANGO_SETTINGS_MODULE isn't set. The 52 # user will find out once they execute the command. 53 pass 54 parser = subcommand_cls.create_parser('', cwords[0]) #创建命令行解析器 55 options.extend( 56 (min(s_opt.option_strings), s_opt.nargs != 0) 57 for s_opt in parser._actions if s_opt.option_strings 58 ) 59 # filter out previously specified options from available options 60 prev_opts = {x.split('=')[0] for x in cwords[1:cword - 1]} 61 options = (opt for opt in options if opt[0] not in prev_opts) 62 63 # filter options by current input 64 options = sorted((k, v) for k, v in options if k.startswith(curr)) 65 for opt_label, require_arg in options: 66 # append '=' to options which require args 67 if require_arg: 68 opt_label += '=' 69 print(opt_label) 70 # Exit code of the bash completion function is never passed back to 71 # the user, so it's safe to always exit with 0. 72 # For more details see #25420. 73 sys.exit(0)
在run_from_argv中
1 def run_from_argv(self, argv): 2 """ 3 Set up any environment changes requested (e.g., Python path 4 and Django settings), then run this command. If the 5 command raises a ``CommandError``, intercept it and print it sensibly 6 to stderr. If the ``--traceback`` option is present or the raised 7 ``Exception`` is not ``CommandError``, raise it. 8 """ 9 self._called_from_command_line = True 10 parser = self.create_parser(argv[0], argv[1]) #创建命令行解析器 11 12 options = parser.parse_args(argv[2:]) #获取命令行后续参数[2:] 13 cmd_options = vars(options) 14 # Move positional args out of options to mimic legacy optparse 15 args = cmd_options.pop('args', ()) 16 handle_default_options(options) 17 try: 18 self.execute(*args, **cmd_options) #调用BaseCommand的execute方法 19 except Exception as e: 20 if options.traceback or not isinstance(e, CommandError): 21 raise 22 23 # SystemCheckError takes care of its own formatting. 24 if isinstance(e, SystemCheckError): 25 self.stderr.write(str(e), lambda x: x) 26 else: 27 self.stderr.write('%s: %s' % (e.__class__.__name__, e)) 28 sys.exit(1) 29 finally: 30 try: 31 connections.close_all() 32 except ImproperlyConfigured: 33 # Ignore if connections aren't setup at this point (e.g. no 34 # configured settings). 35 pass
在BaseCommand的execute方法
1 def execute(self, *args, **options): 2 """ 3 Try to execute this command, performing system checks if needed (as 4 controlled by the ``requires_system_checks`` attribute, except if 5 force-skipped). 6 """ 7 if options['no_color']: #做一些系统检查 8 self.style = no_style() 9 self.stderr.style_func = None 10 if options.get('stdout'): 11 self.stdout = OutputWrapper(options['stdout']) 12 if options.get('stderr'): 13 self.stderr = OutputWrapper(options['stderr'], self.stderr.style_func) 14 15 if self.requires_system_checks and not options.get('skip_checks'): 16 self.check() 17 if self.requires_migrations_checks: 18 self.check_migrations() 19 output = self.handle(*args, **options) #在这里需要调用django.core.management.commands.runserver.Command.handle方法 20 if output: 21 if self.output_transaction: 22 connection = connections[options.get('database', DEFAULT_DB_ALIAS)] 23 output = '%s\n%s\n%s' % ( 24 self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()), 25 output, 26 self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()), 27 ) 28 self.stdout.write(output) 29 return output
在django.core.management.commands.runserver.Command.handle方法中
1 def handle(self, *args, **options): 2 if not settings.DEBUG and not settings.ALLOWED_HOSTS: 3 raise CommandError('You must set settings.ALLOWED_HOSTS if DEBUG is False.') 4 5 self.use_ipv6 = options['use_ipv6'] 6 if self.use_ipv6 and not socket.has_ipv6: 7 raise CommandError('Your Python does not support IPv6.') 8 self._raw_ipv6 = False 9 if not options['addrport']: 10 self.addr = '' 11 self.port = self.default_port 12 else: 13 m = re.match(naiveip_re, options['addrport']) 14 if m is None: 15 raise CommandError('"%s" is not a valid port number ' 16 'or address:port pair.' % options['addrport']) 17 self.addr, _ipv4, _ipv6, _fqdn, self.port = m.groups() 18 if not self.port.isdigit(): 19 raise CommandError("%r is not a valid port number." % self.port) 20 if self.addr: 21 if _ipv6: 22 self.addr = self.addr[1:-1] 23 self.use_ipv6 = True 24 self._raw_ipv6 = True 25 elif self.use_ipv6 and not _fqdn: 26 raise CommandError('"%s" is not a valid IPv6 address.' % self.addr) 27 if not self.addr: 28 self.addr = self.default_addr_ipv6 if self.use_ipv6 else self.default_addr 29 self._raw_ipv6 = self.use_ipv6 30 self.run(**options)
在django.core.management.commands.runserver.Command.run方法中
1 def run(self, **options): 2 """Run the server, using the autoreloader if needed.""" 3 use_reloader = options['use_reloader'] 4 5 if use_reloader: 6 autoreload.main(self.inner_run, None, options) #这里会进入main函数 7 else: 8 self.inner_run(None, **options)
这里会进入autoreload.main方法中调用python_reloader函数
1 def python_reloader(main_func, args, kwargs): #在这里环境里面的RUN_MAIN默认是false的所以会先进入restart_with_reloader,在里面把环境设置为true,再执行subprocess.call函数重新运行命令行参数 2 if os.environ.get("RUN_MAIN") == "true": 3 _thread.start_new_thread(main_func, args, kwargs) 4 try: 5 reloader_thread() 6 except KeyboardInterrupt: 7 pass 8 else: 9 try: 10 exit_code = restart_with_reloader() 11 if exit_code < 0: 12 os.kill(os.getpid(), -exit_code) 13 else: 14 sys.exit(exit_code) 15 except KeyboardInterrupt: 16 pass
在django.core.management.commands.runserver.Command.inner_run方法中
1 def inner_run(self, *args, **options): 2 # If an exception was silenced in ManagementUtility.execute in order 3 # to be raised in the child process, raise it now. 4 autoreload.raise_last_exception() 5 6 threading = options['use_threading'] 7 # 'shutdown_message' is a stealth option. 8 shutdown_message = options.get('shutdown_message', '') 9 quit_command = 'CTRL-BREAK' if sys.platform == 'win32' else 'CONTROL-C' 10 11 self.stdout.write("Performing system checks...\n\n") 12 self.check(display_num_errors=True) 13 # Need to check migrations here, so can't use the 14 # requires_migrations_check attribute. 15 self.check_migrations() #检查数据迁移 16 now = datetime.now().strftime('%B %d, %Y - %X') 17 self.stdout.write(now) 18 self.stdout.write(( 19 "Django version %(version)s, using settings %(settings)r\n" 20 "Starting development server at %(protocol)s://%(addr)s:%(port)s/\n" 21 "Quit the server with %(quit_command)s.\n" 22 ) % { 23 "version": self.get_version(), 24 "settings": settings.SETTINGS_MODULE, 25 "protocol": self.protocol, 26 "addr": '[%s]' % self.addr if self._raw_ipv6 else self.addr, 27 "port": self.port, 28 "quit_command": quit_command, 29 }) 30 31 try: 32 handler = self.get_handler(*args, **options) #在这个方法里调用get_internal_wsgi_application方法 33 run(self.addr, int(self.port), handler, 34 ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls) 35 except socket.error as e: 36 # Use helpful error messages instead of ugly tracebacks. 37 ERRORS = { 38 errno.EACCES: "You don't have permission to access that port.", 39 errno.EADDRINUSE: "That port is already in use.", 40 errno.EADDRNOTAVAIL: "That IP address can't be assigned to.", 41 } 42 try: 43 error_text = ERRORS[e.errno] 44 except KeyError: 45 error_text = e 46 self.stderr.write("Error: %s" % error_text) 47 # Need to use an OS exit because sys.exit doesn't work in a thread 48 os._exit(1) 49 except KeyboardInterrupt: 50 if shutdown_message: 51 self.stdout.write(shutdown_message) 52 sys.exit(0)
在get_internal_wsgi_application中
1 def get_internal_wsgi_application(): 2 """ 3 Load and return the WSGI application as configured by the user in 4 ``settings.WSGI_APPLICATION``. With the default ``startproject`` layout, 5 this will be the ``application`` object in ``projectname/wsgi.py``. 6 7 This function, and the ``WSGI_APPLICATION`` setting itself, are only useful 8 for Django's internal server (runserver); external WSGI servers should just 9 be configured to point to the correct application object directly. 10 11 If settings.WSGI_APPLICATION is not set (is ``None``), return 12 whatever ``django.core.wsgi.get_wsgi_application`` returns. 13 """ 14 from django.conf import settings 15 app_path = getattr(settings, 'WSGI_APPLICATION') #返回settings里面设置的路径 16 if app_path is None: 17 return get_wsgi_application() 18 19 try: 20 return import_string(app_path) #获取WSGI对象 21 except ImportError as err: 22 raise ImproperlyConfigured( 23 "WSGI application '%s' could not be loaded; " 24 "Error importing module." % app_path 25 ) from err
在run函数中启动django web
1 def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer): 2 server_address = (addr, port) 3 if threading: 4 httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {}) 5 else: 6 httpd_cls = server_cls 7 httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6) 8 if threading: 9 # ThreadingMixIn.daemon_threads indicates how threads will behave on an 10 # abrupt shutdown; like quitting the server by the user or restarting 11 # by the auto-reloader. True means the server will not wait for thread 12 # termination before it quits. This will make auto-reloader faster 13 # and will prevent the need to kill the server manually if a thread 14 # isn't terminating correctly. 15 httpd.daemon_threads = True 16 httpd.set_app(wsgi_handler) 17 httpd.serve_forever() #相当于socket的recv方法,等待客户端的连接