How-To: Merge Configurations¶
Dracon excels at layering configurations, applying overrides, and combining settings from multiple sources. This is primarily achieved through the extended YAML merge key (<<:
).
Basic Merging (Multiple Files)¶
When loading multiple files with dracon.load
, they are merged sequentially.
import dracon as dr
# Loads base.yaml, then merges prod.yaml onto it
# Default strategy for multi-file load: <<{<+}[<~]
# (Dict: Recursive Append, New wins; List: Replace, New wins)
config = dr.load(["config/base.yaml", "config/prod.yaml"], context={...})
Explicit Merging (<<:
)¶
You can explicitly merge nodes within a single YAML file using the <<:
key.
defaults: &defaults
timeout: 30
retries: 3
features: [a, b]
service_config:
# Inherit from defaults using the plain merge key
<<: *defaults
# Override specific values
timeout: 60
# Add new values
endpoint: /api/v1
# Add new list - default for list merge is REPLACE, EXISTING wins
# So, the 'features' key from *defaults is kept.
# If we added 'features: [c, d]' here *after* the merge key,
# it would simply overwrite the merged key.
# Resulting service_config (using <<: *defaults default {+>}[~>]):
# { timeout: 30, retries: 3, features: [a, b], endpoint: /api/v1 }
# Note: timeout: 60 defined *after* the merge would override the merged value.
If you want the standard YAML merge behavior (replace keys, new wins), use <<{~<}: *defaults
.
Advanced Merging with Options¶
Dracon extends <<:
with options to control dictionary and list merging precisely:
<<{dict_opts}[list_opts]@target_path: source_node
{dict_opts}
: Controls dictionary merging.+
(Default): Append/Recurse. Merges nested dicts.~
: Replace. Overwrites entire value for conflicting keys.<
: New value wins priority. (Default for~
)>
(Default for+
): Existing value wins priority.N
(e.g.,+2
): Limit recursion depth.[list_opts]
: Controls list merging (only if both existing and new values are lists).~
(Default): Replace list.+
: Concatenate lists.<
: New list wins / Prepends in+
mode.>
(Default): Existing list wins / Appends in+
mode.@target_path
: (Optional) Apply the merge to a sub-key relative to the current node. Uses KeyPath syntax.source_node
: The node to merge in (e.g.,*anchor
,!include file:other.yaml
).
Default <<:
key (no options): Equivalent to <<{+>}[~>]
(Dict: Append/Recurse, Existing Wins; List: Replace, Existing Wins).
Examples:
-
Recursive Dict Merge, New Wins:
base: &base db: { host: localhost, port: 5432 } settings: { theme: light } prod: <<{+<}: *base # Merge recursively (+), new wins (<) db: host: prod.db # Overrides base host # port inherited from base settings: workers: 4 # Added # Result prod: { db: { host: prod.db, port: 5432 }, settings: { theme: light, workers: 4 } }
-
Append to List, Existing First:
defaults: &defaults middlewares: [logging, auth] custom: <<[+>]: *defaults # Concatenate lists (+), existing first (>) middlewares: [cors, caching] # This definition *overwrites* the merged list # Result custom: { middlewares: [cors, caching] } # To actually append, define the list *before* the merge key: custom_append: middlewares: [cors, caching] <<[+>]: *defaults # Concatenates, existing ([cors, caching]) first # Result custom_append: { middlewares: [cors, caching, logging, auth] }
-
Merge into Sub-key:
common_settings: &common timeout: 10 retries: 2 app_config: service_a: endpoint: /a service_b: endpoint: /b # Merge common settings only into service_b # Default for targeted merge is often {<+} (new wins) <<@service_b: *common # Equivalent to <<{<+}@service_b: *common # Result app_config: # { service_a: { endpoint: /a }, # service_b: { endpoint: /b, timeout: 10, retries: 2 } }
See Merging Concepts and the Merge Key Reference for full details and strategy explanations.