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
@@ -243,6 +190,95 @@ def sorted_imports(
243190
244191 return _output_as_string (formatted_output , parsed .line_separator )
245192
193+ # Ignore DeepSource cyclomatic complexity check for this function.
194+ # skipcq: PY-R1000
195+ def _build_import_group (
196+ parsed : parse .ParsedContent ,
197+ config : Config ,
198+ section : str ,
199+ remove_imports : list [str ],
200+ import_type : str ,
201+ * ,
202+ is_lazy : bool ,
203+ ) -> list [str ]:
204+ """Build the sorted import lines for one group (eager or lazy) within a section."""
205+ straight_key : Literal ["lazy_straight" , "straight" ] = "lazy_straight" if is_lazy else "straight"
206+ from_key : Literal ["lazy_from" , "from" ] = "lazy_from" if is_lazy else "from"
207+
208+ straight_modules : Iterable [str ] = parsed .imports [section ][straight_key ]
209+ if not config .only_sections :
210+ straight_modules = sorting .sort (
211+ config ,
212+ straight_modules ,
213+ key = lambda key : sorting .module_key (
214+ key , config , section_name = section , straight_import = True
215+ ),
216+ reverse = config .reverse_sort ,
217+ )
218+
219+ from_modules : Iterable [str ] = parsed .imports [section ][from_key ]
220+ if not config .only_sections :
221+ from_modules = sorting .sort (
222+ config ,
223+ from_modules ,
224+ key = lambda key : sorting .module_key (key , config , section_name = section ),
225+ reverse = config .reverse_sort ,
226+ )
227+
228+ if not is_lazy and config .star_first :
229+ star_modules = []
230+ other_modules = []
231+ for module in from_modules :
232+ if "*" in parsed .imports [section ]["from" ][module ]:
233+ star_modules .append (module )
234+ else :
235+ other_modules .append (module )
236+ from_modules = star_modules + other_modules
237+
238+ straight_imports = _with_straight_imports (
239+ parsed , config , straight_modules , section , remove_imports , import_type , is_lazy = is_lazy
240+ )
241+ from_imports = _with_from_imports (
242+ parsed , config , from_modules , section , remove_imports , import_type , is_lazy = is_lazy
243+ )
244+
245+ lines_between = ["" ] * (config .lines_between_types if from_modules and straight_modules else 0 )
246+ if config .from_first or section == "FUTURE" :
247+ group_output = from_imports + lines_between + straight_imports
248+ else :
249+ group_output = straight_imports + lines_between + from_imports
250+
251+ if config .force_sort_within_sections :
252+ # collapse comments
253+ comments_above : list [str ] = []
254+ new_group_output : list [str ] = []
255+ for line in group_output :
256+ if not line :
257+ continue
258+ if line .startswith ("#" ):
259+ comments_above .append (line )
260+ elif comments_above :
261+ new_group_output .append (_LineWithComments (line , comments_above ))
262+ comments_above = []
263+ else :
264+ new_group_output .append (line )
265+ # only_sections option is not imposed if force_sort_within_sections is True
266+ new_group_output = sorting .sort (
267+ config ,
268+ new_group_output ,
269+ key = partial (sorting .section_key , config = config ),
270+ reverse = config .reverse_sort ,
271+ )
272+ # uncollapse comments
273+ group_output = []
274+ for line in new_group_output :
275+ line_comments = getattr (line , "comments" , ())
276+ if line_comments :
277+ group_output .extend (line_comments )
278+ group_output .append (str (line ))
279+
280+ return group_output
281+
246282
247283# Ignore DeepSource cyclomatic complexity check for this function. It was
248284# already complex when this check was enabled.
@@ -254,14 +290,21 @@ def _with_from_imports(
254290 section : str ,
255291 remove_imports : list [str ],
256292 import_type : str ,
293+ * ,
294+ is_lazy : bool ,
257295) -> list [str ]:
258296 output : list [str ] = []
297+ import_key : Literal ["lazy_from" , "from" ] = "lazy_from" if is_lazy else "from"
298+
259299 for module in from_modules :
260300 if module in remove_imports :
261301 continue
262302
263303 import_start = f"from { module } { import_type } "
264- from_imports = list (parsed .imports [section ]["from" ][module ])
304+ if is_lazy :
305+ import_start = f"lazy { import_start } "
306+ from_imports = list (parsed .imports [section ][import_key ][module ])
307+
265308 if (
266309 not config .no_inline_sort
267310 or (config .force_single_line and module not in config .single_line_exclusions )
@@ -299,7 +342,7 @@ def _with_from_imports(
299342 for from_import in copy .copy (from_imports ):
300343 if from_import in as_imports :
301344 idx = from_imports .index (from_import )
302- if parsed .imports [section ]["from" ][module ][from_import ]:
345+ if parsed .imports [section ][import_key ][module ][from_import ]:
303346 from_imports [(idx + 1 ) : (idx + 1 )] = as_imports .pop (from_import )
304347 else :
305348 from_imports [idx : (idx + 1 )] = as_imports .pop (from_import )
@@ -347,7 +390,7 @@ def _with_from_imports(
347390 )
348391 if from_import in as_imports :
349392 if (
350- parsed .imports [section ]["from" ][module ][from_import ]
393+ parsed .imports [section ][import_key ][module ][from_import ]
351394 and not only_show_as_imports
352395 ):
353396 output .append (
@@ -403,7 +446,7 @@ def _with_from_imports(
403446 parsed .categorized_comments ["straight" ].get (f"{ module } .{ from_import } " ) or []
404447 )
405448 if (
406- parsed .imports [section ]["from" ][module ][from_import ]
449+ parsed .imports [section ][import_key ][module ][from_import ]
407450 and not only_show_as_imports
408451 ):
409452 specific_comment = (
@@ -540,7 +583,7 @@ def _with_from_imports(
540583 from_imports [0 ] not in as_imports
541584 or (
542585 config .combine_as_imports
543- and parsed .imports [section ]["from" ][module ][from_import ]
586+ and parsed .imports [section ][import_key ][module ][from_import ]
544587 )
545588 ):
546589 from_import_section .append (from_imports .pop (0 ))
@@ -633,9 +676,13 @@ def _with_straight_imports(
633676 section : str ,
634677 remove_imports : list [str ],
635678 import_type : str ,
679+ * ,
680+ is_lazy : bool ,
636681) -> list [str ]:
637682 output : list [str ] = []
638683
684+ import_type = f"lazy { import_type } " if is_lazy else import_type
685+
639686 as_imports = any (module in parsed .as_map ["straight" ] for module in straight_modules )
640687
641688 # combine_straight_imports only works for bare imports, 'as' imports not included
@@ -675,7 +722,7 @@ def _with_straight_imports(
675722
676723 import_definition = []
677724 if module in parsed .as_map ["straight" ]:
678- if parsed .imports [section ]["straight" ][module ]:
725+ if parsed .imports [section ]["lazy_straight" if is_lazy else " straight" ][module ]:
679726 import_definition .append ((f"{ import_type } { module } " , module ))
680727 import_definition .extend (
681728 (f"{ import_type } { module } as { as_import } " , f"{ module } as { as_import } " )
0 commit comments