How to Implement autoreload in django developer mode, djangoautoreload
In the process of developing a django application, it is especially convenient to start the service in developer mode. You only need python manage. the py runserver can run the service and provides a very user-friendly autoreload mechanism. You do not need to manually restart the program to modify the code and see the feedback. When I first came into contact, I felt that this function was more user-friendly and I didn't think it was a very high technology. Later, I thought about how to implement autoreload. After thinking about it for a long time, I couldn't understand it. It seems that the first reaction was really low. So I spent some time studying how django implemented autoreload. Every step of the process was based on the source code, and I was not allowed to take it for granted:
1. runserver command. Before getting started, there was actually a lot of nonsense about how to execute the runserver command. It had little to do with the theme. Just take it with me:
After you type python manage. py runserver in the command line, django searches for the execution module of the runserver command
Django \ contrib \ staticfiles \ management \ commands \ runserver. py module:
#django\contrib\staticfiles\management\commands\runserver.pyfrom django.core.management.commands.runserver import \Command as RunserverCommandclass Command(RunserverCommand): help = "Starts a lightweight Web server for development and also serves static files."
The execution function of this Command is here:
#django\core\management\commands\runserver.pyclass Command(BaseCommand): def run(self, **options): """ Runs the server, using the autoreloader if needed """ use_reloader = options['use_reloader'] if use_reloader: autoreload.main(self.inner_run, None, options) else: self.inner_run(None, **options)
Here is the judgment on use_reloader. If -- noreload is not added to the startup command, the program will use the autoreload. main function. If it is added, self. inner_run will be used to directly start the application.
In fact, we can see from the autoreload. main Parameter that it should have encapsulated self. inner_run. The autoreload mechanism is included in these packages. Next we will continue to follow up.
PS: When I look at the source code, I find that the command mode of django is still very beautiful and worth learning.
2. autoreload module. See autoreload. main ():
#django\utils\autoreload.py:def main(main_func, args=None, kwargs=None): if args is None: args = () if kwargs is None: kwargs = {} if sys.platform.startswith('java'): reloader = jython_reloader else: reloader = python_reloader wrapped_main_func = check_errors(main_func) reloader(wrapped_main_func, args, kwargs)
Here we have made a difference between jpython and other python. Ignore jpython first; check_errors is to handle the main_func error, and ignore it first. View python_reloader:
#django\utils\autoreload.py:def python_reloader(main_func, args, kwargs): if os.environ.get("RUN_MAIN") == "true": thread.start_new_thread(main_func, args, kwargs) try: reloader_thread() except KeyboardInterrupt: pass else: try: exit_code = restart_with_reloader() if exit_code < 0: os.kill(os.getpid(), -exit_code) else: sys.exit(exit_code) except KeyboardInterrupt: pass
When we first came here, the RUN_MAIN variable in the environment variable was not "true" or even none, so we went through else to see restart_with_reloader:
#django\utils\autoreload.py:def restart_with_reloader(): while True: args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv if sys.platform == "win32": args = ['"%s"' % arg for arg in args] new_environ = os.environ.copy() new_environ["RUN_MAIN"] = 'true' exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ) if exit_code != 3: return exit_code
Here we start with a while LOOP, first change RUN_MAIN to "true", and then use the OS. spawnve method to open a sub-process (subprocess). Let's take a look at the description of OS. spawnve:
#os.py
def spawnve(mode, file, args, env): """spawnve(mode, file, args, env) -> integer Execute file with arguments from args in a subprocess with the specified environment. If mode == P_NOWAIT return the pid of the process. If mode == P_WAIT return the process's exit code if it exits normally; otherwise return -SIG, where SIG is the signal that killed it. """
return _spawnvef(mode, file, args, env, execve)
In fact, the command line is called again, and the python manage. py runserver is used again.
Next, let's look at the while loop in restart_with_reloader. Note that the only condition for exit of the while loop is exit_code! = 3. If the sub-process does not exit, it will remain in the OS. spawnve: if the child process exits and the exit code is not 3, The while process is terminated. If it is 3, continue the loop and recreate the child process. From this logic, we can guess the autoreload mechanism: if the current process (the main process) does nothing, we can monitor the running status of the sub-process. The sub-process is the real sub-process; if the sub-process exits with exit_code = 3 (the file modification should be detected), start the sub-process again, and the new Code will naturally take effect. If the sub-process uses exit_code! = 3 exit, and the main process also ends. The entire django program crashes. This is just a conjecture. Next we will verify it.
3. sub-process. In fact, the above question is, since it was restarted once, why does the child process not generate the child process? The reason is that the environment variable RUN_MAIN is changed to true in the main process. When the sub-process goes to the python_reloader function:
#django\utils\autoreload.py:def python_reloader(main_func, args, kwargs): if os.environ.get("RUN_MAIN") == "true": thread.start_new_thread(main_func, args, kwargs) try: reloader_thread() except KeyboardInterrupt: pass else: try: exit_code = restart_with_reloader() if exit_code < 0: os.kill(os.getpid(), -exit_code) else: sys.exit(exit_code) except KeyboardInterrupt: pass
The if condition is met, and the master process follows a different logical branch. Here, first open a thread and run main_func, that is, the preceding Command. inner_run. Here the thread module is imported as follows:
#django\utils\autoreload.py:from django.utils.six.moves import _thread as thread
Here, the role of the six module is to be compatible with various python versions:
[Codeblock six] # django \ utils \ six. pyclass _ SixMetaPathImporter (object): "A meta path importer to import six. moves and its submodules. this class implements a PEP302 finder and loader. it shoshould be compatiblewith Python 2.5 and all existing versions of Python3 "" Official Website: # define Python 2 and 3 Compatibility LibrarySix provides simple utilities for wrapping over differences between Python 2 and Python 3. it is intended to support codebases that work on both Python 2 and 3 without modification. six consists of only one Python file, so it is painless to copy into a project.
So if the program wants to run on python2 and python3, and lubang, six is an important tool. Next, take a moment to look at six and mark it.
Then open another reloader_thread:
[codeblock autoreload_reloader_thread]#django\utils\autoreload.py:def reloader_thread(): ensure_echo_on() if USE_INOTIFY: fn = inotify_code_changed else: fn = code_changed
while RUN_RELOADER: change = fn() if change == FILE_MODIFIED: sys.exit(3) # force reload elif change == I18N_MODIFIED: reset_translations() time.sleep(1)
Ensure_echo_on () is not clear yet. It seems that it is for unix-like system file processing and skipped;
USE_INOTIFY is also a variable related to system file operations. It is used to detect file changes based on whether inotify is available.
While LOOP, check the file status every one second. If a common file changes, the process exits and the exit code is 3. When the main process looks at it, the exit code is 3, restart the sub-process .... In this way, it is connected to the above; if it is not a normal file change, it is I18N_MODIFIED (. changes to files with the mo suffix, binary library files, and so on), then reset_translations, probably means to clear the cache of the loaded library and re-Load it next time.
The above is the process of the autoreload mechanism. Some of the details are not very clear, such as the detection of file changes in different operating systems, but they are all details and do not involve the main process. After reading this, I asked myself again how I would design the autoreload mechanism. Now my answer is: Use the django \ utils \ autoreload. py file directly. In fact, this is a very independent module, and it is particularly common. It can be used as a general autoreload solution. I also write a Mao.