Code

Ticket #16675: importutils.diff

File importutils.diff, 5.8 KB (added by jezdez, 3 years ago)

diff from https://github.com/jezdez/django/compare/feature/importutils

Line 
1diff --git a/django/utils/importlib.py b/django/utils/importlib.py
2index ef4d0e4..fe3f546 100644
3--- a/django/utils/importlib.py
4+++ b/django/utils/importlib.py
5@@ -34,3 +34,63 @@ def import_module(name, package=None):
6         name = _resolve_name(name[level:], package, level)
7     __import__(name)
8     return sys.modules[name]
9+
10+
11+def import_attribute(name, exception_handler=None):
12+    """
13+    Loads an object from an 'name', like in MIDDLEWARE_CLASSES and the likes.
14+
15+    Import paths should be: "mypackage.mymodule.MyObject". It then imports the
16+    module up until the last dot and tries to get the attribute after that dot
17+    from the imported module.
18+
19+    If the import path does not contain any dots, a TypeError is raised.
20+
21+    If the module cannot be imported, an ImportError is raised.
22+
23+    If the attribute does not exist in the module, a AttributeError is raised.
24+
25+    You can provide custom error handling using the optional exception_handler
26+    argument which gets called with the exception type, the exception value and
27+    the traceback object if there is an error during loading.
28+
29+    The exception_handler is not called if an invalid import path (one without
30+    a dot in it) is provided.
31+    """
32+    if '.' not in name:
33+        raise TypeError("'name' argument to "
34+                        "'django.utils.importlib.import_attribute' must "
35+                        "contain at least one dot.")
36+    module_name, object_name = name.rsplit('.', 1)
37+    try:
38+        module = import_module(module_name)
39+    except:
40+        if callable(exception_handler):
41+            exctype, excvalue, tb = sys.exc_info()
42+            return exception_handler(name, exctype, excvalue, tb)
43+        else:
44+            raise
45+    try:
46+        return getattr(module, object_name)
47+    except:
48+        if callable(exception_handler):
49+            exctype, excvalue, tb = sys.exc_info()
50+            return exception_handler(name, exctype, excvalue, tb)
51+        else:
52+            raise
53+
54+
55+def iter_import_attributes(names, exception_handler=None):
56+    """
57+    Calls django.contrib.load.import_attribute on all items in the iterable
58+    names and returns a generator that yields the objects to be loaded.
59+
60+    The exception_handler is propagated to import_attribute.
61+
62+    If the exception_handler does not return anything or returns None, the
63+    value is ignored.
64+    """
65+    for name in names:
66+        next = import_attribute(name, exception_handler)
67+        if next:
68+            yield next
69diff --git a/tests/regressiontests/utils/importlib.py b/tests/regressiontests/utils/importlib.py
70new file mode 100644
71index 0000000..bccd0ce
72--- /dev/null
73+++ b/tests/regressiontests/utils/importlib.py
74@@ -0,0 +1,64 @@
75+# -*- coding: utf-8 -*-
76+from __future__ import with_statement
77+from django.contrib.auth.models import User, AnonymousUser
78+from django.test.testcases import TestCase
79+from django.utils.importlib import import_attribute, iter_import_attributes
80+   
81+
82+class LoadTests(TestCase):
83+       
84+    def test_import_attribute(self):
85+        obj = import_attribute('django.contrib.auth.models.User')
86+        self.assertEqual(obj, User)
87+       
88+    def test_import_attribute_fail_type(self):
89+        self.assertRaises(TypeError, import_attribute, 'notanimportpath')
90+       
91+    def test_import_attribute_fail_import(self):
92+        self.assertRaises(ImportError, import_attribute,
93+                          'django.contrib.auth.not_models.User')
94+       
95+    def test_import_attribute_fail_attribute(self):
96+        self.assertRaises(AttributeError, import_attribute,
97+                          'django.contrib.auth.models.NotAUser')
98+       
99+    def test_import_attribute_exception_handler(self):
100+        class MyException(Exception): pass
101+        def exc_handler(*args, **kwargs):
102+            raise MyException
103+        self.assertRaises(MyException, import_attribute,
104+                          'django.contrib.auth.models.NotAUser', exc_handler)
105+        self.assertRaises(MyException, import_attribute,
106+                          'django.contrib.auth.not_models.User', exc_handler)
107+        self.assertRaises(TypeError, import_attribute, 'notanimportpath')
108+
109+    def test_iter_import_attributes(self):
110+        import_paths = ['django.contrib.auth.models.User',
111+                        'django.contrib.auth.models.AnonymousUser']
112+        objs = list(iter_import_attributes(import_paths))
113+        self.assertEqual(len(objs), 2)
114+        self.assertEqual(objs[0], User)
115+        self.assertEqual(objs[1], AnonymousUser)
116+       
117+    def test_iter_import_attributes_propagates_exception_handler(self):
118+        class MyException(Exception): pass
119+        def exc_handler(*args, **kwargs):
120+            raise MyException
121+        gen = iter_import_attributes(['django.contrib.auth.models.User',
122+                                      'django.contrib.auth.models.NotAUser'],
123+                                      exc_handler)
124+        user = gen.next()
125+        self.assertEqual(user, User)
126+        self.assertRaises(MyException, gen.next)
127+   
128+    def test_iter_import_attributes_ignore_exceptions(self):
129+        def exc_handler(*args, **kwargs):
130+            pass
131+
132+        import_paths = ['django.contrib.auth.models.User',
133+                        'django.contrib.auth.models.NotAUser',
134+                        'django.contrib.auth.models.AnonymousUser']
135+        objs = list(iter_import_attributes(import_paths, exc_handler))
136+        self.assertEqual(len(objs), 2)
137+        self.assertEqual(objs[0], User)
138+        self.assertEqual(objs[1], AnonymousUser)
139diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py
140index e91adc9..242b7e2 100644
141--- a/tests/regressiontests/utils/tests.py
142+++ b/tests/regressiontests/utils/tests.py
143@@ -20,3 +20,4 @@ from datetime_safe import *
144 from baseconv import *
145 from jslex import *
146 from ipv6 import *
147+from importlib import *