Coverage for src/mlopus/utils/string_utils.py: 90%

30 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-07-13 14:49 +0000

1import functools 

2import re 

3from typing import Callable 

4 

5 

6class _Patterns: 

7 """Pre-compiled patterns.""" 

8 

9 a = re.compile(r"(.)([A-Z][a-z]+)") 

10 b = re.compile(r"([a-z0-9])([A-Z])") 

11 

12 

13def escape_sql_single_quote(text: str) -> str: 

14 """Single quote escape for SQL strings.""" 

15 return text.replace("'", "''") 

16 

17 

18def unscape_sql_single_quote(text: str) -> str: 

19 """Revert single quote escape in SQL string.""" 

20 return text.replace("''", "'") 

21 

22 

23def camel_to_snake(name: str): 

24 """Convert CamelCase string to snake_case""" 

25 name = _Patterns.a.sub(r"\1_\2", name) 

26 return _Patterns.b.sub(r"\1_\2", name).lower() 

27 

28 

29def retval_matches(pattern: re.Pattern, index: int = None): 

30 """Decorator to enforce that function returns a string in specified pattern.""" 

31 

32 def decorator(func: Callable[[...], str]) -> Callable[[...], str]: 

33 @functools.wraps(func) 

34 def wrapper(*args, **kwargs) -> str: 

35 retval = func(*args, **kwargs) 

36 

37 if isinstance(retval, tuple) and index is not None: 

38 if not isinstance(subject := retval[index], str): 

39 raise ValueError(f"Expected a string: {retval}") 

40 elif isinstance(retval, str): 

41 if index is not None: 

42 raise ValueError(f"Expected a tuple: {retval}") 

43 subject = retval 

44 else: 

45 raise ValueError(f"Expected a string: {retval}") 

46 

47 assert pattern.fullmatch(subject), f"Expected a string matching the pattern '{pattern}': {retval}" 

48 return retval 

49 

50 return wrapper 

51 

52 return decorator