Source code for freshpy.utils.core_utils
# -*- coding: utf-8 -*-
"""
:Module: freshpy.utils.core_utils
:Synopsis: Collection of supporting utilities and functions to complement the primary modules
:Created By: Jeff Shurtliff
:Last Modified: Jeff Shurtliff
:Modified Date: 04 Jan 2026
"""
from collections.abc import Iterable
import urllib.parse
from . import log_utils
from .. import errors
# Initialize logging
logger = log_utils.initialize_logging(__name__)
[docs]
def construct_query_string(existing_query=None, appendage=None):
"""This function assists in constructing query strings for URIs to ensure they follow the appropriate format.
.. version-added:: 1.0.0
:param existing_query: The existing query string (if any)
:type existing_query: str, None
:param appendage: The new addition to the query string to be appended (if any)
:type appendage: str, None
:returns: The constructed query string
"""
combined_query = '' if existing_query is None else existing_query
if appendage:
if existing_query == '?':
combined_query = f'?{appendage}'
elif existing_query:
combined_query = existing_query + f'&{appendage}'
elif appendage.startswith('?'):
combined_query = appendage
else:
combined_query = f'?{appendage}'
return combined_query
[docs]
def url_encode(raw_string):
"""This function encodes a string for use in URLs.
.. version-added:: 1.0.0
:param raw_string: The raw string to be encoded
:type raw_string: str
:returns: The encoded string
"""
return urllib.parse.quote_plus(raw_string)
[docs]
def url_decode(encoded_string):
"""This function decodes a url-encoded string.
.. version-added:: 1.0.0
:param encoded_string: The url-encoded string
:type encoded_string: str
:returns: The unencoded string
"""
return urllib.parse.unquote_plus(encoded_string)
[docs]
def is_iterable(value, exclude_str=True):
"""This function checks whether a value is an iterable, optionally excluding ``str`` and ``bytes``
.. version-added:: 3.0.0
:param value: The value to be evaluated
:param exclude_str: Exclude ``str`` and ``bytes`` data types from being considered iterables
:type exclude_str: bool
:returns: Boolean indicating whether the value is an iterable
"""
if exclude_str:
iterable = isinstance(value, Iterable) and not isinstance(value, (str, bytes))
else:
iterable = isinstance(value, Iterable)
return iterable
[docs]
def is_data_type(value, data_type):
"""This function validates that a given value has an expected data type.
.. version-added:: 3.0.0
:param value: The value to be evaluated
:param data_type: A data type (e.g. ``str``, ``int``, etc.) or an iterable containing multiple data types
:returns: Boolean indicating whether the value matches the data type(s)
"""
if is_iterable(value):
return type(value) in data_type
else:
return isinstance(value, data_type)
[docs]
def validate_data_type(value, data_type, value_id=None, raise_exception=True, custom_msg=None):
"""This function validates whether a value matches given data type(s) and raises an exception and/or logs an error.
.. version-added:: 3.0.0
:param value: The value to be evaluated
:param data_type: A data type (e.g. ``str``, ``int``, etc.) or an iterable containing multiple data types
:param value_id: The name of the variable or field (e.g. ``agent_id``)
:type value_id: str, None
:param raise_exception: Raise an exception if the data types do not match (``True`` by default)
:type raise_exception: bool
:param custom_msg: An optional custom message to append to the error/exception message
:type custom_msg: str, None
:returns: None
:raises: :py:exc:`freshpy.errors.exceptions.InvalidDataTypeError`
"""
if not is_data_type(value, data_type):
value_segment = f"'{value_id}' value" if value_id and isinstance(value_id, str) else "value"
error_msg = f'The {value_segment} is not the expected data type'
if custom_msg and isinstance(custom_msg, str):
error_msg += f' and {custom_msg}'
if raise_exception:
logger.critical(error_msg)
raise errors.exceptions.InvalidDataTypeError(error_msg)
else:
logger.error(error_msg)
[docs]
def validate_numeric_value(value, param_name=None):
"""This function checks a parameter value to ensure that it is an integer or a numeric string.
.. version-added:: 3.0.0
:param value: The parameter value to be validated
:param param_name: The name of the parameter being validated (optional)
:type param_name: str, None
:returns: None
:raises: :py:exc:`freshpy.errors.exceptions.InvalidDataTypeError`
"""
# Specify the parameter name in the exception message when provided
param_str_segment = f"'{param_name}' value" if param_name else 'value'
# Define an exception message when necessary
if not any((isinstance(value, str), isinstance(value, int))):
data_type = type(value).__name__
exc_msg = f"The {param_str_segment} has a(n) '{data_type}' type but must be an integer or numeric string"
elif isinstance(value, str) and not value.isdigit():
exc_msg = f'The {param_str_segment} must be a whole number (integer) if provided as a string'
else:
exc_msg = None
# Log the error and raise an exception if an exception message has been defined
if exc_msg:
logger.critical(exc_msg)
raise errors.exceptions.InvalidDataTypeError(exc_msg)