[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