dict_union.pyPython 3.9
"""
Dictionary union operators allow you to merge dictionaries using
the | (union) and |= (in-place union) operators. These operators
provide a clean and intuitive syntax for combining dictionaries.

This feature was introduced in Python 3.9 through PEP 584. Before
this, you had to use methods like dict.update() or {**dict1, **dict2}
syntax to merge dictionaries.
"""


def main() -> None:
    # Traditional dictionary merging before Python 3.9
    # Method 1: Using dict.update() (modifies the original)
    dict1_old = {"a": 1, "b": 2}
    dict2_old = {"c": 3, "d": 4}
    dict1_old.update(dict2_old)
    assert dict1_old == {"a": 1, "b": 2, "c": 3, "d": 4}

    # Method 2: Using dictionary unpacking (creates a new dict)
    dict3_old = {"a": 1, "b": 2}
    dict4_old = {"c": 3, "d": 4}
    merged_old = {**dict3_old, **dict4_old}
    assert merged_old == {"a": 1, "b": 2, "c": 3, "d": 4}

    # With Python 3.9+, we can use the | operator for union
    # This creates a new dictionary without modifying the originals
    dict1 = {"a": 1, "b": 2}
    dict2 = {"c": 3, "d": 4}
    merged = dict1 | dict2
    assert merged == {"a": 1, "b": 2, "c": 3, "d": 4}

    # The original dictionaries remain unchanged
    assert dict1 == {"a": 1, "b": 2}
    assert dict2 == {"c": 3, "d": 4}

    # When there are overlapping keys, the right operand's values take precedence
    # This is the same behavior as dict.update() and {**d1, **d2}
    dict3 = {"a": 1, "b": 2, "c": 3}
    dict4 = {"b": 20, "c": 30, "d": 4}
    merged2 = dict3 | dict4
    # Keys 'b' and 'c' from dict4 override those from dict3
    assert merged2 == {"a": 1, "b": 20, "c": 30, "d": 4}

    # The order matters! Left operand is the base, right operand overwrites
    merged3 = dict4 | dict3
    # Now keys 'b' and 'c' from dict3 override those from dict4
    assert merged3 == {"b": 2, "c": 3, "d": 4, "a": 1}

    # The |= operator performs in-place union (augmented assignment)
    # This is equivalent to dict.update() but with cleaner syntax
    dict5 = {"a": 1, "b": 2}
    dict6 = {"c": 3, "d": 4}
    dict5 |= dict6
    # dict5 is modified in place
    assert dict5 == {"a": 1, "b": 2, "c": 3, "d": 4}
    # dict6 remains unchanged
    assert dict6 == {"c": 3, "d": 4}

    # The |= operator also handles overlapping keys
    dict7 = {"a": 1, "b": 2, "c": 3}
    dict8 = {"b": 20, "d": 4}
    dict7 |= dict8
    # 'b' from dict8 overwrites 'b' in dict7
    assert dict7 == {"a": 1, "b": 20, "c": 3, "d": 4}

    # You can chain multiple | operations
    dict9 = {"a": 1}
    dict10 = {"b": 2}
    dict11 = {"c": 3}
    dict12 = {"d": 4}
    combined = dict9 | dict10 | dict11 | dict12
    assert combined == {"a": 1, "b": 2, "c": 3, "d": 4}

    # When chaining with overlapping keys, rightmost values win
    dict13 = {"a": 1, "x": 10}
    dict14 = {"b": 2, "x": 20}
    dict15 = {"c": 3, "x": 30}
    combined2 = dict13 | dict14 | dict15
    # 'x' ends up with value 30 from the rightmost dictionary
    assert combined2 == {"a": 1, "b": 2, "c": 3, "x": 30}

    # The union operator works with empty dictionaries
    empty: dict[str, int] = {}
    dict16 = {"a": 1, "b": 2}
    assert empty | dict16 == {"a": 1, "b": 2}
    assert dict16 | empty == {"a": 1, "b": 2}
    assert empty | empty == {}

    # The union operator can be used with dict() constructor results
    dict17 = dict(a=1, b=2)
    dict18 = dict(c=3, d=4)
    merged4 = dict17 | dict18
    assert merged4 == {"a": 1, "b": 2, "c": 3, "d": 4}

    # You can mix different value types in merged dictionaries
    dict19 = {"name": "Alice", "age": 30}
    dict20 = {"city": "NYC", "scores": [85, 90, 95]}
    dict21 = {"active": True}
    person = dict19 | dict20 | dict21
    assert person == {"name": "Alice", "age": 30, "city": "NYC", "scores": [85, 90, 95], "active": True}

    # Practical use case: Configuration merging
    # Start with default configuration
    default_config = {"timeout": 30, "retries": 3, "debug": False, "log_level": "INFO"}

    # User provides custom configuration (partial)
    user_config = {"timeout": 60, "debug": True}

    # Merge configurations, user settings override defaults
    final_config = default_config | user_config
    assert final_config == {
        "timeout": 60,  # Overridden by user
        "retries": 3,  # From default
        "debug": True,  # Overridden by user
        "log_level": "INFO",  # From default
    }

    # Practical use case: Building objects incrementally
    # Start with base attributes
    base = {"id": 1, "type": "user"}

    # Add authentication info
    with_auth = base | {"username": "john", "email": "[email protected]"}

    # Add profile info
    with_profile = with_auth | {"bio": "Developer", "location": "USA"}

    assert with_profile == {"id": 1, "type": "user", "username": "john", "email": "[email protected]", "bio": "Developer", "location": "USA"}

    # Practical use case: Updating records with |=
    user_record = {"id": 100, "name": "Jane", "status": "active", "login_count": 5}

    # Apply update from an external source
    update = {"status": "inactive", "login_count": 6, "last_login": "2024-01-15"}
    user_record |= update

    assert user_record == {"id": 100, "name": "Jane", "status": "inactive", "login_count": 6, "last_login": "2024-01-15"}

    # The union operators only work with dictionaries
    # Attempting to use them with non-dict types raises TypeError
    dict22 = {"a": 1}
    error_raised = False
    try:
        # This will fail because list is not a dict
        dict22 | [("b", 2)]  # type: ignore [operator]
    except TypeError:
        error_raised = True
    assert error_raised is True

    # However, you can use dict() to convert compatible types first
    dict23 = {"a": 1}
    dict24 = dict([("b", 2), ("c", 3)])  # Convert list of tuples to dict
    merged5 = dict23 | dict24
    assert merged5 == {"a": 1, "b": 2, "c": 3}

    # Comparison with the older approaches shows the clarity improvement:

    # OLD: Using update() - modifies original, no expression result
    old1 = {"a": 1}
    old1.update({"b": 2})
    assert old1 == {"a": 1, "b": 2}

    # OLD: Using unpacking - verbose for multiple merges
    old2 = {**{"a": 1}, **{"b": 2}, **{"c": 3}}
    assert old2 == {"a": 1, "b": 2, "c": 3}

    # NEW: Using union operator - clean and chainable
    new1 = {"a": 1} | {"b": 2} | {"c": 3}
    assert new1 == {"a": 1, "b": 2, "c": 3}


if __name__ == "__main__":
    main()