[Python-checkins] cpython: Issue #17117: Have both import itself and importlib.util.set_loader()

brett.cannon python-checkins at python.org
Wed Mar 13 18:45:47 CET 2013


http://hg.python.org/cpython/rev/7647aae9481c
changeset:   82643:7647aae9481c
user:        Brett Cannon <brett at python.org>
date:        Wed Mar 13 10:41:36 2013 -0700
summary:
  Issue #17117: Have both import itself and importlib.util.set_loader()
set __loader__ on a module when set to None.

Thanks to Gökcen Eraslan for the fix.

files:
  Doc/library/importlib.rst                          |     4 +
  Doc/reference/import.rst                           |    16 +-
  Lib/importlib/_bootstrap.py                        |     9 +-
  Lib/test/test_importlib/import_/test___loader__.py |    44 +
  Lib/test/test_importlib/test_util.py               |    42 +-
  Misc/ACKS                                          |     1 +
  Misc/NEWS                                          |     3 +
  Python/importlib.h                                 |  6699 ++++-----
  8 files changed, 3443 insertions(+), 3375 deletions(-)


diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst
--- a/Doc/library/importlib.rst
+++ b/Doc/library/importlib.rst
@@ -774,6 +774,10 @@
       It is recommended that :func:`module_for_loader` be used over this
       decorator as it subsumes this functionality.
 
+   .. versionchanged:: 3.4
+      Set ``__loader__`` if set to ``None`` as well if the attribute does not
+      exist.
+
 
 .. decorator:: set_package
 
diff --git a/Doc/reference/import.rst b/Doc/reference/import.rst
--- a/Doc/reference/import.rst
+++ b/Doc/reference/import.rst
@@ -369,16 +369,18 @@
  * The ``__loader__`` attribute must be set to the loader object that loaded
    the module.  This is mostly for introspection and reloading, but can be
    used for additional loader-specific functionality, for example getting
-   data associated with a loader.
+   data associated with a loader. If the attribute is missing or set to ``None``
+   then the import machinery will automatically set it **after** the module has
+   been imported.
 
- * The module's ``__package__`` attribute should be set.  Its value must be a
+ * The module's ``__package__`` attribute must be set.  Its value must be a
    string, but it can be the same value as its ``__name__``.  If the attribute
    is set to ``None`` or is missing, the import system will fill it in with a
-   more appropriate value.  When the module is a package, its ``__package__``
-   value should be set to its ``__name__``.  When the module is not a package,
-   ``__package__`` should be set to the empty string for top-level modules, or
-   for submodules, to the parent package's name.  See :pep:`366` for further
-   details.
+   more appropriate value **after** the module has been imported.
+   When the module is a package, its ``__package__`` value should be set to its
+   ``__name__``.  When the module is not a package, ``__package__`` should be
+   set to the empty string for top-level modules, or for submodules, to the
+   parent package's name.  See :pep:`366` for further details.
 
    This attribute is used instead of ``__name__`` to calculate explicit
    relative imports for main modules, as defined in :pep:`366`.
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -494,7 +494,7 @@
     """Set __loader__ on the returned module."""
     def set_loader_wrapper(self, *args, **kwargs):
         module = fxn(self, *args, **kwargs)
-        if not hasattr(module, '__loader__'):
+        if getattr(module, '__loader__', None) is None:
             module.__loader__ = self
         return module
     _wrap(set_loader_wrapper, fxn)
@@ -875,12 +875,9 @@
                 module.__cached__ = module.__file__
         else:
             module.__cached__ = module.__file__
-        module.__package__ = name
         if self.is_package(name):
             module.__path__ = [_path_split(module.__file__)[0]]
-        else:
-            module.__package__ = module.__package__.rpartition('.')[0]
-        module.__loader__ = self
+        # __package__ and __loader set by @module_for_loader.
         _call_with_frames_removed(exec, code_object, module.__dict__)
         return module
 
