1from collections import namedtuple 2 3from pkg_resources import iter_entry_points 4 5from flake8_import_order import ClassifiedImport, ImportType, NewLine 6 7Error = namedtuple('Error', ['lineno', 'code', 'message']) 8 9 10def list_entry_points(): 11 return iter_entry_points('flake8_import_order.styles') 12 13 14def lookup_entry_point(name): 15 try: 16 return next(iter_entry_points('flake8_import_order.styles', name=name)) 17 except StopIteration: 18 raise LookupError('Unknown style {}'.format(name)) 19 20 21class Style(object): 22 23 accepts_application_package_names = False 24 25 def __init__(self, nodes): 26 self.nodes = nodes 27 28 def check(self): 29 previous = None 30 previous_import = None 31 for current in self.nodes: 32 if isinstance(current, ClassifiedImport): 33 for error in self._check(previous_import, previous, current): 34 yield error 35 previous_import = current 36 previous = current 37 38 def _check(self, previous_import, previous, current_import): 39 for error in self._check_I666(current_import): 40 yield error 41 for error in self._check_I101(current_import): 42 yield error 43 if previous_import is not None: 44 for error in self._check_I100(previous_import, current_import): 45 yield error 46 for error in self._check_I201( 47 previous_import, previous, current_import, 48 ): 49 yield error 50 for error in self._check_I202( 51 previous_import, previous, current_import, 52 ): 53 yield error 54 55 def _check_I666(self, current_import): # noqa: N802 56 if current_import.type == ImportType.MIXED: 57 yield Error( 58 current_import.lineno, 59 'I666', 60 'Import statement mixes groups', 61 ) 62 63 def _check_I101(self, current_import): # noqa: N802 64 correct_names = self.sorted_names(current_import.names) 65 if correct_names != current_import.names: 66 corrected = ', '.join(correct_names) 67 yield Error( 68 current_import.lineno, 69 'I101', 70 "Imported names are in the wrong order. " 71 "Should be {0}".format(corrected), 72 ) 73 74 def _check_I100(self, previous_import, current_import): # noqa: N802 75 previous_key = self.import_key(previous_import) 76 current_key = self.import_key(current_import) 77 if previous_key > current_key: 78 message = ( 79 "Import statements are in the wrong order. " 80 "'{0}' should be before '{1}'" 81 ).format( 82 self._explain_import(current_import), 83 self._explain_import(previous_import), 84 ) 85 same_section = self.same_section( 86 previous_import, current_import, 87 ) 88 if not same_section: 89 message = "{0} and in a different group.".format(message) 90 yield Error(current_import.lineno, 'I100', message) 91 92 def _check_I201(self, previous_import, previous, current_import): # noqa: N802,E501 93 same_section = self.same_section(previous_import, current_import) 94 has_newline = isinstance(previous, NewLine) 95 if not same_section and not has_newline: 96 yield Error( 97 current_import.lineno, 98 'I201', 99 "Missing newline between import groups. {}".format( 100 self._explain_grouping( 101 current_import, previous_import, 102 ) 103 ), 104 ) 105 106 def _check_I202(self, previous_import, previous, current_import): # noqa: N802,E501 107 same_section = self.same_section(previous_import, current_import) 108 has_newline = isinstance(previous, NewLine) 109 if same_section and has_newline: 110 yield Error( 111 current_import.lineno, 112 'I202', 113 "Additional newline in a group of imports. {}".format( 114 self._explain_grouping( 115 current_import, previous_import, 116 ) 117 ), 118 ) 119 120 @staticmethod 121 def sorted_names(names): 122 return names 123 124 @staticmethod 125 def import_key(import_): 126 return (import_.type,) 127 128 @staticmethod 129 def same_section(previous, current): 130 same_type = current.type == previous.type 131 both_first = ( 132 {previous.type, current.type} <= { 133 ImportType.APPLICATION, ImportType.APPLICATION_RELATIVE, 134 } 135 ) 136 return same_type or both_first 137 138 @staticmethod 139 def _explain_import(import_): 140 if import_.is_from: 141 return "from {}{} import {}".format( 142 import_.level * '.', 143 ', '.join(import_.modules), 144 ', '.join(import_.names), 145 ) 146 else: 147 return "import {}".format(', '.join(import_.modules)) 148 149 @staticmethod 150 def _explain_grouping(current_import, previous_import): 151 return ( 152 "'{0}' is identified as {1} and " 153 "'{2}' is identified as {3}." 154 ).format( 155 Style._explain_import(current_import), 156 current_import.type.name.title().replace('_', ' '), 157 Style._explain_import(previous_import), 158 previous_import.type.name.title().replace('_', ' '), 159 ) 160 161 162class PEP8(Style): 163 pass 164 165 166class Google(Style): 167 168 @staticmethod 169 def sorted_names(names): 170 return sorted(names, key=Google.name_key) 171 172 @staticmethod 173 def name_key(name): 174 return (name.lower(), name) 175 176 @staticmethod 177 def import_key(import_): 178 modules = [Google.name_key(module) for module in import_.modules] 179 names = [Google.name_key(name) for name in import_.names] 180 return (import_.type, import_.level, modules, names) 181 182 183class AppNexus(Google): 184 accepts_application_package_names = True 185 186 187class Smarkets(Style): 188 189 @staticmethod 190 def sorted_names(names): 191 return sorted(names, key=Smarkets.name_key) 192 193 @staticmethod 194 def name_key(name): 195 return (name.lower(), name) 196 197 @staticmethod 198 def import_key(import_): 199 modules = [Smarkets.name_key(module) for module in import_.modules] 200 names = [Smarkets.name_key(name) for name in import_.names] 201 return (import_.type, import_.is_from, import_.level, modules, names) 202 203 204class Edited(Smarkets): 205 accepts_application_package_names = True 206 207 def _check_I202(self, previous_import, previous, current_import): # noqa: N802,E501 208 same_section = self.same_section(previous_import, current_import) 209 has_newline = isinstance(previous, NewLine) 210 optional_split = current_import.is_from and not previous_import.is_from 211 if same_section and has_newline and not optional_split: 212 yield Error( 213 current_import.lineno, 214 'I202', 215 "Additional newline in a group of imports. {}".format( 216 self._explain_grouping( 217 current_import, previous_import, 218 ) 219 ), 220 ) 221 222 @staticmethod 223 def same_section(previous, current): 224 return current.type == previous.type 225 226 227class PyCharm(Smarkets): 228 @staticmethod 229 def sorted_names(names): 230 return sorted(names) 231 232 @staticmethod 233 def import_key(import_): 234 return (import_.type, import_.is_from, import_.level, import_.modules, import_.names) 235 236 237class Cryptography(Style): 238 239 @staticmethod 240 def sorted_names(names): 241 return sorted(names) 242 243 @staticmethod 244 def import_key(import_): 245 if import_.type in {ImportType.THIRD_PARTY, ImportType.APPLICATION}: 246 return ( 247 import_.type, import_.package, import_.is_from, 248 import_.level, import_.modules, import_.names, 249 ) 250 else: 251 return ( 252 import_.type, '', import_.is_from, import_.level, 253 import_.modules, import_.names, 254 ) 255 256 @staticmethod 257 def same_section(previous, current): 258 app_or_third = current.type in { 259 ImportType.THIRD_PARTY, ImportType.APPLICATION, 260 } 261 same_type = current.type == previous.type 262 both_relative = ( 263 previous.type == current.type == ImportType.APPLICATION_RELATIVE 264 ) 265 same_package = previous.package == current.package 266 return ( 267 (not app_or_third and same_type or both_relative) or 268 (app_or_third and same_package) 269 ) 270