示例#1
0
def test_encrypt_info():
    """
    This test currently tests encrypting a message or info.

    Raises:
        ValueError: A failure occurred in section 1.0 while testing the function 'encrypt_info'. The message is not in bytes format.
    """
    print('')
    print('-' * 65)
    print('-' * 65)
    print('Testing Function: encrypt_info')
    print('-' * 65)
    print('-' * 65)
    print('')

    # ############################################################
    # ####Section Test Part 1 (Successful Encryption Checking)####
    # ############################################################
    # ========Tests for a successful encryption.========
    message_encryption_password = '******'
    message_encryption_random_salt = b'ChangeME'

    # Sets the sample message.
    sample_message = 'pytest sample'
    # Converts unencrypted message string into bytes.
    encrypted_message = encrypt_info(sample_message,
                                     message_encryption_password,
                                     message_encryption_random_salt)
    # Expected Sample Return: b'gAAAAABgW0PuVC2XK6QXtpD44P2pHnvAvwSXSV0Ulj8TBzJLHfrvQZF4eFkF22TdOynRx9eMPb7n_dRULQmZWcEz-g85nXK3yg=='
    try:
        type_check(encrypted_message, bytes)
    except FTypeError as exc:
        exc_args = {
            'main_message':
            'A failure occurred in section 1.0 while testing the function \'encrypt_info\'. The message is not in bytes format.',
            'original_error': exc,
        }
        raise FTypeError(exc_args)

    # ========Tests for a successful encryption.========
    message_encryption_password = '******'
    message_encryption_random_salt = str(b'ChangeME')

    # Sets the sample message.
    sample_message = 'pytest sample'
    # Converts unencrypted message string into bytes.
    encrypted_message = encrypt_info(sample_message,
                                     message_encryption_password,
                                     message_encryption_random_salt)
    # Expected Sample Return: b'gAAAAABgW0PuVC2XK6QXtpD44P2pHnvAvwSXSV0Ulj8TBzJLHfrvQZF4eFkF22TdOynRx9eMPb7n_dRULQmZWcEz-g85nXK3yg=='
    try:
        type_check(encrypted_message, bytes)
    except FTypeError as exc:
        exc_args = {
            'main_message':
            'A failure occurred in section 1.1 while testing the function \'encrypt_info\'. The message is not in bytes format.',
            'original_error': exc,
        }
        raise FTypeError(exc_args)
示例#2
0
def convert_relative_to_full_path(relative_path: str) -> str:
    """
    Determines a full file path to file given a relative file path compatible\\
    with PyInstaller(compiler) built-in.

    Args:
        relative_path (str):
        \t\\- The unqualified (relative) file path that needs to converted to\\
        \t  a qualified full path format

    Calling Example:
    \t\\- relative_path = "\\[directory]\\[file].[extension]"

    Raises:
        FTypeError (fexception):
        \t\\- The value '{relative_path}' is not in <class 'str'> format.
        FGeneralError (fexception):
        \t\\- A general exception occurred during the relative to full path conversion.

    Returns:
        str:
        \t\\- Full file path.

    Return Example:\\
    \t\\- "C:\\[root directory]\\[directory]\\[file].[extension]"
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(relative_path, str)
    except FTypeError:
        raise

    logger.debug(
        'Passing parameters:\n'
        f'  - relative_path(str):\n        - {relative_path}\n'
    )

    try:
        if hasattr(sys, '_MEIPASS'):
            # PyInstaller creates a temp folder and stores path in _MEIPASS
            base_path = sys._MEIPASS
        else:
            # When running un-compiled, use normal os calls to determine location
            base_path = os.getcwd()

        return f'{base_path}\\{relative_path}'
    except Exception as exc:
        exc_args = {
            'main_message': 'A general exception occurred during the relative to full path conversion.',
            'original_exception': exc,
        }
        raise FGeneralError(exc_args)
def get_ini_config(ini_config: configparser, section: str, key: str) -> str:
    """
    Gets the ini configuration section key based on the read configuration and the section.

    Args:
        ini_config (configparser): INI read configuration.
        section (str): Section value.
        key (str): Key value.

    Raises:
        FTypeError (fexception):
        \t\\- The value '{section}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{key}' is not in <class 'str'> format.
        FGeneralError (fexception):
        \t\\- A general exception occurred while getting INI configuration information.

    Returns:
        str:
        \t\\- Section key.
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(section, str)
        type_check(key, str)
    except FTypeError:
        raise

    logger.debug(
        'Passing parameters:\n'
        f'  - ini_config (configparser):\n        - {ini_config}\n'
        f'  - section (str):\n        - {section}\n'
        f'  - key (str):\n        - {key}\n'
    )

    # Checks for configuration errors while getting output.
    try:
        # Gets value from config.
        # ini_config must contain .get, .getboolean, etc.
        result = ini_config(section, key)
    except Exception as exc:
        exc_args = {
            'main_message': 'A general exception occurred while getting INI configuration information.',
            'original_exception': exc,
        }
        raise FGeneralError(exc_args)
    else:
        return result
def read_ini_config(ini_file_path: str) -> configparser:
    """
    Reads configuration ini file data and returns the returns the read configuration.

    Args:
        ini_file_path (str):
        \t\\- The file path to the ini file.

    Raises:
        FTypeError (fexception):
        \t\\- The value '{ini_file_path}' is not in <class 'str'> format.
        FGeneralError (fexception):
        \t\\- A general exception occurred while reading the ini configuration file.

    Returns:
        ini:
        \t\\- INI read configuration.
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(ini_file_path, str)
    except FTypeError:
        raise

    logger.debug(
        'Passing parameters:\n'
        f'  - ini_file_path (str):\n        - {ini_file_path}\n'
    )

    # Checks for issues while reading the ini file.
    try:
        # Calls function to pull in ini configuration.
        # Uses RawConfigParser for special characters.
        config = configparser.RawConfigParser()
        config.read(ini_file_path)
    except Exception as exc:
        exc_args = {
            'main_message': 'A general exception occurred while reading the ini configuration file.',
            'original_exception': exc,
        }
        raise FGeneralError(exc_args)
    else:
        return config
示例#5
0
def read_yaml_config(yaml_file_path: str, loader: str) -> yaml:
    """
    Reads configuration yaml file data and returns the returns the read configuration.

    Args:
        yaml_file_path (str):
        \t\\- YAML file path.\\
        loader (str):
        \t\\- Loader for the YAML file.\\
        \t\t\\- loader Options:\\
        \t\t\t\\- FullLoader\\
        \t\t\t\t\\- Used for more trusted YAML input.\\
        \t\t\t\t\\- This option will avoid unpredictable code execution.\\
        \t\t\t\\- SafeLoader\\
        \t\t\t\t\\- Used for untrusted YAML input.\\
        \t\t\t\t\\- This will only load a subset of the YAML language.\\
        \t\t\t\\- BaseLoader\\
        \t\t\t\t\\- Used for the most basic YAML input.\\
        \t\t\t\t\\- All loading is strings.\\
        \t\t\t\\- UnsafeLoader\\
        \t\t\t\t\\- Used for original Loader code but could be\\
        \t\t\t\t   easily exploitable by untrusted YAML input.

    Raises:
        FTypeError (fexception):
        \t\\- The value '{yaml_file_path}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{loader}' is not in <class 'str'> format.
        YamlReadFailure:
        \t\\- Incorrect YAML loader parameter.
        YamlReadFailure:
        \t\\- A failure occurred while reading the YAML file.
        FGeneralError (fexception):
        \t\\- A general failure occurred while opening the YAML file.

    Returns:
        yaml:
        \t\\- YAML read configuration.
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(yaml_file_path, str)
        type_check(loader, str)
    except FTypeError:
        raise

    logger.debug(
        'Passing parameters:\n'
        f'  - yaml_file_path (str):\n        - {yaml_file_path}\n'
        f'  - loader (str):\n        - {loader}\n'
    )

    # Checks for issues while reading the yaml file.
    try:
        # Calls function to pull in yaml configuration.
        with open(yaml_file_path) as file:
            if 'FullLoader' == loader:
                config = yaml.load(file, Loader=yaml.FullLoader)
            elif 'SafeLoader' == loader:
                config = yaml.load(file, Loader=yaml.SafeLoader)
            elif 'BaseLoader' == loader:
                config = yaml.load(file, Loader=yaml.BaseLoader)
            elif 'UnsafeLoader' == loader:
                config = yaml.load(file, Loader=yaml.UnsafeLoader)
            else:
                raise ValueError('Incorrect YAML loader parameter.')
    except Exception as exc:
        if 'Incorrect YAML loader parameter' in str(exc):
            exc_args = {
                'main_message': 'Incorrect YAML loader parameter.',
                'custom_type': YamlReadFailure,
                'expected_result': ['FullLoader', 'SafeLoader', 'BaseLoader', 'UnsafeLoader'],
                'returned_result': loader,
                'suggested_resolution': 'Please verify you have set all required keys and try again.',
            }
            raise YamlReadFailure(FCustomException(exc_args))
        elif 'expected <block end>, but found \'<scalar>\'' in str(exc):
            exc_args = {
                'main_message': 'A failure occurred while reading the YAML file.',
                'custom_type': YamlReadFailure,
                'expected_result': ['FullLoader', 'SafeLoader', 'BaseLoader', 'UnsafeLoader'],
                'returned_result': loader,
                'suggested_resolution': [
                    'Please verify you have the correct punctuation on your entries',
                    'For example, having three single quotes will cause this error to occur',
                    'you are using three single quotes, it will help if you use double quotes to begin and end with a single quote in the middle.'
                ],
            }
            raise YamlReadFailure(FCustomException(exc_args))
        else:
            exc_args = {
                'main_message': 'A general failure occurred while opening the YAML file.',
                'original_exception': exc,
            }
            raise FGeneralError(exc_args)
    else:
        logger.debug(f'Returning value(s):\n  - Return = {config}')
        return config
