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
« prev ^ index » next coverage.py v7.6.1, created at 2025-07-13 14:49 +0000
1import functools
2import re
3from typing import Callable
6class _Patterns:
7 """Pre-compiled patterns."""
9 a = re.compile(r"(.)([A-Z][a-z]+)")
10 b = re.compile(r"([a-z0-9])([A-Z])")
13def escape_sql_single_quote(text: str) -> str:
14 """Single quote escape for SQL strings."""
15 return text.replace("'", "''")
18def unscape_sql_single_quote(text: str) -> str:
19 """Revert single quote escape in SQL string."""
20 return text.replace("''", "'")
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()
29def retval_matches(pattern: re.Pattern, index: int = None):
30 """Decorator to enforce that function returns a string in specified pattern."""
32 def decorator(func: Callable[[...], str]) -> Callable[[...], str]:
33 @functools.wraps(func)
34 def wrapper(*args, **kwargs) -> str:
35 retval = func(*args, **kwargs)
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}")
47 assert pattern.fullmatch(subject), f"Expected a string matching the pattern '{pattern}': {retval}"
48 return retval
50 return wrapper
52 return decorator