[Python-checkins] cpython: Issue #23014: Make importlib.abc.Loader.create_module() required when

brett.cannon python-checkins at python.org
Fri Jan 9 17:39:41 CET 2015


https://hg.python.org/cpython/rev/ab72f30bcd9f
changeset:   94091:ab72f30bcd9f
user:        Brett Cannon <brett at python.org>
date:        Fri Jan 09 11:39:21 2015 -0500
summary:
  Issue #23014: Make importlib.abc.Loader.create_module() required when
importlib.abc.Loader.exec_module() is also defined.

Before this change, create_module() was optional **and** could return
None to trigger default semantics. This change now reduces the
options for choosing default semantics to one and in the most
backporting-friendly way (define create_module() to return None).

files:
  Doc/library/importlib.rst                          |    24 +-
  Doc/reference/import.rst                           |     8 +-
  Doc/whatsnew/3.5.rst                               |     8 +
  Lib/importlib/_bootstrap.py                        |    14 +
  Lib/importlib/abc.py                               |     3 -
  Lib/test/test_importlib/import_/test___loader__.py |     3 +
  Lib/test/test_importlib/import_/test_api.py        |     4 +
  Lib/test/test_importlib/test_spec.py               |     3 +
  Lib/test/test_importlib/test_util.py               |    12 +-
  Lib/test/test_pkgutil.py                           |     3 +
  Python/importlib.h                                 |  4719 +++++----
  11 files changed, 2447 insertions(+), 2354 deletions(-)


diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst
--- a/Doc/library/importlib.rst
+++ b/Doc/library/importlib.rst
@@ -347,13 +347,16 @@
 
     .. method:: create_module(spec)
 
-       An optional method that returns the module object to use when
-       importing a module.  create_module() may also return ``None``,
-       indicating that the default module creation should take place
-       instead.
+       A method that returns the module object to use when
+       importing a module.  This method may return ``None``,
+       indicating that default module creation semantics should take place.
 
        .. versionadded:: 3.4
 
+       .. versionchanged:: 3.5
+          Starting in Python 3.6, this method will not be optional when
+          :meth:`exec_module` is defined.
+
     .. method:: exec_module(module)
 
        An abstract method that executes the module in its own namespace
@@ -417,7 +420,7 @@
 
         .. deprecated:: 3.4
            The recommended API for loading a module is :meth:`exec_module`
-           (and optionally :meth:`create_module`).  Loaders should implement
+           (and :meth:`create_module`).  Loaders should implement
            it instead of load_module().  The import machinery takes care of
            all the other responsibilities of load_module() when exec_module()
            is implemented.
@@ -1136,9 +1139,9 @@
 
 .. function:: module_from_spec(spec)
 
-   Create a new module based on **spec**.
+   Create a new module based on **spec** and ``spec.loader.create_module()``.
 
-   If the module object is from ``spec.loader.create_module()``, then any
+   If ``spec.loader.create_module()`` does not return ``None``, then any
    pre-existing attributes will not be reset. Also, no :exc:`AttributeError`
    will be raised if triggered while accessing **spec** or setting an attribute
    on the module.
@@ -1234,9 +1237,10 @@
    module has an attribute accessed.
 
    This class **only** works with loaders that define