示例#6
0
def test_search_file():
    """
    Tests searching a file.

    Raises:
        ValueError: A failure occurred in section 1.0 while testing the function 'search_file'. The function did not return a type 'list'.
        ValueError: A failure occurred in section 1.1 while testing the function 'search_file'. The returned search list length should have equaled 1.
        ValueError: A failure occurred in section 1.2 while testing the function 'search_multiple_files' with a single search value against multiple paths.. The function did not return a type \'list\'.
        ValueError: A failure occurred in section 1.3 while testing the function 'search_multiple_files' with a single search value against multiple paths. The returned search list length should have equaled 2.
        ValueError: A failure occurred in section 1.4 while testing the function 'search_multiple_files' with multiple search values against multiple paths. The function did not return a type \'list\'.
        ValueError: A failure occurred in section 1.5 while testing the function 'search_multiple_files' with multiple search values against multiple paths. The returned search list length should have equaled 2.
    """
    print('')
    print('-' * 65)
    print('-' * 65)
    print('Testing Function: search_file')
    print('-' * 65)
    print('-' * 65)
    print('')

    # ############################################################
    # ######Section Test Part 1 (Successful Value Checking)#######
    # ############################################################
    # ========Tests for a successful output return.========
    # Gets the programs root directory.
    preset_root_directory = os.path.dirname(os.path.realpath(__file__))
    # Sets the sample file path.
    sample_file_path = os.path.abspath(f'{preset_root_directory}\\temp_pytest_read_write.py')
    found_value = search_file(sample_file_path, 'line1')

    try:
        type_check(found_value, list)
    except FTypeError as exc:
        exc_args = {
            'main_message': 'A failure occurred in section 1.0 while testing the function \'search_file\'. The function did not return a type \'list\'.',
            'expected_result': 'non-list error',
            'returned_result': exc,
        }
        raise FValueError(exc_args)

    # Return length should equal 1.
    # Expected Return: [{'search_entry': 'line1', 'found_entry': 'testing line1'}]
    if len(found_value) != 1:
        exc_args = {
            'main_message': 'A failure occurred in section 1.1 while testing the function \'search_file\'. The returned search list length should have equaled 1.',
        }
        raise FValueError(exc_args)

    # ========Tests for a successful output return.========
    # Gets the programs root directory.
    preset_root_directory = os.path.dirname(os.path.realpath(__file__))

    # Sets the sample file path.
    sample_file_path = os.path.abspath(f'{preset_root_directory}\\temp_pytest_read_write.py')

    # Single search value against the same path twice.
    found_value = search_file(list([sample_file_path, sample_file_path]), 'line1')

    try:
        type_check(found_value, list)
    except FTypeError as exc:
        exc_args = {
            'main_message': 'A failure occurred in section 1.2 while testing the function \'search_multiple_files\' with a single search value against multiple paths.. The function did not return a type \'list\'.',
            'expected_result': 'non-list error',
            'returned_result': exc,
        }
        raise FValueError(exc_args)

    # Return length should equal 2.
    # Expected Return: [{'search_entry': 'line1', 'found_entry': 'testing line1'}, {'search_entry': 'line1', 'found_entry': 'testing line1'}]
    if len(found_value) != 2:
        exc_args = {
            'main_message': 'A failure occurred in section 1.3 while testing the function \'search_multiple_files\' with a single search value against multiple paths. The returned search list length should have equaled 2.',
        }
        raise FValueError(exc_args)

    # ========Tests for a successful output return.========
    # Multi search value against the same path twice.
    found_value = search_file(list([sample_file_path, sample_file_path]), list(['line1', 'line2']))

    try:
        type_check(found_value, list)
    except FTypeError as exc:
        exc_args = {
            'main_message': 'A failure occurred in section 1.4 while testing the function \'search_multiple_files\' with multiple search values against multiple paths. The function did not return a type \'list\'.',
            'expected_result': 'non-list error',
            'returned_result': exc,
        }
        raise FValueError(exc_args)

    # Return length should equal 2.
    # Expected Return: [{'search_entry': ['line1', 'line2'], 'found_entry': 'testing line1'}, {'search_entry': ['line1', 'line2'], 'found_entry': 'testing line2'}]
    if len(found_value) != 2:
        exc_args = {
            'main_message': 'A failure occurred in section 1.5 while testing the function \'search_multiple_files\' with multiple search values against multiple paths. The returned search list length should have equaled 2.',
        }
        raise FValueError(exc_args)

    # Removes the testing file once the test is complete.
    os.remove(sample_file_path)
示例#7
0
def test_decrypt_info():
    """
    This test currently tests decryptting a message or info.

    Raises:
        ValueError: A failure occurred in section 1.0 while testing the function 'decrypt_info'. The message is not in bytes format.
        ValueError: A failure occurred in section 1.1 while testing the function 'decrypt_info'. The message returned the wrong result.
    """
    print('')
    print('-' * 65)
    print('-' * 65)
    print('Testing Function: decrypt_info')
    print('-' * 65)
    print('-' * 65)
    print('')

    # ############################################################
    # ####Section Test Part 1 (Successful Decryption Checking)####
    # ############################################################
    # ========Tests for a successful decryption.========
    message_encryption_password = '******'
    message_encryption_random_salt = b'ChangeME'

    # Sets the sample message.
    encoded_message = b'gAAAAABgW0PuVC2XK6QXtpD44P2pHnvAvwSXSV0Ulj8TBzJLHfrvQZF4eFkF22TdOynRx9eMPb7n_dRULQmZWcEz-g85nXK3yg=='
    decrypted_message = decrypt_info(encoded_message,
                                     message_encryption_password,
                                     message_encryption_random_salt)
    try:
        type_check(decrypted_message, str)
    except FTypeError as exc:
        exc_args = {
            'main_message':
            'A failure occurred in section 1.0 while testing the function \'decrypt_info\'. The message is not in bytes format.',
            'original_error': exc,
        }
        raise FTypeError(exc_args)

    # ========Tests for a successful decryption.========
    message_encryption_password = '******'
    message_encryption_random_salt = b'ChangeME'

    # Sets the sample message.
    encoded_message = b'gAAAAABgW0PuVC2XK6QXtpD44P2pHnvAvwSXSV0Ulj8TBzJLHfrvQZF4eFkF22TdOynRx9eMPb7n_dRULQmZWcEz-g85nXK3yg=='
    decrypted_message = decrypt_info(encoded_message,
                                     message_encryption_password,
                                     message_encryption_random_salt)

    # Expected Return: b'pytest sample'
    if decrypted_message != 'pytest sample':
        exc_args = {
            'main_message':
            'A failure occurred in section 1.1 while testing the function \'decrypt_info\'. The message returned the wrong result.',
            'expected_result': 'b\'pytest sample\'',
            'returned_result': decrypted_message,
        }
        raise FValueError(exc_args)

    # ========Tests for a successful decryption.========
    message_encryption_password = '******'
    message_encryption_random_salt = str(b'ChangeME')

    # Sets the sample message.
    encoded_message = b'gAAAAABgW0PuVC2XK6QXtpD44P2pHnvAvwSXSV0Ulj8TBzJLHfrvQZF4eFkF22TdOynRx9eMPb7n_dRULQmZWcEz-g85nXK3yg=='
    decrypted_message = decrypt_info(encoded_message,
                                     message_encryption_password,
                                     message_encryption_random_salt)

    # Expected Return: b'pytest sample'
    if decrypted_message != 'pytest sample':
        exc_args = {
            'main_message':
            'A failure occurred in section 1.1 while testing the function \'decrypt_info\'. The message returned the wrong result.',
            'expected_result': 'b\'pytest sample\'',
            'returned_result': decrypted_message,
        }
        raise FValueError(exc_args)
