|
32 | 32 | Inspired by and based on ``uprefix`` by Vinay M. Sajip.
|
33 | 33 | """
|
34 | 34 |
|
35 |
| -try: |
36 |
| - import imp |
37 |
| -except ImportError: |
38 |
| - import importlib |
39 | 35 | import logging
|
40 |
| -import marshal |
41 | 36 | import os
|
42 | 37 | import sys
|
43 | 38 | import copy
|
|
46 | 41 |
|
47 | 42 | from libfuturize import fixes
|
48 | 43 |
|
| 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 |
49 | 55 |
|
50 | 56 | logger = logging.getLogger(__name__)
|
51 | 57 | logger.setLevel(logging.DEBUG)
|
@@ -228,6 +234,81 @@ def detect_python2(source, pathname):
|
228 | 234 | return False
|
229 | 235 |
|
230 | 236 |
|
| 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 | + |
231 | 312 | class Py2Fixer(object):
|
232 | 313 | """
|
233 | 314 | An import hook class that uses lib2to3 for source-to-source translation of
|
@@ -261,151 +342,30 @@ def exclude(self, paths):
|
261 | 342 | """
|
262 | 343 | self.exclude_paths += paths
|
263 | 344 |
|
| 345 | + # For Python 3.3 |
264 | 346 | 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) |
282 | 351 | 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 |
360 | 368 |
|
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 |
409 | 369 |
|
410 | 370 | _hook = Py2Fixer()
|
411 | 371 |
|
|
0 commit comments