22import itertools
33from collections .abc import Iterable
44from functools import partial
5- from typing import Any
5+ from typing import Any , Literal
66
77from isort .format import format_simplified
88
@@ -35,96 +35,43 @@ def sorted_imports(
3535 sections : Iterable [str ] = itertools .chain (parsed .sections , config .forced_separate )
3636
3737 if config .no_sections :
38- parsed .imports ["no_sections" ] = {"straight" : {}, "from" : {}}
38+ parsed .imports ["no_sections" ] = {
39+ "straight" : {},
40+ "from" : {},
41+ "lazy_straight" : {},
42+ "lazy_from" : {},
43+ }
3944 base_sections : tuple [str , ...] = ()
4045 for section in sections :
4146 if section == "FUTURE" :
4247 base_sections = ("FUTURE" ,)
4348 continue
44- parsed .imports ["no_sections" ]["straight" ].update (
45- parsed .imports [section ].get ("straight" , {})
49+ parsed .imports ["no_sections" ]["straight" ].update (parsed .imports [section ]["straight" ])
50+ parsed .imports ["no_sections" ]["from" ].update (parsed .imports [section ]["from" ])
51+ parsed .imports ["no_sections" ]["lazy_straight" ].update (
52+ parsed .imports [section ]["lazy_straight" ]
4653 )
47- parsed .imports ["no_sections" ]["from " ].update (parsed .imports [section ]. get ( "from" , {}) )
54+ parsed .imports ["no_sections" ]["lazy_from " ].update (parsed .imports [section ][ "lazy_from" ] )
4855 sections = (* base_sections , "no_sections" )
4956
5057 output : list [str ] = []
5158 seen_headings : set [str ] = set ()
5259 pending_lines_before = False
5360 for section in sections :
54- straight_modules : Iterable [str ] = parsed .imports [section ]["straight" ]
55- if not config .only_sections :
56- straight_modules = sorting .sort (
57- config ,
58- straight_modules ,
59- key = lambda key : sorting .module_key (
60- key , config , section_name = section , straight_import = True
61- ),
62- reverse = config .reverse_sort ,
63- )
64-
65- from_modules : Iterable [str ] = parsed .imports [section ]["from" ]
66- if not config .only_sections :
67- from_modules = sorting .sort (
68- config ,
69- from_modules ,
70- key = lambda key : sorting .module_key (key , config , section_name = section ),
71- reverse = config .reverse_sort ,
72- )
73-
74- if config .star_first :
75- star_modules = []
76- other_modules = []
77- for module in from_modules :
78- if "*" in parsed .imports [section ]["from" ][module ]:
79- star_modules .append (module )
80- else :
81- other_modules .append (module )
82- from_modules = star_modules + other_modules
83-
84- straight_imports = _with_straight_imports (
85- parsed , config , straight_modules , section , remove_imports , import_type
86- )
87- from_imports = _with_from_imports (
88- parsed , config , from_modules , section , remove_imports , import_type
61+ section_output = _build_import_group (
62+ parsed , config , section , remove_imports , import_type , is_lazy = False
8963 )
9064
91- lines_between = ["" ] * (
92- config .lines_between_types if from_modules and straight_modules else 0
65+ # PEP 810 lazy imports always follow all eager imports within the section.
66+ lazy_section_output = _build_import_group (
67+ parsed , config , section , remove_imports , import_type , is_lazy = True
9368 )
94- if config .from_first or section == "FUTURE" :
95- section_output = from_imports + lines_between + straight_imports
96- else :
97- section_output = straight_imports + lines_between + from_imports
9869
99- if config .force_sort_within_sections :
100- # collapse comments
101- comments_above = []
102- new_section_output : list [str ] = []
103- for line in section_output :
104- if not line :
105- continue
106- if line .startswith ("#" ):
107- comments_above .append (line )
108- elif comments_above :
109- new_section_output .append (_LineWithComments (line , comments_above ))
110- comments_above = []
111- else :
112- new_section_output .append (line )
113- # only_sections options is not imposed if force_sort_within_sections is True
114- new_section_output = sorting .sort (
115- config ,
116- new_section_output ,
117- key = partial (sorting .section_key , config = config ),
118- reverse = config .reverse_sort ,
119- )
120-
121- # uncollapse comments
122- section_output = []
123- for line in new_section_output :
124- comments = getattr (line , "comments" , ())
125- if comments :
126- section_output .extend (comments )
127- section_output .append (str (line ))
70+ if lazy_section_output :
71+ if section_output :
72+ section_output += ["" ] * config .lines_between_types + lazy_section_output
73+ else :
74+ section_output = lazy_section_output
12875
12976 section_name = section
13077 no_lines_before = section_name in config .no_lines_before
@@ -244,6 +191,96 @@ def sorted_imports(
244191 return _output_as_string (formatted_output , parsed .line_separator )
245192
246193
194+ # Ignore DeepSource cyclomatic complexity check for this function.
195+ # skipcq: PY-R1000
196+ def _build_import_group (
197+ parsed : parse .ParsedContent ,
198+ config : Config ,
199+ section : str ,
200+ remove_imports : list [str ],
201+ import_type : str ,
202+ * ,
203+ is_lazy : bool ,
204+ ) -> list [str ]:
205+ """Build the sorted import lines for one group (eager or lazy) within a section."""
206+ straight_key : Literal ["lazy_straight" , "straight" ] = "lazy_straight" if is_lazy else "straight"
207+ from_key : Literal ["lazy_from" , "from" ] = "lazy_from" if is_lazy else "from"
208+
209+ straight_modules : Iterable [str ] = parsed .imports [section ][straight_key ]
210+ if not config .only_sections :
211+ straight_modules = sorting .sort (
212+ config ,
213+ straight_modules ,
214+ key = lambda key : sorting .module_key (
215+ key , config , section_name = section , straight_import = True
216+ ),
217+ reverse = config .reverse_sort ,
218+ )
219+
220+ from_modules : Iterable [str ] = parsed .imports [section ][from_key ]
221+ if not config .only_sections :
222+ from_modules = sorting .sort (
223+ config ,
224+ from_modules ,
225+ key = lambda key : sorting .module_key (key , config , section_name = section ),
226+ reverse = config .reverse_sort ,
227+ )
228+
229+ if not is_lazy and config .star_first :
230+ star_modules = []
231+ other_modules = []
232+ for module in from_modules :
233+ if "*" in parsed .imports [section ]["from" ][module ]:
234+ star_modules .append (module )
235+ else :
236+ other_modules .append (module )
237+ from_modules = star_modules + other_modules
238+
239+ straight_imports = _with_straight_imports (
240+ parsed , config , straight_modules , section , remove_imports , import_type , is_lazy = is_lazy
241+ )
242+ from_imports = _with_from_imports (
243+ parsed , config , from_modules , section , remove_imports , import_type , is_lazy = is_lazy
244+ )
245+
246+ lines_between = ["" ] * (config .lines_between_types if from_modules and straight_modules else 0 )
247+ if config .from_first or section == "FUTURE" :
248+ group_output = from_imports + lines_between + straight_imports
249+ else :
250+ group_output = straight_imports + lines_between + from_imports
251+
252+ if config .force_sort_within_sections :
253+ # collapse comments
254+ comments_above : list [str ] = []
255+ new_group_output : list [str ] = []
256+ for line in group_output :
257+ if not line :
258+ continue
259+ if line .startswith ("#" ):
260+ comments_above .append (line )
261+ elif comments_above :
262+ new_group_output .append (_LineWithComments (line , comments_above ))
263+ comments_above = []
264+ else :
265+ new_group_output .append (line )
266+ # only_sections option is not imposed if force_sort_within_sections is True
267+ new_group_output = sorting .sort (
268+ config ,
269+ new_group_output ,
270+ key = partial (sorting .section_key , config = config ),
271+ reverse = config .reverse_sort ,
272+ )
273+ # uncollapse comments
274+ group_output = []
275+ for line in new_group_output :
276+ line_comments = getattr (line , "comments" , ())
277+ if line_comments :
278+ group_output .extend (line_comments )
279+ group_output .append (str (line ))
280+
281+ return group_output
282+
283+
247284# Ignore DeepSource cyclomatic complexity check for this function. It was
248285# already complex when this check was enabled.
249286# skipcq: PY-R1000
@@ -254,14 +291,21 @@ def _with_from_imports(
254291 section : str ,
255292 remove_imports : list [str ],
256293 import_type : str ,
294+ * ,
295+ is_lazy : bool ,
257296) -> list [str ]:
258297 output : list [str ] = []
298+ import_key : Literal ["lazy_from" , "from" ] = "lazy_from" if is_lazy else "from"
299+
259300 for module in from_modules :
260301 if module in remove_imports :
261302 continue
262303
263304 import_start = f"from { module } { import_type } "
264- from_imports = list (parsed .imports [section ]["from" ][module ])
305+ if is_lazy :
306+ import_start = f"lazy { import_start } "
307+ from_imports = list (parsed .imports [section ][import_key ][module ])
308+
265309 if (
266310 not config .no_inline_sort
267311 or (config .force_single_line and module not in config .single_line_exclusions )
@@ -299,7 +343,7 @@ def _with_from_imports(
299343 for from_import in copy .copy (from_imports ):
300344 if from_import in as_imports :
301345 idx = from_imports .index (from_import )
302- if parsed .imports [section ]["from" ][module ][from_import ]:
346+ if parsed .imports [section ][import_key ][module ][from_import ]:
303347 from_imports [(idx + 1 ) : (idx + 1 )] = as_imports .pop (from_import )
304348 else :
305349 from_imports [idx : (idx + 1 )] = as_imports .pop (from_import )
@@ -347,7 +391,7 @@ def _with_from_imports(
347391 )
348392 if from_import in as_imports :
349393 if (
350- parsed .imports [section ]["from" ][module ][from_import ]
394+ parsed .imports [section ][import_key ][module ][from_import ]
351395 and not only_show_as_imports
352396 ):
353397 output .append (
@@ -403,7 +447,7 @@ def _with_from_imports(
403447 parsed .categorized_comments ["straight" ].get (f"{ module } .{ from_import } " ) or []
404448 )
405449 if (
406- parsed .imports [section ]["from" ][module ][from_import ]
450+ parsed .imports [section ][import_key ][module ][from_import ]
407451 and not only_show_as_imports
408452 ):
409453 specific_comment = (
@@ -540,7 +584,7 @@ def _with_from_imports(
540584 from_imports [0 ] not in as_imports
541585 or (
542586 config .combine_as_imports
543- and parsed .imports [section ]["from" ][module ][from_import ]
587+ and parsed .imports [section ][import_key ][module ][from_import ]
544588 )
545589 ):
546590 from_import_section .append (from_imports .pop (0 ))
@@ -633,9 +677,13 @@ def _with_straight_imports(
633677 section : str ,
634678 remove_imports : list [str ],
635679 import_type : str ,
680+ * ,
681+ is_lazy : bool ,
636682) -> list [str ]:
637683 output : list [str ] = []
638684
685+ import_type = f"lazy { import_type } " if is_lazy else import_type
686+
639687 as_imports = any (module in parsed .as_map ["straight" ] for module in straight_modules )
640688
641689 # combine_straight_imports only works for bare imports, 'as' imports not included
@@ -675,7 +723,7 @@ def _with_straight_imports(
675723
676724 import_definition = []
677725 if module in parsed .as_map ["straight" ]:
678- if parsed .imports [section ]["straight" ][module ]:
726+ if parsed .imports [section ]["lazy_straight" if is_lazy else " straight" ][module ]:
679727 import_definition .append ((f"{ import_type } { module } " , module ))
680728 import_definition .extend (
681729 (f"{ import_type } { module } as { as_import } " , f"{ module } as { as_import } " )
0 commit comments