Skip to content

trestle.common.list_utils

trestle.common.list_utils ¤

Trestle List Utils.

Attributes¤

Classes¤

Functions:¤

as_dict(dict_or_none) ¤

Convert dict or None object to itself or an empty dict if none.

Source code in trestle/common/list_utils.py
64
65
66
def as_dict(dict_or_none: Optional[Dict[TG, TG2]]) -> Dict[TG, TG2]:
    """Convert dict or None object to itself or an empty dict if none."""
    return dict_or_none if dict_or_none else {}

as_filtered_list(list_or_none, filter_condition) ¤

Convert to list and filter based on the condition.

Source code in trestle/common/list_utils.py
57
58
59
60
61
def as_filtered_list(list_or_none: Optional[List[TG]], filter_condition: Callable[[TG], bool]) -> List[TG]:
    """Convert to list and filter based on the condition."""
    result_list = as_list(list_or_none)
    result_list = list(filter(filter_condition, result_list))
    return result_list

as_list(list_or_none) ¤

Convert list or None object to itself or an empty list if none.

Use this for safe iteration and list operations.

For Pydantic v2 models with min_length constraints, use this pattern: temp_list = as_list(obj.field) # Get working list temp_list.append(item) # Modify it obj.field = none_if_empty(temp_list) # Assign back, converting [] to None

Source code in trestle/common/list_utils.py
24
25
26
27
28
29
30
31
32
33
34
def as_list(list_or_none: Optional[List[TG]]) -> List[TG]:
    """Convert list or None object to itself or an empty list if none.

    Use this for safe iteration and list operations.

    For Pydantic v2 models with min_length constraints, use this pattern:
        temp_list = as_list(obj.field)  # Get working list
        temp_list.append(item)           # Modify it
        obj.field = none_if_empty(temp_list)  # Assign back, converting [] to None
    """
    return list_or_none if list_or_none else []

comma_colon_sep_to_dict(string_or_none) ¤

Convert optional comma and colon-sep list to dict.

Source code in trestle/common/list_utils.py
43
44
45
46
47
48
49
50
51
52
53
54
def comma_colon_sep_to_dict(string_or_none: Optional[str]) -> Dict[str, str]:
    """Convert optional comma and colon-sep list to dict."""
    entries = comma_sep_to_list(string_or_none)
    dic = {}
    for entry in entries:
        # if more than one colon include any colons in the value after the first one
        token = entry.split(':', 1)
        if len(token) == 1:
            dic[token[0].strip()] = token[0].strip()
        else:
            dic[token[0].strip()] = token[1].strip()
    return dic

comma_sep_to_list(string_or_none) ¤

Convert optional comma-sep string to list of strings and strip.

Source code in trestle/common/list_utils.py
37
38
39
40
def comma_sep_to_list(string_or_none: Optional[str]) -> List[str]:
    """Convert optional comma-sep string to list of strings and strip."""
    string_or_none = string_or_none.strip() if string_or_none else None
    return list(map(str.strip, string_or_none.split(','))) if string_or_none else []

deep_append(dic, path, value) ¤

Append to list in dict.

Source code in trestle/common/list_utils.py
195
196
197
198
199
200
201
202
203
204
def deep_append(dic: Dict[str, Any], path: List[str], value: Any) -> None:
    """Append to list in dict."""
    if not path:
        raise TrestleError('Error appending value in deep append with empty path.')
    for node in path[:-1]:
        dic[node] = dic.get(node, {})
        dic = dic[node]
    if path[-1] not in dic:
        dic[path[-1]] = []
    dic[path[-1]].append(value)

deep_get(dic, path, default=None) ¤

Get value from deep in dictionary.

Source code in trestle/common/list_utils.py
174
175
176
177
178
179
180
181
182
def deep_get(dic: Dict[str, Any], path: List[str], default: Any = None) -> Any:
    """Get value from deep in dictionary."""
    if not path:
        raise TrestleError('Error getting value in deep get with empty path.')
    for node in path[:-1]:
        if node not in dic:
            return default
        dic = dic[node]
    return dic.get(path[-1], default)

deep_set(dic, path, value, pop_if_none=True) ¤

Set value deep in dictionary.

pop_if_none will cause the key to be removed if value is None

Source code in trestle/common/list_utils.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def deep_set(dic: Dict[str, Any], path: List[str], value: Any, pop_if_none: bool = True) -> None:
    """
    Set value deep in dictionary.

    pop_if_none will cause the key to be removed if value is None
    """
    if not path:
        raise TrestleError('Error setting value in deep set with empty path.')
    for node in path[:-1]:
        dic[node] = dic.get(node, {})
        dic = dic[node]
    if value or not pop_if_none:
        dic[path[-1]] = value
    else:
        dic.pop(path[-1], None)

deep_update(dic, path, dic_value) ¤

Update the dict based on path.

Source code in trestle/common/list_utils.py
185
186
187
188
189
190
191
192
def deep_update(dic: Dict[str, Any], path: List[str], dic_value: Dict[str, Any]) -> None:
    """Update the dict based on path."""
    if not path:
        raise TrestleError('Error updating value in deep update with empty path.')
    for node in path:
        dic[node] = dic.get(node, {})
        dic = dic[node]
    dic.update(dic_value)

delete_item_from_list(item_list, value, key) ¤

Remove the first matching item if it is present in a list based on the callable key matching the query value.

