[PYTHON] Source analysis for Django--INSTALLED_APPS

templarbit-illustration-django-1a748506.jpg

Preface

I will explain the process leading up to this article, I had this question on teratail, I don't know how to teach the project when enabling the Django model

The content of the question was as follows.

Why would Django's INSTALLED_APPS module be put in like ʻapp name.apps.app name config, In terms of directory structure, it should be ../app name / apps.app name config`.

So I'd like to explain Django's source code for ʻINSTALLED_APPS`. If you're interested in Django's core, stay tuned to the end. : relaxed:

Preparation

To explain, I will create a simple project called django_test, the directory structure is as follows.

djnago_test
|-- django_test
|-- |-- __init__.py
|-- |-- asgi.py
|-- |-- settings.py
|-- |-- urls.py
|-- |-- wsgi.py
|-- manage.py

runserver Launch your Django project from the command line.

python manage.py runserver

So what happened after that, let's take a look at the contents of manage.py.

manage.py


#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test.settings')
    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()

--setdefault adds the dictionary data of key and value to ʻenviron, if the key exists, it gets the value. --Import the managementmodule. --Execute ʻexecute_from_command_line (sys.argv).

First, let's look at the contents of sys.argv, the path of manage.py and the execution command.

['manage.py', 'runserver']

Next, look at the contents of ʻexecute_from_command_line (sys.argv)`.

management/__init__.py


def execute_from_command_line(argv=None):
    """Run a ManagementUtility."""
    utility = ManagementUtility(argv)
    utility.execute()

The content is very simple, it takes the value of sys.argv, I instantiated the ManagementUtility and executed the ʻexecutefunction. Let's take a look at theinitfunction of theManagementUtility`.

management.py


    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

in this case:

Let's look at the next. What did the ʻexecute` function do? The content of the function is very long, so I will explain only the important parts.

management.py


try:
    subcommand = self.argv[1]
except IndexError:
    subcommand = 'help'

python manage.py runserver When running, the value of subcommand will be runserver. Then enter the block below.

management.py


...
if subcommand == 'runserver' and '--noreload' not in self.argv:
   try:
      autoreload.check_errors(django.setup)()
...

Let's take a look at the contents of django.setup.

django/__init__.py


...
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
        )
    apps.populate(settings.INSTALLED_APPS)

At this point, the process related to ʻINSTALLED_APPShas become clear, Let's take a look at the source ofpopulate`, it's fairly long, so I'll explain only the important parts.

registry.py


...
    def populate(self, installed_apps=None):
        ...
            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)

                self.app_configs[app_config.label] = app_config
                app_config.apps = self
...

The contents of this ʻinstalled_appsare as follows, loop and take out one by one and start the next process. One thing to note is that content like'django.contrib.admin'` is of type ** string **.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

The content of the loop is a conditional judgment of the inheritance relationship for the elements contained in ʻINSTALLED_APPS`.

if isinstance(entry, AppConfig):
    app_config = entry
else:
    app_config = AppConfig.create(entry)

Of course, it's just a string right now, so it goes into the else block.

app_config = AppConfig.create(entry)

Let's take a look at the contents of create ().

config.py


    def create(cls, entry):
        ...
        try:
            module = import_module(entry)
        except ImportError:
            module = None
            mod_path, _, cls_name = entry.rpartition('.')

            if not mod_path:
                raise
        ...

At this point, I'd like to actually use the ʻimport_module function, Create a ʻusers ʻapp and add it to ʻINSTALLED_APPS.

python manage.py startapp users
INSTALLED_APPS= [
...
users or users.apps.UsersConfig
]

When added as ʻusers`.

test.py


import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test.settings')

from importlib import import_module
print(import_module("users"))
# <module 'users' from '/Users/user/django/django_test/users/__init__.py'>

He found the app properly.

ʻWhen added as users.apps.UsersConfig`.

test.py


import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test.settings')

from importlib import import_module
print(import_module("users.apps.UsersConfig"))
# ModuleNotFoundError: No module named 'users.apps.UsersConfig'; 'users.apps' is not a package

I can't seem to find the app, so I'm in the block below.

config.py


except ImportError:
    module = None
    mod_path, _, cls_name = entry.rpartition('.')

    if not mod_path:
       raise

mod_path, _, cls_name = entry.rpartition ('.') Is executed and The value of mod_path will be ʻusers.apps and cls_name will be ʻUsersConfig. I will test it again.

main.py


import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test.settings')

from importlib import import_module
mod_path, _, cls_name = "users.apps.UsersConfig".rpartition('.')
print(import_module(mod_path))
# <module 'users.apps' from '/Users/user/django/django_test/users/apps.py'>

He found the app.

Recommended Posts

Source analysis for Django--INSTALLED_APPS
Folder structure for analysis
Python for Data Analysis Chapter 4
10 Open Source Web Crawlers for 2020
Python for Data Analysis Chapter 2
Tips for data analysis ・ Notes
Python for Data Analysis Chapter 3
Notes for challenging basketball video analysis
Preprocessing template for data analysis (Python)
Data analysis for improving POG 3-Regression analysis-
linux (kernel) source analysis: system call call
Python visualization tool for data analysis work
Program for Twitter Trend Analysis (Personal Note)
JupyterLab Basic Setting 2 (pip) for data analysis
JupyterLab Basic Setup for Data Analysis (pip)
Analysis for Data Scientists: Qiita Self-Article Summary 2020