Arthur de Jong

Open Source / Free Software developer

summaryrefslogtreecommitdiffstats
path: root/django/utils/module_loading.py
blob: 4a2c5003216808e8b919dc37409566cba1e60f4c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import copy
import os
import sys
from importlib import import_module

from django.utils import six


def import_string(dotted_path):
    """
    Import a dotted module path and return the attribute/class designated by the
    last name in the path. Raise ImportError if the import failed.
    """
    try:
        module_path, class_name = dotted_path.rsplit('.', 1)
    except ValueError:
        msg = "%s doesn't look like a module path" % dotted_path
        six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])

    module = import_module(module_path)

    try:
        return getattr(module, class_name)
    except AttributeError:
        msg = 'Module "%s" does not define a "%s" attribute/class' % (
            module_path, class_name)
        six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])


def autodiscover_modules(*args, **kwargs):
    """
    Auto-discover INSTALLED_APPS modules and fail silently when
    not present. This forces an import on them to register any admin bits they
    may want.

    You may provide a register_to keyword parameter as a way to access a
    registry. This register_to object must have a _registry instance variable
    to access it.
    """
    from django.apps import apps

    register_to = kwargs.get('register_to')
    for app_config in apps.get_app_configs():
        for module_to_search in args:
            # Attempt to import the app's module.
            try:
                if register_to:
                    before_import_registry = copy.copy(register_to._registry)

                import_module('%s.%s' % (app_config.name, module_to_search))
            except:
                # Reset the registry to the state before the last import
                # as this import will have to reoccur on the next request and
                # this could raise NotRegistered and AlreadyRegistered
                # exceptions (see #8245).
                if register_to:
                    register_to._registry = before_import_registry

                # Decide whether to bubble up this error. If the app just
                # doesn't have the module in question, we can ignore the error
                # attempting to import it, otherwise we want it to bubble up.
                if module_has_submodule(app_config.module, module_to_search):
                    raise


if six.PY3:
    from importlib.util import find_spec as importlib_find

    def module_has_submodule(package, module_name):
        """See if 'module' is in 'package'."""
        try:
            package_name = package.__name__
            package_path = package.__path__
        except AttributeError:
            # package isn't a package.
            return False

        full_module_name = package_name + '.' + module_name
        return importlib_find(full_module_name, package_path) is not None

else:
    import imp

    def module_has_submodule(package, module_name):
        """See if 'module' is in 'package'."""
        name = ".".join([package.__name__, module_name])
        try:
            # None indicates a cached miss; see mark_miss() in Python/import.c.
            return sys.modules[name] is not None
        except KeyError:
            pass
        try:
            package_path = package.__path__   # No __path__, then not a package.
        except AttributeError:
            # Since the remainder of this function assumes that we're dealing with
            # a package (module with a __path__), so if it's not, then bail here.
            return False
        for finder in sys.meta_path:
            if finder.find_module(name, package_path):
                return True
        for entry in package_path:
            try:
                # Try the cached finder.
                finder = sys.path_importer_cache[entry]
                if finder is None:
                    # Implicit import machinery should be used.
                    try:
                        file_, _, _ = imp.find_module(module_name, [entry])
                        if file_:
                            file_.close()
                        return True
                    except ImportError:
                        continue
                # Else see if the finder knows of a loader.
                elif finder.find_module(name):
                    return True
                else:
                    continue
            except KeyError:
                # No cached finder, so try and make one.
                for hook in sys.path_hooks:
                    try:
                        finder = hook(entry)
                        # XXX Could cache in sys.path_importer_cache
                        if finder.find_module(name):
                            return True
                        else:
                            # Once a finder is found, stop the search.
                            break
                    except ImportError:
                        # Continue the search for a finder.
                        continue
                else:
                    # No finder found.
                    # Try the implicit import machinery if searching a directory.
                    if os.path.isdir(entry):
                        try:
                            file_, _, _ = imp.find_module(module_name, [entry])
                            if file_:
                                file_.close()
                            return True
                        except ImportError:
                            pass
                    # XXX Could insert None or NullImporter
        else:
            # Exhausted the search, so the module cannot be found.
            return False


def module_dir(module):
    """
    Find the name of the directory that contains a module, if possible.

    Raise ValueError otherwise, e.g. for namespace packages that are split
    over several directories.
    """
    # Convert to list because _NamespacePath does not support indexing on 3.3.
    paths = list(getattr(module, '__path__', []))
    if len(paths) == 1:
        return paths[0]
    else:
        filename = getattr(module, '__file__', None)
        if filename is not None:
            return os.path.dirname(filename)
    raise ValueError("Cannot determine directory containing %s" % module)