Commit 1c605297 authored by Brett Cannon's avatar Brett Cannon

Issue #14646: __import__() now sets __loader__ if need be.

importlib.util.module_for_loader also will set __loader__ along with
__package__. This is in conjunction to a forthcoming update to PEP 302
which will make these two attributes required for loaders to set.
parent daa4d0d0
...@@ -697,22 +697,30 @@ an :term:`importer`. ...@@ -697,22 +697,30 @@ an :term:`importer`.
signature taking two positional arguments signature taking two positional arguments
(e.g. ``load_module(self, module)``) for which the second argument (e.g. ``load_module(self, module)``) for which the second argument
will be the module **object** to be used by the loader. will be the module **object** to be used by the loader.
Note that the decorator Note that the decorator will not work on static methods because of the
will not work on static methods because of the assumption of two assumption of two arguments.
arguments.
The decorated method will take in the **name** of the module to be loaded The decorated method will take in the **name** of the module to be loaded
as expected for a :term:`loader`. If the module is not found in as expected for a :term:`loader`. If the module is not found in
:data:`sys.modules` then a new one is constructed with its :data:`sys.modules` then a new one is constructed with its
:attr:`__name__` attribute set. Otherwise the module found in :attr:`__name__` attribute set to **name**, :attr:`__loader__` set to
:data:`sys.modules` will be passed into the method. If an **self**, and :attr:`__package__` set if
exception is raised by the decorated method and a module was added to :meth:`importlib.abc.InspectLoader.is_package` is defined for **self** and
does not raise :exc:`ImportError` for **name**. If a new module is not
needed then the module found in :data:`sys.modules` will be passed into the
method.
If an exception is raised by the decorated method and a module was added to
:data:`sys.modules` it will be removed to prevent a partially initialized :data:`sys.modules` it will be removed to prevent a partially initialized
module from being in left in :data:`sys.modules`. If the module was already module from being in left in :data:`sys.modules`. If the module was already
in :data:`sys.modules` then it is left alone. in :data:`sys.modules` then it is left alone.
Use of this decorator handles all the details of which module object a Use of this decorator handles all the details of which module object a
loader should initialize as specified by :pep:`302`. loader should initialize as specified by :pep:`302` as best as possible.
.. versionchanged:: 3.3
:attr:`__loader__` and :attr:`__package__` are automatically set
(when possible).
.. decorator:: set_loader .. decorator:: set_loader
...@@ -722,6 +730,12 @@ an :term:`importer`. ...@@ -722,6 +730,12 @@ an :term:`importer`.
does nothing. It is assumed that the first positional argument to the does nothing. It is assumed that the first positional argument to the
wrapped method (i.e. ``self``) is what :attr:`__loader__` should be set to. wrapped method (i.e. ``self``) is what :attr:`__loader__` should be set to.
.. note::
It is recommended that :func:`module_for_loader` be used over this
decorator as it subsumes this functionality.
.. decorator:: set_package .. decorator:: set_package
A :term:`decorator` for a :term:`loader` to set the :attr:`__package__` A :term:`decorator` for a :term:`loader` to set the :attr:`__package__`
...@@ -736,3 +750,7 @@ an :term:`importer`. ...@@ -736,3 +750,7 @@ an :term:`importer`.
attribute set and thus can be used by global level code during attribute set and thus can be used by global level code during
initialization. initialization.
.. note::
It is recommended that :func:`module_for_loader` be used over this
decorator as it subsumes this functionality.
...@@ -257,9 +257,14 @@ def module_for_loader(fxn): ...@@ -257,9 +257,14 @@ def module_for_loader(fxn):
The decorated function is passed the module to use instead of the module The decorated function is passed the module to use instead of the module
name. The module passed in to the function is either from sys.modules if name. The module passed in to the function is either from sys.modules if
it already exists or is a new module which has __name__ set and is inserted it already exists or is a new module. If the module is new, then __name__
into sys.modules. If an exception is raised and the decorator created the is set the first argument to the method, __loader__ is set to self, and
module it is subsequently removed from sys.modules. __package__ is set accordingly (if self.is_package() is defined) will be set
before it is passed to the decorated function (if self.is_package() does
not work for the module it will be set post-load).
If an exception is raised and the decorator created the module it is
subsequently removed from sys.modules.
The decorator assumes that the decorated function takes the module name as The decorator assumes that the decorated function takes the module name as
the second argument. the second argument.
...@@ -274,7 +279,18 @@ def module_for_loader(fxn): ...@@ -274,7 +279,18 @@ def module_for_loader(fxn):
# infinite loop. # infinite loop.
module = _new_module(fullname) module = _new_module(fullname)
sys.modules[fullname] = module sys.modules[fullname] = module
module.__loader__ = self
try:
is_package = self.is_package(fullname)
except (ImportError, AttributeError):
pass
else:
if is_package:
module.__package__ = fullname
else:
module.__package__ = fullname.rpartition('.')[0]
try: try:
# If __package__ was not set above, __import__() will do it later.
return fxn(self, module, *args, **kwargs) return fxn(self, module, *args, **kwargs)
except: except:
if not is_reload: if not is_reload:
...@@ -1012,6 +1028,12 @@ def _find_and_load(name, import_): ...@@ -1012,6 +1028,12 @@ def _find_and_load(name, import_):
module.__package__ = module.__package__.rpartition('.')[0] module.__package__ = module.__package__.rpartition('.')[0]
except AttributeError: except AttributeError:
pass pass
# Set loader if need be.
if not hasattr(module, '__loader__'):
try:
module.__loader__ = loader
except AttributeError:
pass
return module return module
......
...@@ -79,6 +79,34 @@ class ModuleForLoaderTests(unittest.TestCase): ...@@ -79,6 +79,34 @@ class ModuleForLoaderTests(unittest.TestCase):
given = self.return_module(name) given = self.return_module(name)
self.assertTrue(given is module) self.assertTrue(given is module)
def test_attributes_set(self):
# __name__, __loader__, and __package__ should be set (when
# is_package() is defined; undefined implicitly tested elsewhere).
class FakeLoader:
def __init__(self, is_package):
self._pkg = is_package
def is_package(self, name):
return self._pkg
@util.module_for_loader
def load_module(self, module):
return module
name = 'pkg.mod'
with test_util.uncache(name):
loader = FakeLoader(False)
module = loader.load_module(name)
self.assertEqual(module.__name__, name)
self.assertIs(module.__loader__, loader)
self.assertEqual(module.__package__, 'pkg')
name = 'pkg.sub'
with test_util.uncache(name):
loader = FakeLoader(True)
module = loader.load_module(name)
self.assertEqual(module.__name__, name)
self.assertIs(module.__loader__, loader)
self.assertEqual(module.__package__, name)
class SetPackageTests(unittest.TestCase): class SetPackageTests(unittest.TestCase):
......
...@@ -10,6 +10,8 @@ What's New in Python 3.3.0 Alpha 3? ...@@ -10,6 +10,8 @@ What's New in Python 3.3.0 Alpha 3?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #14646: __import__() sets __loader__ if the loader did not.
- Issue #14605: No longer have implicit entries in sys.meta_path. If - Issue #14605: No longer have implicit entries in sys.meta_path. If
sys.meta_path is found to be empty, raise ImportWarning. sys.meta_path is found to be empty, raise ImportWarning.
...@@ -79,6 +81,9 @@ Core and Builtins ...@@ -79,6 +81,9 @@ Core and Builtins
Library Library
------- -------
- Issue #14646: importlib.util.module_for_loader() now sets __loader__ and
__package__ (when possible).
- Issue #14664: It is now possible to use @unittest.skip{If,Unless} on a - Issue #14664: It is now possible to use @unittest.skip{If,Unless} on a
test class that doesn't inherit from TestCase (i.e. a mixin). test class that doesn't inherit from TestCase (i.e. a mixin).
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment