377def combineConfigFiles(local, config_path, fragment_key="include"):
378 """
379 Recursively combine configuration fragments into `local`.
380
381 - Looks for `fragment_key` at any dict node.
382 - If value is a string/path: merge that fragment.
383 - If value is a list: merge all fragments in order.
384 For conflicts between fragments, the **earlier** file in the list wins.
385 Local keys still override the merged fragments.
386
387 Returns True if any merging happened below this node.
388 """
389 if not isinstance(config_path, (list, Path, str)):
390 raise ValueError("Please specify the path or a list of paths where configuration is expected to reside")
391 if isinstance(config_path, list):
392 config_paths = [Path(path) for path in config_path]
393 else:
394 warnings.warn(
395 "Passing a single path to combineConfigFiles is deprecated, please pass a list of paths instead",
396 TextConfigWarning,
397 stacklevel=2,
398 )
399 config_paths = [Path(config_path)]
400
401 combined = False
402
403
404 if isinstance(local, dict):
405 to_combine = local.values()
406 elif isinstance(local, list):
407 to_combine = local
408 else:
409 return combined
410
411
412 for sub in to_combine:
413 combined = combineConfigFiles(sub, config_paths, fragment_key=fragment_key) or combined
414
415
416 if fragment_key not in local:
417 return combined
418
419
420 if not isinstance(local, dict):
421 return combined
422
423
424 value = local[fragment_key]
425 if isinstance(value, (str, Path)):
426 warnings.warn(
427 f"{fragment_key} should be followed with a list of files",
428 TextConfigWarning,
429 stacklevel=2,
430 )
431 paths = [value]
432 elif isinstance(value, list):
433 paths = value
434 else:
435 raise TypeError(f"'{fragment_key}' must be a string path or a list of paths, got {type(value).__name__}")
436
437
438 fragments_acc = {}
439 for entry in paths:
440 fragment_path = _find_fragment(Path(entry), config_paths)
441 fragment = _load_fragment(fragment_path)
442
443
444 combineConfigFiles(fragment, [fragment_path.parent, *config_paths], fragment_key=fragment_key)
445
446
447 _merge_dicts(fragments_acc, fragment)
448
449
450 del local[fragment_key]
451
452
453 _merge_dicts(local, fragments_acc)
454
455 return True
456
457