@@ -228,6 +228,7 @@ def __init__(self, stream: str) -> None:
228228 super ().__init__ (stream )
229229 self .top_level_key_values : dict [t .Any , list [t .Any ]] = {}
230230 self ._mapping_depth = 0
231+ self .top_level_items : list [tuple [t .Any , t .Any ]] = []
231232
232233
233234def _duplicate_tracking_construct_mapping (
@@ -248,7 +249,9 @@ def _duplicate_tracking_construct_mapping(
248249 value = construct (value_node )
249250
250251 if loader ._mapping_depth == 1 :
251- loader .top_level_key_values .setdefault (key , []).append (copy .deepcopy (value ))
252+ duplicated_value = copy .deepcopy (value )
253+ loader .top_level_key_values .setdefault (key , []).append (duplicated_value )
254+ loader .top_level_items .append ((copy .deepcopy (key ), duplicated_value ))
252255
253256 mapping [key ] = value
254257
@@ -270,20 +273,27 @@ def __init__(
270273 content : RawConfigData ,
271274 * ,
272275 duplicate_sections : dict [str , list [t .Any ]] | None = None ,
276+ top_level_items : list [tuple [str , t .Any ]] | None = None ,
273277 ) -> None :
274278 super ().__init__ (content )
275279 self ._duplicate_sections = duplicate_sections or {}
280+ self ._top_level_items = top_level_items or []
276281
277282 @property
278283 def duplicate_sections (self ) -> dict [str , list [t .Any ]]:
279284 """Mapping of top-level keys to the list of duplicated values."""
280285 return self ._duplicate_sections
281286
287+ @property
288+ def top_level_items (self ) -> list [tuple [str , t .Any ]]:
289+ """Ordered list of top-level items, including duplicates."""
290+ return copy .deepcopy (self ._top_level_items )
291+
282292 @classmethod
283293 def _load_yaml_with_duplicates (
284294 cls ,
285295 content : str ,
286- ) -> tuple [dict [str , t .Any ], dict [str , list [t .Any ]]]:
296+ ) -> tuple [dict [str , t .Any ], dict [str , list [t .Any ]], list [ tuple [ str , t . Any ]] ]:
287297 loader = _DuplicateTrackingSafeLoader (content )
288298
289299 try :
@@ -306,33 +316,42 @@ def _load_yaml_with_duplicates(
306316 if len (values ) > 1
307317 }
308318
309- return loaded , duplicate_sections
319+ top_level_items = [
320+ (t .cast ("str" , key ), copy .deepcopy (value ))
321+ for key , value in loader .top_level_items
322+ ]
323+
324+ return loaded , duplicate_sections , top_level_items
310325
311326 @classmethod
312327 def _load_from_path (
313328 cls ,
314329 path : pathlib .Path ,
315- ) -> tuple [dict [str , t .Any ], dict [str , list [t .Any ]]]:
330+ ) -> tuple [dict [str , t .Any ], dict [str , list [t .Any ]], list [ tuple [ str , t . Any ]] ]:
316331 if path .suffix .lower () in {".yaml" , ".yml" }:
317332 content = path .read_text (encoding = "utf-8" )
318333 return cls ._load_yaml_with_duplicates (content )
319334
320- return ConfigReader ._from_file (path ), {}
335+ return ConfigReader ._from_file (path ), {}, []
321336
322337 @classmethod
323338 def from_file (cls , path : pathlib .Path ) -> DuplicateAwareConfigReader :
324- content , duplicate_sections = cls ._load_from_path (path )
325- return cls (content , duplicate_sections = duplicate_sections )
339+ content , duplicate_sections , top_level_items = cls ._load_from_path (path )
340+ return cls (
341+ content ,
342+ duplicate_sections = duplicate_sections ,
343+ top_level_items = top_level_items ,
344+ )
326345
327346 @classmethod
328347 def _from_file (cls , path : pathlib .Path ) -> dict [str , t .Any ]:
329- content , _ = cls ._load_from_path (path )
348+ content , _ , _ = cls ._load_from_path (path )
330349 return content
331350
332351 @classmethod
333352 def load_with_duplicates (
334353 cls ,
335354 path : pathlib .Path ,
336- ) -> tuple [dict [str , t .Any ], dict [str , list [t .Any ]]]:
355+ ) -> tuple [dict [str , t .Any ], dict [str , list [t .Any ]], list [ tuple [ str , t . Any ]] ]:
337356 reader = cls .from_file (path )
338- return reader .content , reader .duplicate_sections
357+ return reader .content , reader .duplicate_sections , reader . top_level_items
0 commit comments