quart-imp/quart_imp/utilities.py
David Carmichael 9687db5a96 Initial commit
2024-02-11 21:59:18 +00:00

168 lines
4.6 KiB
Python

import functools
import logging
import os
import re
import sys
import typing as t
from pathlib import Path
class Sprinkles:
HEADER = "\033[95m"
OKBLUE = "\033[94m"
OKCYAN = "\033[96m"
OKGREEN = "\033[92m"
WARNING = "\033[93m"
FAIL = "\033[91m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"
END = "\033[0m"
def deprecated(message: str):
def func_wrapper(func):
@functools.wraps(func)
def proc_function(*args, **kwargs):
logging.critical(
f"{Sprinkles.FAIL}Function deprecated: {message}{Sprinkles.END}"
)
return func(*args, **kwargs)
return proc_function
return func_wrapper
def if_env_replace(
env_value: t.Optional[t.Any], ignore_missing_env_variables: bool = False
) -> t.Any:
"""
Looks for the replacement pattern to swap out values in the config file with environment variables.
"""
pattern = re.compile(r"<(.*?)>")
if isinstance(env_value, str):
if re.match(pattern, env_value):
env_var = re.findall(pattern, env_value)[0]
if env_var:
if os.environ.get(env_var):
return parse_config_env_var(os.environ.get(env_var))
if ignore_missing_env_variables:
return None
raise ValueError(f"Environment variable {env_value} not found")
return env_value
def process_dict(
this_dict: t.Optional[dict],
key_case_switch: str = "upper",
ignore_missing_env_variables: bool = False,
crawl: bool = False,
) -> dict:
"""
Used to process the config from_file dictionary and replace environment variables. Turns all keys to upper case.
"""
if this_dict is None:
return {}
return_dict = {}
for key, value in this_dict.items():
if key_case_switch == "ignore":
cs_key = key
else:
cs_key = key.upper() if key_case_switch == "upper" else key.lower()
if crawl:
if isinstance(value, dict):
return_dict[cs_key] = process_dict(
value, key_case_switch, ignore_missing_env_variables, crawl
)
continue
return_dict[cs_key] = if_env_replace(value, ignore_missing_env_variables)
return return_dict
def cast_to_import_str(app_name: str, folder_path: Path) -> str:
"""
Takes the folder path and converts it to a string that can be imported
"""
folder_parts = folder_path.parts
parts = folder_parts[folder_parts.index(app_name) :]
if sys.version_info.major == 3:
if sys.version_info.minor < 9:
return ".".join(parts).replace(".py", "")
return ".".join(parts).removesuffix(".py")
raise NotImplementedError("Python version not supported")
def snake(value: str) -> str:
"""
Switches name of the class CamelCase to snake_case
"""
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", value)
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
def class_field(class_: str, field: str) -> str:
"""
Switches name of the class CamelCase to snake_case and tacks on the field name
Used for SQLAlchemy foreign key assignments
INFO ::: This function may not produce the correct information if you are using __tablename__ in your class
"""
return f"{snake(class_)}.{field}"
def cast_to_bool(value: t.Union[str, bool, None]) -> bool:
"""
Casts an array of truly string values to a boolean. Used for config files.
"""
if value is None:
return False
if isinstance(value, bool):
return value
if isinstance(value, str):
true_str = ("true", "yes", "y", "1")
false_str = ("false", "no", "n", "0")
if value.lower() in true_str:
return True
elif value.lower() in false_str:
return False
else:
raise TypeError(f"Cannot cast {value} to bool")
else:
raise TypeError(f"Cannot cast {value} to bool")
def parse_config_env_var(value: t.Optional[str]) -> t.Optional[t.Union[bool, str, int]]:
"""
Casts value to a boolean, string, or int if possible. If not, returns none.
"""
if value == "None":
return None
if isinstance(value, str):
true_str = ("true", "yes", "y", "1")
false_str = ("false", "no", "n", "0")
if value.lower() in true_str:
return True
elif value.lower() in false_str:
return False
else:
try:
return int(value)
except ValueError:
return value
return None