1- from __future__ import annotations
1+ """Common functions used throughout the project."""
22
33import copy
44import hashlib
5- from collections import OrderedDict
6- from collections .abc import Callable , Iterable , Iterator
5+ from collections .abc import Callable
76from functools import lru_cache , wraps
87from typing import Any
98
1211
1312
1413def exists (obj : Any ) -> bool :
14+ """Check that `obj` is not `None`."""
1515 return obj is not None
1616
1717
1818def default (obj : Any , default : Any ) -> Any :
19+ """Return `obj` if not `None`, otherwise return `default`."""
1920 return obj if exists (obj ) else default
2021
2122
22- def deduplicate_iterator (iterator : Iterable ) -> Iterator :
23- """Deduplicate an iterator while preserving order."""
24- return iter (OrderedDict .fromkeys (iterator ))
25-
26-
2723def to_hashable (element : Any ) -> Any :
2824 """Convert an element to a hashable type."""
2925 return element if isinstance (element , int | str | np .integer | np .str_ ) else tuple (element )
3026
3127
28+ def string_to_md5_hash (s : str , truncate : int = 32 ) -> str :
29+ """Generate an MD5 hash of a string and return the first `truncate` characters."""
30+ full_hash = hashlib .md5 (s .encode ("utf-8" )).hexdigest ()
31+ return full_hash [:truncate ]
32+
33+
3234def sum_string_arrays (* objs : np .ndarray | str ) -> np .ndarray :
3335 """
34- Sum a list of string arrays / strings into a single string array by concatenating them and
36+ Sum a list of string arrays or strings into a single string array by concatenating them and
3537 determining the shortest string length to set as dtype.
3638 """
3739 return reduce (np .char .add , objs ).astype (object ).astype (str )
@@ -47,6 +49,24 @@ def listmap(func: Callable, *iterables) -> list:
4749 return compose (list , map )(func , * iterables )
4850
4951
52+ def as_list (value : Any ) -> list :
53+ """Convert a value to a list.
54+
55+ Handles various types using duck typing:
56+ - Iterable objects (lists, tuples, strings, etc.): converted to list
57+ - Single values: wrapped in a list
58+ """
59+ try :
60+ # Try to iterate over the value (duck typing approach)
61+ # Exclude strings since they're iterable but we want to treat them as single values
62+ if isinstance (value , str ):
63+ return [value ]
64+ return list (value )
65+ except TypeError :
66+ # If it's not iterable, wrap it in a list
67+ return [value ]
68+
69+
5070def immutable_lru_cache (maxsize : int = 128 , typed : bool = False , deepcopy : bool = True ) -> Callable :
5171 """An immutable version of `lru_cache` for caching functions that return mutable objects."""
5272 copy_func = copy .deepcopy if deepcopy else copy .copy
@@ -89,9 +109,3 @@ def __call__(self, value: Any) -> int:
89109 self .key_to_id [value ] = self .next_id
90110 self .next_id += 1
91111 return self .key_to_id [value ]
92-
93-
94- def md5_hash_string (s : str , length : int = 32 ) -> str :
95- """Generate an MD5 hash of a string and return the first `length` characters."""
96- full_hash = hashlib .md5 (s .encode ("utf-8" )).hexdigest ()
97- return full_hash [:length ]
0 commit comments