示例#8
0
def encrypt_info(decrypted_info: str, message_encryption_password: str,
                 message_encryption_random_salt: Union[bytes, str]) -> bytes:
    """
    This function encrypts any message that is sent.

    Args:
        decrypted_info (str):
        \t\\- decrypted info in bytes format.
        message_encryption_password (str):
        \t\\- The password needing to be used to encrypt the info.
        message_encryption_random_salt (Union[bytes, str]):
        \t\\- A random salt in bytes format.\\
        \t\\- If the value is sent as str format the value will be re-encoded.\\
        \t\\- A string type can pass if the value is set in a YAML or configuration file and not re-encoded correctly.

    Raises:
        FTypeError (fexception):
        \t\\- The value '{decrypted_info}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{message_encryption_password}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{message_encryption_random_salt}' is not in [<class 'bytes'>, <class 'str'>] format.
        EncryptionFailure:
        \t\\- A failure occurred while encrypting the message.

    Returns:
        bytes:\\
        \t\\- encrypted info
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(decrypted_info, str)
        type_check(message_encryption_password, str)
        type_check(message_encryption_random_salt, [bytes, str])
    except FTypeError:
        raise

    logger.debug(
        'Passing parameters:\n'
        f'  - decrypted_info (str):\n        - {str(decrypted_info)}\n'
        f'  - message_encryption_password (str):\n        - {message_encryption_password}\n'
        f'  - message_encryption_random_salt (bytes, str):\n        - {str(message_encryption_random_salt)}\n'
    )

    logger.debug(f'Starting to encrypt the info')
    try:
        logger.debug('Converting the pre-defined encryption password to bytes')
        # Converts decrypted message string into bytes.
        decrypted_info = decrypted_info.encode()
        # Converting the pre-defined encryption password to bytes.
        password = message_encryption_password.encode()

        # Checks if incoming salt is in str format, so the salt can be re-encoded.
        if isinstance(message_encryption_random_salt, str):
            if message_encryption_random_salt[:2] == "b'":
                # Strips the bytes section off the input.
                # Removes first 2 characters.
                unconverted_message_encryption_random_salt = message_encryption_random_salt[
                    2:]
                # Removes last character.
                unconverted_message_encryption_random_salt = unconverted_message_encryption_random_salt[:
                                                                                                        -1]
                # Re-encodes the info.
                message_encryption_random_salt = unconverted_message_encryption_random_salt.encode(
                ).decode('unicode_escape').encode("raw_unicode_escape")

        logger.debug('Deriving a cryptographic key from a password')
        # Calling function to derive a cryptographic key from a password.
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),  # An instance of HashAlgorithm
            length=
            32,  # The desired length of the derived key in bytes. Maximum is (232 - 1)
            salt=
            message_encryption_random_salt,  # Secure values [1] are 128-bits (16 bytes) or longer and randomly generated
            iterations=
            100000,  # The number of iterations to perform of the hash function
            backend=default_backend(
            )  # An optional instance of PBKDF2HMACBackend
        )

        logger.debug(
            'Returned from imported function (PBKDF2HMAC) to function (encrypt_info)'
        )
        logger.debug(
            'Encoding the string using the pre-defined encryption password and the cryptographic key into the binary form'
        )
        # Encoding the string using the pre-defined encryption password and the cryptographic key into the binary form.
        key = base64.urlsafe_b64encode(kdf.derive(password))

        logger.debug(
            'Creating a symmetric authenticated cryptography (secret key)')
        # Creating a symmetric authenticated cryptography (secret key).
        f = Fernet(key)

        logger.debug(
            'Encrypting the info using the secret key to create a Fernet token'
        )
        # Encrypting the info using the secret key to create a Fernet token.
        encrypted_info = f.encrypt(decrypted_info)
    except Exception as exc:
        exc_args = {
            'main_message': 'A failure occurred while encrypting the message.',
            'custom_type': EncryptionFailure,
            'original_exception': exc
        }
        raise EncryptionFailure(FCustomException(exc_args))
    else:
        logger.debug(
            f'Returning the encrypted info. encrypted_info = {encrypted_info}')
        # Returning the encrypted info.
        return encrypted_info
示例#9
0
def setup_logger_yaml(yaml_path: str,
                      separate_default_logs: bool = False,
                      allow_basic: bool = None) -> None:
    """
    This function sets up a logger for the program using a YAML file.\\
    The configuration must be setup with a YAML file.\\
    This method is the best method for using logging in to additional modules.\\

    Default Path Option Notes:
    \t\\- Default file log handler paths are supported.\\
    \t\\- Cross-platform usage can be a pain and require the path to be the full path.\\
    \t\\- Having default enabled allows the program to set the filename for each log handler.\\
    \t\\- This function allows the ability to have all file log handlers log to the same file,\\
    \t   which is named the same name as the main program, or be individual log files\\
    \t   per file hander, which will be named based on the file handler key name.\\
    \t\\- The "filename:" key value has to be "DEFAULT" in call caps to work.

    Additional Default Option Notes:
    \t\\- A user can define DEFAULT path logs by added :<log name> to the end of DEFAULT.\\
    \t\\- All default logs will be at the root of the main program in a folder called logs.\\
    \t\t\\- default YAML example1 = filename: DEFAULT\\
    \t\t\\- default YAML example2 = filename: DEFAULT:mylog

    Usage:
    \t\\- Setup your logger by running the command below.\\
    \t\t\\- logger = logging.getLogger(__name__)\\
    \t\\- Call this function to setup the logger. No return is required.\\
    \t\\- Call the logger using something similar to the command below.\\
    \t\t\\- logger.info('testing')\\
    \t\\- When using the same logger in other modules the only requirement is to run the command\\
    \t   below within the function. Do not run at the module level. This can cause issues.

    Args:
        yaml_path (str):
        \t\\- yaml configuration file.\\
        separate_default_logs (bool, optional):\\
        \t\\- If default file handelers are being used this allows the files to be separated\\
        \t   using the file handler YAML key name.\\
        \t\t\\- Defaults to False.\\
        \t\t\\- Note:\\
        \t\t\t\\- Default log paths per file hander can only be enabled by setting the key value\\
        \t   for filename: to DEFAULT.\\
        allow_basic (bool, optional):\\
        \t\\- Allows the default log level of "INFO" to be used if the YAML file configuration\\
        \t   fails when set to "True".

    Raises:
        FTypeError (fexception):
        \t\\- The value '{yaml_path}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{separate_default_logs}' is not in <class 'bool'> format.
        FTypeError (fexception):
        \t\\- The value '{allow_basic}' is not in <class 'bool'> format.
        LoggerSetupFailure:
        \t\\- The logging hander failed to create.
        FGeneralError (fexception):
        \t\\- A general exception occurred the logger setup.
    """

    try:
        type_check(yaml_path, str)
        if separate_default_logs:
            type_check(separate_default_logs, bool)
        if allow_basic:
            type_check(allow_basic, bool)
    except FTypeError:
        raise

    # Sets up the logger based on the YAML.
    try:
        # Calls function to pull in YAML configuration.
        config = read_yaml_config(yaml_path, 'FullLoader')

        # #######################################################################
        # ###########Checks/Sets Up Default File Logger Path If Required#########
        # #######################################################################
        # Gets YAML return keys.
        all_keys = list(config.keys())
        # Checks if the log handler is a key.
        if 'handlers' in str(all_keys):
            # Gets all handler keys.
            handler_keys = list(config['handlers'].keys())
            # Loops through each hander key.
            for handler_key in handler_keys:
                # Gets all handler setting keys for the specific handler entry.
                handler_setting_keys = list(
                    config['handlers'][handler_key].keys())
                # Loops through each handler setting.
                for setting_keys in handler_setting_keys:
                    # Checks if one of the keys contains filename to check if it needs the default log path set.
                    if 'filename' in str(setting_keys):
                        # Gets the value from the filename: key.
                        filename_value = config['handlers'][handler_key][
                            'filename']
                        # Checks if the filename value is "DEFAULT" to set the log with the main program name.
                        if 'DEFAULT' == filename_value:
                            # Gets the main program path and file name of the program.
                            # Note: The main program path should not be pulled from the os.path.split command because it does not work correctly on Linux.
                            main_program_path = pathlib.Path.cwd()
                            main_program_file_name = os.path.split(
                                sys.argv[0])[1]
                            # Sets the program log path for the default log path in the YAML.
                            log_path = os.path.abspath(
                                f'{main_program_path}/logs')
                            # Check if main file path exists with a "logs" folder. If not create the folder.
                            # Checks if the save_log_path exists and if not it will be created.
                            # This is required because the logs do not save to the root directory.
                            if not os.path.exists(log_path):
                                os.makedirs(log_path)
                            # Checks if the user wants default log file hander files to be separate.
                            if separate_default_logs:
                                log_file_path = os.path.abspath(
                                    f'{log_path}/{handler_key}.log')
                            else:
                                # Removes the .py from the main program name
                                main_program_name = main_program_file_name.replace(
                                    '.py', '')
                                log_file_path = os.path.abspath(
                                    f'{log_path}/{main_program_name}.log')
                            # Update the file log handler file path to the main root.
                            config['handlers'][handler_key][
                                'filename'] = log_file_path
                        # Checks if the filename value is "DEFAULT:" to set the log with the user defined log name.
                        elif 'DEFAULT:' in filename_value:
                            # Gets the main program path.
                            # Note: The main program path should not be pulled from the os.path.split command because it does not work correctly on Linux.
                            main_program_path = pathlib.Path.cwd()
                            # Sets the program log path for the default log path in the YAML.
                            log_path = os.path.abspath(
                                f'{main_program_path}/logs')
                            # Check if main file path exists with a "logs" folder. If not create the folder.
                            # Checks if the save_log_path exists and if not it will be created.
                            # This is required because the logs do not save to the root directory.
                            if not os.path.exists(log_path):
                                os.makedirs(log_path)
                            # Checks if the user wants default log file hander files to be separate.
                            if separate_default_logs:
                                log_file_path = os.path.abspath(
                                    f'{log_path}/{handler_key}.log')
                            else:
                                # Removes the .py from the main program name
                                # Original Example: DEFAULT:mylog
                                # Returned Example: mylog
                                user_defined_log_name = filename_value.split(
                                    ':')[1]
                                log_file_path = os.path.abspath(
                                    f'{log_path}/{user_defined_log_name}.log')
                            # Update the file log handler file path to the main root.
                            config['handlers'][handler_key][
                                'filename'] = log_file_path
        # Sets the logging configuration from the YAML configuration.
        logging.config.dictConfig(config)
    except Exception as exc:
        # Checks if allow_default is enabled to setup default "Info" logging.
        if allow_basic:
            # Sets the basic logger setup configuration.
            logging.basicConfig(level=logging.INFO)
        else:
            if 'Unable to configure handler' in str(exc):
                exc_args = {
                    'main_message':
                    'The logging hander failed to create.',
                    'custom_type':
                    LoggerSetupFailure,
                    'suggested_resolution':
                    'Please verify YAML file configuration.',
                }
                raise LoggerSetupFailure(FCustomException(exc_args))
            else:
                exc_args = {
                    'main_message':
                    'A general exception occurred the logger setup.',
                    'original_exception': exc,
                }
                raise FGeneralError(exc_args)
示例#10
0
def write_file(file_path: str, write_value: str) -> None:
    """
    Writes a value to the file.

    Write validation is performed after the write. Supports writes with
    new lines (\\n).

    Args:
        file_path (str):
        \t\\- The file path being written into.
        write_value (str):
        \t\\- The value being written into the file.

    Raises:
        FTypeError (fexception):
        \t\\- The value '{file_path}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{write_value}' is not in <class 'str'> format.
        FileWriteFailure:
        \t\\- The file failed to write.
        FFileNotFoundError (fexception):
        \t\\- The file does not exist in the validating file path ({file_path}).
        FileWriteFailure:
        \t\\- Writing file value ({write_value}) to file ({file_path}) did not complete.
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(file_path, str)
        type_check(write_value, str)
    except FTypeError:
        raise

    logger.debug(
        'Passing parameters:\n'
        f'  - file_path (str):\n        - {file_path}\n'
        f'  - write_value (str):\n        - {write_value}\n'
    )

    try:
        logger.debug(f'Begining to write the value to the file. write_value = {write_value}')
        logger.debug('Writing the value to the file')
        # Using "with" to take care of open and closing.
        with open(file_path, 'a') as f:
            if '\n' in write_value:
                f.writelines(write_value + "\n")
            else:
                f.write(write_value + "\n")
    except Exception as exc:
        exc_args = {
            'main_message': 'The file failed to write.',
            'custom_type': FileWriteFailure,
            'original_exception': exc,
        }
        raise FileWriteFailure(FCustomException(exc_args))
    else:
        try:
            # Checks if a file that was written had new lines, so each line can get checked
            # individually.
            if '\n' in write_value:
                split_new_line_write_value = write_value.split('\n')
                for line in split_new_line_write_value:
                    return_search = search_file(file_path, line)
                    if not return_search:
                        break
            else:
                # Checking if the file entry written to the file.
                # Calling Example: search_file(<log file>, <search string>, <configured logger>)
                return_search = search_file(file_path, write_value)
        except FFileNotFoundError:
            raise

        # Validates file entry wrote.
        if return_search is None:
            exc_args = {
                'main_message': f'Writing file value ({write_value}) to file ({file_path}) did not complete.',
                'custom_type': FileWriteFailure,
                'returned_result': ' No return search value were returned.',
            }
            raise FileWriteFailure(FCustomException(exc_args))
