Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

isort.py 52KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. """isort.py.
  2. Exposes a simple library to sort through imports within Python code
  3. usage:
  4. SortImports(file_name)
  5. or:
  6. sorted = SortImports(file_contents=file_contents).output
  7. Copyright (C) 2013 Timothy Edmund Crosley
  8. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  9. documentation files (the "Software"), to deal in the Software without restriction, including without limitation
  10. the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
  11. to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  12. The above copyright notice and this permission notice shall be included in all copies or
  13. substantial portions of the Software.
  14. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  15. TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  16. THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  17. CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  18. OTHER DEALINGS IN THE SOFTWARE.
  19. """
  20. from __future__ import absolute_import, division, print_function, unicode_literals
  21. import copy
  22. import io
  23. import itertools
  24. import os
  25. import re
  26. import sys
  27. import sysconfig
  28. from collections import OrderedDict, namedtuple
  29. from datetime import datetime
  30. from difflib import unified_diff
  31. from fnmatch import fnmatch
  32. from glob import glob
  33. from . import settings
  34. from .natural import nsorted
  35. from .pie_slice import OrderedSet, input, itemsview
  36. KNOWN_SECTION_MAPPING = {
  37. 'STDLIB': 'STANDARD_LIBRARY',
  38. 'FUTURE': 'FUTURE_LIBRARY',
  39. 'FIRSTPARTY': 'FIRST_PARTY',
  40. 'THIRDPARTY': 'THIRD_PARTY',
  41. }
  42. class SortImports(object):
  43. incorrectly_sorted = False
  44. skipped = False
  45. def __init__(self, file_path=None, file_contents=None, write_to_stdout=False, check=False,
  46. show_diff=False, settings_path=None, ask_to_apply=False, **setting_overrides):
  47. if not settings_path and file_path:
  48. settings_path = os.path.dirname(os.path.abspath(file_path))
  49. settings_path = settings_path or os.getcwd()
  50. self.config = settings.from_path(settings_path).copy()
  51. for key, value in itemsview(setting_overrides):
  52. access_key = key.replace('not_', '').lower()
  53. # The sections config needs to retain order and can't be converted to a set.
  54. if access_key != 'sections' and type(self.config.get(access_key)) in (list, tuple):
  55. if key.startswith('not_'):
  56. self.config[access_key] = list(set(self.config[access_key]).difference(value))
  57. else:
  58. self.config[access_key] = list(set(self.config[access_key]).union(value))
  59. else:
  60. self.config[key] = value
  61. if self.config['force_alphabetical_sort']:
  62. self.config.update({'force_alphabetical_sort_within_sections': True,
  63. 'no_sections': True,
  64. 'lines_between_types': 1,
  65. 'from_first': True})
  66. indent = str(self.config['indent'])
  67. if indent.isdigit():
  68. indent = " " * int(indent)
  69. else:
  70. indent = indent.strip("'").strip('"')
  71. if indent.lower() == "tab":
  72. indent = "\t"
  73. self.config['indent'] = indent
  74. self.config['comment_prefix'] = self.config['comment_prefix'].strip("'").strip('"')
  75. self.place_imports = {}
  76. self.import_placements = {}
  77. self.remove_imports = [self._format_simplified(removal) for removal in self.config['remove_imports']]
  78. self.add_imports = [self._format_natural(addition) for addition in self.config['add_imports']]
  79. self._section_comments = ["# " + value for key, value in itemsview(self.config) if
  80. key.startswith('import_heading') and value]
  81. self.file_encoding = 'utf-8'
  82. file_name = file_path
  83. self.file_path = file_path or ""
  84. if file_path:
  85. file_path = os.path.abspath(file_path)
  86. if settings.should_skip(file_path, self.config):
  87. self.skipped = True
  88. if self.config['verbose']:
  89. print("WARNING: {0} was skipped as it's listed in 'skip' setting"
  90. " or matches a glob in 'skip_glob' setting".format(file_path))
  91. file_contents = None
  92. elif not file_contents:
  93. self.file_path = file_path
  94. self.file_encoding = coding_check(file_path)
  95. with io.open(file_path, encoding=self.file_encoding, newline='') as file_to_import_sort:
  96. file_contents = file_to_import_sort.read()
  97. if file_contents is None or ("isort:" + "skip_file") in file_contents:
  98. self.skipped = True
  99. self.output = None
  100. return
  101. if self.config['line_ending']:
  102. self.line_separator = self.config['line_ending']
  103. else:
  104. if '\r\n' in file_contents:
  105. self.line_separator = '\r\n'
  106. elif '\r' in file_contents:
  107. self.line_separator = '\r'
  108. else:
  109. self.line_separator = '\n'
  110. self.in_lines = file_contents.split(self.line_separator)
  111. self.original_length = len(self.in_lines)
  112. if (self.original_length > 1 or self.in_lines[:1] not in ([], [""])) or self.config['force_adds']:
  113. for add_import in self.add_imports:
  114. self.in_lines.append(add_import)
  115. self.number_of_lines = len(self.in_lines)
  116. self.out_lines = []
  117. self.comments = {'from': {}, 'straight': {}, 'nested': {}, 'above': {'straight': {}, 'from': {}}}
  118. self.imports = OrderedDict()
  119. self.as_map = {}
  120. section_names = self.config['sections']
  121. self.sections = namedtuple('Sections', section_names)(*[name for name in section_names])
  122. for section in itertools.chain(self.sections, self.config['forced_separate']):
  123. self.imports[section] = {'straight': OrderedSet(), 'from': OrderedDict()}
  124. self.known_patterns = []
  125. for placement in reversed(self.sections):
  126. known_placement = KNOWN_SECTION_MAPPING.get(placement, placement)
  127. config_key = 'known_{0}'.format(known_placement.lower())
  128. known_patterns = self.config.get(config_key, [])
  129. for known_pattern in known_patterns:
  130. self.known_patterns.append((re.compile('^' + known_pattern.replace('*', '.*').replace('?', '.?') + '$'),
  131. placement))
  132. self.index = 0
  133. self.import_index = -1
  134. self._first_comment_index_start = -1
  135. self._first_comment_index_end = -1
  136. self._parse()
  137. if self.import_index != -1:
  138. self._add_formatted_imports()
  139. self.length_change = len(self.out_lines) - self.original_length
  140. while self.out_lines and self.out_lines[-1].strip() == "":
  141. self.out_lines.pop(-1)
  142. self.out_lines.append("")
  143. self.output = self.line_separator.join(self.out_lines)
  144. if self.config['atomic']:
  145. try:
  146. compile(self._strip_top_comments(self.out_lines, self.line_separator), self.file_path, 'exec', 0, 1)
  147. except SyntaxError:
  148. self.output = file_contents
  149. self.incorrectly_sorted = True
  150. try:
  151. compile(self._strip_top_comments(self.in_lines, self.line_separator), self.file_path, 'exec', 0, 1)
  152. print("ERROR: {0} isort would have introduced syntax errors, please report to the project!".
  153. format(self.file_path))
  154. except SyntaxError:
  155. print("ERROR: {0} File contains syntax errors.".format(self.file_path))
  156. return
  157. if check:
  158. check_output = self.output
  159. check_against = file_contents
  160. if self.config['ignore_whitespace']:
  161. check_output = check_output.replace(self.line_separator, "").replace(" ", "")
  162. check_against = check_against.replace(self.line_separator, "").replace(" ", "")
  163. if check_output == check_against:
  164. if self.config['verbose']:
  165. print("SUCCESS: {0} Everything Looks Good!".format(self.file_path))
  166. return
  167. print("ERROR: {0} Imports are incorrectly sorted.".format(self.file_path))
  168. self.incorrectly_sorted = True
  169. if show_diff or self.config['show_diff']:
  170. self._show_diff(file_contents)
  171. elif write_to_stdout:
  172. sys.stdout.write(self.output)
  173. elif file_name and not check:
  174. if self.output == file_contents:
  175. return
  176. if ask_to_apply:
  177. self._show_diff(file_contents)
  178. answer = None
  179. while answer not in ('yes', 'y', 'no', 'n', 'quit', 'q'):
  180. answer = input("Apply suggested changes to '{0}' [y/n/q]? ".format(self.file_path)).lower()
  181. if answer in ('no', 'n'):
  182. return
  183. if answer in ('quit', 'q'):
  184. sys.exit(1)
  185. with io.open(self.file_path, encoding=self.file_encoding, mode='w', newline='') as output_file:
  186. print("Fixing {0}".format(self.file_path))
  187. output_file.write(self.output)
  188. def _show_diff(self, file_contents):
  189. for line in unified_diff(
  190. file_contents.splitlines(1),
  191. self.output.splitlines(1),
  192. fromfile=self.file_path + ':before',
  193. tofile=self.file_path + ':after',
  194. fromfiledate=str(datetime.fromtimestamp(os.path.getmtime(self.file_path))
  195. if self.file_path else datetime.now()),
  196. tofiledate=str(datetime.now())
  197. ):
  198. sys.stdout.write(line)
  199. @staticmethod
  200. def _strip_top_comments(lines, line_separator):
  201. """Strips # comments that exist at the top of the given lines"""
  202. lines = copy.copy(lines)
  203. while lines and lines[0].startswith("#"):
  204. lines = lines[1:]
  205. return line_separator.join(lines)
  206. def place_module(self, module_name):
  207. """Tries to determine if a module is a python std import, third party import, or project code:
  208. if it can't determine - it assumes it is project code
  209. """
  210. for forced_separate in self.config['forced_separate']:
  211. # Ensure all forced_separate patterns will match to end of string
  212. path_glob = forced_separate
  213. if not forced_separate.endswith('*'):
  214. path_glob = '%s*' % forced_separate
  215. if fnmatch(module_name, path_glob) or fnmatch(module_name, '.' + path_glob):
  216. return forced_separate
  217. if module_name.startswith("."):
  218. return self.sections.LOCALFOLDER
  219. # Try to find most specific placement instruction match (if any)
  220. parts = module_name.split('.')
  221. module_names_to_check = ['.'.join(parts[:first_k]) for first_k in range(len(parts), 0, -1)]
  222. for module_name_to_check in module_names_to_check:
  223. for pattern, placement in self.known_patterns:
  224. if pattern.match(module_name_to_check):
  225. return placement
  226. # Use a copy of sys.path to avoid any unintended modifications
  227. # to it - e.g. `+=` used below will change paths in place and
  228. # if not copied, consequently sys.path, which will grow unbounded
  229. # with duplicates on every call to this method.
  230. paths = list(sys.path)
  231. virtual_env = self.config.get('virtual_env') or os.environ.get('VIRTUAL_ENV')
  232. virtual_env_src = False
  233. if virtual_env:
  234. paths += [path for path in glob('{0}/lib/python*/site-packages'.format(virtual_env))
  235. if path not in paths]
  236. paths += [path for path in glob('{0}/src/*'.format(virtual_env)) if os.path.isdir(path)]
  237. virtual_env_src = '{0}/src/'.format(virtual_env)
  238. # handle case-insensitive paths on windows
  239. stdlib_lib_prefix = os.path.normcase(sysconfig.get_paths()['stdlib'])
  240. for prefix in paths:
  241. package_path = "/".join((prefix, module_name.split(".")[0]))
  242. is_module = (exists_case_sensitive(package_path + ".py") or
  243. exists_case_sensitive(package_path + ".so"))
  244. is_package = exists_case_sensitive(package_path) and os.path.isdir(package_path)
  245. if is_module or is_package:
  246. if ('site-packages' in prefix or 'dist-packages' in prefix or
  247. (virtual_env and virtual_env_src in prefix)):
  248. return self.sections.THIRDPARTY
  249. elif os.path.normcase(prefix).startswith(stdlib_lib_prefix):
  250. return self.sections.STDLIB
  251. else:
  252. return self.config['default_section']
  253. return self.config['default_section']
  254. def _get_line(self):
  255. """Returns the current line from the file while incrementing the index."""
  256. line = self.in_lines[self.index]
  257. self.index += 1
  258. return line
  259. @staticmethod
  260. def _import_type(line):
  261. """If the current line is an import line it will return its type (from or straight)"""
  262. if "isort:skip" in line:
  263. return
  264. elif line.startswith('import '):
  265. return "straight"
  266. elif line.startswith('from '):
  267. return "from"
  268. def _at_end(self):
  269. """returns True if we are at the end of the file."""
  270. return self.index == self.number_of_lines
  271. @staticmethod
  272. def _module_key(module_name, config, sub_imports=False, ignore_case=False):
  273. prefix = ""
  274. if ignore_case:
  275. module_name = str(module_name).lower()
  276. else:
  277. module_name = str(module_name)
  278. if sub_imports and config['order_by_type']:
  279. if module_name.isupper() and len(module_name) > 1:
  280. prefix = "A"
  281. elif module_name[0:1].isupper():
  282. prefix = "B"
  283. else:
  284. prefix = "C"
  285. module_name = module_name.lower()
  286. return "{0}{1}{2}".format(module_name in config['force_to_top'] and "A" or "B", prefix,
  287. config['length_sort'] and (str(len(module_name)) + ":" + module_name) or module_name)
  288. def _add_comments(self, comments, original_string=""):
  289. """
  290. Returns a string with comments added
  291. """
  292. return comments and "{0}{1} {2}".format(self._strip_comments(original_string)[0],
  293. self.config['comment_prefix'],
  294. "; ".join(comments)) or original_string
  295. def _wrap(self, line):
  296. """
  297. Returns an import wrapped to the specified line-length, if possible.
  298. """
  299. wrap_mode = self.config['multi_line_output']
  300. if len(line) > self.config['line_length'] and wrap_mode != settings.WrapModes.NOQA:
  301. line_without_comment = line
  302. comment = None
  303. if '#' in line:
  304. line_without_comment, comment = line.split('#', 1)
  305. for splitter in ("import ", ".", "as "):
  306. exp = r"\b" + re.escape(splitter) + r"\b"
  307. if re.search(exp, line_without_comment) and not line_without_comment.strip().startswith(splitter):
  308. line_parts = re.split(exp, line_without_comment)
  309. if comment:
  310. line_parts[-1] = '{0}#{1}'.format(line_parts[-1], comment)
  311. next_line = []
  312. while (len(line) + 2) > (self.config['wrap_length'] or self.config['line_length']) and line_parts:
  313. next_line.append(line_parts.pop())
  314. line = splitter.join(line_parts)
  315. if not line:
  316. line = next_line.pop()
  317. cont_line = self._wrap(self.config['indent'] + splitter.join(next_line).lstrip())
  318. if self.config['use_parentheses']:
  319. output = "{0}{1}({2}{3}{4}{5})".format(
  320. line, splitter, self.line_separator, cont_line,
  321. "," if self.config['include_trailing_comma'] else "",
  322. self.line_separator if wrap_mode in (
  323. settings.WrapModes.VERTICAL_HANGING_INDENT,
  324. settings.WrapModes.VERTICAL_GRID_GROUPED,
  325. ) else "")
  326. lines = output.split(self.line_separator)
  327. if self.config['comment_prefix'] in lines[-1] and lines[-1].endswith(')'):
  328. line, comment = lines[-1].split(self.config['comment_prefix'], 1)
  329. lines[-1] = line + ')' + self.config['comment_prefix'] + comment[:-1]
  330. return self.line_separator.join(lines)
  331. return "{0}{1}\\{2}{3}".format(line, splitter, self.line_separator, cont_line)
  332. elif len(line) > self.config['line_length'] and wrap_mode == settings.WrapModes.NOQA:
  333. if "# NOQA" not in line:
  334. return "{0}{1} NOQA".format(line, self.config['comment_prefix'])
  335. return line
  336. def _add_straight_imports(self, straight_modules, section, section_output):
  337. for module in straight_modules:
  338. if module in self.remove_imports:
  339. continue
  340. if module in self.as_map:
  341. import_definition = ''
  342. if self.config['keep_direct_and_as_imports']:
  343. import_definition = "import {0}\n".format(module)
  344. import_definition += "import {0} as {1}".format(module, self.as_map[module])
  345. else:
  346. import_definition = "import {0}".format(module)
  347. comments_above = self.comments['above']['straight'].pop(module, None)
  348. if comments_above:
  349. section_output.extend(comments_above)
  350. section_output.append(self._add_comments(self.comments['straight'].get(module), import_definition))
  351. def _add_from_imports(self, from_modules, section, section_output, ignore_case):
  352. for module in from_modules:
  353. if module in self.remove_imports:
  354. continue
  355. import_start = "from {0} import ".format(module)
  356. from_imports = list(self.imports[section]['from'][module])
  357. if not self.config['no_inline_sort'] or self.config['force_single_line']:
  358. from_imports = nsorted(from_imports, key=lambda key: self._module_key(key, self.config, True, ignore_case))
  359. if self.remove_imports:
  360. from_imports = [line for line in from_imports if not "{0}.{1}".format(module, line) in
  361. self.remove_imports]
  362. sub_modules = ['{0}.{1}'.format(module, from_import) for from_import in from_imports]
  363. as_imports = dict((from_import, "{0} as {1}".format(from_import, self.as_map[sub_module])) for
  364. from_import, sub_module in zip(from_imports, sub_modules) if sub_module in self.as_map)
  365. if self.config['combine_as_imports'] and not ("*" in from_imports and self.config['combine_star']):
  366. for from_import in copy.copy(from_imports):
  367. if from_import in as_imports:
  368. from_imports[from_imports.index(from_import)] = as_imports.pop(from_import)
  369. while from_imports:
  370. comments = self.comments['from'].pop(module, ())
  371. if "*" in from_imports and self.config['combine_star']:
  372. import_statement = self._wrap(self._add_comments(comments, "{0}*".format(import_start)))
  373. from_imports = None
  374. elif self.config['force_single_line']:
  375. import_statements = []
  376. while from_imports:
  377. from_import = from_imports.pop(0)
  378. if from_import in as_imports:
  379. from_comments = self.comments['straight'].get('{}.{}'.format(module, from_import))
  380. import_statements.append(self._add_comments(from_comments,
  381. self._wrap(import_start + as_imports[from_import])))
  382. continue
  383. single_import_line = self._add_comments(comments, import_start + from_import)
  384. comment = self.comments['nested'].get(module, {}).pop(from_import, None)
  385. if comment:
  386. single_import_line += "{0} {1}".format(comments and ";" or self.config['comment_prefix'],
  387. comment)
  388. import_statements.append(self._wrap(single_import_line))
  389. comments = None
  390. import_statement = self.line_separator.join(import_statements)
  391. else:
  392. while from_imports and from_imports[0] in as_imports:
  393. from_import = from_imports.pop(0)
  394. from_comments = self.comments['straight'].get('{}.{}'.format(module, from_import))
  395. section_output.append(self._add_comments(from_comments,
  396. self._wrap(import_start + as_imports[from_import])))
  397. star_import = False
  398. if "*" in from_imports:
  399. section_output.append(self._add_comments(comments, "{0}*".format(import_start)))
  400. from_imports.remove('*')
  401. star_import = True
  402. comments = None
  403. for from_import in copy.copy(from_imports):
  404. if from_import in as_imports:
  405. continue
  406. comment = self.comments['nested'].get(module, {}).pop(from_import, None)
  407. if comment:
  408. single_import_line = self._add_comments(comments, import_start + from_import)
  409. single_import_line += "{0} {1}".format(comments and ";" or self.config['comment_prefix'],
  410. comment)
  411. above_comments = self.comments['above']['from'].pop(module, None)
  412. if above_comments:
  413. section_output.extend(above_comments)
  414. section_output.append(self._wrap(single_import_line))
  415. from_imports.remove(from_import)
  416. comments = None
  417. from_import_section = []
  418. while from_imports and from_imports[0] not in as_imports:
  419. from_import_section.append(from_imports.pop(0))
  420. if star_import:
  421. import_statement = import_start + (", ").join(from_import_section)
  422. else:
  423. import_statement = self._add_comments(comments, import_start + (", ").join(from_import_section))
  424. if not from_import_section:
  425. import_statement = ""
  426. do_multiline_reformat = False
  427. force_grid_wrap = self.config['force_grid_wrap']
  428. if force_grid_wrap and len(from_import_section) >= force_grid_wrap:
  429. do_multiline_reformat = True
  430. if len(import_statement) > self.config['line_length'] and len(from_import_section) > 1:
  431. do_multiline_reformat = True
  432. # If line too long AND have imports AND we are NOT using GRID or VERTICAL wrap modes
  433. if (len(import_statement) > self.config['line_length'] and len(from_import_section) > 0 and
  434. self.config['multi_line_output'] not in (1, 0)):
  435. do_multiline_reformat = True
  436. if do_multiline_reformat:
  437. import_statement = self._multi_line_reformat(import_start, from_import_section, comments)
  438. if not do_multiline_reformat and len(import_statement) > self.config['line_length']:
  439. import_statement = self._wrap(import_statement)
  440. if import_statement:
  441. above_comments = self.comments['above']['from'].pop(module, None)
  442. if above_comments:
  443. section_output.extend(above_comments)
  444. section_output.append(import_statement)
  445. def _multi_line_reformat(self, import_start, from_imports, comments):
  446. output_mode = settings.WrapModes._fields[self.config['multi_line_output']].lower()
  447. formatter = getattr(self, "_output_" + output_mode, self._output_grid)
  448. dynamic_indent = " " * (len(import_start) + 1)
  449. indent = self.config['indent']
  450. line_length = self.config['wrap_length'] or self.config['line_length']
  451. import_statement = formatter(import_start, copy.copy(from_imports),
  452. dynamic_indent, indent, line_length, comments)
  453. if self.config['balanced_wrapping']:
  454. lines = import_statement.split(self.line_separator)
  455. line_count = len(lines)
  456. if len(lines) > 1:
  457. minimum_length = min(len(line) for line in lines[:-1])
  458. else:
  459. minimum_length = 0
  460. new_import_statement = import_statement
  461. while (len(lines[-1]) < minimum_length and
  462. len(lines) == line_count and line_length > 10):
  463. import_statement = new_import_statement
  464. line_length -= 1
  465. new_import_statement = formatter(import_start, copy.copy(from_imports),
  466. dynamic_indent, indent, line_length, comments)
  467. lines = new_import_statement.split(self.line_separator)
  468. if import_statement.count(self.line_separator) == 0:
  469. return self._wrap(import_statement)
  470. return import_statement
  471. def _add_formatted_imports(self):
  472. """Adds the imports back to the file.
  473. (at the index of the first import) sorted alphabetically and split between groups
  474. """
  475. sort_ignore_case = self.config['force_alphabetical_sort_within_sections']
  476. sections = itertools.chain(self.sections, self.config['forced_separate'])
  477. if self.config['no_sections']:
  478. self.imports['no_sections'] = {'straight': [], 'from': {}}
  479. for section in sections:
  480. self.imports['no_sections']['straight'].extend(self.imports[section].get('straight', []))
  481. self.imports['no_sections']['from'].update(self.imports[section].get('from', {}))
  482. sections = ('no_sections', )
  483. output = []
  484. prev_section_has_imports = False
  485. for section in sections:
  486. straight_modules = self.imports[section]['straight']
  487. straight_modules = nsorted(straight_modules, key=lambda key: self._module_key(key, self.config))
  488. from_modules = self.imports[section]['from']
  489. from_modules = nsorted(from_modules, key=lambda key: self._module_key(key, self.config))
  490. section_output = []
  491. if self.config['from_first']:
  492. self._add_from_imports(from_modules, section, section_output, sort_ignore_case)
  493. if self.config['lines_between_types'] and from_modules and straight_modules:
  494. section_output.extend([''] * self.config['lines_between_types'])
  495. self._add_straight_imports(straight_modules, section, section_output)
  496. else:
  497. self._add_straight_imports(straight_modules, section, section_output)
  498. if self.config['lines_between_types'] and from_modules and straight_modules:
  499. section_output.extend([''] * self.config['lines_between_types'])
  500. self._add_from_imports(from_modules, section, section_output, sort_ignore_case)
  501. if self.config['force_sort_within_sections']:
  502. def by_module(line):
  503. section = 'B'
  504. if line.startswith('#'):
  505. return 'AA'
  506. line = re.sub('^from ', '', line)
  507. line = re.sub('^import ', '', line)
  508. if line.split(' ')[0] in self.config['force_to_top']:
  509. section = 'A'
  510. if not self.config['order_by_type']:
  511. line = line.lower()
  512. return '{0}{1}'.format(section, line)
  513. section_output = nsorted(section_output, key=by_module)
  514. if section_output:
  515. section_name = section
  516. if section_name in self.place_imports:
  517. self.place_imports[section_name] = section_output
  518. continue
  519. section_title = self.config.get('import_heading_' + str(section_name).lower(), '')
  520. if section_title:
  521. section_comment = "# {0}".format(section_title)
  522. if section_comment not in self.out_lines[0:1] and section_comment not in self.in_lines[0:1]:
  523. section_output.insert(0, section_comment)
  524. if prev_section_has_imports and section_name in self.config['no_lines_before']:
  525. while output and output[-1].strip() == '':
  526. output.pop()
  527. output += section_output + ([''] * self.config['lines_between_sections'])
  528. prev_section_has_imports = bool(section_output)
  529. while output and output[-1].strip() == '':
  530. output.pop()
  531. while output and output[0].strip() == '':
  532. output.pop(0)
  533. output_at = 0
  534. if self.import_index < self.original_length:
  535. output_at = self.import_index
  536. elif self._first_comment_index_end != -1 and self._first_comment_index_start <= 2:
  537. output_at = self._first_comment_index_end
  538. self.out_lines[output_at:0] = output
  539. imports_tail = output_at + len(output)
  540. while [character.strip() for character in self.out_lines[imports_tail: imports_tail + 1]] == [""]:
  541. self.out_lines.pop(imports_tail)
  542. if len(self.out_lines) > imports_tail:
  543. next_construct = ""
  544. self._in_quote = False
  545. tail = self.out_lines[imports_tail:]
  546. for index, line in enumerate(tail):
  547. in_quote = self._in_quote
  548. if not self._skip_line(line) and line.strip():
  549. if line.strip().startswith("#") and len(tail) > (index + 1) and tail[index + 1].strip():
  550. continue
  551. next_construct = line
  552. break
  553. elif not in_quote:
  554. parts = line.split()
  555. if len(parts) >= 3 and parts[1] == '=' and "'" not in parts[0] and '"' not in parts[0]:
  556. next_construct = line
  557. break
  558. if self.config['lines_after_imports'] != -1:
  559. self.out_lines[imports_tail:0] = ["" for line in range(self.config['lines_after_imports'])]
  560. elif next_construct.startswith("def ") or next_construct.startswith("class ") or \
  561. next_construct.startswith("@") or next_construct.startswith("async def"):
  562. self.out_lines[imports_tail:0] = ["", ""]
  563. else:
  564. self.out_lines[imports_tail:0] = [""]
  565. if self.place_imports:
  566. new_out_lines = []
  567. for index, line in enumerate(self.out_lines):
  568. new_out_lines.append(line)
  569. if line in self.import_placements:
  570. new_out_lines.extend(self.place_imports[self.import_placements[line]])
  571. if len(self.out_lines) <= index or self.out_lines[index + 1].strip() != "":
  572. new_out_lines.append("")
  573. self.out_lines = new_out_lines
  574. def _output_grid(self, statement, imports, white_space, indent, line_length, comments):
  575. statement += "(" + imports.pop(0)
  576. while imports:
  577. next_import = imports.pop(0)
  578. next_statement = self._add_comments(comments, statement + ", " + next_import)
  579. if len(next_statement.split(self.line_separator)[-1]) + 1 > line_length:
  580. lines = ['{0}{1}'.format(white_space, next_import.split(" ")[0])]
  581. for part in next_import.split(" ")[1:]:
  582. new_line = '{0} {1}'.format(lines[-1], part)
  583. if len(new_line) + 1 > line_length:
  584. lines.append('{0}{1}'.format(white_space, part))
  585. else:
  586. lines[-1] = new_line
  587. next_import = self.line_separator.join(lines)
  588. statement = (self._add_comments(comments, "{0},".format(statement)) +
  589. "{0}{1}".format(self.line_separator, next_import))
  590. comments = None
  591. else:
  592. statement += ", " + next_import
  593. return statement + ("," if self.config['include_trailing_comma'] else "") + ")"
  594. def _output_vertical(self, statement, imports, white_space, indent, line_length, comments):
  595. first_import = self._add_comments(comments, imports.pop(0) + ",") + self.line_separator + white_space
  596. return "{0}({1}{2}{3})".format(
  597. statement,
  598. first_import,
  599. ("," + self.line_separator + white_space).join(imports),
  600. "," if self.config['include_trailing_comma'] else "",
  601. )
  602. def _output_hanging_indent(self, statement, imports, white_space, indent, line_length, comments):
  603. statement += imports.pop(0)
  604. while imports:
  605. next_import = imports.pop(0)
  606. next_statement = self._add_comments(comments, statement + ", " + next_import)
  607. if len(next_statement.split(self.line_separator)[-1]) + 3 > line_length:
  608. next_statement = (self._add_comments(comments, "{0}, \\".format(statement)) +
  609. "{0}{1}{2}".format(self.line_separator, indent, next_import))
  610. comments = None
  611. statement = next_statement
  612. return statement
  613. def _output_vertical_hanging_indent(self, statement, imports, white_space, indent, line_length, comments):
  614. return "{0}({1}{2}{3}{4}{5}{2})".format(
  615. statement,
  616. self._add_comments(comments),
  617. self.line_separator,
  618. indent,
  619. ("," + self.line_separator + indent).join(imports),
  620. "," if self.config['include_trailing_comma'] else "",
  621. )
  622. def _output_vertical_grid_common(self, statement, imports, white_space, indent, line_length, comments,
  623. need_trailing_char):
  624. statement += self._add_comments(comments, "(") + self.line_separator + indent + imports.pop(0)
  625. while imports:
  626. next_import = imports.pop(0)
  627. next_statement = "{0}, {1}".format(statement, next_import)
  628. current_line_length = len(next_statement.split(self.line_separator)[-1])
  629. if imports or need_trailing_char:
  630. # If we have more imports we need to account for a comma after this import
  631. # We might also need to account for a closing ) we're going to add.
  632. current_line_length += 1
  633. if current_line_length > line_length:
  634. next_statement = "{0},{1}{2}{3}".format(statement, self.line_separator, indent, next_import)
  635. statement = next_statement
  636. if self.config['include_trailing_comma']:
  637. statement += ','
  638. return statement
  639. def _output_vertical_grid(self, statement, imports, white_space, indent, line_length, comments):
  640. return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments,
  641. True) + ")"
  642. def _output_vertical_grid_grouped(self, statement, imports, white_space, indent, line_length, comments):
  643. return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments,
  644. True) + self.line_separator + ")"
  645. def _output_vertical_grid_grouped_no_comma(self, statement, imports, white_space, indent, line_length, comments):
  646. return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments,
  647. False) + self.line_separator + ")"
  648. def _output_noqa(self, statement, imports, white_space, indent, line_length, comments):
  649. retval = '{0}{1}'.format(statement, ', '.join(imports))
  650. comment_str = ' '.join(comments)
  651. if comments:
  652. if len(retval) + len(self.config['comment_prefix']) + 1 + len(comment_str) <= line_length:
  653. return '{0}{1} {2}'.format(retval, self.config['comment_prefix'], comment_str)
  654. else:
  655. if len(retval) <= line_length:
  656. return retval
  657. if comments:
  658. if "NOQA" in comments:
  659. return '{0}{1} {2}'.format(retval, self.config['comment_prefix'], comment_str)
  660. else:
  661. return '{0}{1} NOQA {2}'.format(retval, self.config['comment_prefix'], comment_str)
  662. else:
  663. return '{0}{1} NOQA'.format(retval, self.config['comment_prefix'])
  664. @staticmethod
  665. def _strip_comments(line, comments=None):
  666. """Removes comments from import line."""
  667. if comments is None:
  668. comments = []
  669. new_comments = False
  670. comment_start = line.find("#")
  671. if comment_start != -1:
  672. comments.append(line[comment_start + 1:].strip())
  673. new_comments = True
  674. line = line[:comment_start]
  675. return line, comments, new_comments
  676. @staticmethod
  677. def _format_simplified(import_line):
  678. import_line = import_line.strip()
  679. if import_line.startswith("from "):
  680. import_line = import_line.replace("from ", "")
  681. import_line = import_line.replace(" import ", ".")
  682. elif import_line.startswith("import "):
  683. import_line = import_line.replace("import ", "")
  684. return import_line
  685. @staticmethod
  686. def _format_natural(import_line):
  687. import_line = import_line.strip()
  688. if not import_line.startswith("from ") and not import_line.startswith("import "):
  689. if "." not in import_line:
  690. return "import {0}".format(import_line)
  691. parts = import_line.split(".")
  692. end = parts.pop(-1)
  693. return "from {0} import {1}".format(".".join(parts), end)
  694. return import_line
  695. def _skip_line(self, line):
  696. skip_line = self._in_quote
  697. if self.index == 1 and line.startswith("#"):
  698. self._in_top_comment = True
  699. return True
  700. elif self._in_top_comment:
  701. if not line.startswith("#"):
  702. self._in_top_comment = False
  703. self._first_comment_index_end = self.index - 1
  704. if '"' in line or "'" in line:
  705. index = 0
  706. if self._first_comment_index_start == -1 and (line.startswith('"') or line.startswith("'")):
  707. self._first_comment_index_start = self.index
  708. while index < len(line):
  709. if line[index] == "\\":
  710. index += 1
  711. elif self._in_quote:
  712. if line[index:index + len(self._in_quote)] == self._in_quote:
  713. self._in_quote = False
  714. if self._first_comment_index_end < self._first_comment_index_start:
  715. self._first_comment_index_end = self.index
  716. elif line[index] in ("'", '"'):
  717. long_quote = line[index:index + 3]
  718. if long_quote in ('"""', "'''"):
  719. self._in_quote = long_quote
  720. index += 2
  721. else:
  722. self._in_quote = line[index]
  723. elif line[index] == "#":
  724. break
  725. index += 1
  726. return skip_line or self._in_quote or self._in_top_comment
  727. def _strip_syntax(self, import_string):
  728. import_string = import_string.replace("_import", "[[i]]")
  729. for remove_syntax in ['\\', '(', ')', ',']:
  730. import_string = import_string.replace(remove_syntax, " ")
  731. import_list = import_string.split()
  732. for key in ('from', 'import'):
  733. if key in import_list:
  734. import_list.remove(key)
  735. import_string = ' '.join(import_list)
  736. import_string = import_string.replace("[[i]]", "_import")
  737. return import_string.replace("{ ", "{|").replace(" }", "|}")
  738. def _parse(self):
  739. """Parses a python file taking out and categorizing imports."""
  740. self._in_quote = False
  741. self._in_top_comment = False
  742. while not self._at_end():
  743. raw_line = line = self._get_line()
  744. line = line.replace("from.import ", "from . import ")
  745. line = line.replace("\t", " ").replace('import*', 'import *')
  746. line = line.replace(" .import ", " . import ")
  747. statement_index = self.index
  748. skip_line = self._skip_line(line)
  749. if line in self._section_comments and not skip_line:
  750. if self.import_index == -1:
  751. self.import_index = self.index - 1
  752. continue
  753. if "isort:imports-" in line and line.startswith("#"):
  754. section = line.split("isort:imports-")[-1].split()[0].upper()
  755. self.place_imports[section] = []
  756. self.import_placements[line] = section
  757. if ";" in line:
  758. for part in (part.strip() for part in line.split(";")):
  759. if part and not part.startswith("from ") and not part.startswith("import "):
  760. skip_line = True
  761. import_type = self._import_type(line)
  762. if not import_type or skip_line:
  763. self.out_lines.append(raw_line)
  764. continue
  765. for line in (line.strip() for line in line.split(";")):
  766. import_type = self._import_type(line)
  767. if not import_type:
  768. self.out_lines.append(line)
  769. continue
  770. if self.import_index == -1:
  771. self.import_index = self.index - 1
  772. nested_comments = {}
  773. import_string, comments, new_comments = self._strip_comments(line)
  774. stripped_line = [part for part in self._strip_syntax(import_string).strip().split(" ") if part]
  775. if import_type == "from" and len(stripped_line) == 2 and stripped_line[1] != "*" and new_comments:
  776. nested_comments[stripped_line[-1]] = comments[0]
  777. if "(" in line.split("#")[0] and not self._at_end():
  778. while not line.strip().endswith(")") and not self._at_end():
  779. line, comments, new_comments = self._strip_comments(self._get_line(), comments)
  780. stripped_line = self._strip_syntax(line).strip()
  781. if import_type == "from" and stripped_line and " " not in stripped_line and new_comments:
  782. nested_comments[stripped_line] = comments[-1]
  783. import_string += self.line_separator + line
  784. else:
  785. while line.strip().endswith("\\"):
  786. line, comments, new_comments = self._strip_comments(self._get_line(), comments)
  787. # Still need to check for parentheses after an escaped line
  788. if "(" in line.split("#")[0] and not self._at_end():
  789. stripped_line = self._strip_syntax(line).strip()
  790. if import_type == "from" and stripped_line and " " not in stripped_line and new_comments:
  791. nested_comments[stripped_line] = comments[-1]
  792. import_string += self.line_separator + line
  793. while not line.strip().endswith(")") and not self._at_end():
  794. line, comments, new_comments = self._strip_comments(self._get_line(), comments)
  795. stripped_line = self._strip_syntax(line).strip()
  796. if import_type == "from" and stripped_line and " " not in stripped_line and new_comments:
  797. nested_comments[stripped_line] = comments[-1]
  798. import_string += self.line_separator + line
  799. stripped_line = self._strip_syntax(line).strip()
  800. if import_type == "from" and stripped_line and " " not in stripped_line and new_comments:
  801. nested_comments[stripped_line] = comments[-1]
  802. if import_string.strip().endswith(" import") or line.strip().startswith("import "):
  803. import_string += self.line_separator + line
  804. else:
  805. import_string = import_string.rstrip().rstrip("\\") + " " + line.lstrip()
  806. if import_type == "from":
  807. import_string = import_string.replace("import(", "import (")
  808. parts = import_string.split(" import ")
  809. from_import = parts[0].split(" ")
  810. import_string = " import ".join([from_import[0] + " " + "".join(from_import[1:])] + parts[1:])
  811. imports = [item.replace("{|", "{ ").replace("|}", " }") for item in
  812. self._strip_syntax(import_string).split()]
  813. if "as" in imports and (imports.index('as') + 1) < len(imports):
  814. while "as" in imports:
  815. index = imports.index('as')
  816. if import_type == "from":
  817. module = imports[0] + "." + imports[index - 1]
  818. self.as_map[module] = imports[index + 1]
  819. else:
  820. module = imports[index - 1]
  821. self.as_map[module] = imports[index + 1]
  822. if not self.config['combine_as_imports']:
  823. self.comments['straight'][module] = comments
  824. comments = []
  825. del imports[index:index + 2]
  826. if import_type == "from":
  827. import_from = imports.pop(0)
  828. placed_module = self.place_module(import_from)
  829. if self.config['verbose']:
  830. print("from-type place_module for %s returned %s" % (import_from, placed_module))
  831. if placed_module == '':
  832. print(
  833. "WARNING: could not place module {0} of line {1} --"
  834. " Do you need to define a default section?".format(import_from, line)
  835. )
  836. root = self.imports[placed_module][import_type]
  837. for import_name in imports:
  838. associated_comment = nested_comments.get(import_name)
  839. if associated_comment:
  840. self.comments['nested'].setdefault(import_from, {})[import_name] = associated_comment
  841. comments.pop(comments.index(associated_comment))
  842. if comments:
  843. self.comments['from'].setdefault(import_from, []).extend(comments)
  844. if len(self.out_lines) > max(self.import_index, self._first_comment_index_end + 1, 1) - 1:
  845. last = self.out_lines and self.out_lines[-1].rstrip() or ""
  846. while (last.startswith("#") and not last.endswith('"""') and not last.endswith("'''") and
  847. 'isort:imports-' not in last):
  848. self.comments['above']['from'].setdefault(import_from, []).insert(0, self.out_lines.pop(-1))
  849. if len(self.out_lines) > max(self.import_index - 1, self._first_comment_index_end + 1, 1) - 1:
  850. last = self.out_lines[-1].rstrip()
  851. else:
  852. last = ""
  853. if statement_index - 1 == self.import_index:
  854. self.import_index -= len(self.comments['above']['from'].get(import_from, []))
  855. if root.get(import_from, False):
  856. root[import_from].update(imports)
  857. else:
  858. root[import_from] = OrderedSet(imports)
  859. else:
  860. for module in imports:
  861. if comments:
  862. self.comments['straight'][module] = comments
  863. comments = None
  864. if len(self.out_lines) > max(self.import_index, self._first_comment_index_end + 1, 1) - 1:
  865. last = self.out_lines and self.out_lines[-1].rstrip() or ""
  866. while (last.startswith("#") and not last.endswith('"""') and not last.endswith("'''") and
  867. 'isort:imports-' not in last):
  868. self.comments['above']['straight'].setdefault(module, []).insert(0,
  869. self.out_lines.pop(-1))
  870. if len(self.out_lines) > 0:
  871. last = self.out_lines[-1].rstrip()
  872. else:
  873. last = ""
  874. if self.index - 1 == self.import_index:
  875. self.import_index -= len(self.comments['above']['straight'].get(module, []))
  876. placed_module = self.place_module(module)
  877. if self.config['verbose']:
  878. print("else-type place_module for %s returned %s" % (module, placed_module))
  879. if placed_module == '':
  880. print(
  881. "WARNING: could not place module {0} of line {1} --"
  882. " Do you need to define a default section?".format(import_from, line)
  883. )
  884. self.imports[placed_module][import_type].add(module)
  885. def coding_check(fname, default='utf-8'):
  886. # see https://www.python.org/dev/peps/pep-0263/
  887. pattern = re.compile(br'coding[:=]\s*([-\w.]+)')
  888. coding = default
  889. with io.open(fname, 'rb') as f:
  890. for line_number, line in enumerate(f, 1):
  891. groups = re.findall(pattern, line)
  892. if groups:
  893. coding = groups[0].decode('ascii')
  894. break
  895. if line_number > 2:
  896. break
  897. return coding
  898. def exists_case_sensitive(path):
  899. """
  900. Returns if the given path exists and also matches the case on Windows.
  901. When finding files that can be imported, it is important for the cases to match because while
  902. file os.path.exists("module.py") and os.path.exists("MODULE.py") both return True on Windows, Python
  903. can only import using the case of the real file.
  904. """
  905. result = os.path.exists(path)
  906. if (sys.platform.startswith('win') or sys.platform == 'darwin') and result:
  907. directory, basename = os.path.split(path)
  908. result = basename in os.listdir(directory)
  909. return result