Resetting utils subtree.

This commit is contained in:
2024-12-04 13:35:23 +05:30
parent 964656702a
commit 033a617980
90 changed files with 38 additions and 15145 deletions
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
-168
View File
@@ -1,168 +0,0 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Saturday, 18th May, 2024
OBJECTIVE:
To provide a quick set of functions to work with fuzzy logic.
REFERENCES:
1) https://www.w3schools.com/python/python_json.asp
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# To apply fuzzy logic:
from thefuzz import fuzz, process
# To work with tabulated data:
import pandas as pd
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
def get_best_match(
target,
choices,
threshold = 0.70,
partial = False
):
if partial: scorer = fuzz.partial_token_sort_ratio
else: scorer = fuzz.ratio
result = process.extractOne(
target,
choices,
score_cutoff = threshold * 100,
scorer = scorer
)
try: return result[0]
except: return None
# ---------------------------------------------------------------------------------------------------------------------
def rank(target, choices, partial = True):
if partial: scorer = fuzz.partial_token_sort_ratio
else: scorer = fuzz.ratio
result = process.extract(
target,
choices,
limit = len(choices),
scorer = scorer
)
result = pd.DataFrame(result, columns = ["choice", "closeness"])
result["closeness"] = result["closeness"] / 100.0
return result
# ---------------------------------------------------------------------------------------------------------------------
def match(targets, choices, threshold = 0.7, partial = False, allow_null = False):
all_matches_df = None
all_matches = {target: None for target in targets}
something_is_null = False
for target in targets:
match_df = rank(target, choices, partial = partial)
match_df["target"] = target
if all_matches_df is None: all_matches_df = match_df
else: all_matches_df = pd.concat([all_matches_df, match_df])
all_matches_df = all_matches_df.sort_values(by = ["closeness"], ascending = False).reset_index(drop = True)
for target in targets:
target_df = all_matches_df[all_matches_df["target"] == target].reset_index(drop = True)
if target_df.empty: continue
if target_df.at[0, "closeness"] >= threshold:
choice = target_df.at[0, "choice"]
all_matches[target] = choice
all_matches_df = all_matches_df[all_matches_df["choice"] != choice]
else:
all_matches[target] = None
something_is_null = True
# print(all_matches)
if something_is_null and not allow_null: return None
else: return all_matches
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
import async_json_utils
awb_numbers = [
"SF1111BIC",
"SF2222BIC",
"SF3333BIC",
"SF4444BIC",
]
chat_text = "SF1112BIC"
# print(chat_text == names[0])
best_match = get_best_match(chat_text, awb_numbers, threshold = 0.60, partial = False)
print(f"Best match for '{chat_text}' is '{best_match}'")
-177
View File
@@ -1,177 +0,0 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Saturday, 24th Aug., 2024
OBJECTIVE:
To provide an overview of any function or class in a string.
The generated overview can then either be shown on the terminal, or transmitted over some other medium for
collaborative work.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# System-level activities:
import io
import inspect
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
def get_help_for_class(cls, skip_methods = None):
"""
Returns the help documentation to use this class.
:param cls: The class whose help string is desired.
:param skip_methods: A list of methods to NOT include in the help text.
:return: This help documentation.
"""
separator = "\n\n" + ("=" * 120) + "\n\n"
if skip_methods is None: skip_methods = []
elif not isinstance(skip_methods, list): skip_methods = [skip_methods]
# Get class name and docstring:
class_name = cls.__name__
docstring = inspect.getdoc(cls) or ""
help_string = "HELP FOR:\n\n"
help_string += class_name + "\n\n"
help_string += "This document has upto 120 chars per line.\n"
help_string += "Best viewed with monospaced font :)"
help_string += docstring + separator
# Get all methods and their docstrings.
# Then note the documentation of the methods while ignoring the blacklisted ones:
members = inspect.getmembers(cls, predicate = inspect.isfunction)
func_help = []
for name, method in members:
# Ignore if asked, or extract the details:
if name in skip_methods or name.startswith(f"_{class_name}__"): continue
else: func_help.append(get_help_for_function(method))
# Put all the things together:
func_help = separator.join(func_help)
help_string += func_help
# Done here:
return help_string
# ---------------------------------------------------------------------------------------------------------------------
def get_help_for_function(func):
"""
Get the help string for one function.
It could be a standalone function, or a method of a class.
:param func: The function (or method) whose help string is needed.
:return: The help string of the function.
"""
# Get the name and documentation:
func_name = func.__name__
async_indicator = " (async)" if inspect.iscoroutinefunction(func) else ""
func_doc = inspect.getdoc(func) or ""
# Create the decorated header:
func_decorator = "-" * (len(func_name) + 2)
func_head = "." + func_decorator + f".\n| {func_name} |{async_indicator}\n`" + func_decorator + "`\n\n"
# Add the 'args' and 'kwargs':
func_args = []
for name, param in inspect.signature(func).parameters.items():
default = param.default
if isinstance(default, str): default = f"\"{default}\""
if default == inspect.Parameter.empty: func_args.append(f"{name}")
else: func_args.append(f"{name}: {type(default).__name__} = {default}")
if len(func_args) > 0: func_args = f"{func_name} (\n\t" + "\n\t".join(func_args) + "\n):\n\n"
else: func_args = f"{func_name} ():\n\n"
# Get the params and return value part from the doc:
params_start = func_doc.find(":param")
return_start = func_doc.find(":return")
func_params = "\n" + func_doc[params_start:return_start] if params_start >= 0 else ""
func_return = "\n" + func_doc[return_start:] if return_start >= 0 else ""
# Isolate the documentation part:
if params_start >= 0: func_doc = func_doc[:params_start]
elif return_start >= 0: func_doc = func_doc[:return_start]
# Done here:
return func_head + func_args + func_doc + func_params + func_return
# ---------------------------------------------------------------------------------------------------------------------
def get_help(entity, skip_methods = None):
"""
Get the help documentation for anything from its docstring.
:param entity: The entity you want to get help for.
:param skip_methods: A list of methods to ignore if inspecting a class. Not valid for standalone functions.
:return: The help string.
"""
if inspect.isclass(entity): return get_help_for_class(entity, skip_methods = skip_methods)
else: return get_help_for_function(entity)
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
-202
View File
@@ -1,202 +0,0 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Create: Saturday, 18th May, 2022
Update: Thursday, 22nd Aug. 2024
OBJECTIVE:
To provide an easy way to work with '.json' data and files.
REFERENCES:
1) https://www.w3schools.com/python/python_json.asp
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# System-level activities:
import io
# To work with the JSON standard:
import json
# To work with files:
from utils_v2.system import files
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
def from_string(json_data):
"""
Decodes a JSON string to a pythonic variable like a dict.
:param json_data: The JSON string to decode.
:return: The decoded pythonic variable.
"""
python_data = json.loads(json_data)
return python_data
# ---------------------------------------------------------------------------------------------------------------------
def to_string(
python_data,
indent = 4,
default = None,
separators = None,
no_space = False
):
"""
Converts the given pythonic data to a JSON string.
:param python_data: The input data like a dict.
:param indent: The tab-width for pretty presentation.
:param default: The function to use on something that cannot be directly parsed into a JSON string.
:param separators: Custom separators to use.
:param no_space: If you want a dense JSON string that saves memory by not using spaces or tabs or line-breaks. Not
good for human readability, very good for saving memory. WARNING: THIS OVERRIDES EVERY OTHER PARAMETER EXCEPT
'default'.
:return: The JSON string representation of the input pythonic data.
"""
if no_space:
json_data = json.dumps(
python_data,
default = default,
separators = (',', ':')
)
else:
json_data = json.dumps(
python_data,
indent = indent,
default = default,
separators = separators
)
return json_data
# ---------------------------------------------------------------------------------------------------------------------
def from_file(file):
"""
Reads a JSON file and returns it as a pythonic variable like a dict.
:param file: The path to the file on the disk or a file held in RAM as a BytesIO object.
:return: The decoded pythonic variable.
"""
if isinstance(file, io.BytesIO):
file.seek(0)
json_data = file.getvalue()
else: json_data = files.read_file(file)
python_data = from_string(json_data)
return python_data
# ---------------------------------------------------------------------------------------------------------------------
def to_file(
file,
python_data,
indent = 4,
default = None,
separators = None,
no_space = False
):
"""
:param file: Either a path to a file on disk, or a buffer in RAM in the form of a BytesIO object.
:param python_data: The pythonic data to be converted to the JSON string.
:param indent: The tab-width for pretty presentation.
:param default: The function to use on something that cannot be directly parsed into a JSON string.
:param separators: Custom separators to use.
:param no_space: If you want a dense JSON string that saves memory by not using spaces or tabs or line-breaks. Not
good for human readability, very good for saving memory. WARNING: THIS OVERRIDES EVERY OTHER PARAMETER EXCEPT
'default'.
:return: True/False if a path was given, else the same BytesIO object with the written JSON data.
"""
json_data = to_string(
python_data,
indent = indent,
default = default,
separators = separators,
no_space = no_space
)
if isinstance(file, io.BytesIO):
file.write(json_data.encode("utf-8"))
file.seek(0)
return file
else:
try:
files.write_file(file, json_data, mode = "w")
return True
except: return False
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
-288
View File
@@ -1,288 +0,0 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Sunday, 28th Apr., 2024
OBJECTIVE:
To provide a convenient way to perform RegEx operations like finding patterns and substituting them.
REFERENCES:
1) https://www.w3schools.com/python/python_regex.asp
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# To work with RegEx:
import re
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# Common RegEx patterns:
REGEX_EMAIL_ID = r"[\d\w_.+]*@[\d\w_]*.[\d\w]{2,}"
REGEX_PASSWORD = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*[\d])(?=.*[!@#$%^&*()_+{}\[\]:;<>,.?~\\\/-]).{8,}$"
REGEX_NAME = r"^[\d\w .\-]{1,30}$"
REGEX_USERNAME = r"^[\d\w_]{8,25}$"
REGEX_CONTACT_NUMBER = r"\+?\d{0,3}\s*\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}"
REGEX_DATE = r"\b(?:\d{4}-\d{2}-\d{2}|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2},?\s+\d{4}|\d{1,2}\/\d{1,2}\/\d{4}|\d{1,2}-\d{1,2}-\d{2}|\d{1,2}(?:st|nd|rd|th)\s+(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?),?\s+\d{4})\b"
REGEX_GSTIN = r"[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}"
REGEX_PAN = r"[A-Z]{5}[0-9]{4}[A-Z]{1}"
REGEX_IPV4 = (r"[0-9]{1,3}\." * 3) + r"[0-9]{1,3}"
REGEX_IPV6 = (r"[0-9a-fA-F]{1,4}:" * 7) + r"[0-9a-fA-F]{1,4}"
REGEX_IFSC = r"[A-Z]{4}0[A-Z0-9]{6}"
REGEX_UPI = r"[a-zA-Z0-9\.\-]{2,256}@[a-zA-Z][a-zA-Z]{2,64}"
REGEX_MAC_ADDRESS = r"([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}\\.[0-9a-fA-F]{4}\\.[0-9a-fA-F]{4})"
REGEX_METRIC_WEIGHT = r"[\d\.]+[ ]?[k]?g"
# RegEx chars (append them to the patterns if needed):
REGEX_START = "^"
REGEX_END = "$"
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
def find(text, pattern, case_sensitive = True, dot_all = False):
"""
Returns a list of substrings that match the given RegEx pattern in the input text.
:param text: The text in which the pattern needs to be found.
:param pattern: The RegEx pattern to look for.
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
:param dot_all: Allow all characters to be matched in ".".
:return: An array (list) of substring that match the pattern. Can be an empty list as well.
"""
# Prepare the flags:
flags = 0
if not case_sensitive: flags |= re.IGNORECASE
if dot_all: flags |= re.DOTALL
# Perform the RegEx operation, and clean the results:
matches = [match if type(match) is str else match[1] for match in re.findall(pattern, text, flags = flags)]
matches = [match for match in matches if len(match) > 0]
# Return the results:
return matches
# ---------------------------------------------------------------------------------------------------------------------
def find_first(text, pattern, case_sensitive = True, dot_all = False):
"""
Returns the first substring that matches the given RegEx pattern in the input text.
:param text: The text in which the pattern needs to be found.
:param pattern: The RegEx pattern to look for.
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
:param dot_all: Allow all characters to be matched in ".".
:return: The first match as a string, or None if no match was found..
"""
matches = find(
text = text,
pattern = pattern,
case_sensitive = case_sensitive,
dot_all = dot_all
)
if not matches: return None
else: return matches[0]
# ---------------------------------------------------------------------------------------------------------------------
def replace(text, pattern, substitute_text, case_sensitive = True, dot_all = False):
"""
Replaces any substring in the text that matches the RegEx pattern.
:param text: The text in which the substitutions need to be made.
:param pattern: The RegEx pattern that needs to be substituted.
:param substitute_text: The text that will replace the matches that were found.
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
:param dot_all: Allow all characters to be matched in ".".
:return: The text with the substitutions. If no matches are found, the original string is returned.
"""
# Prepare the flags:
flags = 0
if not case_sensitive: flags |= re.IGNORECASE
if dot_all: flags |= re.DOTALL
# Perform the RegEx operation, and return the results:
return re.sub(pattern, substitute_text, text, flags = flags)
# ---------------------------------------------------------------------------------------------------------------------
def search(text, pattern, case_sensitive = True, dot_all = False):
"""
Checks if the given RegEx pattern occurs ANYWHERE in the text that was provided.
:param text: The text that needs to be matched against the pattern.
:param pattern: The RegEx pattern to look for.
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
:param dot_all: Allow all characters to be matched in ".".
:return: True if the pattern matches, else False.
"""
# Prepare the flags:
flags = 0
if not case_sensitive: flags |= re.IGNORECASE
if dot_all: flags |= re.DOTALL
# Perform the RegEx operation, and return the results:
if re.search(pattern, text, flags = flags): return True
else: return False
# ---------------------------------------------------------------------------------------------------------------------
def match(text, pattern, case_sensitive = True, dot_all = False):
"""
Checks if the given text matches the RegEx pattern that was provided. The check is made only at the start of the
input string.
:param text: The text that needs to be matched against the pattern.
:param pattern: The RegEx pattern to look for.
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
:param dot_all: Allow all characters to be matched in ".".
:return: True if the pattern matches, else False.
"""
# Prepare the flags:
flags = 0
if not case_sensitive: flags |= re.IGNORECASE
if dot_all: flags |= re.DOTALL
# Perform the RegEx operation, and return the results:
if re.match(pattern, text, flags = flags): return True
else: return False
# ---------------------------------------------------------------------------------------------------------------------
def split(text, pattern, case_sensitive = True, dot_all = False):
"""
Splits an input string based on the pattern that is being matched.
:param text: The text that needs to be matched against the pattern.
:param pattern: The RegEx pattern to look for.
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
:param dot_all: Allow all characters to be matched in ".".
:return: True if the pattern matches, else False.
"""
# Prepare the flags:
flags = 0
if not case_sensitive: flags |= re.IGNORECASE
if dot_all: flags |= re.DOTALL
# Perform the RegEx operation, and return the results:
substrings = re.split(pattern, text, flags = flags)
if len(substrings) > 0 and substrings[0] == "": substrings.pop(0)
return substrings
# ---------------------------------------------------------------------------------------------------------------------
def to_json(text, pattern, case_sensitive = True, dot_all = False):
"""
Gives out a dict from the extracted features in a string. It is based on the concept of Named Groups.
Consider the following example (assuming the search is case-insensitive):
TEXT: "UPI/309258561479/14:17:35/UPI/omsainurses@okhdfc"
PATTERN: "upi/.*/(?P<time>.*)/.*/(?P<ref>.*)"
RESULT: {'time': '14:17:35', 'ref': 'omsainurses@okhdfc'}
:param text: The text that needs to be matched against the pattern.
:param pattern: The RegEx pattern to look for.
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
:param dot_all: Allow all characters to be matched in ".".
:return: A dict with all the extracted features.
"""
# Prepare the flags:
flags = 0
if not case_sensitive: flags |= re.IGNORECASE
if dot_all: flags |= re.DOTALL
# Perform the RegEx operation, and return the results:
matches = re.search(pattern, text, flags = flags)
regex_json = matches.groupdict() if matches else {}
return regex_json
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
import json_utils
for narr in [
# r"UPI/309258561479/14:17:35/UPI/omsainurses@okhdf cb",
# r"UPI/309258561479/14:17:35/omsainurses@okhdf cb",
# r"NEFT-N095232403538009-RELIGARE BROKING LIMITED MAI",
r"Product listing - My product - 75g - Super combo pack",
r"Product listing - My product - 750 g",
r"0.5 kgs mini pack"
]:
result = to_json(
narr,
r"upi/.*/(?P<time>.*)/.*/(?P<ref>.*)",
case_sensitive = False
)
print(result)