Skip to content

Fuso

Fuso is a Python library for merging and manipulating dictionaries and lists of dictionaries.

Fuso provides functions for merging dictionaries and lists of dictionaries, sorting, and converting between different data structures. It also includes a factory function for creating custom merge functions.

create_merge_factory(merge_functions=None, key_order=None, post_processor=None)

Create a merge function that merges arbitrarily nested dictionaries.

Parameters:

Name Type Description Default
merge_functions dict[str, Callable[[Any, Any], Any]] | None

Dictionary of functions to use for merging specific keys

None
key_order list[str] | None

List of keys to determine the order of merging.

None
post_processor callable | None

Function to process the result after merging

None

Returns:

Name Type Description
Callable Callable[[dict, dict], dict]

Function that merges two arbitrarily nested dictionaries.

Example
merge_with_custom_functions = create_merge_factory(
    merge_functions={
        "age": lambda o, u: min(o, u),
    },
    post_processor=lambda d: d.update({"name": d["name"].upper()})
)
original = {
    "name": "Alice",
    "age": 30,
    "tags": ["user"],
}
updates = {
    "age": 31,
    "tags": ["editor"],
}
merged = merge_with_custom_functions(original, updates)
assert merged == {
    "name": "ALICE",
    "age": 30,
    "tags": ["user", "editor"],
}
Source code in src/fuso/factories.py
def create_merge_factory(
    merge_functions: dict[str, Callable[[Any, Any], Any]] | None = None,
    key_order: list[str] | None = None,
    post_processor: Callable[[dict], dict] | None = None,
) -> Callable[[dict, dict], dict]:
    """Create a merge function that merges arbitrarily nested dictionaries.

    Args:
        merge_functions (dict[str, Callable[[Any, Any], Any]] | None):
            Dictionary of functions to use for merging specific keys
        key_order (list[str] | None): List of keys to determine the order of merging.
        post_processor (callable | None): Function to process the result after merging

    Returns:
        Callable: Function that merges two arbitrarily nested dictionaries.

    Example:
        ```py
        merge_with_custom_functions = create_merge_factory(
            merge_functions={
                "age": lambda o, u: min(o, u),
            },
            post_processor=lambda d: d.update({"name": d["name"].upper()})
        )
        original = {
            "name": "Alice",
            "age": 30,
            "tags": ["user"],
        }
        updates = {
            "age": 31,
            "tags": ["editor"],
        }
        merged = merge_with_custom_functions(original, updates)
        assert merged == {
            "name": "ALICE",
            "age": 30,
            "tags": ["user", "editor"],
        }
        ```
    """

    def factory(values, updates):
        return merge_dict(
            values,
            updates,
            merge_functions=merge_functions,
            key_order=key_order,
            post_processor=post_processor,
        )

    return factory

create_merge_list_of_dicts_by_key_factory(key, default_key=None, merge_functions=None, object_key_order=None)

Create a merge function that merges two lists of dictionaries by a specified key.

Parameters:

Name Type Description Default
key str

Key to use for merging

required
default_key str | None

Key to use for default updates

None
merge_functions dict[str, Callable[[Any, Any], Any]] | None

Dictionary of functions to use for merging specific keys

None
object_key_order list[str] | None

Non-exhaustive list of keys to sort objects by

None

Returns:

Name Type Description
Callable Callable[[list[dict], list[dict]], list[dict]]

Function that merges two lists of dictionaries by a specified key.

