Source code for sertit.strings

# -*- coding: utf-8 -*-
# Copyright 2024, SERTIT-ICube - France, https://sertit.unistra.fr/
# This file is part of sertit-utils project
#     https://github.com/sertit/sertit-utils
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" Tools concerning strings """

import argparse
import logging
import re
import uuid
from datetime import date, datetime
from typing import Any, Union

from sertit.logs import SU_NAME

LOGGER = logging.getLogger(SU_NAME)
DATE_FORMAT = "%Y-%m-%dT%H:%M:%S"


[docs] def str_to_bool(bool_str: str) -> bool: """ Convert a string to a bool. Accepted values (compared in lower case): - :code:`True` <=> :code:`yes`, :code:`true`, :code:`t`, :code:`1` - :code:`False` <=> :code:`no`, :code:`false`, :code:`f`, :code:`0` Args: bool_str: Bool as a string Returns: bool: Boolean value Examples: >>> str_to_bool("yes") == True # Works with "yes", "true", "t", "y", "1" (accepted with any letter case) True >>> str_to_bool("no") == False # Works with "no", "false", "f", "n", "0" (accepted with any letter case) True """ if isinstance(bool_str, bool): return bool_str true_str = ("yes", "true", "t", "y", "1") false_str = ("no", "false", "f", "n", "0") if bool_str.lower() in true_str: bool_val = True elif bool_str.lower() in false_str: bool_val = False else: raise ValueError( f"Invalid true or false value, " f"should be {true_str} if True or {false_str} if False, not {bool_str}" ) return bool_val
[docs] def str_to_verbosity(verbosity_str: str) -> int: """ Return a logging level from a string (compared in lower case). - :code:`DEBUG` <=> {:code:`debug`, :code:`d`, :code:`10`} - :code:`INFO` <=> {:code:`info`, :code:`i`, :code:`20`} - :code:`WARNING` <=> {:code:`warning`, :code:`w`, :code:`warn`} - :code:`ERROR` <=> {:code:`error`, :code:`e`, :code:`err`} Args: verbosity_str (str): String to be converted Returns: logging level: Logging level (INFO, DEBUG, WARNING, ERROR) Examples: >>> str_to_bool("d") == logging.DEBUG # Works with 'debug', 'd', 10 (accepted with any letter case) True >>> str_to_bool("i") == logging.INFO # Works with 'info', 'i', 20 (accepted with any letter case) True >>> str_to_bool("w") == logging.WARNING # Works with 'warning', 'w', 'warn', 30 (accepted with any letter case) True >>> str_to_bool("e") == logging.ERROR # Works with 'error', 'e', 'err', 40 (accepted with any letter case) True """ debug_str = ("debug", "d", 10) info_str = ("info", "i", 20) warn_str = ("warning", "w", "warn", 30) err_str = ("error", "e", "err", 40) if isinstance(verbosity_str, str): verbosity_str = verbosity_str.lower() if verbosity_str in info_str: verbosity = logging.INFO elif verbosity_str in debug_str: verbosity = logging.DEBUG elif verbosity_str in warn_str: verbosity = logging.WARNING elif verbosity_str in err_str: verbosity = logging.ERROR else: raise argparse.ArgumentTypeError( f"Incorrect logging level value: {verbosity_str}, " f"should be {info_str}, {debug_str}, {warn_str} or {err_str}" ) return verbosity
[docs] def str_to_list( list_str: Union[str, list], additional_separator: str = "", case: str = None ) -> list: """ Convert str to list with :code:`,`, :code:`;`, :code:`\x20` separators. Args: list_str (Union[str, list]): List as a string additional_separator (str): Additional separators. Base ones are :code:`,`, :code:`;`, :code:`\\\x20` (space). case (str): {none, 'lower', 'upper'} Returns: list: A list from split string Example: >>> str_to_list("A, B; C D") ["A", "B", "C", "D"] """ if isinstance(list_str, str): # Concatenate separators separators = ",|;| " if additional_separator: separators += "|" + additional_separator # Split listed_str = re.split(separators, list_str) elif isinstance(list_str, list): listed_str = list_str else: raise ValueError( f"List should be given as a string or a list of string: {list_str}" ) out_list = [] for item in listed_str: # Check if there are null items if item: if case == "lower": item_case = item.lower() elif case == "upper": item_case = item.upper() else: item_case = item out_list.append(item_case) return out_list
[docs] def str_to_date( date_str: Union[str, datetime], date_format: str = DATE_FORMAT ) -> datetime: """ Convert string to a :code:`datetime.datetime`. Also accepted date formats: - :code:`now`: datetime.today() - Usual JSON date format: :code:`%Y-%m-%d` - Already formatted datetimes and dates Args: date_str (str): Date as a string date_format (str): Format of the date (as ingested by strptime) Returns: datetime.datetime: A date as a python datetime object Examples: # Default date format (isoformat) >>> str_to_date("2020-05-05T08:05:15") datetime(2020, 5, 5, 8, 5, 15) # This usual JSON format is also accepted >>> str_to_date("2019-08-06") datetime(2019, 8, 6) # User date's format >>> str_to_date("20200909105055", date_format="%Y%m%d%H%M%S") datetime(2020, 9, 9, 10, 50, 55) """ if isinstance(date_str, datetime): dtm = date_str elif isinstance(date_str, date): dtm = datetime.fromisoformat(date_str.isoformat()) else: try: if date_str.lower() == "now": # Now with correct format (no microseconds if not specified and so on) dtm = datetime.strptime( datetime.today().strftime(date_format), date_format ) else: dtm = datetime.strptime(date_str, date_format) except ValueError: # Just try with the usual JSON format json_date_format = "%Y-%m-%d" try: dtm = datetime.strptime(date_str, json_date_format) except ValueError as ex: raise ValueError( f"Invalid date format: {date_str}; should be {date_format} " f"or {json_date_format}" ) from ex return dtm
[docs] def str_to_list_of_dates( date_str: Union[list, str], date_format: str = DATE_FORMAT, additional_separator: str = "", ) -> list: """ Convert a string containing a list of dates to a list of :code:`datetime.datetime`. Also accepted date formats: - :code:`now`: datetime.today() - Usual JSON date format: :code:`%Y-%m-%d` - Already formatted datetimes and dates Args: date_str (Union[list, str]): Date as a string date_format (str): Format of the date (as ingested by strptime) additional_separator (str): Additional separator Returns: list: A list containing datetimes objects Example: >>> # Default date format (isoformat) >>> str_to_list_of_dates("20200909105055, 2019-08-06;19560702121212\t2020-08-09", >>> date_format="%Y%m%d%H%M%S", >>> additional_separator="\t") [datetime(2020, 9, 9, 10, 50, 55), datetime(2019, 8, 6), datetime(1956, 7, 2, 12, 12, 12), datetime(2020, 8, 9)] """ # Split string to get a list of strings list_of_dates_str = str_to_list(date_str, additional_separator) # Convert strings to date list_of_dates = [str_to_date(dt, date_format) for dt in list_of_dates_str] return list_of_dates
[docs] def to_cmd_string(unquoted_str: Any) -> str: """ Add quotes around the string in order to make the command understand it's a string (useful with tricky symbols like :code:`&` or white spaces): Args: unquoted_str (str): String to update Returns: str: Quoted string Example: >>> # This str wont work in the terminal without quotes (because of the &) >>> pb_str = r"D:/Minab_4-DA&VHR/Minab_4-DA&VHR.shp" >>> to_cmd_string(pb_str) "\"D:/Minab_4-DA&VHR/Minab_4-DA&VHR.shp\"" """ if not isinstance(unquoted_str, str): unquoted_str = str(unquoted_str) cmd_str = unquoted_str if not unquoted_str.startswith('"'): cmd_str = '"' + cmd_str if not unquoted_str.endswith('"'): cmd_str = cmd_str + '"' return cmd_str
[docs] def snake_to_camel_case(snake_str: str) -> str: """ Convert a :code:`snake_case` string to :code:`CamelCase`. Args: snake_str (str): String formatted in snake_case Returns: str: String formatted in CamelCase Example: >>> snake_to_camel_case("snake_case") "SnakeCase" """ return "".join((w.capitalize() for w in snake_str.split("_")))
[docs] def camel_to_snake_case(snake_str: str) -> str: """ Convert a :code:`CamelCase` string to :code:`snake_case`. Args: snake_str (str): String formatted in CamelCase Returns: str: String formatted in snake_case Example: >>> camel_to_snake_case("CamelCase") "camel_case" """ return "".join(["_" + c.lower() if c.isupper() else c for c in snake_str]).lstrip( "_" )
[docs] def is_uuid(uuid_string, version=4): """ Check if uuid_string is a valid UUID Args: uuid_string (str): UUID to be tested version (int): 4 (default), UUID version Returns: bool: Valid UUID or not """ if not 1 <= version <= 5: raise ValueError( f"Illegal version number: {version} (should be between 1 and 5)" ) try: uid = uuid.UUID(str(uuid_string), version=version) return uid.hex == uuid_string.replace("-", "") except ValueError: return False