示例#11
0
def search_file(file_path: Union[str, list], searching_value: Union[str, list],
                include_next_line_value: Union[str, list] = None) -> Union[list, None]:
    """
    Searches a single or multiple files for a value.

    The search can look for multiple values when the searching value arguments are passed as a list.

    A single-string search is supported as well.

    Args:
        file_path (list):
        \t\\- A list of file path being checked.
        searching_value (Union[str, list]):
        \t\\- search value that is looked for within the file.\\
        \t\\- The entry can be a single string or a list to search
        include_next_line_value (Union[str, list], optional):
        \t\\- Includes any next line containing a character or characters that match.\\
        \t\\- Ideal when logs add new line information and the output needs returned.\\
        \t\\- The next line value check can be a single string or a list to search.

    Raises:
        FTypeError (fexception):
        \t\\- The value '{file_path}' is not in <class '[str, list]'> format.
        FTypeError (fexception):
        \t\\- The value '{searching_value}' is not in [<class 'str'>, <class 'list'>] format.
        FFileNotFoundError (fexception):
        \t\\- The file does not exist in the validating file path ({file_path}).
        FileSearchFailure (fexception):
        \t\\- A failure occurred while searching the file. The file path does not include a file with an extension.
        FGeneralError (fexception):
        \t\\- A general failure occurred while while searching the file.

    Returns:
        Union[list, None]:
        \t\\- list:\\
        \t\t\\- A list of discovered search values.
        \t\t\\- Each discovered value is per element.
        \t\\- None:\\
        \t\t\\- No discovered values will return None.

    Return Examples:
    \t\\- [{'search_entry': '|Error|', 'found_entry': 'the entry found'},
    \t   {'search_entry': '|Warning|', 'found_entry': 'the entry found'}]

    Return Usage Keys:
    \t\\- search_entry\\
    \t\\- found_entry
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(file_path, [str, list])
        type_check(searching_value, [str, list])
    except FTypeError:
        raise

    if isinstance(file_path, list):
        formatted_file_path = '  - file_path (list):' + str('\n        - ' + '\n        - '.join(map(str, file_path)))
    elif isinstance(file_path, str):
        formatted_file_path = f'  - file_path (str):\n        - {file_path}'
    if isinstance(searching_value, list):
        formatted_searching_value = ('  - searching_value (list):'
                                     + str('\n        - ' + '\n        - '.join(map(str, searching_value))))
    elif isinstance(searching_value, str):
        formatted_searching_value = f'  - searching_value (str):\n        - {searching_value}'
    logger.debug(
        'Passing parameters:\n'
        f'{formatted_file_path}\n'
        f'{formatted_searching_value}\n'
    )

    # Assigns list variable to be used in this function.
    # Required to return multiple found strings.
    matched_entries: list = []
    grouped_found_file_lines: list = []

    try:
        if isinstance(file_path, list):
            # Sets count on total files being searched.
            total_files = len(file_path)
            logger.debug('Starting to loop through file(s)')
            logger.debug(f'Begining to search the files \"{file_path}\" for a value \"{searching_value}\"')
            # Loops through each file path to add all lines into a single list.
            for index, file_path in enumerate(file_path):
                # Checks that the passing file_path contains a file extension.
                if '.' not in file_path:
                    exc_args = {
                        'main_message': 'A failure occurred while searching the file. The file path does not include a file with an extension.',
                        'custom_type': FileSearchFailure,
                        'expected_result': 'A file with an extension (ex: myfile.txt)',
                        'returned_result': file_path,
                        'suggested_resolution': 'Please verify you have sent a full file path and not a directory.',
                    }
                    raise FileSearchFailure(FCustomException(exc_args))

                logger.debug(f'Reading in all lines from the file \"{file_path}\"')
                # Sets the basename for cleaner logging output.
                basename_searched_file = os.path.basename(file_path)
                logger.debug(f'Looping through file \"{basename_searched_file}\" {index + 1} of {total_files}')

                try:
                    file_check(file_path)
                except FFileNotFoundError:
                    raise

                # Using "with" to take care of open and closing.
                with open(file_path, 'r') as f:
                    readLines = f.readlines()
                    # Loops through each line.
                    for line in readLines:
                        # Adds line to list.
                        grouped_found_file_lines.append(line)

                logger.debug('Looping through all lines from the files 1 by 1')
        else:
            # Using "with" to take care of open and closing.
            with open(file_path, 'r') as f:
                readLines = f.readlines()
                # Loops through each line.
                for line in readLines:
                    # Adds line to list.
                    grouped_found_file_lines.append(line)

        # Looping through all lines from the log file(s) line list 1 by 1.
        for index, line in enumerate(grouped_found_file_lines):
            # Strips off the '\n' character.
            stripped_line: str = str(line).strip()
            # Checks if searching_value is a str or list
            if isinstance(searching_value, str):
                # Checks if a value exists as each line is read.
                if searching_value in stripped_line:
                    logger.debug(f'Searched file value \"{searching_value}\" found. Adding file value to the returning list \"matched_entries\"')
                    # Checks if the next line needs to be included in the search.
                    if include_next_line_value:
                        multi_line_builder: list = []
                        line_tracker: int = index
                        while True:
                            # Adds found line and search value to list
                            multi_line_builder.append(grouped_found_file_lines[line_tracker].strip())

                            line_tracker += 1

                            # Checks if the end of the file lines.
                            if line_tracker >= len(grouped_found_file_lines):
                                break
                            # Checks if the next line value filters do not match to break loop.
                            if isinstance(include_next_line_value, str):
                                if include_next_line_value not in str(grouped_found_file_lines[line_tracker]):
                                    break
                            if isinstance(include_next_line_value, list):
                                found: bool = True
                                for next_line_value in include_next_line_value:
                                    if next_line_value in str(grouped_found_file_lines[line_tracker]):
                                        found = True
                                        break
                                    else:
                                        found = False
                                if found is False:
                                    break
                        matched_entries.append({'search_entry': searching_value, 'found_entry': str('\n'.join(multi_line_builder))})
                    else:
                        # Adds found line and search value to list
                        matched_entries.append({'search_entry': searching_value, 'found_entry': stripped_line})
            elif isinstance(searching_value, list):
                # Loops through each search value
                for search_value in searching_value:
                    # Checks if a value exists as each line is read.
                    if search_value in stripped_line:
                        logger.debug(f'Searched file value \"{search_value}\" from value list \"{searching_value}\" found. Adding file value \"{stripped_line}\" to the returning list \"matched_entries\"')
                        # Checks if the next line needs to be included in the search.
                        if include_next_line_value:
                            multi_line_builder: list = []
                            line_tracker: int = index
                            while True:
                                # Adds found line and search value to list
                                multi_line_builder.append(grouped_found_file_lines[line_tracker].strip())

                                line_tracker += 1

                                # Checks if the next line value filters do not match to break loop.
                                if isinstance(include_next_line_value, str):
                                    if include_next_line_value not in str(grouped_found_file_lines[line_tracker]):
                                        break
                                if isinstance(include_next_line_value, list):
                                    found: bool = True
                                    for next_line_value in include_next_line_value:
                                        if next_line_value in str(grouped_found_file_lines[line_tracker]):
                                            found = True
                                            break
                                        else:
                                            found = False
                                    if found is False:
                                        break
                            matched_entries.append({'search_entry': searching_value, 'found_entry': str('\n'.join(multi_line_builder))})
                        else:
                            # Adds found line and search value to list
                            matched_entries.append({'search_entry': searching_value, 'found_entry': stripped_line})

        # Checking if the list has discovered values for potential cleanup.
        if matched_entries:
            # Checks if searching_value is str or list to clean up any potential duplicates
            if isinstance(searching_value, str):
                logger.debug('Searched file value has been found')
            elif isinstance(searching_value, list):
                logger.debug('Searched file values have been found')
                logger.debug(f'A list of all found search matches is listed below: {matched_entries}')
                logger.debug(f'Removing any duplicate entries that may have matched multiple times with similar search info')
                # Removes any duplicate matched values using the 2nd entry (1st element). This can happen if a search list has a similar search word that discovers the same line.
                # Example Return: [{'search_entry': '|Error|', 'found_entry': 'the entry found2'}]
                matched_entries = remove_duplicate_dict_values_in_list(matched_entries, 1)
                logger.debug(f'The adjusted match list with removed duplicates is listed below: {matched_entries}')
    except FFileNotFoundError:
        raise
    except Exception as exc:
        exc_args = {
            'main_message': 'A general failure occurred while while searching the file.',
            'original_exception': exc,
        }
        raise FGeneralError(exc_args)
    else:
        # Checking if the list has discovered log entry values.
        if matched_entries:
            logger.debug('Returning found values')
            # Returns found lines(s).
            return matched_entries
        elif not matched_entries:
            logger.debug('No searched value has have been found')
            logger.debug('Returning None')
            # Returns "None" because no strings found.
            return None
示例#12
0
def create_template_email(email_template_name: str, email_template_path: str,
                          **template_args: Optional[dict]) -> str:
    """
    Uses the jinja2 module to create a template with users passing email template arguments.

    Args:
        email_template_name (str):
        \t\\- The name of the template.
        email_template_path (str):
        \t\\- The full path to the templates directory.
        **template_args(dict, optional):
        \t\\- The template arguments are used to populate the HTML template variables. Defaults to None.

    Raises:
        FTypeError (fexception):
        \t\\- The value '{email_template_name}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{email_template_path}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{template_args}' is not in <class 'dict'> format.
        CreateTemplateFailure:
        \t\\- The email HTML template path does not exist.
        FGeneralError (fexception):
        \t\\- A general exception occurred while rendering the HTML template.

    Returns:
        str:
        \t\\- A formatted HTML email template with all the arguments updated.
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(email_template_name, str)
        type_check(email_template_path, str)
        if template_args:
            type_check(template_args, dict)
    except FTypeError:
        raise

    if template_args:
        formatted_template_args = (
            '  - template_args (dict):\n        - ' +
            '\n        - '.join(': '.join((key, str(val)))
                                for (key, val) in template_args.items()))
    else:
        formatted_template_args = '  - template_args (dict):\n        - None'

    logger.debug(
        'Passing parameters:\n'
        f'  - email_template_name (str):\n        - {email_template_name}\n'
        f'  - email_template_path (str):\n        - {email_template_path}\n'
        f'{formatted_template_args}\n')

    # Checks if the email_template_path exists.
    if not os.path.exists(email_template_path):
        exc_args = {
            'main_message':
            'The email HTML template path does not exist.',
            'custom_type':
            CreateTemplateFailure,
            'expected_result':
            'A valid email template path.',
            'returned_result':
            email_template_path,
            'suggested_resolution':
            'Please verify you have set the correct path and try again.',
        }
        raise CreateTemplateFailure(FCustomException(exc_args))

    try:
        # Gets the main program module name.
        # Output Example: C:\Repositories\smtpredirect\smtpredirect\smtpredirect.py
        main_module_file_path = os.path.realpath(
            sys.argv[0]) if sys.argv[0] else None
        # Gets the main program base name.
        # Output Example: smtpredirect.py
        module_base_name = os.path.basename(main_module_file_path)
        # Gets the main program name.
        # Output Example: smtpredirect
        module_name = os.path.splitext(module_base_name)[0]

        env = Environment(loader=PackageLoader(module_name,
                                               email_template_path),
                          autoescape=select_autoescape(['html', 'xml']))
        template = env.get_template(email_template_name)
    except Exception as exc:
        exc_args = {
            'main_message':
            'A general exception occurred while rendering the HTML template.',
            'original_exception': exc,
        }
        raise FGeneralError(exc_args)
    else:
        return template.render(**template_args)