Example
merge_by_id = create_merge_list_of_dicts_by_key_factory(key="id")
values = [
    {"id": 1, "name": "Alice", "tags": ["user"]},
    {"id": 2, "name": "Bob", "tags": ["admin"]},
]
updates = [
    {"id": 1, "tags": ["editor"]},
    {"id": 3, "name": "Charlie", "tags": ["user"]},
]
merged = merge_by_id(values, updates)
assert merged == [
    {"id": 1, "name": "Alice", "tags": ["user", "editor"]},
    {"id": 2, "name": "Bob", "tags": ["admin"]},
    {"id": 3, "name": "Charlie", "tags": ["user"]},
]
Source code in src/fuso/factories.py
def create_merge_list_of_dicts_by_key_factory(
    key: str,
    default_key: str | None = None,
    merge_functions: dict[str, Callable[[Any, Any], Any]] | None = None,
    object_key_order: list[str] | None = None,
) -> Callable[[list[dict], list[dict]], list[dict]]:
    """Create a merge function that merges two lists of dictionaries by a specified key.

    Args:
        key (str): Key to use for merging
        default_key (str | None): Key to use for default updates
        merge_functions (dict[str, Callable[[Any, Any], Any]] | None):
            Dictionary of functions to use for merging specific keys
        object_key_order (list[str] | None): Non-exhaustive list of keys to sort objects
            by

    Returns:
        Callable: Function that merges two lists of dictionaries by a specified key.

    Example:
        ```py
        merge_by_id = create_merge_list_of_dicts_by_key_factory(key="id")
        values = [
            {"id": 1, "name": "Alice", "tags": ["user"]},
            {"id": 2, "name": "Bob", "tags": ["admin"]},
        ]
        updates = [
            {"id": 1, "tags": ["editor"]},
            {"id": 3, "name": "Charlie", "tags": ["user"]},
        ]
        merged = merge_by_id(values, updates)
        assert merged == [
            {"id": 1, "name": "Alice", "tags": ["user", "editor"]},
            {"id": 2, "name": "Bob", "tags": ["admin"]},
            {"id": 3, "name": "Charlie", "tags": ["user"]},
        ]
        ```
    """

    def factory(values, updates):
        return merge_list_of_dicts_by_key(
            values=values,
            updates=updates,
            key=key,
            default_key=default_key,
            merge_functions=merge_functions,
            object_key_order=object_key_order,
        )

    return factory

merge_dict(original, updates, merge_functions=None, post_processor=None, key_order=None)

Merge two dictionaries.

Parameters:

Name Type Description Default
original dict

Original dictionary

required
updates dict

Dictionary with updates

required
merge_functions dict[str, Callable[[Any, Any], Any]] | None

Dictionary of functions to use for merging specific keys

None
post_processor callable | None

Function to process the result after merging

None
key_order list[str] | None

Non-exhaustive list of keys to sort by

None

Returns:

Name Type Description
dict dict

Merged dictionary

Example
original = {
    "name": "Alice",
    "age": 30,
    "tags": ["user"],
}
updates = {
    "age": 31,
    "tags": ["editor"],
}
merged = merge_dict(
    original,
    updates,
    merge_functions={
        "age": lambda o, u: min(o, u),
    },
    post_processor=lambda d: d.update({"name": d["name"].upper()})
)
assert merged == {
    "name": "ALICE",
    "age": 30,
    "tags": ["user", "editor"],
}
Source code in src/fuso/dicts.py
def merge_dict(
    original: dict | None,
    updates: dict | None,
    merge_functions: dict[str, Callable[[Any, Any], Any]] | None = None,
    post_processor: Callable[[dict], dict] | None = None,
    key_order: list[str] | None = None,
) -> dict:
    """Merge two dictionaries.

    Args:
        original (dict): Original dictionary
        updates (dict): Dictionary with updates
        merge_functions (dict[str, Callable[[Any, Any], Any]] | None):
            Dictionary of functions to use for merging specific keys
        post_processor (callable | None): Function to process the result after merging
        key_order (list[str] | None): Non-exhaustive list of keys to sort by

    Returns:
        dict: Merged dictionary

    Example:
        ```py
        original = {
            "name": "Alice",
            "age": 30,
            "tags": ["user"],
        }
        updates = {
            "age": 31,
            "tags": ["editor"],
        }
        merged = merge_dict(
            original,
            updates,
            merge_functions={
                "age": lambda o, u: min(o, u),
            },
            post_processor=lambda d: d.update({"name": d["name"].upper()})
        )
        assert merged == {
            "name": "ALICE",
            "age": 30,
            "tags": ["user", "editor"],
        }
        ```
    """
    if merge_functions is None:
        merge_functions = {}
    original = original or {}
    updates = updates or {}
    result = {}
    all_keys = set(original.keys()).union(updates.keys())
    for key in all_keys:
        original_value = original.get(key)
        update_value = updates.get(key)
        merge_function = merge_functions.get(key)
        if merge_function:
            result[key] = merge_function(original_value, update_value)
        elif update_value is None:
            result[key] = original_value
        else:
            result[key] = _merge(original_value, update_value)
    if post_processor:
        result = post_processor(result)
    return sort_dict(result, key_order=key_order)

merge_list_of_dicts_by_key(values, updates, key, default_key=None, merge_functions=None, object_key_order=None)

Merge two lists of dictionaries by a specified key.

Parameters:

Name Type Description Default
values list[dict]

List of original dictionaries

required
updates list[dict]

List of dictionaries with updates

required
key str

Key to use for merging

required
default_key str | None