-   :meth:`importlib.abc.Loader.exec_module` as control over what module type
-   is used for the module is required. For the same reasons, the loader
-   **cannot** define :meth:`importlib.abc.Loader.create_module`. Finally,
+   :meth:`~importlib.abc.Loader.exec_module` as control over what module type
+   is used for the module is required. For those same reasons, the loader's
+   :meth:`~importlib.abc.Loader.create_module` method will be ignored (i.e., the
+   loader's method should only return ``None``). Finally,
    modules which substitute the object placed into :attr:`sys.modules` will
    not work as there is no way to properly replace the module references
    throughout the interpreter safely; :exc:`ValueError` is raised if such a
diff --git a/Doc/reference/import.rst b/Doc/reference/import.rst
--- a/Doc/reference/import.rst
+++ b/Doc/reference/import.rst
@@ -339,6 +339,7 @@
 
     module = None
     if spec.loader is not None and hasattr(spec.loader, 'create_module'):
+        # It is assumed 'exec_module' will also be defined on the loader.
         module = spec.loader.create_module(spec)
     if module is None:
         module = ModuleType(spec.name)
@@ -427,7 +428,7 @@
 by implementing a :meth:`~importlib.abc.Loader.create_module` method.
 It takes one argument, the module spec, and returns the new module object
 to use during loading.  ``create_module()`` does not need to set any attributes
-on the module object.  If the loader does not define ``create_module()``, the
+on the module object.  If the method returns ``None``, the
 import machinery will create the new module itself.
 
 .. versionadded:: 3.4
@@ -462,6 +463,11 @@
       module(s), and only if the loader itself has loaded the module(s)
       explicitly.
 
+.. versionchanged:: 3.5
+   A :exc:`DeprecationWarning` is raised when ``exec_module()`` is defined but
+   ``create_module()`` is not. Starting in Python 3.6 it will be an error to not
+   define ``create_module()`` on a loader attached to a ModuleSpec.
+
 Module spec
 -----------
 
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -456,6 +456,14 @@
   `http.client` and `http.server` remain available for backwards compatibility.
   (Contributed by Demian Brecht in :issue:`21793`.)
 
+* When an import loader defines :meth:`~importlib.machinery.Loader.exec_module`
+  it is now expected to also define
+  :meth:`~importlib.machinery.Loader.create_module` (raises a
+  :exc:`DeprecationWarning` now, will be an error in Python 3.6). If the loader
+  inherits from :class:`importlib.abc.Loader` then there is nothing to do, else
+  simply define :meth:`~importlib.machinery.Loader.create_module` to return
+  ``None`` (:issue:`23014`).
+
 Changes in the C API
 --------------------
 
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -1055,6 +1055,10 @@
         # If create_module() returns `None` then it means default
         # module creation should be used.
         module = spec.loader.create_module(spec)
+    elif hasattr(spec.loader, 'exec_module'):
+        _warnings.warn('starting in Python 3.6, loaders defining exec_module() '
+                       'must also define create_module()',
+                       DeprecationWarning, stacklevel=2)
     if module is None:
         module = _new_module(spec.name)
     _init_module_attrs(spec, module)
@@ -1298,6 +1302,10 @@
         """
         return cls if _imp.is_frozen(fullname) else None
 
+    @classmethod
+    def create_module(cls, spec):
+        """Use default semantics for module creation."""
+
     @staticmethod
     def exec_module(module):
         name = module.__spec__.name
@@ -1411,6 +1419,9 @@
         tail_name = fullname.rpartition('.')[2]
         return filename_base == '__init__' and tail_name != '__init__'
 
+    def create_module(self, spec):
+        """Use default semantics for module creation."""
+
     def exec_module(self, module):
         """Execute the module."""
         code = self.get_code(module.__name__)
@@ -1771,6 +1782,9 @@
     def get_code(self, fullname):
         return compile('', '<string>', 'exec', dont_inherit=True)
 
+    def create_module(self, spec):
+        """Use default semantics for module creation."""
+
     def exec_module(self, module):
         pass
 
diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py
--- a/Lib/importlib/abc.py
+++ b/Lib/importlib/abc.py
@@ -122,9 +122,6 @@
         This method should raise ImportError if anything prevents it
         from creating a new module.  It may return None to indicate
         that the spec should create the new module.
-
-        create_module() is optional.
-
         """
         # By default, defer to default semantics for the new module.
         return None
diff --git a/Lib/test/test_importlib/import_/test___loader__.py b/Lib/test/test_importlib/import_/test___loader__.py
--- a/Lib/test/test_importlib/import_/test___loader__.py
+++ b/Lib/test/test_importlib/import_/test___loader__.py
@@ -11,6 +11,9 @@
     def find_spec(self, fullname, path=None, target=None):
         return machinery.ModuleSpec(fullname, self)
 
+    def create_module(self, spec):
+        return None
+
     def exec_module(self, module):
         pass
 
diff --git a/Lib/test/test_importlib/import_/test_api.py b/Lib/test/test_importlib/import_/test_api.py
--- a/Lib/test/test_importlib/import_/test_api.py
+++ b/Lib/test/test_importlib/import_/test_api.py
@@ -17,6 +17,10 @@
             return spec
 
     @staticmethod
+    def create_module(spec):
+        return None
+
+    @staticmethod
     def exec_module(module):
         if module.__name__ == SUBMOD_NAME:
             raise ImportError('I cannot be loaded!')
diff --git a/Lib/test/test_importlib/test_spec.py b/Lib/test/test_importlib/test_spec.py
--- a/Lib/test/test_importlib/test_spec.py
+++ b/Lib/test/test_importlib/test_spec.py
@@ -34,6 +34,9 @@
     def _is_package(self, name):
         return self.package
 
+    def create_module(self, spec):
+        return None
+
 
 class NewLoader(TestLoader):
 
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
@@ -41,10 +41,16 @@
 class ModuleFromSpecTests:
 
     def test_no_create_module(self):
-        class Loader(self.abc.Loader):
-            pass
+        class Loader:
+            def exec_module(self, module):
+                pass
         spec = self.machinery.ModuleSpec('test', Loader())
-        module = self.util.module_from_spec(spec)
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter('always')
+            module = self.util.module_from_spec(spec)
+        self.assertEqual(1, len(w))
+        self.assertTrue(issubclass(w[0].category, DeprecationWarning))
+        self.assertIn('create_module', str(w[0].message))
         self.assertIsInstance(module, types.ModuleType)
         self.assertEqual(module.__name__, spec.name)
 
diff --git a/Lib/test/test_pkgutil.py b/Lib/test/test_pkgutil.py
--- a/Lib/test/test_pkgutil.py
+++ b/Lib/test/test_pkgutil.py
@@ -104,6 +104,9 @@
 class PkgutilPEP302Tests(unittest.TestCase):
 
     class MyTestLoader(object):
+        def create_module(self, spec):
+            return None
+
         def exec_module(self, mod):
             # Count how many times the module is reloaded
             mod.__dict__['loads'] = mod.__dict__.get('loads', 0) + 1
diff --git a/Python/importlib.h b/Python/importlib.h
--- a/Python/importlib.h
+++ b/Python/importlib.h
[stripped]

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


More information about the Python-checkins mailing list