示例#13
0
def send_email(email_settings: dict,
               subject: str,
               body: str = None,
               template_args: dict = None) -> None:
    """
    This function offers many customized options when sending an email.

    Email can be sent with port 25 or using TLS.

    Email sections can be sent encrypted or unencrypted.

    Emails can be sent using one of two options:
    \tNon-HTML:
    \t\t\\- These messages are basic non HTML emails. Multiple lines are supported.
    \tHTML:
    \t\t\\- These messages use HTML templates that can have variables in the HTML template get populated with data.\\
    \t\t\\- HTML will be preferred if both are configured.\\
    \t\t\\- Templates and variables require structure from HTMLs://jinja.palletsprojects.com.\\
    \t\t\\- When using HTML, the jinja module is used in conjunction with some added features and simplified use from this function.

    Email Encryption:
    \t String encryption can be added to any string in an email with the email_settings variables setup and\\
    \t the string section identified as needing to be encrypted.

    \t Any string section starting with @START-ENCRYPT@ and ending with @END-ENCRYPT@ will have that code section\\
    \t get encrypted and supported for the body or **template_args parameters.

    \t Encryption Requirements:
    \t\t\\- The encrypt_info function is required when enabling message encryption.\\
    \t\t\\- The decrypt_info function is required when decrypting the message.\\
    \t\t\t\\- This function can be used outside the main program.\\
    \t\t\t\\- The decrypt_info can be a small separate program or a website using flask.\\
    \t\t\\- To create a random "salt" use this command "print("urandom16 Key:", os.urandom(16))"\\

    \t Format Example:
    \t\t\\- The encrypted message will be in bytes format. Formatting needs in the template or body of the message.\\
    \t\t\t\\- Example1:
    \t\t\t\t\\- <p>     Decryption Code:@START-ENCRYPT@This is my original string@END-ENCRYPT@</p>\\
    \t\t\t\\- Example2:
    \t\t\t\t\\- @START-ENCRYPT@This is my original string@END-ENCRYPT@\\

    Args:
        email_settings (dict):
        \t\\- Email settings constructed within a dictionary\\
        subject (str):
        \t\\- Email subject information\\
        body (str, optional):
        \t\\- The email body is for raw non-HTML email messages.\\
        \t\\- Adding a message to this body will override any template options and use\\
        \t   a basic non-HTML email message.\\
        \t\\- Defaults to None.\\
        template_args(dict, optional):
        \t\\- The template arguments are used to populate the HTML template variables.\\
        \t\\- Defaults to None.\\
        \t\t\\- Example (url is the passing parameter):\\
        \t\t\t\\- <p><a href="{{ url }}">Decrypt</a></p>\\

    Arg Keys:
        email_settings Keys:\\
        \t\\- smtp (str):\\
        \t\t\\- SMTP server.\\
        \t\\- authentication_required (bool):\\
        \t\t\\- Enables authentication.\\
        \t\\- use_tls (str):\\
        \t\t\\- Enables TLS.
        \t\\- username (str):\\
        \t\t\\- Username for email authentication.\\
        \t\\- password (str):\\
        \t\t\\- Password for email authentication.\\
        \t\\- from_email (str):\\
        \t\t\\- From email address.\\
        \t\\- to_email (str):\\
        \t\t\\- To email address.\\
        \t\\- attachment_path (str, optional):\\
        \t\t\\- Allows sending an email attachment.\\
        \t\t\\- Defaults to None.\\
        \t\\- send_email_template (bool, optional):\\
        \t\t\\- Allows sending an email with an HTML template.\\
        \t\t\\- Defaults to False.\\
        \t\\- email_template_name (str, optional):\\
        \t\t\\- The template name.\\
        \t\t\\- Defaults to None.\\
        \t\\- email_template_path (str, optional):\\
        \t\t\\- The template folder path.\\
        \t\t\\- This directory will hold all HTML templates.\\
        \t\t\\- This path is commonly the directory of the main program.\\
        \t\t\\- Defaults to None.\\
        \t\\- message_encryption_password (str, optional):\\
        \t\t\\- Encryption password if encryption is enabled.\\
        \t\t\\- Set to None if no encryption.\\
        \t\\- message_encryption_random_salt (bytes, optional):\\
        \t\t\\- Random salt in bytes format.\\
        \t\t\\- Set to None if no encryption.

    Raises:
        FTypeError (fexception):
        \t\\- The value '{email_settings}' is not in <class 'dict'> format.
        FTypeError (fexception):
        \t\\- The value '{subject}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{body}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{template_args}' is not in <class 'dict'> format.
        EmailSendFailure:
        \t\\- An error occurred while sending the email.
        CreateTemplateFailure:
        \t\\- The email HTML template path does not exist.
        EncryptionFailure:
        \t\\- A failure occurred while encrypting the message.
        FGeneralError (fexception):
        \t\\- A general exception occurred while creating the email message body.
        EmailSendFailure:
        \t\\- The attachment path for the email message does not exist.
        FGeneralError (fexception):
        \t\\- A general exception occurred while preparing the email message structure.
        EmailSendFailure:
        \t\\- Failed to initialize SMTP connection using TLS.
        EmailSendFailure:
        \t\\- Failed to send the email message. Connection to SMTP server failed.
        EmailSendFailure:
        \t\\- Failed to reach the SMTP server.
        FGeneralError (fexception):
        \t\\- A general exception occurred while sending the email message.
        EmailSendFailure:
        \t\\- SMTP authentication is set to required but it is not supported by the server.
        EmailSendFailure:
        \t\\- The SMTP server rejected the connection.
        EmailSendFailure:
        \t\\- SMTP server authentication failed.
        EmailSendFailure:
        \t\\- Incorrect username and/or password or authentication_required is not enabled\\
        \t  or Less Secure Apps needs enabled in your gmail settings.
        EmailSendFailure:
        \t\\- Incorrect username and/or password or the authentication_required setting is not enabled.
        FGeneralError (fexception):
        \t\\- A general exception occurred while sending the email.
        EmailSendFailure:
        \t\\- Failed to send message. SMTP terminatation error occurred.
        FGeneralError (fexception):
        \t\\- A general exception occurred while terminating the SMTP object.
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(email_settings, dict)
        type_check(subject, str)
        if body:
            type_check(body, str)
        if template_args:
            type_check(template_args, dict)
    except FTypeError:
        raise

    formatted_email_settings = (
        '  - email_settings (dict):\n        - ' +
        '\n        - '.join(': '.join((key, str(val)))
                            for (key, val) in email_settings.items()))
    if body:
        formatted_body = f'- email_template_name (str):\n        - {body}'
    else:
        formatted_body = f'- email_template_name (str):\n        - None'
    if template_args:
        formatted_template_args = (
            '  - template_args (dict):\n        - ' +
            '\n        - '.join(': '.join((key, str(val)))
                                for (key, val) in template_args.items()))
    else:
        formatted_template_args = '  - template_args (dict):\n        - None'

    logger.debug('Passing parameters:\n'
                 f'{formatted_email_settings}\n'
                 f'  - subject (str):\n        - {subject}\n'
                 f'{formatted_body}\n'
                 f'{formatted_template_args}\n')

    logger.debug(f'Starting to send an email message')

    try:
        logger.debug(f'Checking if the email is sending non-HTML or HTML')
        # Gets send_email_template option.
        send_email_template = email_settings.get('send_email_template')
        # Checks if the email is template or non-HTML.
        if (send_email_template is True and template_args):
            logger.debug(f'Sending HTML templated email')
            email_template_name = email_settings.get('email_template_name')
            email_template_path = email_settings.get('email_template_path')

            # Creates the email template.
            email_body = create_template_email(email_template_name,
                                               email_template_path,
                                               **template_args)
        elif body:
            logger.debug(f'Sending non-HTML email')
            email_body = body
        else:
            exc_args = {
                'main_message':
                'An error occurred while sending the email.',
                'custom_type':
                EmailSendFailure,
                'expected_result':
                'Body or HTML Template',
                'returned_result':
                'No body or template was sent.',
                'suggested_resolution':
                'Ensure the body or template is being passed to the email_director module functions.',
            }
            raise EmailSendFailure(FCustomException(exc_args))
        # Holds the updating email body lines.
        updating_body = []
        # Checks the email for any encryption identifiers.
        # Splits the email into individual lines that can be looped through.
        adjusted_body = email_body.split('\n')
        logger.debug(
            f'Looping through each line of the email to check for any encryption identifiers'
        )
        for email_line in adjusted_body:
            # Checks if the body contains any encryption identifiers.
            if ('@START-ENCRYPT@' in email_line
                    and '@END-ENCRYPT@' in email_line):
                logger.debug(
                    f'Encryption identifiers found. Encrypting this section of the output.'
                )
                # Original String: <p>     Decryption Code: @START-ENCRYPT@This is my original string@END-ENCRYPT@</p>
                # Matched String: This is my original string
                unencrypted_string = (re.search(
                    '@START-ENCRYPT@(.*)@END-ENCRYPT@', email_line)).group(1)
                logger.debug(
                    'Converting unencrypted message string into bytes')
                password = email_settings.get('message_encryption_password')
                salt = email_settings.get('message_encryption_random_salt')
                # Calls function to sends unencrypted message for encryption.
                # Return Example: <encrypted message>
                encrypted_info = encrypt_info(unencrypted_string, password,
                                              salt)
                # Removes the encryption string identifiers and sets encryption on the string.
                updating_body.append(
                    email_line.replace('@START-ENCRYPT@', '').replace(
                        '@END-ENCRYPT@', '').replace(unencrypted_string,
                                                     str(encrypted_info)))
            else:
                # Adds the non-encrypted line to the list.
                updating_body.append(email_line)

        logger.debug(f'Setting the updated email body')
        # Converts the list back into a string with new lines for each entry.
        updated_body = "\n".join(updating_body)
    except CreateTemplateFailure:
        raise
    except EmailSendFailure:
        raise
    except EncryptionFailure:
        raise
    except Exception as exc:
        exc_args = {
            'main_message':
            'A general exception occurred while creating the email message body.',
            'original_exception': exc,
        }
        raise FGeneralError(exc_args)

    try:
        logger.debug('Preparing the email message structure')
        # Preparing email message structure.
        message = EmailMessage()
        message['Subject'] = subject
        message['From'] = email_settings.get('from_email')
        message['To'] = [email_settings.get('to_email')]
        # Sets header based on HTML or text be passed to the function.
        if (send_email_template is True and template_args):
            # Sets header for text and html. Required to allow html template.
            message.add_header('Content-Type', 'text/html')
        elif body:
            # Sets header for text only. Required to allow new lines.
            message.add_header('Content-Type', 'text')
        logger.debug('Setting payload to HTML')
        # Setting email body payload.
        message.set_payload(updated_body)
        # Attaches file if a path is sent.
        if email_settings.get('attachment_path'):
            # Checks that the attachment file exists.
            attachment_path = os.path.abspath(
                email_settings.get('attachment_path'))
            if not os.path.exists(attachment_path):
                exc_args = {
                    'main_message':
                    'The attachment path for the email message does not exist.',
                    'custom_type':
                    EmailSendFailure,
                    'expected_result':
                    'A valid email attachment path.',
                    'returned_result':
                    attachment_path,
                    'suggested_resolution':
                    'Please verify you have set the correct path and try again.',
                }
                raise EmailSendFailure(FCustomException(exc_args))
            # Gets the mime type to determine the type of message being sent.
            mime_type, _ = mimetypes.guess_type(attachment_path)
            # Gets the MIME type and subtype.
            mime_type, mime_subtype = mime_type.split('/', 1)
            # Attaches the attachment to the message.
            with open(attachment_path, 'rb') as ap:
                message.add_attachment(
                    ap.read(),
                    maintype=mime_type,
                    subtype=mime_subtype,
                    filename=os.path.basename(attachment_path))
    except EmailSendFailure:
        raise
    except Exception as exc:
        exc_args = {
            'main_message':
            'A general exception occurred while preparing the email message structure.',
            'original_exception': exc,
        }
        raise FGeneralError(exc_args)

    try:
        logger.debug('Setting up SMTP object')
        # Setting up SMTP object.
        if email_settings.get('use_tls') is True:
            logger.debug(
                'Opening connection to SMTP server on port 587 for TLS')
            smtp_Object = smtplib.SMTP(email_settings.get('smtp'), 587)

            try:
                smtp_Object.ehlo()
                logger.debug('Sending StartTLS message')
                smtp_Object.starttls()
            except Exception as exc:
                exc_args = {
                    'main_message':
                    'Failed to initialize SMTP connection using TLS.',
                    'custom_type': EmailSendFailure,
                    'original_exception': exc
                }
                raise EmailSendFailure(FCustomException(exc_args))
        else:
            logger.debug('Opening connection to SMTP server on port 25')
            smtp_Object = smtplib.SMTP(email_settings.get('smtp'), 25)
    except EmailSendFailure:
        raise
    except Exception as exc:
        if ("target machine actively refused it" in str(
                exc
        ) or "connected party did not properly respond after a period of time"
                in str(exc) or "getaddrinfo failed" in str(exc)):
            exc_args = {
                'main_message':
                'Failed to send the email message. Connection to SMTP server failed.',
                'custom_type': EmailSendFailure,
                'suggested_resolution':
                'Ensure the server address and TLS options are set correctly.',
                'original_exception': exc
            }
            raise EmailSendFailure(FCustomException(exc_args))
        elif 'Connection unexpectedly closed' in str(exc):
            exc_args = {
                'main_message': 'Failed to reach the SMTP server.',
                'custom_type': EmailSendFailure,
                'suggested_resolution': 'Ensure SMTP is reachable.',
                'original_exception': exc
            }
            raise EmailSendFailure(FCustomException(exc_args))
        else:
            exc_args = {
                'main_message':
                'A general exception occurred while sending the email message.',
                'original_exception': exc,
            }
            raise FGeneralError(exc_args)

    # Sends email.
    try:
        # If authentication required, log in to mail server with credentials.
        if email_settings.get('authentication_required') is True:
            logger.debug(
                'SMTP server authentication required, logging into server')
            smtp_Object.login(email_settings.get('username'),
                              email_settings.get('password'))
            logger.debug('Sending the email')

        smtp_Object.sendmail(email_settings.get('from_email'),
                             email_settings.get('to_email'),
                             str(message).encode('utf-8').strip())
    except Exception as exc:
        if "SMTP AUTH extension not supported" in str(exc):
            exc_args = {
                'main_message':
                'SMTP authentication is set to required but it is not supported by the server.',
                'custom_type': EmailSendFailure,
                'suggested_resolution':
                'Try changing the INI [email] AuthenticationRequired value to False',
                'original_exception': exc
            }
        elif "Client host rejected: Access denied" in str(exc):
            exc_args = {
                'main_message': 'The SMTP server rejected the connection.',
                'custom_type': EmailSendFailure,
                'suggested_resolution':
                'Authentication may be required, ensure the INI [email] AuthenticationRequired is set correctly.',
                'original_exception': exc
            }
        elif "authentication failed" in str(exc):
            exc_args = {
                'main_message': 'SMTP server authentication failed.',
                'custom_type': EmailSendFailure,
                'suggested_resolution':
                'Ensure the INI [email] Username and Password are set correctly.',
                'original_exception': exc
            }
        elif " Authentication Required. Learn more at\n5.7.0  HTMLs://support.google.com" in str(
                exc):
            exc_args = {
                'main_message':
                'Incorrect username and/or password or authentication_required is not enabled or Less Secure Apps needs enabled in your gmail settings.',
                'custom_type': EmailSendFailure,
                'original_exception': exc
            }
        elif "Authentication Required" in str(exc):
            exc_args = {
                'main_message':
                'Incorrect username and/or password or the authentication_required setting is not enabled.',
                'custom_type': EmailSendFailure,
                'original_exception': exc
            }
        else:
            exc_args = {
                'main_message':
                'A general exception occurred while sending the email.',
                'original_exception': exc,
            }
            raise FGeneralError(exc_args)

        raise EmailSendFailure(FCustomException(exc_args))

    finally:

        try:
            logger.debug(f'Terminating SMTP object')
            # Terminating SMTP object.
            smtp_Object.quit()
        except Exception as exc:
            if 'Failed to send message' in str(exc):
                exc_args = {
                    'main_message':
                    'Failed to send message. SMTP terminatation error occurred.',
                    'custom_type': EmailSendFailure,
                    'original_exception': exc
                }
                raise EmailSendFailure(FCustomException(exc_args))
            else:
                exc_args = {
                    'main_message':
                    'A general exception occurred while terminating the SMTP object.',
                    'original_exception': exc,
                }
                raise FGeneralError(exc_args)
def start_function_thread(passing_program_function, program_function_name: str, infinite_loop_option: bool) -> None:
    """
    This function is used to start any other function inside it's own thread.

    This is ideal if you need to have part of the program sleep and another part of the program\\
    always active. (ex: Web Interface = Always Active & Log Checking = 10 Minute Sleep)

    Thread exception capturing offers a challenge because the initialized child thread is in its dedicated\\
    context with its dedicated stack. When an exception is thrown in, the child thread can potentially never\\
    report to the parent function. The only time the messages can be present is during the initial call to the\\
    child thread. A message bucket is used to hold any potential exception messages, and a 2 minutes sleep is\\
    set to give time for the thread to either start or fail. If neither occurs after 1 minute, the thread\\
    will end and throw a value error.

    Requires calling program to use "from functools import partial" when calling.

    Calling Examples:\\
    \tExamples:\\
    \t\t\\- start_function_thread(partial(PassingFunction,\\
    \t\t\t\t\t\t\t\t Parameter1,\\
    \t\t\t\t\t\t\t\t Parameter2,\\
    \t\t\t\t\t\t\t\t Parameter3, etc), <function name), <bool>)

    Args:
        passing_program_function (function):
        \t\\- The function without or with parameters using functools.
        program_function_name (str):
        \t\\- The function name used to identify the thread.
        infinite_loop_option (bool):
        \t\\- Enabled infinite loop.

    Raises:
        FTypeError (fexception):
        \t\\- The value '{program_function_name}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{infinite_loop_option}' is not in <class 'bool'> format.
        ThreadStartFailure:
        \t\\- A failure occurred while staring the function thread.
        ThreadStartFailure:
        \t\\- The thread ({program_function_name}) timeout has reached its threshold of 1 minute.
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(program_function_name, str)
        type_check(infinite_loop_option, bool)
    except FTypeError:
        raise

    logger.debug(
        'Passing parameters:\n'
        f'  - program_function_name (str):\n        - {program_function_name}\n'
        f'  - infinite_loop_option (bool):\n        - {infinite_loop_option}\n'
    )

    # Creates a dedicated thread class to run the companion decryptor.
    # This is required because the main() function will sleep x minutes between checks.
    class start_function_thread(threading.Thread):

        # Automatically creates these items when called.
        def __init__(self, bucket):
            threading.Thread.__init__(self)

            # Sets name to track treads activity.
            self.name = program_function_name
            self.daemon = True
            self.bucket = bucket

        def run(self):
            """Runs the object as self and calls the function."""
            # Stores the exception, if raised by the calling function.
            self.exception = None

            try:
                # Checks if the thread needs to loop.
                if infinite_loop_option:
                    # Infinite Loop.
                    while True:
                        # Starts the function in a loop.
                        passing_program_function()
                        # Sleeps 1 seconds to keep system resources from spiking when called without a sleep inside the calling entry.
                        time.sleep(1)
                else:
                    # Starts the function once.
                    passing_program_function()
            # Returns the calling functions error message if an error occurs.
            except Exception as err:
                # Sets the exception error message
                # self.exception = err
                self.bucket.put(sys.exc_info())

    # Creates a message queue to hold potential exception messages.
    bucket = queue.Queue()
    # Calls class to start the thread.
    thread_obj = start_function_thread(bucket)
    thread_obj.start()

    # Timeout gives enough time for the thread to timeout.
    # This time delay can delay potential incoming streams from a subprocess.
    # If the initial startup is critical, it may be worth creating a small script to delay the start of the subprocess call.
    time.sleep(2)

    # Sets max timeout at 1 minute if the thread does not start or an exception is not thrown.
    timeout = time.time() + 60 * 1

    while True:

        try:
            # Gets the bucket values
            exc = bucket.get(block=False)
        except queue.Empty:
            pass
        else:
            # Sets the bucket values from the exceptions
            exc_type, exc_obj, exc_trace = exc

            # Passes the calling functions error output as the original error.
            exc_args = {
                'main_message': 'A failure occurred while staring the function thread.',
                'custom_type': ThreadStartFailure,
                'original_exception': exc_obj,
            }
            raise ThreadStartFailure(FCustomException(exc_args))

        # Loop breaks if the thread is alive or timeout reached.
        if thread_obj.is_alive() is True:
            break

        if time.time() > timeout:
            exc_args = {
                'main_message': f'The thread ({program_function_name}) timeout has reached its threshold of 1 minute.',
                'custom_type': ThreadStartFailure,
                'suggested_resolution': 'Manual intervention is required for this thread to start.',
            }
            raise ThreadStartFailure(FCustomException(exc_args))