Key to use for default updates

None
merge_functions dict[str, Callable[[Any, Any], Any]] | None

Dictionary of functions to use for merging specific keys

None
object_key_order list[str] | None

Non-exhaustive list of keys to sort objects by

None

Returns:

Type Description
list[dict]

list[dict]: Merged list of dictionaries

Example
values = [
    {"id": 1, "name": "Alice", "tags": ["user"]},
    {"id": 2, "name": "Bob", "tags": ["admin"]},
]
updates = [
    {"id": 1, "tags": ["editor"]},
    {"id": 3, "name": "Charlie", "tags": ["user"]},
]
merged = merge_list_of_dicts_by_key(values, updates, key="id")
assert merged == [
    {"id": 1, "name": "Alice", "tags": ["user", "editor"]},
    {"id": 2, "name": "Bob", "tags": ["admin"]},
    {"id": 3, "name": "Charlie", "tags": ["user"]},
]
Source code in src/fuso/lists.py
def merge_list_of_dicts_by_key(  # noqa: PLR0913 - Too many arguments, but they are all necessary for the functionality
    values: list[dict],
    updates: list[dict],
    key: str,
    default_key: str | None = None,
    merge_functions: dict[str, Callable[[Any, Any], Any]] | None = None,
    object_key_order: list[str] | None = None,
) -> list[dict]:
    """Merge two lists of dictionaries by a specified key.

    Args:
        values (list[dict]): List of original dictionaries
        updates (list[dict]): List of dictionaries with updates
        key (str): Key to use for merging
        default_key (str | None): Key to use for default updates
        merge_functions (dict[str, Callable[[Any, Any], Any]] | None):
            Dictionary of functions to use for merging specific keys
        object_key_order (list[str] | None): Non-exhaustive list of keys to sort objects
            by

    Returns:
        list[dict]: Merged list of dictionaries

    Example:
        ```py
        values = [
            {"id": 1, "name": "Alice", "tags": ["user"]},
            {"id": 2, "name": "Bob", "tags": ["admin"]},
        ]
        updates = [
            {"id": 1, "tags": ["editor"]},
            {"id": 3, "name": "Charlie", "tags": ["user"]},
        ]
        merged = merge_list_of_dicts_by_key(values, updates, key="id")
        assert merged == [
            {"id": 1, "name": "Alice", "tags": ["user", "editor"]},
            {"id": 2, "name": "Bob", "tags": ["admin"]},
            {"id": 3, "name": "Charlie", "tags": ["user"]},
        ]
        ```
    """
    if merge_functions is None:
        merge_functions = {}
    dict_values = to_list_of_dicts_by_key(values or [], key=key)
    try:
        dict_updates = to_list_of_dicts_by_key(updates or [], key=key)
    except KeyError as e:
        raise KeyError(
            f"Key '{key}' not found in update. Available keys: "
            f"{', '.join(updates[0].keys())}"
        ) from e
    if default_key is not None:
        default_updates = dict_updates.pop(default_key, {})
    else:
        default_updates = {}
    result = []
    all_keys = set(dict_values.keys()).union(dict_updates.keys())
    for value_key in all_keys:
        value = dict_values.get(value_key, {})
        specific_update = dict_updates.get(value_key, {})
        merged = merge_dict(
            original=value,
            updates=merge_dict(original=default_updates, updates=specific_update),
            merge_functions=merge_functions,
            key_order=object_key_order,
        )
        merged[key] = value_key
        result.append(merged)
    return sort_list_of_dicts_by_key(result, key=key)

sort_dict(d, key_order=None)

Sort a dictionary by a given (non-exhaustive) key order.

Parameters:

Name Type Description Default
d dict

dictionary to sort

required
key_order list[str]

Non-exhaustive list of keys to sort by

None

Returns:

Name Type Description
dict dict

dictionary with ordered values