Source code in trestle/common/list_utils.py
116
117
118
119
120
121
122
def delete_item_from_list(item_list: List[TG], value: TG2, key: Callable[[TG], TG2]) -> List[TG]:
    """Remove the first matching item if it is present in a list based on the callable key matching the query value."""
    keys = [key(item) for item in item_list]
    if value in keys:
        index = keys.index(value)
        del item_list[index]
    return item_list

delete_list_from_list(item_list, indices) ¤

Delete a list of items from a list based on indices.

Source code in trestle/common/list_utils.py
146
147
148
149
def delete_list_from_list(item_list: List[TG], indices: List[int]) -> None:
    """Delete a list of items from a list based on indices."""
    for index in sorted(indices, reverse=True):
        del item_list[index]

get_default(item, default) ¤

Return the default value for the item if it is not set.

Source code in trestle/common/list_utils.py
83
84
85
def get_default(item: TG, default: TG) -> TG:
    """Return the default value for the item if it is not set."""
    return item if item else default

get_item_from_list(item_list, value, key, remove=False) ¤

Get first item from list if present based on key matching value with option to remove it from the list.

Source code in trestle/common/list_utils.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def get_item_from_list(
    item_list: Optional[List[TG]], value: TG2, key: Callable[[TG], TG2], remove: bool = False
) -> Optional[TG]:
    """Get first item from list if present based on key matching value with option to remove it from the list."""
    if not item_list:
        return None
    keys = [key(item) for item in item_list]
    item = None
    if value in keys:
        index = keys.index(value)
        item = item_list[index]
        if remove:
            del item_list[index]
    return item

is_ordered_sublist(needle, haystack) ¤

Determine if needle is exactly contained in haystack.

The needle list comprises an ordered list of strings. The haystack list comprises an ordered list of strings that is to be searched. If the strings in the needle appear in the haystack in that exact order then return true, else false.

Examples: needle=['a','b','c'], haystack=['x','y','a','b','c','z'], result = True needle=['a','b','c'], haystack=['x','y','a','b','z','c'], result = False

Source code in trestle/common/list_utils.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def is_ordered_sublist(needle: List[str], haystack: List[str]) -> bool:
    """Determine if needle is exactly contained in haystack.

    The needle list comprises an ordered list of strings.
    The haystack list comprises an ordered list of strings that is to be searched.
    If the strings in the needle appear in the haystack in that exact order then
    return true, else false.

    Examples:
    needle=['a','b','c'], haystack=['x','y','a','b','c','z'], result = True
    needle=['a','b','c'], haystack=['x','y','a','b','z','c'], result = False
    """
    n_items = len(needle)
    # an empty needle is trivially contained; a needle longer than the haystack cannot be
    if n_items == 0:
        return True
    return any(haystack[start : start + n_items] == needle for start in range(len(haystack) - n_items + 1))

join_key_to_list_dicts(dict1, dict2) ¤

Join two dicts of str to List.

Source code in trestle/common/list_utils.py
107
108
109
110
111
112
113
def join_key_to_list_dicts(dict1: Dict[str, List[Any]], dict2: Dict[str, List[Any]]) -> Dict[str, List[Any]]:
    """Join two dicts of str to List."""
    # merge like keys
    dict3 = {key: dict1[key] + dict2.get(key, []) for key in dict1}
    # merge unlike keys
    dict3.update({key: dict2[key] for key in dict2 if key not in dict3})
    return dict3

merge_dicts(dest, src) ¤

Merge the two dicts with priority to src.

Source code in trestle/common/list_utils.py
152
153
154
def merge_dicts(dest: Optional[Dict[str, str]], src: Optional[Dict[str, str]]) -> Dict[str, str]:
    """Merge the two dicts with priority to src."""
    return {**as_dict(dest), **as_dict(src)}

none_if_empty(list_) ¤

Convert to None if empty list, preserving None input.

This is the complement to as_list() for Pydantic v2 compatibility. Use after building a list to ensure empty lists become None for fields with min_length constraints.

Pattern for Pydantic v2 field assignment

temp_list = as_list(obj.field) # Get list ([] if None) temp_list.append(item) # Modify obj.field = none_if_empty(temp_list) # Assign (None if empty)

Source code in trestle/common/list_utils.py
69
70
71
72
73
74
75
76
77
78
79
80
def none_if_empty(list_: Optional[List[TG]]) -> Optional[List[TG]]:
    """Convert to None if empty list, preserving None input.

    This is the complement to as_list() for Pydantic v2 compatibility.
    Use after building a list to ensure empty lists become None for fields with min_length constraints.

    Pattern for Pydantic v2 field assignment:
        temp_list = as_list(obj.field)  # Get list ([] if None)
        temp_list.append(item)           # Modify
        obj.field = none_if_empty(temp_list)  # Assign (None if empty)
    """
    return list_ if list_ else None

pop_item_from_list(item_list, value, key) ¤

Pop first matching item from a list if it is present based on the key matching the value.

Source code in trestle/common/list_utils.py
141
142
143
def pop_item_from_list(item_list: Optional[List[TG]], value: TG2, key: Callable[[TG], TG2]) -> Optional[TG]:
    """Pop first matching item from a list if it is present based on the key matching the value."""
    return get_item_from_list(item_list, value, key, True)

set_or_pop(dic, key, value) ¤

Set if value is non-empty list or not None otherwise remove.

Source code in trestle/common/list_utils.py
207
208
209
210
211
212
def set_or_pop(dic: Dict[str, Any], key: str, value: Any) -> None:
    """Set if value is non-empty list or not None otherwise remove."""
    if value:
        dic[key] = value
    else:
        dic.pop(key, None)

handler: python