Source code for pawnlib.typing.check

import copy
import os
import re
import json
import sys
import inspect
from rich.panel import Panel
from pawnlib.config import pawn

try:
    from typing import Any, Union
except ImportError:
    from typing_extensions import Any, Union
from pawnlib.typing.constants import const


[docs]def is_json(s) -> bool: """ Check if a string is valid JSON. :param s: a string to check if it is valid JSON. :return: True if the string is valid JSON, False otherwise. Example: .. code-block:: python check.is_json('{"name": "John", "age": 30, "city": "New York"}') # >> True check.is_json('{"name": "John", "age": 30, "city": "New York",}') # >> False """ if not (isinstance(s, str) and (s.startswith('{') or s.startswith('['))): return False try: json.loads(s) except ValueError: return False return True
[docs]def is_float(s) -> bool: """ Check if a value is float :param s: A value to check if it is a float :type s: Any :return: True if the value is a float, False otherwise :rtype: bool Example: .. code-block:: python check.is_float(3.14) # >> True check.is_float("3.14") # >> True check.is_float("hello") # >> False """ if isinstance(s, bool): # Exclude bool explicitly return False if isinstance(s, float): return True if isinstance(s, str): s = s.strip() try: float_value = float(s) return '.' in str(s) or 'e' in str(s).lower() # Check if it has a decimal point or scientific notation except (TypeError, ValueError): return False
[docs]def is_int(s) -> bool: """ Check if a value is integer. :param s: A value to check. :type s: Any :return: True if the value is an integer, False otherwise. :rtype: bool Example: .. code-block:: python check.is_int(1) # >> True check.is_int(1.0) # >> False check.is_int("2") # >> True check.is_int("2.0") # >> False """ if isinstance(s, bool): # Exclude bool explicitly return False if isinstance(s, int): return True if isinstance(s, str): s = s.strip() try: int_value = int(s) return str(int_value) == str(s) # Ensure the string representation matches the original input except (TypeError, ValueError): return False
[docs]def is_number(s) -> bool: """ Check if a value is an int or a float (number). :param s: A value to check if it is a number :type s: Any :return: True if the value is an int or float, False otherwise :rtype: bool Example: .. code-block:: python is_number(42) # >> True is_number(3.14) # >> True is_number("42") # >> True is_number("3.14") # >> True is_number("hello") # >> False """ if isinstance(s, bool): # Exclude bool explicitly return False if isinstance(s, str): s = s.strip() if isinstance(s, (int, float)): # Direct check for int or float return True try: float(s) # Try converting to a float return True except (TypeError, ValueError): return False
[docs]def is_hex(s) -> bool: """ Check if a value is hexadecimal :param s: string to check :return: True if s is hexadecimal, False otherwise Example: .. code-block:: python check.is_hex("1a") # >> True check.is_hex("g") # >> False """ if not isinstance(s, str): return False if s.startswith(("0x", "0X")): s = s[2:] # Remove '0x' prefix for validation try: int(s, 16) except TypeError: return False except ValueError: return False return True
[docs]def is_regex_keyword(keyword: str, value: str) -> bool: """ The is_regex_keyword function takes two strings, a keyword and a value. If the keyword starts with / and ends with /, then it is treated as a regex pattern. The function checks if the regex pattern is contained within the value string. If so, True is returned; otherwise False. :param keyword:str: Check if the value:str parameter matches the keyword :param value:str: Check if the keyword is in the value :return: True if the keyword is a regex and matches Example: .. code-block:: python check.is_regex_keyword("/hello/", "hello world") # >> True check.is_regex_keyword("(hello)+", "hello world") # >> True check.is_regex_keyword("hello", "world") # >> False """ if len(keyword) <= 0 or len(value) <= 0: return False if keyword[0] == "/" and keyword[-1] == "/": keyword = keyword.replace("/", "") if keyword in value: return True elif keyword[0] == "(" and keyword[-1] == ")": if re.findall(keyword, value): return True else: if keyword == value: return True
[docs]def is_regex_keywords(keywords, value)-> bool: """ Check the value of the keyword regular expression. :param keywords: :param value: :return: Example: .. code-block:: python from pawnlib.typing import check check.is_regex_keywords(keywords="/sdsd/", value="sdsd") # >> True check.is_regex_keywords(keywords="/ad/", value="sdsd") # >> False """ if not isinstance(keywords, list): keywords = [keywords] if isinstance(keywords, list): for keyword in keywords: result = is_regex_keyword(keyword, value) if result: return True return False
[docs]def is_valid_ipv4(ip): """ Validates IPv4 addresses. :param ip: (str) IPv4 address to validate. :return: (bool) True if valid IPv4 address, False otherwise. Example: .. code-block:: python check.is_valid_ipv4("192.168.0.1") # >> True check.is_valid_ipv4("255.255.255.0") # >> True check.is_valid_ipv4("300.168.0.1") # >> False """ pattern = re.compile( # r"^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$", const.PATTERN_IP_ADDRESS, re.VERBOSE | re.IGNORECASE ) return pattern.match(ip) is not None
[docs]def is_valid_ipv6(ip): """ Validates IPv6 addresses. :param ip: A string representing an IPv6 address. :return: True if the given string is a valid IPv6 address, False otherwise. Example: .. code-block:: python check.is_valid_ipv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334") # >> True check.is_valid_ipv6("2001:0db8:85a3::8a2e:0370:7334") # >> True check.is_valid_ipv6("2001:0db8:85a3:0:0:8a2e:0370:7334:1234") # >> False """ pattern = re.compile(r""" ^ \s* # Leading whitespace (?!.*::.*::) # Only a single whildcard allowed (?:(?!:)|:(?=:)) # Colon iff it would be part of a wildcard (?: # Repeat 6 times: [0-9a-f]{0,4} # A group of at most four hexadecimal digits (?:(?<=::)|(?<!::):) # Colon unless preceeded by wildcard ){6} # (?: # Either [0-9a-f]{0,4} # Another group (?:(?<=::)|(?<!::):) # Colon unless preceeded by wildcard [0-9a-f]{0,4} # Last group (?: (?<=::) # Colon iff preceeded by exacly one colon | (?<!:) # | (?<=:) (?<!::) : # ) # OR | # A v4 address with NO leading zeros (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d) (?: \. (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d) ){3} ) \s* # Trailing whitespace $ """, re.VERBOSE | re.IGNORECASE | re.DOTALL) return pattern.match(ip) is not None
[docs]def is_valid_url(url, strict=True): """ Check if the given url is valid. :param url: (str) url to check :param strict: If False, URLs without a TLD (e.g., "http://example") are considered valid. Defaults to True. :return: (bool) True if valid, False otherwise Example: .. code-block:: python check.is_valid_url("google.com") # >> True check.is_valid_url("http://google.com") # >> True check.is_valid_url("https://www.google.com/search?q=python") # >> True check.is_valid_url("ftp://example.com") # >> False """ if not url: return False if url and not (url.startswith("http://") or url.startswith("https://")): url = f"http://{url}" # regex = re.compile( # r'^https?://' # http:// or https:// # r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' # domain... # r'localhost|' # localhost... # r'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}\b)' # ...or ip # r'(?::\d+)?' # optional port # r'(?:/?|[/?]\S+)', # re.IGNORECASE) if strict: # Standard regex pattern requiring a TLD regex_pattern = ( r'^https?://' r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' # domain r'localhost|' # localhost r'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}\b)' # IP r'(?::\d+)?' # optional port r'(?:/?|[/?]\S+)' # optional path ) else: # Regex pattern that allows URLs without a TLD regex_pattern = ( r'^https?://' # http:// or https:// r'(?:[A-Z0-9]+(?:[A-Z0-9-]*[A-Z0-9])?\.)?' # domain r'(?:[A-Z0-9]+(?:[A-Z0-9-]*[A-Z0-9]))' # parse r'(?:\.[A-Z]{2,6})?' # TLD (선택사항) r'(?::\d+)?' # optional port r'(?:/?|[/?]\S+)?$' # optional path ) regex = re.compile(regex_pattern, re.IGNORECASE) return bool(regex.search(url))
[docs]def is_valid_private_key(text=None): """ Validates the Private Key :param text: A string of private key text. :type text: str :return: A boolean value indicating whether the private key is valid or not. :rtype: bool Example: .. code-block:: python is_valid_private_key("0x1234567890123456789012345678901234567890123456789012345678901234") # >> True is_valid_private_key("0x12345678901234567890123456789012345678901234567890123456789012345") # >> False """ private_length = 64 if text and is_hex(text): if text.startswith("0x"): text = text[2:] if len(text) == private_length: return True return False
[docs]def is_valid_token_address(text=None, prefix="hx"): """ Validates the token address. :param text: A string representing the token address to be validated. :param prefix: A string representing the prefix of the token address. Default value is "hx". :return: A boolean value indicating whether the token address is valid or not. Example: .. code-block:: python is_valid_token_address("hx1234567890123456789012345678901234567890") # >> True is_valid_token_address("tx1234567890123456789012345678901234567890") # >> False """ if text and prefix \ and len(text) == 42 \ and text.startswith(prefix) \ and is_hex(text[2:]): return True return False
[docs]def is_valid_tx_hash(text=None): """ Validates the txHash :param text: A string of txHash text. :type text: str :return: A boolean value indicating whether the txHash is valid or not. :rtype: bool Example: .. code-block:: python is_valid_tx_hash("0x1234567890123456789012345678901234567890123456789012345678901234") # >> True is_valid_tx_hash("0x12345678901234567890123456789012345678901234567890123456789012345") # >> False """ tx_hash_length = 64 if text and is_hex(text): if text.startswith("0x"): text = text[2:] if len(text) == tx_hash_length: return True return False
[docs]def is_valid_icon_keystore_file(keystore=None): from pawnlib.typing.converter import flatten if not isinstance(keystore, dict): return False required_keys = [ "address", "crypto.cipher", "crypto.cipherparams.iv", "crypto.ciphertext", "crypto.kdf", "crypto.kdfparams.dklen", "crypto.kdfparams.n", "crypto.kdfparams.r", "crypto.kdfparams.p", "crypto.kdfparams.salt", "crypto.mac", "id", "version", "coinType", ] flatten_keystore = flatten(keystore) missing_keys = [key for key in required_keys if key not in flatten_keystore] if missing_keys: missing_keys_str = ", ".join(missing_keys) raise ValueError(f"<Invalid Keystore> Missing required key(s): {missing_keys_str}") return True
[docs]def list_depth(l): """ Returns the depth count of a list. :param l: A list. :return: An integer representing the depth count of the list. Example: .. code-block:: python list_depth([1, 2, 3]) # >> 1 list_depth([1, [2, 3], [4, [5, 6]]]) # >> 3 """ if isinstance(l, list): return 1 + max(list_depth(item) for item in l) else: return 0
[docs]def guess_type(s): """ Guess the type of string. :param s: :return: Example: .. code-block:: python from pawnlib.typing import check check.guess_type("True") # >> <class 'bool'> check.guess_type("2.2") # >> <class 'float'> """ s = str(s) if s == "": return None elif re.match(r"^(\d+)\.(\d+)$", s): return float elif re.match(r"^(\d)+$", s): return int elif re.match(r"^(true|false)$", s, re.IGNORECASE): return bool else: return str
# else: # return type(s) def _str2bool(v) -> bool: """ This function returns boolean type of given string. :param v: A string to be converted to boolean type. :type v: str :return: Boolean value of given string. :rtype: bool Example: .. code-block:: python >>> _str2bool('True') True >>> _str2bool('false') False >>> _str2bool('1') True >>> _str2bool(None) False """ if v is None: return False elif type(v) == bool: return v if isinstance(v, str): if v.lower() in ('yes', 'true', 't', 'y', '1'): return True elif v.lower() in ('no', 'false', 'f', 'n', '0'): return False elif v == 1: return True return False
[docs]def return_guess_type(value): """ This function returns the result of :func:`guess_type` and :func:`_strbool` :param value: A value to guess the type of. :type value: any :return: The guessed type of the input value. :rtype: any Example: .. code-block:: python return_guess_type("True") # >> <class 'bool'> return_guess_type("2.2") # >> <class 'float'> """ guessed_type = guess_type(value) if guessed_type is None or guessed_type == "": return value if isinstance(guessed_type(), bool): return _str2bool(value) elif value is not None and value != "": return guessed_type(value) else: return value
[docs]def error_and_exit(message, title="Error Occurred", exit_code=1): """ Print an error message with the caller's file name and line number, then exit the program. :param message: The error message to display. :param title: The error title to display. :param exit_code: The exit code to use when terminating the program (default is 1). """ caller_frame = inspect.stack()[1] file_name = os.path.basename(caller_frame.filename) line_number = caller_frame.lineno error_message = f"[bold red]Error:[/bold red] {message}" subtitle = f"{file_name}:{line_number}" print("") pawn.console.print( Panel( error_message, title=f"[bold red]{title} (-{exit_code})[/bold red]", expand=True, subtitle=subtitle, padding=1, ) ) print("") sys.exit(exit_code)
[docs]def sys_exit(message="", return_code=-1): """ This function executes the sys.exit() method. :param message: A message to be printed before exiting. (default="") :type message: str :param return_code: An exit code to be returned. (default=-1) :type return_code: int :return: None Example: .. code-block:: python # Example 1: Exit with default return code and message sys_exit() # Example 2: Exit with custom return code and message sys_exit("An error occurred!", 1) """ if message: pawn.console.log(f"[red]\[Exit {return_code}] {message}", _stack_offset=2) sys.exit(return_code)
[docs]def is_include_list(target=None, include_list=[], ignore_case=True): """ Check if target string exists in list. :param target: Target string to check. :type target: str :param include_list: List of strings to check. :type include_list: list :param ignore_case: If True, ignore case sensitive. Default is True. :type ignore_case: bool :return: Return True if target string exists in include_list, else False. :rtype: bool Example: .. code-block:: python result = is_include_list("hello world", ["hello", "world"]) # >> True result = is_include_list("hello world", ["hello", "world"], ignore_case=False) # >> False """ if target and include_list: for include_key in include_list: if ignore_case and include_key.lower() in target.lower(): return True if include_key in target: return True return False
def _traverse_keys(element: Union[dict, list], keys: tuple) -> Any: """ Helper function to traverse nested dictionaries and lists using the provided keys. :param element: The dictionary or list to traverse. :param keys: The keys or indices to traverse in the dictionary or list. :return: The value if all keys/indices exist, else raises an exception. """ current_element = element for key in keys: if isinstance(current_element, list): key = int(key) # Convert key to int if the current element is a list current_element = current_element[key] return current_element
[docs]def keys_exists(element: dict, *keys: str) -> bool: """ Check if **keys** (nested) exist in `element` (dict). :param element: The dictionary to search. :param keys: The keys to traverse in the dictionary. :return: True if all keys exist, False otherwise. Example: .. code-block:: python dict_example = { "name": "example", "description": { "description_2": "222", "description_3": "333", }, "none_value_key": None, } keys_exists(dict_example, 'name', 'description') # >> True keys_exists(dict_example, 'name', 'none_value_key') # >> True keys_exists(dict_example, 'name', 'none_key') # >> False """ try: _traverse_keys(element, keys) return True except (KeyError, TypeError, IndexError, ValueError): return False
[docs]def get_if_keys_exist(element: dict, *keys: str, default: Any = None) -> Any: """ Retrieve the value from a nested dictionary if **keys** exists in `element`. :param element: The dictionary to search. :param keys: The keys to traverse in the dictionary. :param default: The default value to return if the keys do not exist. :return: The value if all keys exist, else the default value. Example: .. code-block:: python dict_example = { "name": "example", "description": { "description_2": "222", "description_3": "333", }, "none_value_key": None, "nested_list": [{"key1": "value1"}, {"key2": "value2"}] } get_if_keys_exist(dict_example, 'name') # >> 'example' get_if_keys_exist(dict_example, 'description', 'description_2') # >> '222' get_if_keys_exist(dict_example, 'none_value_key') # >> None get_if_keys_exist(dict_example, 'name', 'none_key') # >> None get_if_keys_exist(dict_example, 'nested_list', '1', 'key2') # >> 'value2' get_if_keys_exist(dict_example, 'nested_list', '0', 'key1') # >> 'value1' """ try: return _traverse_keys(element, keys) except (KeyError, TypeError, IndexError, ValueError) as e: key_path = ' -> '.join(keys) pawn.console.debug(f"Error accessing key/index '{key_path}': {e}") return default
[docs]def detect_encoding(byte_data, default_encode="utf8"): """ Detects the encoding of byte data. :param byte_data: The byte data to be decoded. :type byte_data: bytes :param default_encode: The default encoding to be used if no suitable encoding is found. Defaults to "utf8". :type default_encode: str :return: The detected encoding. :rtype: str :raises UnicodeDecodeError: If the byte data cannot be decoded using any of the available encodings. Examples: Detect the encoding of byte data: .. code-block:: python byte_data = b"\x41\x42\x43" detect_encoding(byte_data) # Output: 'ascii' byte_data = b"\xea\xb0\x80\xeb\x82\x98\xeb\x8b\xa4" detect_encoding(byte_data) # Output: 'utf8' byte_data = b"\xb0\xa1\xb1\xe2\xc6\xae" detect_encoding(byte_data) # Output: 'euc-kr' """ encodings = ["ascii", "utf8", "euc-kr", "iso2022_jp", "euc_jp", "shift_jis", "cp932", "latin1"] for encoding in encodings: try: byte_data.decode(encoding) return encoding except UnicodeDecodeError: pass return default_encode
[docs]def check_key_and_type(data, key, expected_type): """ Checks if a specific key exists in a dictionary and if its value is of the expected type. :param data: The dictionary to check. :param key: The key to check for in the dictionary. :param expected_type: The expected type of the value (e.g., list, dict, etc.). :return: True if the key exists and its value is of the expected type, False otherwise. Examples: .. code-block:: python result = { 'res': [1, 2, 3], 'config': {'option': True}, 'count': 10 } # Check if 'res' exists and is a list is_res_list = check_key_and_type(result, 'res', list) print(is_res_list) # Output: True # Check if 'config' exists and is a dict is_config_dict = check_key_and_type(result, 'config', dict) print(is_config_dict) # Output: True # Check if 'count' exists and is a string is_count_string = check_key_and_type(result, 'count', str) print(is_count_string) # Output: False # Check if 'nonexistent_key' exists and is a list is_nonexistent_list = check_key_and_type(result, 'nonexistent_key', list) print(is_nonexistent_list) # Output: False """ if key in data and isinstance(data[key], expected_type): return True return False
[docs]def get_procfs_path(): """ Determine the appropriate procfs path based on the environment variables and file system structure. Returns: str: The path to the proc filesystem. """ is_docker = os.environ.get("IS_DOCKER", "").lower() in ["true", "1", "yes"] docker_proc_path = "/rootfs/proc" if is_docker and os.path.exists(docker_proc_path): procfs_path = docker_proc_path pawn.console.log(f"Running in Docker mode on {procfs_path}") else: procfs_path = "/proc" pawn.console.log(f"Running in Host mode on {procfs_path}") return procfs_path