Example
d = {
    "b": 2,
    "a": 1,
    "c": 3,
}
assert sort_dict(d, key_order=["c", "a"]) == {
    "c": 3,
    "a": 1,
    "b": 2,
}
assert sort_dict(d, key_order=["b", "a"]) == {
    "b": 2,
    "a": 1,
    "c": 3,
}
assert sort_dict(d) == {
    "a": 1,
    "b": 2,
    "c": 3,
}
Source code in src/fuso/utils.py
def sort_dict(d: dict, key_order: list[str] | None = None) -> dict:
    """Sort a dictionary by a given (non-exhaustive) key order.

    Args:
        d (dict): dictionary to sort
        key_order (list[str]): Non-exhaustive list of keys to sort by

    Returns:
        dict: dictionary with ordered values

    Example:
        ```py
        d = {
            "b": 2,
            "a": 1,
            "c": 3,
        }
        assert sort_dict(d, key_order=["c", "a"]) == {
            "c": 3,
            "a": 1,
            "b": 2,
        }
        assert sort_dict(d, key_order=["b", "a"]) == {
            "b": 2,
            "a": 1,
            "c": 3,
        }
        assert sort_dict(d) == {
            "a": 1,
            "b": 2,
            "c": 3,
        }
        ```
    """
    if not key_order:
        key_order = list(sorted(d.keys()))
    other_keys = sorted([k for k in d.keys() if k not in key_order])
    order = key_order + other_keys
    return dict(sorted(d.items(), key=lambda x: order.index(x[0])))

sort_list_of_dicts_by_key(values, key, reverse=False)

Sort a list of dictionaries by a specified key.

Parameters:

Name Type Description Default
values list[dict]

List of dictionaries to sort

required
key str

Key to sort by

required
reverse bool

Whether to sort in descending order

False

Returns:

Type Description
list[dict]

list[dict]: Sorted list of dictionaries

Example
values = [
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25},
]
assert sort_list_of_dicts_by_key(values, key="age") == [
    {"name": "Bob", "age": 25},
    {"name": "Alice", "age": 30},
]
assert sort_list_of_dicts_by_key(values, key="age", reverse=True) == [
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25},
]
Source code in src/fuso/utils.py
def sort_list_of_dicts_by_key(
    values: list[dict], key: str, reverse: bool = False
) -> list[dict]:
    """Sort a list of dictionaries by a specified key.

    Args:
        values (list[dict]): List of dictionaries to sort
        key (str): Key to sort by
        reverse (bool): Whether to sort in descending order

    Returns:
        list[dict]: Sorted list of dictionaries

    Example:
        ```py
        values = [
            {"name": "Alice", "age": 30},
            {"name": "Bob", "age": 25},
        ]
        assert sort_list_of_dicts_by_key(values, key="age") == [
            {"name": "Bob", "age": 25},
            {"name": "Alice", "age": 30},
        ]
        assert sort_list_of_dicts_by_key(values, key="age", reverse=True) == [
            {"name": "Alice", "age": 30},
            {"name": "Bob", "age": 25},
        ]
        ```
    """
    return sorted(values, key=lambda x: x[key], reverse=reverse)

to_list_of_dicts_by_key(values, key='name')

Convert a list of dictionaries to a dictionary of dictionaries using a specified key.

Parameters:

Name Type Description Default
values list

List of dictionaries to convert

required
key str

Key to use as the dictionary key

'name'

Returns:

Name Type Description
dict dict

Dictionary of dictionaries

Raises:

Type Description
KeyError

If key is missing in an item or if duplicate key values are found.

Example
values = [
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25},
]
assert to_list_of_dicts_by_key(values, "name") == {
    "Alice": {"age": 30},
    "Bob": {"age": 25},
}
assert to_list_of_dicts_by_key(values, "age") == {
    30: {"name": "Alice"},
    25: {"name": "Bob"},
}
Source code in src/fuso/utils.py
def to_list_of_dicts_by_key(values: list[dict], key: str = "name") -> dict:
    """Convert a list of dictionaries to a dictionary of dictionaries
        using a specified key.

    Args:
        values (list): List of dictionaries to convert
        key (str): Key to use as the dictionary key

    Returns:
        dict: Dictionary of dictionaries

    Raises:
        KeyError: If `key` is missing in an item or if duplicate key values are found.

    Example:
        ```py
        values = [
            {"name": "Alice", "age": 30},
            {"name": "Bob", "age": 25},
        ]
        assert to_list_of_dicts_by_key(values, "name") == {
            "Alice": {"age": 30},
            "Bob": {"age": 25},
        }
        assert to_list_of_dicts_by_key(values, "age") == {
            30: {"name": "Alice"},
            25: {"name": "Bob"},
        }
        ```
    """
    result = {}
    for value in values:
        value_copy = value.copy()
        try:
            value_key = value_copy.pop(key)
        except KeyError:
            all_keys = ", ".join(value.keys())
            raise KeyError(
                f"Key '{key}' not found in value. Available keys: {all_keys}"
            )
        if value_key in result:
            raise KeyError(f"Duplicate key '{value_key}' found for lookup key '{key}'")
        result[value_key] = value_copy
    return result