Source code for openghg.util._strings

import re
from typing import Any, Dict, List, Optional, Set, Tuple, Union, Iterable, overload

from openghg.util import not_set_metadata_values, null_metadata_values

__all__ = ["clean_string", "to_lowercase"]


@overload
def clean_string(to_clean: str) -> str: ...


@overload
def clean_string(to_clean: None) -> None: ...


[docs] def clean_string(to_clean: Optional[str]) -> Union[str, None]: """Returns a lowercase string with only alphanumeric characters and underscores. Args: to_clean: String to clean Returns: str or None: Clean string """ import re if to_clean is None: return None if isinstance(to_clean, bool): return str(to_clean).lower() try: # This might be used with numbers if is_number(to_clean): return str(to_clean) # Removes all whitespace cleaner = re.sub(r"\s+", "", to_clean, flags=re.UNICODE).lower() # Removes non-alphanumeric characters but keep underscores # cleanest = re.sub(r"\W+", "", cleaner) cleanest = re.sub(r"[^\w-]+", "", cleaner) except TypeError: return to_clean return cleanest
@overload def to_lowercase(d: Dict, skip_keys: Optional[List] = None) -> Dict: ... @overload def to_lowercase(d: List, skip_keys: Optional[List] = None) -> List: ... @overload def to_lowercase(d: Tuple, skip_keys: Optional[List] = None) -> Tuple: ... @overload def to_lowercase(d: Set, skip_keys: Optional[List] = None) -> Set: ... @overload def to_lowercase(d: str, skip_keys: Optional[List] = None) -> str: ...
[docs] def to_lowercase( d: Union[Dict, List, Tuple, Set, str], skip_keys: Optional[List] = None ) -> Union[Dict, List, Tuple, Set, str]: """Convert an object to lowercase. All keys and values in a dictionary will be converted to lowercase as will all objects in a list, tuple or set. You can optionally pass in a list of keys to skip when lowercasing a dictionary. Based on the answer https://stackoverflow.com/a/40789531/1303032 Args: d: Object to lower case skip_keys: List of keys to skip when lowercasing. Returns: dict: Dictionary of lower case keys and values """ if skip_keys is None: skip_keys = [] if isinstance(d, dict): lowercased = {k.lower(): to_lowercase(v) for k, v in d.items() if k not in skip_keys} if skip_keys: missing = {k: v for k, v in d.items() if k not in lowercased} lowercased.update(missing) return lowercased elif isinstance(d, (list, set, tuple)): t = type(d) return t(to_lowercase(o) for o in d) elif isinstance(d, str): return d.lower() else: return d
[docs] def is_number(s: Any) -> bool: """Is it a number? https://stackoverflow.com/q/354038 Args: s: String which may be a number Returns: bool """ if isinstance(s, bool): return False try: float(s) return True except (ValueError, TypeError): return False
[docs] def remove_punctuation(s: str) -> str: """Removes punctuation and converts the passed string to lowercase Args: s: String to convert Returns: str: Unpunctuated, lowercased string """ import re s = s.lower() return re.sub(r"[^\w\s]", "", s)
def extract_float(s: str) -> float: """Extract float from string. This extends the built-in `float` function to find floats within a larger string. Args: s: string to extract float from Returns: first float value found in given string Raises: ValueError if no floats found """ # construct regex for float following Python's float grammar: # https://docs.python.org/3/library/functions.html#grammar-token-float-floatvalue sign = r"[+-]?" # optional sign letter_neg_lookbehind = r"(?<![a-zA-Z])" # negative lookbehind assertion for letters letter_neg_lookahead = r"(?![a-zA-Z])" # negative lookahead assertion for letters infinity = letter_neg_lookbehind + r"(Infinity|inf)" + letter_neg_lookahead nan = letter_neg_lookbehind + "nan" + letter_neg_lookahead digit_part = r"(\d(_?\d)*)" # underscores ignored number = ( rf"({digit_part}?\.{digit_part}|{digit_part}\.?)" # at least 1 number before or after decimal place ) exponent = r"([eE]?[+-]?\d+)?" # optional exponent float_number = number + exponent abs_float_value = "(" + "|".join([float_number, infinity, nan]) + ")" float_pat = re.compile(sign + abs_float_value, re.IGNORECASE) if m := float_pat.search(s): return float(m.group(0)) raise ValueError(f"No float values found in '{s}'") def check_and_set_null_variable( param: Union[str, None], not_set_value: Optional[str] = None, null_values: Iterable = null_metadata_values(), ) -> Union[str, None]: """ Check whether a variable is set to a null value (e.g. None) and if so replace this with a defined string used to indicate the variable has not been set. This is typically: "not_set" Args: param: variable to check not_set_value: Optional value to replace if None. By default details from openghg.util.not_set_metadata_values() will be used. null_values: Values to identify as null. By default details from openghg.util.null_metadata_values() will be used. Returns: str: Original string or value to indicate this is not set None: Only returned if value is None and None is not included as one of the null_values. """ if not_set_value is None: not_set_values = not_set_metadata_values() not_set_value = not_set_values[0] if param in null_values: param = not_set_value return param