Skip to content

Commit 4fcbe2e

Browse files
committed
Use importlib instead of imp for Python 3.12 support
Also remove some unused imports.
1 parent 1901c1c commit 4fcbe2e

File tree

6 files changed

+126
-210
lines changed

6 files changed

+126
-210
lines changed

src/future/backports/test/support.py

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@
2828
# import collections.abc # not present on Py2.7
2929
import re
3030
import subprocess
31-
try:
32-
from imp import cache_from_source
33-
except ImportError:
34-
from importlib.util import cache_from_source
3531
import time
3632
try:
3733
import sysconfig
@@ -344,37 +340,6 @@ def rmtree(path):
344340
if error.errno != errno.ENOENT:
345341
raise
346342

347-
def make_legacy_pyc(source):
348-
"""Move a PEP 3147 pyc/pyo file to its legacy pyc/pyo location.
349-
350-
The choice of .pyc or .pyo extension is done based on the __debug__ flag
351-
value.
352-
353-
:param source: The file system path to the source file. The source file
354-
does not need to exist, however the PEP 3147 pyc file must exist.
355-
:return: The file system path to the legacy pyc file.
356-
"""
357-
pyc_file = cache_from_source(source)
358-
up_one = os.path.dirname(os.path.abspath(source))
359-
legacy_pyc = os.path.join(up_one, source + ('c' if __debug__ else 'o'))
360-
os.rename(pyc_file, legacy_pyc)
361-
return legacy_pyc
362-
363-
def forget(modname):
364-
"""'Forget' a module was ever imported.
365-
366-
This removes the module from sys.modules and deletes any PEP 3147 or
367-
legacy .pyc and .pyo files.
368-
"""
369-
unload(modname)
370-
for dirname in sys.path:
371-
source = os.path.join(dirname, modname + '.py')
372-
# It doesn't matter if they exist or not, unlink all possible
373-
# combinations of PEP 3147 and legacy pyc and pyo files.
374-
unlink(source + 'c')
375-
unlink(source + 'o')
376-
unlink(cache_from_source(source, debug_override=True))
377-
unlink(cache_from_source(source, debug_override=False))
378343

379344
# On some platforms, should not run gui test even if it is allowed
380345
# in `use_resources'.

src/future/standard_library/__init__.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,7 @@
6262

6363
import sys
6464
import logging
65-
try:
66-
import importlib
67-
except ImportError:
68-
import imp
6965
import contextlib
70-
import types
7166
import copy
7267
import os
7368

@@ -82,6 +77,9 @@
8277

8378
from future.utils import PY2, PY3
8479

80+
if PY2:
81+
import imp
82+
8583
# The modules that are defined under the same names on Py3 but with
8684
# different contents in a significant way (e.g. submodules) are:
8785
# pickle (fast one)
@@ -300,11 +298,8 @@ def _find_and_load_module(self, name, path=None):
300298
flog.debug('What to do here?')
301299

302300
name = bits[0]
303-
try:
304-
module_info = imp.find_module(name, path)
305-
return imp.load_module(name, *module_info)
306-
except AttributeError:
307-
return importlib.import_module(name, path)
301+
module_info = imp.find_module(name, path)
302+
return imp.load_module(name, *module_info)
308303

309304

310305
class hooks(object):

src/past/translation/__init__.py

