Issue #15056: imp.cache_from_source() and source_from_cache() raise

NotimplementedError when sys.implementation.cache_tag is None.

Thanks to Pranav Ravichandran for taking an initial stab at the patch.
This commit is contained in:
Brett Cannon 2012-07-09 13:58:07 -04:00
parent bf7eab077f
commit 19a2f5961c
6 changed files with 3875 additions and 3802 deletions

View file

@ -180,14 +180,19 @@ file paths.
source *path*. For example, if *path* is ``/foo/bar/baz.py`` the return source *path*. For example, if *path* is ``/foo/bar/baz.py`` the return
value would be ``/foo/bar/__pycache__/baz.cpython-32.pyc`` for Python 3.2. value would be ``/foo/bar/__pycache__/baz.cpython-32.pyc`` for Python 3.2.
The ``cpython-32`` string comes from the current magic tag (see The ``cpython-32`` string comes from the current magic tag (see
:func:`get_tag`). The returned path will end in ``.pyc`` when :func:`get_tag`; if :attr:`sys.implementation.cache_tag` is not defined then
``__debug__`` is True or ``.pyo`` for an optimized Python :exc:`NotImplementedError` will be raised). The returned path will end in
``.pyc`` when ``__debug__`` is True or ``.pyo`` for an optimized Python
(i.e. ``__debug__`` is False). By passing in True or False for (i.e. ``__debug__`` is False). By passing in True or False for
*debug_override* you can override the system's value for ``__debug__`` for *debug_override* you can override the system's value for ``__debug__`` for
extension selection. extension selection.
*path* need not exist. *path* need not exist.
.. versionchanged:: 3.3
If :attr:`sys.implementation.cache_tag` is ``None``, then
:exc:`NotImplementedError` is raised.
.. function:: source_from_cache(path) .. function:: source_from_cache(path)
@ -195,7 +200,13 @@ file paths.
file path. For example, if *path* is file path. For example, if *path* is
``/foo/bar/__pycache__/baz.cpython-32.pyc`` the returned path would be ``/foo/bar/__pycache__/baz.cpython-32.pyc`` the returned path would be
``/foo/bar/baz.py``. *path* need not exist, however if it does not conform ``/foo/bar/baz.py``. *path* need not exist, however if it does not conform
to :pep:`3147` format, a ``ValueError`` is raised. to :pep:`3147` format, a ``ValueError`` is raised. If
:attr:`sys.implementation.cache_tag` is not defined,
:exc:`NotImplementedError` is raised.
.. versionchanged:: 3.3
Raise :exc:`NotImplementedError` when
:attr:`sys.implementation.cache_tag` is not defined.
.. function:: get_tag() .. function:: get_tag()
@ -203,6 +214,10 @@ file paths.
Return the :pep:`3147` magic tag string matching this version of Python's Return the :pep:`3147` magic tag string matching this version of Python's
magic number, as returned by :func:`get_magic`. magic number, as returned by :func:`get_magic`.
.. note::
You may use :attr:`sys.implementation.cache_tag` directly starting
in Python 3.3.
The following functions help interact with the import system's internal The following functions help interact with the import system's internal
locking mechanism. Locking semantics of imports are an implementation locking mechanism. Locking semantics of imports are an implementation

View file

@ -58,9 +58,12 @@ def source_from_cache(path):
The .pyc/.pyo file does not need to exist; this simply returns the path to The .pyc/.pyo file does not need to exist; this simply returns the path to
the .py file calculated to correspond to the .pyc/.pyo file. If path does the .py file calculated to correspond to the .pyc/.pyo file. If path does
not conform to PEP 3147 format, ValueError will be raised. not conform to PEP 3147 format, ValueError will be raised. If
sys.implementation.cache_tag is None then NotImplementedError is raised.
""" """
if sys.implementation.cache_tag is None:
raise NotImplementedError('sys.implementation.cache_tag is None')
head, pycache_filename = os.path.split(path) head, pycache_filename = os.path.split(path)
head, pycache = os.path.split(head) head, pycache = os.path.split(head)
if pycache != _bootstrap._PYCACHE: if pycache != _bootstrap._PYCACHE:

View file

