1# Copyright 2017 Google Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Logic for resolving import paths.""" 16 17import collections 18import logging 19import sys 20 21from . import import_finder 22from . import utils 23 24 25class ParseError(Exception): 26 """Error parsing a file with python.""" 27 pass 28 29 30class ImportStatement(collections.namedtuple( 31 'ImportStatement', 32 ['name', 'new_name', 'is_from', 'is_star', 'source'])): 33 """A Python import statement, such as "import foo as bar".""" 34 35 def __new__(cls, name, new_name=None, is_from=False, is_star=False, 36 source=None): 37 """Create a new ImportStatement. 38 39 Args: 40 name: Name of the module to be imported. E.g. "sys". 41 new_name: What the module is renamed to. The "y" in 42 "import x as y". 43 is_from: If the last part of the name (the "z" in "x.y.z") can 44 be an element within a module, instead of a module itself. Happens 45 e.g. for "from sys import argv". 46 is_star: If this is an import of the form "from x import *". 47 source: The path to the file as resolved by python. 48 Returns: 49 A new ImportStatement instance. 50 """ 51 return super(ImportStatement, cls).__new__( 52 cls, name, new_name or name, is_from, is_star, source) 53 54 def is_relative(self): 55 return self.name.startswith('.') 56 57 def __str__(self): 58 if self.is_star: 59 assert self.name == self.new_name 60 assert self.is_from 61 return 'from ' + self.name + ' import *' 62 if self.is_from: 63 try: 64 left, right = self.name.rsplit('.', 2) 65 except ValueError: 66 left, right = self.name, '' 67 module = left + '[.' + right + ']' 68 else: 69 module = self.name 70 if self.new_name != self.name: 71 return 'import ' + module + ' as ' + self.new_name 72 else: 73 return 'import ' + module 74 75 76def get_imports(filename, python_version): 77 if python_version == sys.version_info[0:2]: 78 # Invoke import_finder directly 79 try: 80 imports = import_finder.get_imports(filename) 81 except Exception: 82 raise ParseError(filename) 83 else: 84 # Call the appropriate python version in a subprocess 85 f = sys.modules['importlab.import_finder'].__file__ 86 if f.rsplit('.', 1)[-1] == 'pyc': 87 # In host Python 2, importlab ships with .pyc files. 88 f = f[:-1] 89 ret, stdout, stderr = utils.run_py_file(python_version, f, filename) 90 if not ret: 91 if sys.version_info[0] == 3: 92 stdout = stdout.decode('ascii') 93 imports = import_finder.read_imports(stdout) 94 else: 95 if sys.version_info[0] == 3: 96 stderr = stderr.decode('ascii') 97 logging.info(stderr) 98 raise ParseError(filename) 99 return [ImportStatement(*imp) for imp in imports] 100