Lines changed: 107 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,7 @@
3232
Inspired by and based on ``uprefix`` by Vinay M. Sajip.
3333
"""
3434

35-
try:
36-
import imp
37-
except ImportError:
38-
import importlib
3935
import logging
40-
import marshal
4136
import os
4237
import sys
4338
import copy
@@ -46,6 +41,17 @@
4641

4742
from libfuturize import fixes
4843

44+
try:
45+
from importlib.machinery import (
46+
PathFinder,
47+
SourceFileLoader,
48+
)
49+
except ImportError:
50+
PathFinder = None
51+
SourceFileLoader = object
52+
53+
if sys.version_info[:2] < (3, 4):
54+
import imp
4955

5056
logger = logging.getLogger(__name__)
5157
logger.setLevel(logging.DEBUG)
@@ -228,6 +234,81 @@ def detect_python2(source, pathname):
228234
return False
229235

230236

237+
def transform(source, pathname):
238+
# This implementation uses lib2to3,
239+
# you can override and use something else
240+
# if that's better for you
241+
242+
# lib2to3 likes a newline at the end
243+
RTs.setup()
244+
source += '\n'
245+
try:
246+
tree = RTs._rt.refactor_string(source, pathname)
247+
except ParseError as e:
248+
if e.msg != 'bad input' or e.value != '=':
249+
raise
250+
tree = RTs._rtp.refactor_string(source, pathname)
251+
# could optimise a bit for only doing str(tree) if
252+
# getattr(tree, 'was_changed', False) returns True
253+
return str(tree)[:-1] # remove added newline
254+
255+
256+
class PastSourceFileLoader(SourceFileLoader):
257+
exclude_paths = []
258+
include_paths = []
259+
260+
def _convert_needed(self):
261+
fullname = self.name
262+
if any(fullname.startswith(path) for path in self.exclude_paths):
263+
convert = False
264+
elif any(fullname.startswith(path) for path in self.include_paths):
265+
convert = True
266+
else:
267+
convert = False
268+
return convert
269+
270+
def _exec_transformed_module(self, module):
271+
source = self.get_source(self.name)
272+
pathname = self.path
273+
if detect_python2(source, pathname):
274+
source = transform(source, pathname)
275+
code = compile(source, pathname, "exec")
276+
exec(code, module.__dict__)
277+
278+
# For Python 3.3
279+
def load_module(self, fullname):
280+
logger.debug("Running load_module for %s", fullname)
281+
if fullname in sys.modules:
282+
mod = sys.modules[fullname]
283+
else:
284+
if self._convert_needed():
285+
logger.debug("Autoconverting %s", fullname)
286+
mod = imp.new_module(fullname)
287+
sys.modules[fullname] = mod
288+
289+
# required by PEP 302
290+
mod.__file__ = self.path
291+
mod.__loader__ = self
292+
if self.is_package(fullname):
293+
mod.__path__ = []
294+
mod.__package__ = fullname
295+
else:
296+
mod.__package__ = fullname.rpartition('.')[0]
297+
self._exec_transformed_module(mod)
298+
else:
299+
mod = super().load_module(fullname)
300+
return mod
301+
302+
# For Python >=3.4
303+
def exec_module(self, module):
304+
logger.debug("Running exec_module for %s", module)
305+
if self._convert_needed():
306+
logger.debug("Autoconverting %s", self.name)
307+
self._exec_transformed_module(module)
308+
else:
309+
super().exec_module(module)
310+
311+
231312
class Py2Fixer(object):
232313
"""
233314
An import hook class that uses lib2to3 for source-to-source translation of
@@ -261,151 +342,30 @@ def exclude(self, paths):
261342
"""
262343
self.exclude_paths += paths
263344

345+
# For Python 3.3
264346
def find_module(self, fullname, path=None):
265-
logger.debug('Running find_module: {0}...'.format(fullname))
266-
if '.' in fullname:
267-
parent, child = fullname.rsplit('.', 1)
268-
if path is None:
269-
loader = self.find_module(parent, path)
270-
mod = loader.load_module(parent)
271-
path = mod.__path__
272-
fullname = child
273-
274-
# Perhaps we should try using the new importlib functionality in Python
275-
# 3.3: something like this?
276-
# thing = importlib.machinery.PathFinder.find_module(fullname, path)
277-
try:
278-
self.found = imp.find_module(fullname, path)
279-
except Exception as e:
280-
logger.debug('Py2Fixer could not find {0}')
281-
logger.debug('Exception was: {0})'.format(fullname, e))
347+
logger.debug("Running find_module: (%s, %s)", fullname, path)
348+
loader = PathFinder.find_module(fullname, path)
349+
if not loader:
350+
logger.debug("Py2Fixer could not find %s", fullname)
282351
return None
283-
self.kind = self.found[-1][-1]
284-
if self.kind == imp.PKG_DIRECTORY:
285-
self.pathname = os.path.join(self.found[1], '__init__.py')
286-
elif self.kind == imp.PY_SOURCE:
287-
self.pathname = self.found[1]
288-
return self
289-
290-
def transform(self, source):
291-
# This implementation uses lib2to3,
292-
# you can override and use something else
293-
# if that's better for you
294-
295-
# lib2to3 likes a newline at the end
296-
RTs.setup()
297-
source += '\n'
298-
try:
299-
tree = RTs._rt.refactor_string(source, self.pathname)
300-
except ParseError as e:
301-
if e.msg != 'bad input' or e.value != '=':
302-
raise
303-
tree = RTs._rtp.refactor_string(source, self.pathname)
304-
# could optimise a bit for only doing str(tree) if
305-
# getattr(tree, 'was_changed', False) returns True
306-
return str(tree)[:-1] # remove added newline
307-
308-
def load_module(self, fullname):
309-
logger.debug('Running load_module for {0}...'.format(fullname))
310-
if fullname in sys.modules:
311-
mod = sys.modules[fullname]
312-
else:
313-
if self.kind in (imp.PY_COMPILED, imp.C_EXTENSION, imp.C_BUILTIN,
314-
imp.PY_FROZEN):
315-
convert = False
316-
# elif (self.pathname.startswith(_stdlibprefix)
317-
# and 'site-packages' not in self.pathname):
318-
# # We assume it's a stdlib package in this case. Is this too brittle?
319-
# # Please file a bug report at https://github.com/PythonCharmers/python-future
320-
# # if so.
321-
# convert = False
322-
# in theory, other paths could be configured to be excluded here too
323-
elif any([fullname.startswith(path) for path in self.exclude_paths]):
324-
convert = False
325-
elif any([fullname.startswith(path) for path in self.include_paths]):
326-
convert = True
327-
else:
328-
convert = False
329-
if not convert:
330-
logger.debug('Excluded {0} from translation'.format(fullname))
331-
mod = imp.load_module(fullname, *self.found)
332-
else:
333-
logger.debug('Autoconverting {0} ...'.format(fullname))
334-
mod = imp.new_module(fullname)
335-
sys.modules[fullname] = mod
336-
337-
# required by PEP 302
338-
mod.__file__ = self.pathname
339-
mod.__name__ = fullname
340-
mod.__loader__ = self
341-
342-
# This:
343-
# mod.__package__ = '.'.join(fullname.split('.')[:-1])
344-
# seems to result in "SystemError: Parent module '' not loaded,
345-
# cannot perform relative import" for a package's __init__.py
346-
# file. We use the approach below. Another option to try is the
347-
# minimal load_module pattern from the PEP 302 text instead.
348-
349-
# Is the test in the next line more or less robust than the
350-
# following one? Presumably less ...
351-
# ispkg = self.pathname.endswith('__init__.py')
352-
353-
if self.kind == imp.PKG_DIRECTORY:
354-
mod.__path__ = [ os.path.dirname(self.pathname) ]
355-
mod.__package__ = fullname
356-
else:
357-
#else, regular module
358-
mod.__path__ = []
359-
mod.__package__ = fullname.rpartition('.')[0]
352+
loader.__class__ = PastSourceFileLoader
353+
loader.exclude_paths = self.exclude_paths
354+
loader.include_paths = self.include_paths
355+
return loader
356+
357+
# For Python >=3.4
358+
def find_spec(self, fullname, path=None, target=None):
359+
logger.debug("Running find_spec: (%s, %s, %s)", fullname, path, target)
360+
spec = PathFinder.find_spec(fullname, path, target)
361+
if not spec:
362+
logger.debug("Py2Fixer could not find %s", fullname)
363+
return None
364+
spec.loader.__class__ = PastSourceFileLoader
365+
spec.loader.exclude_paths = self.exclude_paths
366+
spec.loader.include_paths = self.include_paths
367+
return spec
360368

361-
try:
362-
cachename = imp.cache_from_source(self.pathname)
363-
if not os.path.exists(cachename):
364-
update_cache = True
365-
else:
366-
sourcetime = os.stat(self.pathname).st_mtime
367-
cachetime = os.stat(cachename).st_mtime
368-
update_cache = cachetime < sourcetime
369-
# # Force update_cache to work around a problem with it being treated as Py3 code???
370-
# update_cache = True
371-
if not update_cache:
372-
with open(cachename, 'rb') as f:
373-
data = f.read()
374-
try:
375-
code = marshal.loads(data)
376-
except Exception:
377-
# pyc could be corrupt. Regenerate it
378-
update_cache = True
379-
if update_cache:
380-
if self.found[0]:
381-
source = self.found[0].read()
382-
elif self.kind == imp.PKG_DIRECTORY:
383-
with open(self.pathname) as f:
384-
source = f.read()
385-
386-
if detect_python2(source, self.pathname):
387-
source = self.transform(source)
388-
389-
code = compile(source, self.pathname, 'exec')
390-
391-
dirname = os.path.dirname(cachename)
392-
try:
393-
if not os.path.exists(dirname):
394-
os.makedirs(dirname)
395-
with open(cachename, 'wb') as f:
396-
data = marshal.dumps(code)
397-
f.write(data)
398-
except Exception: # could be write-protected
399-
pass
400-
exec(code, mod.__dict__)
401-
except Exception as e:
402-
# must remove module from sys.modules
403-
del sys.modules[fullname]
404-
raise # keep it simple
405-
406-
if self.found[0]:
407-
self.found[0].close()
408-
return mod
409369

410370
_hook = Py2Fixer()
411371

tests/test_future/test_standard_library.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import sys
1111
import tempfile
12-
import os
1312
import copy
1413
import textwrap
1514
from subprocess import CalledProcessError
@@ -448,13 +447,11 @@ def test_reload(self):
448447
reload has been moved to the imp module
449448
"""
450449
try:
451-
import imp
452-
imp.reload(imp)
453-
self.assertTrue(True)
450+
from importlib import reload
454451
except ImportError:
455-
import importlib
456-
importlib.reload(importlib)
457-
self.assertTrue(True)
452+
from imp import reload
453+
reload(sys)
454+
self.assertTrue(True)
458455

459456
def test_install_aliases(self):
460457
"""

0 commit comments

Comments
 (0)