示例#15
0
def user_file_selection(prompt: str, criteria: str, root_dir: str = None) -> str:
    """
    Provides a simple user interface that numerically lists a set of files\\
    found using user submitted criteria.

    User is prompted to submit the numeric value of the file that is to be used.

    Args:
        prompt (str):
        \t\\- Literal prompt string to present to the user.\\
        criteria (str):
        \t\\- Filter to apply when searching for files.\\
        \t\\- Expects standard OS search criteria\\
        root_dir (str, optional):
        \t\\- Manually sets the root directory to search.\\
        \t\\- Requires an absolute path format.\\
        \t\\- Defaults to None.

    Calling Examples:
    \t\\- prompt = "Enter the database name to import"\\
    \t\\- criteria = "*.db" or "*config*"\\
    \t\\- root_dir = "C:\\Directory\\Subdirectory\"

    Raises:
        FTypeError (fexception):
        \t\\- The value '{prompt}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{criteria}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{root_dir}' is not in <class 'str'> format.
        FFileNotFoundError (fexception):
        \t\\- No files were found matching the required criteria.
        FGeneralError (fexception):
        \t\\- A general failure occurred during the user file selection.

    Returns:
        str:
        \t\\- Returns the path of the file that was selected in the format provided

    Return Example:\\
    \t\\- "test.py" or "c:\\folder\\test.py"
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(prompt, str)
        type_check(criteria, str)
        if root_dir:
            type_check(root_dir, str)
    except FTypeError:
        raise

    if root_dir:
        formatted_root_dir = f'  - relative_path (str):\n        - {root_dir}'
    else:
        formatted_root_dir = f'  - relative_path (str):\n        - None'
    logger.debug(
        'Passing parameters:\n'
        f'  - prompt (str):\n        - {prompt}\n'
        f'  - criteria (str):\n        - {criteria}\n'
        f'{formatted_root_dir}\n'
    )

    try:
        # Initialize an empty list that will contain files found during search
        files = []
        # Print the prompt
        print(prompt)
        """
        # Search for files in current working directory
        for file in glob(criteria):
            # Do not match on temporary files beginning with '~'
            if search('^~', file) is None:
                # Add file to list
                files.append(file)
                print(f'  [{[i for i, x in enumerate(files) if x == file][0]}] {file}')
        """
        if root_dir:
            # Use provided root directory for search
            search_path = root_dir
        else:
            # If path not provided, use current working directory
            search_path = os.path.abspath(os.curdir)

        for file in Path(search_path).glob(criteria):
            # Add file to list
            files.append(file)
            print(f'  [{[i for i, x in enumerate(files) if x == file][0]}] {os.path.basename(file)}')

        # If no files were found matching user provided criteria, raise exception
        if len(files) == 0:
            exc_args = {
                'main_message': 'No files were found matching the required criteria.',
                'expected_result': 'A matching file.',
                'returned_result': 0,
            }
            raise FFileNotFoundError(exc_args)

        # Loop until valid input is provided by user
        while True:
            try:
                selection = int(input('\nSelection [#]:  '))
            except ValueError:
                print("Invalid entry")
                logger.debug("User entered non-numeric input")
                continue
            # Check user input for basic validity
            if selection < 0:
                # User is being a dick and submitted a negative number, re-prompt
                print("Invalid entry")
                logger.debug("User entered negative number input")
                continue
            elif selection not in range(len(files)):
                # Number input is greater than max selectable value, re-prompt
                print("Invalid entry")
                logger.debug("User entered number greater than returned file count")
                continue
            else:
                # Valid input provided, return absolute path of file selected
                return files[selection]
    except Exception as exc:
        exc_args = {
            'main_message': 'A general failure occurred during the user file selection.',
            'original_exception': exc,
        }
        raise FGeneralError(exc_args)
def start_subprocess(program_arguments: Union[str, list]) -> AttributeDictionary:
    """
    This function runs a subprocess when called and returns the output in an easy-to-reference\\
    attribute style dictionary similar to the original subprocess output return.

    This function is not designed for sub-processing continuous output.

    Calling this function will run the sub-process and will wait until the process ends before\\
    returning the output.

    Args:
        program_arguments (Union[str, list]):
        \t\\- Processing arguments such as ifconfig, ipconfig, python, PowerShell.exe,
        \t   or any other arguments may be passed.

    Raises:
        FTypeError (fexception):
        \t\\- The value '{program_arguments}' is not in [<class 'str'>, <class 'list'>] format.
        SubprocessStartFailure:
        \t\\- An error occurred while running the subprocess ({program_arguments}).

    Returns:
        AttributeDictionary(dict):
        \t\\- Attribute dictionary containing args and stdout

    Return Options:
    \t Two options are avaliable:
    \t\t\\- <process return name>.args\\
    \t\t\\- <process return name>.stdout
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(program_arguments, [str, list])
    except FTypeError:
        raise

    if isinstance(program_arguments, list):
        formatted_program_arguments = '  - program_arguments (list):' + str('\n        - ' + '\n        - '.join(map(str, program_arguments)))
    elif isinstance(program_arguments, str):
        formatted_program_arguments = f'  - program_arguments (str):\n        - {program_arguments}'

    logger.debug(
        'Passing parameters:\n'
        f'{formatted_program_arguments}\n'
    )

    try:
        # Runs the subprocess and returns output
        output = subprocess.Popen(program_arguments, stdout=subprocess.PIPE)

        # Creates an empty list to store standard output.
        process_output = []

        # Reads through each standard output line.
        for line in io.TextIOWrapper(output.stdout, encoding="utf-8"):
            # Adds found line to the list and removes whitespace.
            process_output.append(line.rstrip())

        # Adds entries into the dictionary using the attribute notation. Attribute notation is used to give a similar return experience.
        subprocess_output = AttributeDictionary({'args': output.args, 'stdout': process_output})

        output.wait()
        output.kill()
    except Exception as exc:
        exc_args = {
            'main_message': f'An error occurred while running the subprocess ({program_arguments}).',
            'custom_type': SubprocessStartFailure,
            'original_exception': exc,
        }
        raise SubprocessStartFailure(FCustomException(exc_args))
    else:
        return subprocess_output