@ -321,6 +321,8 @@ def cache_from_source(path, debug_override=None):
If debug_override is not None, then it must be a boolean and is taken as If debug_override is not None, then it must be a boolean and is taken as
the value of __debug__ instead. the value of __debug__ instead.
If sys.implementation.cache_tag is None then NotImplementedError is raised.
""" """
debug = __debug__ if debug_override is None else debug_override debug = __debug__ if debug_override is None else debug_override
if debug: if debug:
@ -329,7 +331,10 @@ def cache_from_source(path, debug_override=None):
suffixes = OPTIMIZED_BYTECODE_SUFFIXES suffixes = OPTIMIZED_BYTECODE_SUFFIXES
head, tail = _path_split(path) head, tail = _path_split(path)
base_filename, sep, _ = tail.partition('.') base_filename, sep, _ = tail.partition('.')
filename = ''.join([base_filename, sep, _TAG, suffixes[0]]) tag = sys.implementation.cache_tag
if tag is None:
raise NotImplementedError('sys.implementation.cache_tag is None')
filename = ''.join([base_filename, sep, tag, suffixes[0]])
return _path_join(head, _PYCACHE, filename) return _path_join(head, _PYCACHE, filename)
@ -649,7 +654,10 @@ class _LoaderBasics:
code_object = self.get_code(name) code_object = self.get_code(name)
module.__file__ = self.get_filename(name) module.__file__ = self.get_filename(name)
if not sourceless: if not sourceless:
module.__cached__ = cache_from_source(module.__file__) try:
module.__cached__ = cache_from_source(module.__file__)
except NotImplementedError:
module.__cached__ = module.__file__
else: else:
module.__cached__ = module.__file__ module.__cached__ = module.__file__
module.__package__ = name module.__package__ = name
@ -718,9 +726,12 @@ class SourceLoader(_LoaderBasics):
""" """
source_path = self.get_filename(fullname) source_path = self.get_filename(fullname)
bytecode_path = cache_from_source(source_path)
source_mtime = None source_mtime = None
if bytecode_path is not None: try:
bytecode_path = cache_from_source(source_path)
except NotImplementedError:
bytecode_path = None
else:
try: try:
st = self.path_stats(source_path) st = self.path_stats(source_path)
except NotImplementedError: except NotImplementedError:
@ -1417,7 +1428,6 @@ def __import__(name, globals={}, locals={}, fromlist=[], level=0):
_MAGIC_NUMBER = None # Set in _setup() _MAGIC_NUMBER = None # Set in _setup()
_TAG = None # Set in _setup()
def _setup(sys_module, _imp_module): def _setup(sys_module, _imp_module):
@ -1479,7 +1489,6 @@ def _setup(sys_module, _imp_module):
# Constants # Constants
setattr(self_module, '_relax_case', _make_relax_case()) setattr(self_module, '_relax_case', _make_relax_case())
setattr(self_module, '_MAGIC_NUMBER', _imp_module.get_magic()) setattr(self_module, '_MAGIC_NUMBER', _imp_module.get_magic())
setattr(self_module, '_TAG', sys.implementation.cache_tag)
if builtin_os == 'nt': if builtin_os == 'nt':
SOURCE_SUFFIXES.append('.pyw') SOURCE_SUFFIXES.append('.pyw')

View file

@ -231,6 +231,8 @@ class PEP3147Tests(unittest.TestCase):
tag = imp.get_tag() tag = imp.get_tag()
@unittest.skipUnless(sys.implementation.cache_tag is not None,
'requires sys.implementation.cache_tag not be None')
def test_cache_from_source(self): def test_cache_from_source(self):
# Given the path to a .py file, return the path to its PEP 3147 # Given the path to a .py file, return the path to its PEP 3147
# defined .pyc file (i.e. under __pycache__). # defined .pyc file (i.e. under __pycache__).
@ -239,6 +241,12 @@ class PEP3147Tests(unittest.TestCase):
'qux.{}.pyc'.format(self.tag)) 'qux.{}.pyc'.format(self.tag))
self.assertEqual(imp.cache_from_source(path, True), expect) self.assertEqual(imp.cache_from_source(path, True), expect)
def test_cache_from_source_no_cache_tag(self):
# Non cache tag means NotImplementedError.
with support.swap_attr(sys.implementation, 'cache_tag', None):
with self.assertRaises(NotImplementedError):
imp.cache_from_source('whatever.py')
def test_cache_from_source_no_dot(self): def test_cache_from_source_no_dot(self):
# Directory with a dot, filename without dot. # Directory with a dot, filename without dot.
path = os.path.join('foo.bar', 'file') path = os.path.join('foo.bar', 'file')
@ -283,6 +291,9 @@ class PEP3147Tests(unittest.TestCase):
imp.cache_from_source('\\foo\\bar\\baz/qux.py', True), imp.cache_from_source('\\foo\\bar\\baz/qux.py', True),
'\\foo\\bar\\baz\\__pycache__\\qux.{}.pyc'.format(self.tag)) '\\foo\\bar\\baz\\__pycache__\\qux.{}.pyc'.format(self.tag))
@unittest.skipUnless(sys.implementation.cache_tag is not None,
'requires sys.implementation.cache_tag to not be '
'None')
def test_source_from_cache(self): def test_source_from_cache(self):
# Given the path to a PEP 3147 defined .pyc file, return the path to # Given the path to a PEP 3147 defined .pyc file, return the path to
# its source. This tests the good path. # its source. This tests the good path.
@ -291,6 +302,13 @@ class PEP3147Tests(unittest.TestCase):
expect = os.path.join('foo', 'bar', 'baz', 'qux.py') expect = os.path.join('foo', 'bar', 'baz', 'qux.py')
self.assertEqual(imp.source_from_cache(path), expect) self.assertEqual(imp.source_from_cache(path), expect)
def test_source_from_cache_no_cache_tag(self):
# If sys.implementation.cache_tag is None, raise NotImplementedError.
path = os.path.join('blah', '__pycache__', 'whatever.pyc')
with support.swap_attr(sys.implementation, 'cache_tag', None):
with self.assertRaises(NotImplementedError):
imp.source_from_cache(path)
def test_source_from_cache_bad_path(self): def test_source_from_cache_bad_path(self):
# When the path to a pyc file is not in PEP 3147 format, a ValueError # When the path to a pyc file is not in PEP 3147 format, a ValueError
# is raised. # is raised.

View file

@ -31,6 +31,9 @@ Core and Builtins
Library Library
------- -------
- Issue #15056: imp.cache_from_source() and source_from_cache() raise
NotImplementedError when sys.implementation.cache_tag is set to None.
- Issue #15256: Grammatical mistake in exception raised by imp.find_module(). - Issue #15256: Grammatical mistake in exception raised by imp.find_module().
- Issue #5931: wsgiref environ variable SERVER_SOFTWARE will specify an - Issue #5931: wsgiref environ variable SERVER_SOFTWARE will specify an

File diff suppressed because it is too large Load diff