#!/usr/bin/env python '''Django's command-line utility for administrative tasks.''' import os import sys
def main(): '''Run administrative tasks.''' os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hello.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: ... execute_from_command_line(sys.argv)
if __name__ == '__main__': main()
ManagementUtility的主要结构如下:
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 main_help_text(self, commands_only=False): '''Return the script's main help text, as a string.''' pass
def fetch_command(self, subcommand): ''' Try to fetch the given subcommand, printing a message with the appropriate command called from the command line (usually 'django-admin' or 'manage.py') if it can't be found. ''' pass
...
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. ''' pass
init函数解析argv的命令行参数
main_help_text 输出命令的帮助信息
fetch_command 查找django模块的子命令
execute 执行子命令
一个好的命令行工具,离不开清晰的帮助输出。默认的帮助信息是调用main_help_text函数:
def main_help_text(self, commands_only=False): '''Return the script's main help text, as a string.'''
usage = [ '', 'Type '%s help <subcommand>' for help on a specific subcommand.' % self.prog_name, '', 'Available subcommands:', ] commands_dict = defaultdict(lambda: []) for name, app in get_commands().items(): commands_dict[app].append(name) style = color_style() for app in sorted(commands_dict): usage.append('') usage.append(style.NOTICE('[%s]' % app)) for name in sorted(commands_dict[app]): usage.append(' %s' % name) # Output an extra note if settings are not properly configured if self.settings_exception is not None: usage.append(style.NOTICE( 'Note that only Django core commands are listed ' 'as settings are not properly configured (error: %s).' % self.settings_exception))
return'\n'.join(usage)
main_help_text主要利用了下面4个函数去查找命令清单:
def find_commands(management_dir): ''' Given a path to a management directory, return a list of all the command names that are available. ''' command_dir = os.path.join(management_dir, 'commands') return [name for _, name, is_pkg in pkgutil.iter_modules([command_dir]) if not is_pkg and not name.startswith('_')]
def load_command_class(app_name, name): ''' Given a command name and an application name, return the Command class instance. Allow all errors raised by the import process (ImportError, AttributeError) to propagate. ''' module = import_module('%s.management.commands.%s' % (app_name, name)) return module.Command()
def get_commands(): commands = {name: 'django.core'for name in find_commands(__path__[0])}
if not settings.configured: return commands
for app_config in reversed(list(apps.get_app_configs())): path = os.path.join(app_config.path, 'management') commands.update({name: app_config.name for name in find_commands(path)})
def fetch_command(self, subcommand): ''' Try to fetch the given subcommand, printing a message with the appropriate command called from the command line (usually 'django-admin' or 'manage.py') if it can't be found. ''' # Get commands outside of try block to prevent swallowing exceptions commands = get_commands() try: app_name = commands[subcommand] except KeyError: ... if isinstance(app_name, BaseCommand): # If the command is already loaded, use it directly. klass = app_name else: klass = load_command_class(app_name, subcommand) return klass
def handle(self, *args, **options): ''' The actual logic of the command. Subclasses must implement this method. ''' raise NotImplementedError('subclasses of BaseCommand must provide a handle() method')
def get_internal_wsgi_application(): ''' Load and return the WSGI application as configured by the user in ``settings.WSGI_APPLICATION``. With the default ``startproject`` layout, this will be the ``application`` object in ``projectname/wsgi.py``.
This function, and the ``WSGI_APPLICATION`` setting itself, are only useful for Django's internal server (runserver); external WSGI servers should just be configured to point to the correct application object directly.
If settings.WSGI_APPLICATION is not set (is ``None``), return whatever ``django.core.wsgi.get_wsgi_application`` returns. ''' from django.conf import settings app_path = getattr(settings, 'WSGI_APPLICATION') if app_path is None: return get_wsgi_application()
try: return import_string(app_path) except ImportError as err: ...
def inner_run(self, *args, **options): try: handler = self.get_handler(*args, **options) run(self.addr, int(self.port), handler, ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls) except OSError as e: ... # Need to use an OS exit because sys.exit doesn't work in a thread os._exit(1) except KeyboardInterrupt: .. sys.exit(0)
def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer): server_address = (addr, port) if threading: httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {}) else: httpd_cls = server_cls httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6) if threading: # ThreadingMixIn.daemon_threads indicates how threads will behave on an # abrupt shutdown; like quitting the server by the user or restarting # by the auto-reloader. True means the server will not wait for thread # termination before it quits. This will make auto-reloader faster # and will prevent the need to kill the server manually if a thread # isn't terminating correctly. httpd.daemon_threads = True httpd.set_app(wsgi_handler) httpd.serve_forever()
# python manage.py runserver Watching for file changes with StatReloader Performing system checks...
System check identified no issues (0 silenced). March 05, 2022 - 09:06:07 Django version 4.0, using settings 'hello.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. /Users/yoo/tmp/django/hello/api/views.py changed, reloading. Watching for file changes with StatReloader Performing system checks...
System check identified no issues (0 silenced). March 05, 2022 - 09:12:59 Django version 4.0, using settings 'hello.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
def run(self, **options): '''Run the server, using the autoreloader if needed.''' ... use_reloader = options['use_reloader']
if use_reloader: autoreload.run_with_reloader(self.inner_run, **options) else: self.inner_run(None, **options)
autoreload的继承关系如下:
class BaseReloader: pass
class StatReloader(BaseReloader): pass
class WatchmanReloader(BaseReloader): pass
def get_reloader(): '''Return the most suitable reloader for this environment.''' try: WatchmanReloader.check_availability() except WatchmanUnavailable: return StatReloader() return WatchmanReloader()
def execute(self): ... try: settings.INSTALLED_APPS except ImproperlyConfigured as exc: self.settings_exception = exc except ImportError as exc: self.settings_exception = exc ...
Settings中会载下面的一些模块,主要是INSTALLED_APPS:
class Settings: def __init__(self, settings_module): ... # store the settings module in case someone later cares self.SETTINGS_MODULE = settings_module
mod = importlib.import_module(self.SETTINGS_MODULE)
possible_matches = get_close_matches(subcommand, commands) sys.stderr.write('Unknown command: %r' % subcommand) if possible_matches: sys.stderr.write('. Did you mean %s?' % possible_matches[0])
另外一个小技巧是一个命名的异化细节。class 一般在很多开发语言中都是关键字,如果我们要定义一个class类型的变量名时候,避免关键字冲突。一种方法是使用 klass 替代:
# if isinstance(app_name, BaseCommand): # If the command is already loaded, use it directly. klass = app_name else: klass = load_command_class(app_name, subcommand)
另一种是使用 clazz 替代:
if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); ... if ( cur ) { j = 0; while ( ( clazz = classes[ j++ ] ) ) {