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.
This commit is contained in:
Brett Cannon 2013-03-13 10:41:36 -07:00
parent aa73a1c9c9
commit 4802becb16
8 changed files with 3446 additions and 3378 deletions

View File

@ -774,6 +774,10 @@ an :term:`importer`.
It is recommended that :func:`module_for_loader` be used over this It is recommended that :func:`module_for_loader` be used over this
decorator as it subsumes this functionality. 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 .. decorator:: set_package

View File

@ -369,16 +369,18 @@ Loaders must satisfy the following requirements:
* The ``__loader__`` attribute must be set to the loader object that loaded * The ``__loader__`` attribute must be set to the loader object that loaded
the module. This is mostly for introspection and reloading, but can be the module. This is mostly for introspection and reloading, but can be
used for additional loader-specific functionality, for example getting 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 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 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__`` more appropriate value **after** the module has been imported.
value should be set to its ``__name__``. When the module is not a package, When the module is a package, its ``__package__`` value should be set to its
``__package__`` should be set to the empty string for top-level modules, or ``__name__``. When the module is not a package, ``__package__`` should be
for submodules, to the parent package's name. See :pep:`366` for further set to the empty string for top-level modules, or for submodules, to the
details. parent package's name. See :pep:`366` for further details.
This attribute is used instead of ``__name__`` to calculate explicit This attribute is used instead of ``__name__`` to calculate explicit
relative imports for main modules, as defined in :pep:`366`. relative imports for main modules, as defined in :pep:`366`.

View File

@ -494,7 +494,7 @@ def set_loader(fxn):
"""Set __loader__ on the returned module.""" """Set __loader__ on the returned module."""
def set_loader_wrapper(self, *args, **kwargs): def set_loader_wrapper(self, *args, **kwargs):
module = fxn(self, *args, **kwargs) module = fxn(self, *args, **kwargs)
if not hasattr(module, '__loader__'): if getattr(module, '__loader__', None) is None:
module.__loader__ = self module.__loader__ = self
return module return module
_wrap(set_loader_wrapper, fxn) _wrap(set_loader_wrapper, fxn)
@ -875,12 +875,9 @@ class _LoaderBasics:
module.__cached__ = module.__file__ module.__cached__ = module.__file__
else: else:
module.__cached__ = module.__file__ module.__cached__ = module.__file__
module.__package__ = name
if self.is_package(name): if self.is_package(name):
module.__path__ = [_path_split(module.__file__)[0]] module.__path__ = [_path_split(module.__file__)[0]]
else: # __package__ and __loader set by @module_for_loader.
module.__package__ = module.__package__.rpartition('.')[0]
module.__loader__ = self
_call_with_frames_removed(exec, code_object, module.__dict__) _call_with_frames_removed(exec, code_object, module.__dict__)
return module return module
@ -1551,7 +1548,7 @@ def _find_and_load_unlocked(name, import_):
except AttributeError: except AttributeError:
pass pass
# Set loader if need be. # Set loader if need be.
if not hasattr(module, '__loader__'): if getattr(module, '__loader__', None) is None:
try: try:
module.__loader__ = loader module.__loader__ = loader
except AttributeError: except AttributeError:

View File

@ -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()

View File

@ -162,6 +162,37 @@ class SetPackageTests(unittest.TestCase):
self.assertEqual(wrapped.__qualname__, fxn.__qualname__) 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): class ResolveNameTests(unittest.TestCase):
"""Tests importlib.util.resolve_name().""" """Tests importlib.util.resolve_name()."""
@ -195,14 +226,5 @@ class ResolveNameTests(unittest.TestCase):
util.resolve_name('..bacon', 'spam') util.resolve_name('..bacon', 'spam')
def test_main():
from test import support
support.run_unittest(
ModuleForLoaderTests,
SetPackageTests,
ResolveNameTests
)
if __name__ == '__main__': if __name__ == '__main__':
test_main() unittest.main()

View File

@ -339,6 +339,7 @@ David Ely
Jeff Epler Jeff Epler
Jeff McNeil Jeff McNeil
Tom Epperly Tom Epperly
Gökcen Eraslan
Stoffel Erasmus Stoffel Erasmus
Jürgen A. Erhard Jürgen A. Erhard
Michael Ernst Michael Ernst

View File

@ -10,6 +10,9 @@ What's New in Python 3.4.0 Alpha 1?
Core and Builtins 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 #17327: Add PyDict_SetDefault.
- Issue #17032: The "global" in the "NameError: global name 'x' is not defined" - Issue #17032: The "global" in the "NameError: global name 'x' is not defined"

File diff suppressed because it is too large Load Diff