示例#17
0
def launch_decryptor_website(encryption_password: str,
                             random_salt: Union[bytes, str],
                             decryptor_template_path: str = None,
                             port: int = None) -> None:
    """
    Creates the decryptor website to decrypt messages.

    The html file in the templates path must be called decryptor.html, but the file can be edited.

    Args:
        encryption_password (str):
        \t\\- Password used to encrypt the info.
        random_salt (Union[bytes, str]):
        \t\\- Random salt string used to encrypt the info. If the value is sent as str format the value will be re-encoded.\\
        \t\\- A string type can happen if the value is set in a YAML or configuration file and not re-encoded correctly.\\
        decryptor_template_path (str, optional):
        \t\\- The full path to the decryptor template directory.\\
        \t\\- Default creates/uses a template folder in the programs main program path.
        port (int, optional):
        \t\\- A port number to access the decrytor site. Defaults to port 5000.

    Raises:
        FTypeError (fexception):
        \t\\- The value '{encryption_password}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{random_salt}' is not in [<class 'bytes'>, <class 'str'>] format.
        FTypeError (fexception):
        \t\\- The value '{decryptor_template_path}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{port}' is not in <class 'int'> format.
        DecryptionSiteFailure:
        \t\\- The decryption website failed to start.
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(encryption_password, str)
        type_check(random_salt, [bytes, str])
        if decryptor_template_path:
            type_check(decryptor_template_path, str)
        if port:
            type_check(port, int)
    except FTypeError:
        raise

    if decryptor_template_path:
        formatted_decryptor_template_path = f'  - decryptor_template_path (str):\n        - {decryptor_template_path}'
    else:
        formatted_decryptor_template_path = f'  - decryptor_template_path (str):\n        - None'
    if port:
        formatted_port = f'  - port (int):\n        - {port}'
    else:
        formatted_port = f'  - port (int):\n        - None'

    logger.debug(
        'Passing parameters:\n'
        f'  - encryption_password (str):\n        - {encryption_password}\n'
        f'  - random_salt (bytes, str):\n        - {str(random_salt)}\n'
        f'{formatted_decryptor_template_path}\n'
        f'{formatted_port}\n')

    try:
        if decryptor_template_path is None:
            # Gets the main program root directory.
            main_script_path = pathlib.Path.cwd()
            decryptor_template_path = os.path.abspath(
                f'{main_script_path}/templates')
            # Checks if the decryptor_template_path exists and if not it will be created.
            if not os.path.exists(decryptor_template_path):
                os.makedirs(decryptor_template_path)
            logger.debug(
                f'No template path was sent. Using the default path.\n  - {decryptor_template_path}'
            )
        else:
            logger.debug(
                f'Template path was sent. Using the template path.\n  - {decryptor_template_path}'
            )
        decryptor_template_file_path = os.path.abspath(
            f'{decryptor_template_path}/decrypt.html')
        file_check(decryptor_template_file_path, 'decrypt.html')

        # Creates Flask instance with a specified template folder path.
        http_info_decryptor = Flask(__name__,
                                    template_folder=decryptor_template_path)

        # Checks if incoming salt is in str format, so the salt can be re-encoded.
        if isinstance(random_salt, str):
            if random_salt[:2] == "b'":
                # Strips the bytes section off the input.
                # Removes first 2 characters.
                unconverted_random_salt = random_salt[2:]
                # Removes last character.
                unconverted_random_salt = unconverted_random_salt[:-1]
                # Re-encodes the info.
                random_salt = unconverted_random_salt.encode().decode(
                    'unicode_escape').encode("raw_unicode_escape")

        @http_info_decryptor.route('/')
        def decryptor_form():
            return render_template('decrypt.html')

        @http_info_decryptor.route('/', methods=['POST'])
        def decryptor_form_post():
            # Requests input.
            encrypted_info = request.form['text']
            logger.debug(
                f'The user submitted encrypted_info through the website.\n  - {encrypted_info}'
            )
            try:
                # Calling function to decrypt the encrypted info.
                decrypted_message = decrypt_info(encrypted_info,
                                                 encryption_password,
                                                 random_salt)
                logger.debug(
                    f'The encrypted message has been decrypted.\n  - {decrypted_message}'
                )
            except Exception as exc:
                if 'An invalid Key failure occurred while decrypting the info' in str(
                        exc):
                    decrypted_message = f'The submitted encrypted message does not have a matching key or salt to decrypt. The info did not decrypt.'
                else:
                    decrypted_message = exc
            # Returns values to web.
            return render_template('decrypt.html',
                                   decrypted_message=decrypted_message)

        logger.debug('Sending the decrypted message to the webpage')
        if port:
            serve(http_info_decryptor, host="0.0.0.0", port=port)
        else:
            serve(http_info_decryptor, host="0.0.0.0", port=5000)
    except FFileNotFoundError:
        raise
    except Exception as exc:
        exc_args = {
            'main_message': 'The decryption website failed to start.',
            'custom_type': DecryptionSiteFailure,
            'original_exception': exc
        }
        raise DecryptionSiteFailure(FCustomException(exc_args))
示例#18
0
def create_logger(logger_settings: dict) -> logging.Logger:
    """
    This function creates a logger based on specific parameters.\\
    The logger is passed back and can be used throughout the program.\\
    This function is ideal when needing to create new loggers on the fly or for custom usage.\\
    General programing logging should utilize a YAML file with the setup_logger_yaml function.\\
    Checks that existing log handlers do not exist. Log handlers can exist when looping.\\
    This check will prevent child loggers from being created and having duplicate entries.

    Args:
        logger_settings (dict):
        \t\\- formatted dictionary containing all the logger settings.

    Arg Keys:
        logger_settings Keys:\\
        \t\\- save_path (str):\\
        \t\t\\- log file save path\\
        \t\\- logger_name (str):\\
        \t\t\\- logger name\\
        \t\\- log_name (str):\\
        \t\t\\- logger file name\\
        \t\\- max_bytes (int):\\
        \t\t\\- max log size in bytes\\
        \t\\- file_log_level (str):\\
        \t\t\\- file output log level\\
        \t\\- console_log_level (str):\\
        \t\t\\- console output log level\\
        \t\\- backup_count (int):\\
        \t\t\\- backup log copies\\
        \t\\- format_option (int or str):\\
        \t\t\\- allows the ability to select a pre-defined option\\
        \t\t\t\\- options:\\
        \t\t\t\t 1 - '%(asctime)s|%(levelname)s|%(message)s (Module:%(module)s, Function:%(funcName)s,\\
        \t\t\t\t       Line:%(lineno)s)',datefmt='%Y-%m-%d %H:%M:%S' (Default)\\
        \t\t\t\t 2 - '%(message)s'\\
        \t\t\t\t (str) - '%(asctime)s|%(levelname)s|%(funcName)s|%(message)s' - Manual Entry\\
        \t\\- handler_option (int): handler option\\
        \t\t\\- options:\\
        \t\t\t 1 - Both (Default)\\
        \t\t\t 2 - File Handler\\
        \t\t\t 3 - Console Handler

    Raises:
        FTypeError (fexception):
        \t\\- The value '{logger_settings}' is not in <class 'dict'> format.
        FValueError (fexception):
        \t\\- A general error occurred while validating the logger dictionary keys.
        FKeyError (fexception):
        \t\\- The logger settings dictionary is missing keys.
        FTypeError (fexception):
        \t\\- The value '{save_path}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{logger_name}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{max_bytes}' is not in <class 'int'> format.
        FTypeError (fexception):
        \t\\- The value '{file_log_level}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{console_log_level}' is not in <class 'str'> format.
        FTypeError (fexception):
        \t\\- The value '{backup_count}' is not in <class 'int'> format.
        FTypeError (fexception):
        \t\\- The value '{format_option}' is not in [<class 'str'>, <class 'int'>] format.
        FTypeError (fexception):
        \t\\- The value '{handler_option}' is not in <class 'int'> format.
        LoggerSetupFailure:
        \t\\- Incorrect format_option selection.
        LoggerSetupFailure:
        \t\\- Incorrect handler_option selection.
        FGeneralError (fexception):
        \t\\- A general issue occurred while create the new logger.

    Returns:
        logger:
        \t\\- returns the new logger

    Return Example:
    \t\\- <Logger MySoftware1 (DEBUG)>)
    """
    logger = logging.getLogger(__name__)
    logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
    # Custom flowchart tracking. This is ideal for large projects that move a lot.
    # For any third-party modules, set the flow before making the function call.
    logger_flowchart = logging.getLogger('flowchart')
    # Deletes the flowchart log if one already exists.
    logger_flowchart.debug(f'Flowchart --> Function: {get_function_name()}')

    try:
        type_check(logger_settings, dict)
    except FTypeError:
        raise

    formatted_logger_settings = (
        '  - logger_settings (dict):\n        - ' +
        '\n        - '.join(': '.join((key, str(val)))
                            for (key, val) in logger_settings.items()))
    logger.debug('Passing parameters:\n' f'{formatted_logger_settings}\n')

    # Checks for required dictionary keys.
    try:
        # ####################################################################
        # ###################Dictionary Key Validation########################
        # ####################################################################
        # Gets a list of all expected keys.
        # Return Output: ['save_path', 'logger_name', 'log_name', 'max_bytes', 'file_log_level',
        #                 'console_log_level', 'backup_count', 'format_option', 'handler_option']
        logger_settings_keys = list(logger_settings.keys())
        # Checks if the key words exist in the dictionary.
        # This validates the correct dictionary keys for the logger settings.
        if ('save_path' not in str(logger_settings_keys)
                or 'logger_name' not in str(logger_settings_keys)
                or 'log_name' not in str(logger_settings_keys)
                or 'max_bytes' not in str(logger_settings_keys)
                or 'file_log_level' not in str(logger_settings_keys)
                or 'console_log_level' not in str(logger_settings_keys)
                or 'backup_count' not in str(logger_settings_keys)
                or 'format_option' not in str(logger_settings_keys)
                or 'handler_option' not in str(logger_settings_keys)):
            exc_args = {
                'main_message':
                'The logger settings dictionary is missing keys.',
                'expected_result': [
                    'save_path', 'logger_name', 'log_name', 'max_bytes',
                    'file_log_level', 'console_log_level', 'backup_count',
                    'format_option', 'handler_option'
                ],
                'returned_result':
                logger_settings_keys,
                'suggested_resolution':
                'Please verify you have set all required keys and try again.',
            }
            raise FKeyError(exc_args)
    except FKeyError:
        raise
    except Exception as exc:
        exc_args = {
            'main_message':
            'A general error occurred while validating the logger dictionary keys.',
            'original_exception': exc,
        }
        raise FGeneralError(exc_args)

    save_path = logger_settings.get('save_path')
    logger_name = logger_settings.get('logger_name')
    log_name = logger_settings.get('log_name')
    max_bytes = logger_settings.get('max_bytes')
    file_log_level = logger_settings.get('file_log_level')
    console_log_level = logger_settings.get('console_log_level')
    backup_count = logger_settings.get('backup_count')
    format_option = logger_settings.get('format_option')
    handler_option = logger_settings.get('handler_option')

    try:
        type_check(save_path, str)
        type_check(logger_name, str)
        type_check(log_name, str)
        type_check(max_bytes, int)
        type_check(file_log_level, str)
        type_check(console_log_level, str)
        type_check(backup_count, int)
        type_check(format_option, [str, int])
        type_check(handler_option, int)
    except FTypeError:
        raise

    # Creates or returns a logger.
    try:
        # Sets the log save path using namespace.
        namespace = {}
        namespace['base_dir'] = os.path.abspath(save_path)
        namespace['logfile'] = os.path.join(namespace['base_dir'], log_name)
        # Sets flag as False to start.
        existing_logger_flag = False

        # Loops through all active loggers
        for active_logger_names, active_logger_details in logging.Logger.manager.loggerDict.items(
        ):
            # Checks if the logger already exists.
            if logger_name in active_logger_names:
                existing_logger_flag = True
                break
            else:
                existing_logger_flag = False
        # Checks if a log handler already exists.
        # Log handlers can exist when looping. This check will prevent child loggers from being created and having duplicate entries.
        if existing_logger_flag is False:
            # Sets logger name.
            created_logger = logging.getLogger(logger_name)
            # Sets logger level to Debug to cover all handelers levels that are preset.
            # Default = Warning and will restrict output to the handlers even if they are set to a lower level.
            created_logger.setLevel(logging.DEBUG)
            # Custom level used for supported programs.
            # Created for use when monitoring logs to show its an alert and not an error.
            logging.addLevelName(39, "ALERT")

            # Sets the log format based on a number option or manual based on parameter.
            if format_option == 1 or format_option is None:
                # Sets custom format and date
                formatter = logging.Formatter(
                    fmt=
                    '%(asctime)s|%(levelname)s|%(message)s (Module:%(module)s, Function:%(funcName)s,  Line:%(lineno)s)',
                    datefmt='%Y-%m-%d %H:%M:%S')
            elif format_option == 2:
                # Sets custom format and date.
                formatter = logging.Formatter(fmt='%(message)s')
            elif '%' in f'{format_option}':
                formatter = logging.Formatter(fmt=format_option)
            else:
                exc_args = {
                    'main_message':
                    'Incorrect format_option selection.',
                    'custom_type':
                    LoggerSetupFailure,
                    'suggested_resolution':
                    'Please verify you entered a valid format option number or custom format string.',
                }
                raise LoggerSetupFailure(FCustomException(exc_args))

            # Sets handler option based on parameter.
            if handler_option == 1 or handler_option is None:
                # Sets log rotator.
                file_rotation_handler = RotatingFileHandler(
                    namespace['logfile'],
                    maxBytes=max_bytes,
                    backupCount=backup_count)
                # Sets the level logging entry from a variable.
                file_level = logging.getLevelName(file_log_level)
                # Sets the logging level.
                file_rotation_handler.setLevel(file_level)

                # Sets logging stream handler.
                console_stream_handler = logging.StreamHandler()
                # Sets the level logging entry from a variable.
                console_level = logging.getLevelName(console_log_level)
                # Sets the logging level.
                console_stream_handler.setLevel(console_level)

                console_stream_handler.setFormatter(formatter)
                file_rotation_handler.setFormatter(formatter)
                created_logger.addHandler(console_stream_handler)
                created_logger.addHandler(file_rotation_handler)
            elif handler_option == 2:
                # Sets log rotator.
                file_rotation_handler = RotatingFileHandler(
                    namespace['logfile'],
                    maxBytes=max_bytes,
                    backupCount=backup_count)
                # Sets the level logging entry from a variable.
                file_level = logging.getLevelName(file_log_level)
                # Sets the logging level.
                file_rotation_handler.setLevel(file_level)
                file_rotation_handler.setFormatter(formatter)
                created_logger.addHandler(file_rotation_handler)
            elif handler_option == 3:
                # Sets logging stream handler.
                console_stream_handler = logging.StreamHandler()
                # Sets the level logging entry from a variable.
                console_level = logging.getLevelName(console_log_level)
                # Sets the logging level.
                console_stream_handler.setLevel(console_level)

                console_stream_handler.setFormatter(formatter)
                created_logger.addHandler(console_stream_handler)
            else:
                exc_args = {
                    'main_message':
                    'Incorrect handler_option selection.',
                    'custom_type':
                    LoggerSetupFailure,
                    'suggested_resolution':
                    'Please verify you entered a valid handler option number.',
                }
                raise LoggerSetupFailure(FCustomException(exc_args))
        else:
            # Setting the existing logger.
            created_logger = logging.getLogger(logger_name)

        logger.debug(f'Returning value(s):\n  - Return = {created_logger}')
        # Returns logger
        return created_logger
    except LoggerSetupFailure:
        raise
    except Exception as exc:
        exc_args = {
            'main_message':
            'A general issue occurred while create the new logger.',
            'original_exception': exc,
        }
        raise FGeneralError(exc_args)
示例#19
0
    def feed(self, html_output: str, convert_option: str) -> str:
        """
        Main feed to convert HTML to text.

        Args:
            html_output (str):
            \t\\- HTML output requiring conversion.
            convert_option (str):
            \t\\- Conversion option.\\
            \t\\- Only 'text' is supported currently.

        Calling Example:
        \t\\- html_output = '<HTML as a string>'\\
        \t\\- convert_option = 'text

        Raises:
            FTypeError (fexception):
            \t\\- The value '{html_output}' is not in <class 'str'> format.
            FTypeError (fexception):
            \t\\- The value '{convert_option}' is not in <class 'str'> format.
            FValueError (fexception):
            \t\\- The HTML output could not be converted because the conversion option is not valid.
            FGeneralError (fexception):
            \t\\- A general failure occurred while converting HTML to text.

        Returns:
            str:
            \t\\- Converted HTML to text.
        """
        logger = logging.getLogger(__name__)
        logger.debug(f'=' * 20 + get_function_name() + '=' * 20)
        # Custom flowchart tracking. This is ideal for large projects that move a lot.
        # For any third-party modules, set the flow before making the function call.
        logger_flowchart = logging.getLogger('flowchart')
        # Deletes the flowchart log if one already exists.
        logger_flowchart.debug(
            f'Flowchart --> Function: {get_function_name()}')

        try:
            type_check(html_output, str)
            type_check(convert_option, str)
        except FTypeError:
            raise

        logger.debug(
            'Passing parameters:\n'
            f'  - html_output (str):\n        - {str(html_output)}\n'
            f'  - convert_option (str):\n        - {convert_option}\n')

        if not any(convert_option == c for c in (None, 'text')):
            exc_args = {
                'main_message':
                'The HTML output could not be converted because the conversion option is not valid.',
                'expected_result':
                'text',
                'returned_result':
                convert_option,
                'suggested_resolution':
                'Please verify you have sent a valid conversion option.',
            }
            raise FValueError(exc_args)

        try:
            self.output = ""
            super(HTMLConverter, self).feed(html_output)
            # Supports text conversion, but other conversions such as PDF, image, etc can be added in the future.
            if convert_option == 'text':
                logging.debug(
                    'The HTML was converted to text. Returning the output.')
                # Removes all html before the last "}". Some HTML can return additional style information with text output.
                self.output = str(self.output).split('}')[-1].strip()
        except Exception as exc:
            exc_args = {
                'main_message':
                'A general failure occurred while converting HTML to text.',
                'original_exception': exc,
            }
            raise FGeneralError(exc_args)
        else:
            return self.output