@@ -1551,7 +1548,7 @@
         except AttributeError:
             pass
     # Set loader if need be.
-    if not hasattr(module, '__loader__'):
+    if getattr(module, '__loader__', None) is None:
         try:
             module.__loader__ = loader
         except AttributeError:
diff --git a/Lib/test/test_importlib/import_/test___loader__.py b/Lib/test/test_importlib/import_/test___loader__.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_importlib/import_/test___loader__.py
@@ -0,0 +1,44 @@
+import imp
+import sys
+import unittest
+
+from .. import util
+from . import util as import_util
+
+
+class LoaderMock:
+
+    def find_module(self, fullname, path=None):
+        return self
+
+    def load_module(self, fullname):
+        sys.modules[fullname] = self.module
+        return self.module
+
+
+class LoaderAttributeTests(unittest.TestCase):
+
+    def test___loader___missing(self):
+        module = imp.new_module('blah')
+        try:
+            del module.__loader__
+        except AttributeError:
+            pass
+        loader = LoaderMock()
+        loader.module = module
+        with util.uncache('blah'), util.import_state(meta_path=[loader]):
+            module = import_util.import_('blah')
+        self.assertEqual(loader, module.__loader__)
+
+    def test___loader___is_None(self):
+        module = imp.new_module('blah')
+        module.__loader__ = None
+        loader = LoaderMock()
+        loader.module = module
+        with util.uncache('blah'), util.import_state(meta_path=[loader]):
+            returned_module = import_util.import_('blah')
+        self.assertEqual(loader, module.__loader__)
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py
--- a/Lib/test/test_importlib/test_util.py
+++ b/Lib/test/test_importlib/test_util.py
@@ -162,6 +162,37 @@
         self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
 
 
+class SetLoaderTests(unittest.TestCase):
+
+    """Tests importlib.util.set_loader()."""
+
+    class DummyLoader:
+        @util.set_loader
+        def load_module(self, module):
+            return self.module
+
+    def test_no_attribute(self):
+        loader = self.DummyLoader()
+        loader.module = imp.new_module('blah')
+        try:
+            del loader.module.__loader__
+        except AttributeError:
+            pass
+        self.assertEqual(loader, loader.load_module('blah').__loader__)
+
+    def test_attribute_is_None(self):
+        loader = self.DummyLoader()
+        loader.module = imp.new_module('blah')
+        loader.module.__loader__ = None
+        self.assertEqual(loader, loader.load_module('blah').__loader__)
+
+    def test_not_reset(self):
+        loader = self.DummyLoader()
+        loader.module = imp.new_module('blah')
+        loader.module.__loader__ = 42
+        self.assertEqual(42, loader.load_module('blah').__loader__)
+
+
 class ResolveNameTests(unittest.TestCase):
 
     """Tests importlib.util.resolve_name()."""
@@ -195,14 +226,5 @@
             util.resolve_name('..bacon', 'spam')
 
 
-def test_main():
-    from test import support
-    support.run_unittest(
-            ModuleForLoaderTests,
-            SetPackageTests,
-            ResolveNameTests
-        )
-
-
 if __name__ == '__main__':
-    test_main()
+    unittest.main()
diff --git a/Misc/ACKS b/Misc/ACKS
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -339,6 +339,7 @@
 Jeff Epler
 Jeff McNeil
 Tom Epperly
+Gökcen Eraslan
 Stoffel Erasmus
 Jürgen A. Erhard
 Michael Ernst
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@
 Core and Builtins
 -----------------
 
+- Issue #17117: Import and @importlib.util.set_loader now set __loader__ when
+  it has a value of None or the attribute doesn't exist.
+
 - Issue #17327: Add PyDict_SetDefault.
 
 - Issue #17032: The "global" in the "NameError: global name 'x' is not defined"
diff --git a/Python/importlib.h b/Python/importlib.h
--- a/Python/importlib.h
+++ b/Python/importlib.h
[stripped]

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list