#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import annotations

import configparser
import json
import os
import urllib.parse as parser
import uuid
from enum import Enum
from time import time
from typing import Dict

import boto3
import requests
from aws_xray_sdk.core import xray_recorder as xray
from requests_toolbelt import MultipartEncoder

from iobot.models import IOBotList
from iobot.models import IOBotObject
from iobot.views import ModalView
from psi_logger import logger
from psi_secrets import SecretsManager
from slater.auth import SLaterRoleEntitlementLevel
from slater.auth import SLaterUserEntitlementLevel
from slater.exceptions import SLaterClientMissingRequiredParameters  # noqa: E501, F401
from slater.exceptions import SLaterDeprecatedFeatureException
from slater.exceptions import SLaterUnAuthorizedSlackUserGroupError  # noqa: E501
from slater.models import Account  # noqa: F401
from slater.models import Accounts  # noqa: F401
from slater.models import AccountType  # noqa: F401
from slater.models import AccountTypes  # noqa: F401
from slater.models import Application  # noqa: F401
from slater.models import ApplicationContact  # noqa: F401
from slater.models import ApplicationContacts  # noqa: F401
from slater.models import Applications  # noqa: F401
from slater.models import BatchError  # noqa: F401
from slater.models import BatchErrors  # noqa: F401
from slater.models import BatchImport  # noqa: F401
from slater.models import BatchImports  # noqa: F401
from slater.models import BatchLoad  # noqa: F401
from slater.models import BatchLoads  # noqa: F401
from slater.models import BusinessUnit  # noqa: F401
from slater.models import BusinessUnits  # noqa: F401
from slater.models import Contact  # noqa: F401
from slater.models import Contacts  # noqa: F401
from slater.models import ContactType  # noqa: F401
from slater.models import ContactTypes  # noqa: F401
from slater.models import CustomField  # noqa: F401
from slater.models import CustomFields  # noqa: F401
from slater.models import Export  # noqa: F401
from slater.models import Exports  # noqa: F401
from slater.models import LOB  # noqa: F401
from slater.models import LOBs  # noqa: F401
from slater.models import MasterPayer  # noqa: F401
from slater.models import MasterPayers  # noqa: F401
from slater.models import Tower  # noqa: F401
from slater.models import Towers  # noqa: F401
from slater.views import CloudDeliveryServicesAccountDetailView
from slater.views import CloudDeliveryServicesApplicationDetailView  # noqa: E501
from slater.views import SLaterAccountCreateView  # noqa: F401
from slater.views import SLaterAccountDeleteView  # noqa: F401
from slater.views import SLaterAccountDetailView  # noqa: F401
from slater.views import SLaterAccountEditView  # noqa: F401
from slater.views import SLaterAccountsListView  # noqa: F401
from slater.views import SLaterAccountsSearchView  # noqa: F401
from slater.views import SLaterAccountTypeCreateView  # noqa: F401
from slater.views import SLaterAccountTypeDeleteView  # noqa: F401
from slater.views import SLaterAccountTypeDetailView  # noqa: F401
from slater.views import SLaterAccountTypeEditView  # noqa: F401
from slater.views import SLaterAccountTypesListView  # noqa: F401
from slater.views import SLaterAccountTypesSearchView  # noqa: F401
from slater.views import SLaterAddContactView
from slater.views import SLaterApplicationContactDeleteView  # noqa: F401
from slater.views import SLaterApplicationContactDetailView  # noqa: F401
from slater.views import SLaterApplicationContactEditView  # noqa: F401
from slater.views import SLaterApplicationContactsListView  # noqa: F401
from slater.views import SLaterApplicationContactsSearchView  # noqa: F401
from slater.views import SLaterApplicationCreateView  # noqa: F401
from slater.views import SLaterApplicationDeleteView  # noqa: F401
from slater.views import SLaterApplicationDetailView  # noqa: F401
from slater.views import SLaterApplicationEditView  # noqa: F401
from slater.views import SLaterApplicationsListView  # noqa: F401
from slater.views import SLaterApplicationsSearchView  # noqa: F401
from slater.views import SLaterBatchErrorsListView  # noqa: F401
from slater.views import SLaterBatchLoadsListView  # noqa: F401
from slater.views import SLaterBusinessUnitCreateView  # noqa: F401
from slater.views import SLaterBusinessUnitDeleteView  # noqa: F401
from slater.views import SLaterBusinessUnitDetailView  # noqa: F401
from slater.views import SLaterBusinessUnitEditView  # noqa: F401
from slater.views import SLaterBusinessUnitsListView  # noqa: F401
from slater.views import SLaterBusinessUnitsSearchView  # noqa: F401
from slater.views import SLaterContactCreateView  # noqa: F401
from slater.views import SLaterContactDeleteView  # noqa: F401
from slater.views import SLaterContactDetailView  # noqa: F401
from slater.views import SLaterContactEditView  # noqa: F401
from slater.views import SLaterContactsListView  # noqa: F401
from slater.views import SLaterContactsSearchView
from slater.views import SLaterContactTypeCreateView  # noqa: F401
from slater.views import SLaterContactTypeDeleteView  # noqa: F401
from slater.views import SLaterContactTypeDetailView  # noqa: F401
from slater.views import SLaterContactTypeEditView  # noqa: F401
from slater.views import SLaterContactTypesListView  # noqa: F401
from slater.views import SLaterContactTypesSearchView  # noqa: F401
from slater.views import SLaterCustomFieldsListView  # noqa: F401
from slater.views import SLaterHome
from slater.views import SLaterLOBCreateView  # noqa: F401
from slater.views import SLaterLOBDeleteView  # noqa: F401
from slater.views import SLaterLOBDetailView  # noqa: F401
from slater.views import SLaterLOBEditView  # noqa: F401
from slater.views import SLaterLOBsListView  # noqa: F401
from slater.views import SLaterLOBsSearchView  # noqa: F401
from slater.views import SLaterMasterPayerCreateView  # noqa: F401
from slater.views import SLaterMasterPayerDeleteView  # noqa: F401
from slater.views import SLaterMasterPayerDetailView  # noqa: F401
from slater.views import SLaterMasterPayerEditView  # noqa: F401
from slater.views import SLaterMasterPayersListView  # noqa: F401
from slater.views import SLaterMasterPayersSearchView  # noqa: F401
from slater.views import SLaterRootContactsSearchView
from slater.views import SLaterSlashCommandErrorView
from slater.views import SLaterSlashCommandHelpView
from slater.views import SLaterSlashCommandNoResultsView
from slater.views import SLaterSREDetailView
from slater.views import SLaterSREEditView
from slater.views import SLaterSREsListView
from slater.views import SLaterTowerCreateView  # noqa: F401
from slater.views import SLaterTowerDeleteView  # noqa: F401
from slater.views import SLaterTowerDetailView  # noqa: F401
from slater.views import SLaterTowerEditView  # noqa: F401
from slater.views import SLaterTowersListView  # noqa: F401
from slater.views import SLaterTowersSearchView  # noqa: F401
from iobot.utils import get_envvar
from iobot.utils import init_config
from iobot.xray import annotate
from iobot.xray import put_metadata

PRIMARY_KEYS: dict = {
    'Account': 'cloud_account_id',
    'AccountType': 'account_type_id',
    'Application': 'application_id',
    'ApplicationContact': 'application_contact_id',
    'BusinessUnit': 'business_unit_id',
    'Contact': 'contact_id',
    'ContactType': 'contact_type_id',
    'LOB': 'lob_id',
    'MasterPayer': 'master_payer_id',
    'Tower': 'tower_id'
}


@xray.capture()
class SLater(object):  # TODO: inherit IOBotClient instead of object
    """Initialize a slater client...
    """

    @xray.capture()
    @logger.log
    def __init__(self,
                 log,
                 session: boto3.session.Session,  # TODO: rename to boto_session
                 client: requests.Session,  # TODO: rename to http_session
                 config: configparser.ConfigParser = None,
                 config_file: str = "iobot.ini",
                 slater_user: str = "",
                 entitlement_level: Enum = SLaterUserEntitlementLevel.SLATER_READER,
                 db_version: str = None) -> None:


        log.debug('initializing SLater client')

        if not config:
            config = init_config(config_file=config_file, env=dict(os.environ))

        self.config = config
        self._log = log
        self._secrets = SecretsManager(log, session)
        self.client = client
        self._creds = self.__login()
        if slater_user:
            self.slater_user = slater_user
        else:
            self.slater_user = ""

        self.request = {
            'submitter': {
                'name': self.slater_user,
                'entitlement': entitlement_level
            },
            'submitted': time(),
        }

        if not db_version:
            db_version = config['SLater']['db_version']
        self.db_version = db_version

    @xray.capture()
    @logger.log
    def IsAuthorized(self, required_role_level: Enum = SLaterRoleEntitlementLevel.UNAUTHORIZED):  # noqa: E501
        if self.GetUserRole().value >= required_role_level.value:
            return True
        else:
            return False

    @xray.capture()
    @logger.log
    def GetUserRole(self):
        return self.request.get('submitter').get('entitlement')

    @xray.capture()
    @logger.log
    def GetSLaterRequest(self):
        request = self.request
        request['submitted'] = time()
        return request

    @xray.capture()
    @logger.log
    def GetUnauthorizedMessage(self, required_role_level) -> str:
        return f"You have insufficient privilages to perform this operation :sadparrot: \
            \nTo request access, visit the #io-slater-support channel and click the lightning bolt :bolt: \
            \nRequired role: {required_role_level} \
            \nYour role: {self.GetUserRole().name}"

    @xray.capture()
    @logger.log
    def logged_in(self) -> bool:
        if self._creds:
            return True

        return False

    @xray.capture()
    @logger.log
    def __login(self, 
                usernanme_parameter: str = 'psi-slater-user-prod-latest',
                password_parameter: str = 'psi-slater-pass-prod-latest') -> Dict:
        """Login to the SLater REST API and cache a bearer token
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test__login = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate we logged in correctly
        >>> assert(test__login.logged_in()), 'login should return true'
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        username: str = self._secrets.GetParameter(usernanme_parameter)
        password: str = self._secrets.GetParameter(password_parameter)

        params = parser.urlencode({"grant_type": "client_credentials"})
        url: str = f"{self.config['SLater']['login_api']}?{params}"
        self._log.debug(f"url: {url}")

        headers: Dict[str, str] = {
            "Content-Type": "application/x-www-form-urlencoded"
        }
        self._log.debug(f"headers {headers}")
        response: Dict = self.client.post(
            url, auth=(username, password), headers=headers)

        if response.status_code != 200:
            self._log.error(
                f'Status: {response.status_code}, Headers: {response.headers}, Error Response: {response.content}')
            return None

        self._log.debug(f'login completed with: {response.status_code}')
        return response.json()

    @xray.capture()
    @logger.log
    def Create(self,
               api: str = '',
               object_type: str = '',
               list_type: str = '',
               db_version: str = '',
               request: dict = None,
               **kwargs) -> IOBotObject:
        """
        Use this API to persist the internal state of the current object to
        the appropriate SLater table.

        Parameters
        ----------

        api: str (required)
          Required SLater api endpoint to read data from.
          Reference the SLater section of the iobot.ini config.
          Only mapped values will be accepted.

        object_type: str (required)
          Required name of SLater object you wish to create.

          Supported types:

          - Account
          - AccountType
          - Application
          - ApplicationContact
          - BusinessUnit
          - Contact
          - ContactType
          - LOB
          - MasterPayer
          - Tower

        primary_key: str (required)
          After persisting object, this field will be used to retrieve
          back the saved state.

        db_version: str
          Alternative version of SLater DB Schema. If you set to 'swa', SLater
          will use the DB schema 'slater_swa' instead of its default 'slater'.
          Use this for fixed, or staged testing data sets.

        request: dict
          Slack Request User interacting with SLater (for auditing). It must
          contain both an ID and a submit timestamp.

          `{'submitter':{'name':'john.torres'},'submitted':1595314865}`

        **kwargs:
          K/V parameters (reserved, not currently used)


        Returns
        -------

        IOBotObject:
          A object populated from SLater based on the provided parameters.


        Testing
        =======

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_Create = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate object create
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> object_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> t_appid: str = ''.join(random.choice(string.digits) for i in range(10))

        >>> application = test_Create.Create(api="applications_api", object_type='Application', list_type='Applications', application_name=object_name, t_appid=f'SVC{t_appid}', application_description='Ride a Ostrich', lob='foo', create_date='1970-01-01', update_date='1970-01-01', budget='budget$1',request=request)

        >>> assert(application.get('application_name') == object_name), f"expected name to equal '{object_name}', got {application.get('application_name')}"

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        role_entitlement: Enum = SLaterRoleEntitlementLevel.READ_WRITE

        if not self.IsAuthorized(required_role_level=role_entitlement):
            self._log.warning(
                f'user is not authorized to make this call requires: {role_entitlement.name}, user has role: {self.GetUserRole().name}')
            raise SLaterUnAuthorizedSlackUserGroupError(self.GetUnauthorizedMessage(role_entitlement))

        url = self.config['SLater'][api]
        self._log.debug(f"url: {url}")

        headers: Dict[str, str] = {
            "Content-Type": "application/x-www-form-urlencoded",
            "Authorization": f"Bearer {self._creds['access_token']}",
            "slater_user": request.get('submitter').get('name'),
            "db_version": self.db_version
        }

        self._log.debug(f"headers {headers}")
        create_response: Dict = self.client.post(
            url, headers=headers, data=json.dumps(kwargs))

        new_object = create_response.json()
        put_metadata('slater_creator_response', new_object)
        self._log.debug(f'new object: {new_object}')
        if create_response.status_code not in (200, 201, 202):
            self._log.error(
                f'Status: {create_response.status_code}, Headers: {create_response.headers}, Error Response: {create_response.content}')

            return None
        else:

            self._log.debug(
                f'response: {create_response.status_code}, created: {new_object}')

            # object_type: str = self.GetObjectType(underscore=False)
            primary_key: str = PRIMARY_KEYS[object_type]
            object_id: str = new_object.get(primary_key)

            slater_object = globals()[object_type](
                **new_object)

            annotate(f'create_{object_type}_id', f'{primary_key}={object_id}')
            if not slater_object.get(primary_key):
                self._log.error(
                    f'Status: failed to persist state, no object found for {primary_key}={object_id}')

                return None

            slater_object.initialize_clients(
                self._creds, self._log, self._secrets, self)

            self._log.debug(f'created object: {slater_object}')

            return slater_object

    @logger.log
    def CreateAccount(self, api: str = "accounts_api", request=None, **kwargs) -> Account:
        """Create a account object and save it to SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_CreateAccount = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate account create
        >>> import random, string
        >>> account_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> account_number: str = ''.join(random.choice(string.digits) for i in range(12))
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> account = test_CreateAccount.CreateAccount(cloud_account_name=account_name, cloud_account_number=account_number, cloud_account_description='slater.client test account', master_payer_id=1, account_type_id=1,environment_type_id=1,environment_type_name='non-production', cloud_account_root_email=f"{account_name}@pearson.com",governance_lob_name="lob a", lob_id=1,budget_account_string="foo$1",decommissioned=0,provisioner="cinch",project_number="apifoo",snow_approval_group="slater snow",budget_alert_monthly_threshold=500, cloud_organization_id=1, cloud_organization_name="Assessments",next_link=f"https://localhost/accounts/{account_number}",request=request)
        >>> assert(account.get('cloud_account_name') == account_name), f"expected name to equal {account_name}, got {account.get('cloud_account_name')}"
        >>> search = test_CreateAccount.GetAccountByID(account_number)
        >>> assert(search.get('cloud_account_name') == account_name), f"expected name to equal {account_name}, got {search.get('cloud_account_name')}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Create(api=api,
                           object_type='Account',
                           list_type='Accounts',
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def CreateAccountType(self, api: str = "accounttypes_api", request=None, **kwargs) -> AccountType:
        """Create a account object and save it to SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_CreateAccountType = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate account_type create
        >>> import random, string

        >>> account_type_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))}-{''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> account_type = test_CreateAccountType.CreateAccountType(account_type_name=account_type_name,request=request)

        >>> search = test_CreateAccountType.GetAccountTypeByID(account_type.get('account_type_id'))

        >>> assert(search.get('account_type_name') == account_type_name), f"expected name to equal {account_type_name}, got {search.get('account_type_name')}"

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """

        return self.Create(api=api,
                           object_type='AccountType',
                           list_type='AccountTypes',
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def CreateAccountContact(self, api: str = "accountcontacts_api", request=None, **kwargs) -> Contact:
        """Create a account object and save it to SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_CreateAccountContact = SLater(log,boto_session,http_session,config_file='environments/unit.ini', entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> account_contact = test_CreateAccountContact.CreateAccountContact(cloud_account_id=3, contact_id=4, contact_type_id=5,request=request)

        >>> "account_contact_id" in account_contact.keys()
        True

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """

        return self.Create(api=api,
                           object_type='Contact',
                           list_type='Contacts',
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def CreateApplication(self, api: str = "applications_api",
                          request=None, **kwargs) -> Application:
        """Create a application object and save it to SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_CreateApplication = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate application create
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> application_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> t_appid: str = ''.join(random.choice(string.digits) for i in range(4))

        >>> application = test_CreateApplication.CreateApplication(application_name=application_name, t_appid=f'SVC{t_appid}', application_description='Ride a Ostrich', lob='foo', create_date='1970-01-01', update_date='1970-01-01', budget='budget$1',request=request)

        >>> assert(application.get('application_name') == application_name), f"expected name to equal '{application_name}', got {application.get('application_name')}"

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """

        return self.Create(api=api,
                           object_type='Application',
                           list_type='Applications',
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def CreateApplicationContact(self, api: str = "applicationcontacts_api", request=None, **kwargs) -> Contact:
        """Create an application contact object and save it to SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_CreateApplicationContact = SLater(log,boto_session,http_session,config_file='environments/unit.ini', entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> application_contact = test_CreateApplicationContact.CreateApplicationContact(application_id=3, contact_id=4, contact_type_id=5,request=request)

        >>> "application_contact_id" in application_contact.keys()
        True

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """

        return self.Create(api=api,
                           object_type='Contact',
                           list_type='Contacts',
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def CreateBatchImport(self, group_batch_zip: bytes = b'', group_batch_uuid: uuid.UUID = None, slater_user: str = '') -> Dict:
        """Create Batch Process SLater Import Request

        Setup test environment
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_CreateBatchImport = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        >>> from io import BytesIO

        >>> batch_file_data = BytesIO(b"PK\\x03\\x04\\x14\\x00\\x00\\x00\\x00\\x00\\x00\\x00!\\x00\\xa9\\xb0\\xda\\x0e<\\x05\\x00\\x00<\\x05\\x00\\x00\\n\\x00\\x00\\x00towers.csv.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\ntower_name,tower_owner_name,tower_owner_email\\nBarad-d\\xc3\\xbbr,Sauron DarkLord,dark.lord@darktower.com\\nIsengard,Saruman WhiteHand,saruman.whitehand@lighttower.com\\nEnterprise Systems,Steve Santana,steve.santana@pearson.com\\nCustomer Experience,Corey Farrell,corey.farrell@pearson.com\\nContent,Eliot Pikoulis,eliot.pikoulis@pearson.com\\nInter Div,Iqbal Kahn,iqbal.kahn@pearson.com\\nEmployee Experience,Mark Barfield,mark.barfield@pearson.com\\nI&O,Ian Wright,ian.wright@pearson.com\\nBitesize,Ian Wright,ian.wright@pearson.com\\nPLA,Saleem Brahmanapalli,syedsaleemjavid.brahmanapalli@pearson.com\\nAppEng,Saleem Brahmanapalli,syedsaleemjavid.brahmanapalli@pearson.com\\nBayBridge,Saleem Brahmanapalli,syedsaleemjavid.brahmanapalli@pearson.com\\nIndependent Managed,Saleem Brahmanapalli,syedsaleemjavid.brahmanapalli@pearson.com\\nAssessments,,\\nClinical,,\\nInterco,,\\nCISO-TechOps,Vincent Blake,vincent.blake@pearson.com\\nCISO-PSO,Vincent Blake,vincent.blake@pearson.com\\nCISO-ITSec,Vincent Blake,vincent.blake@pearson.com\\nQuality Assurance,,\\nVUE,Guy Speier,guy.speier@pearson.com\\nES,Guy Speier,guy.speier@pearson.com\\nOBL,Eric Hilfer,eric.hilfer@pearson.com\\nOPM,Iwan Streichenberger,iwan.streichenberger@pearson.com\\nPOLS,Iwan Streichenberger,iwan.streichenberger@pearson.com\\nGBS,Eddie Monteiro,eddie.monteiro@pearson.com\\nPK\\x03\\x04\\x14\\x00\\x00\\x00\\x00\\x00\\x00\\x00!\\x00H\\xf0\\xe48\\x9f\\x02\\x00\\x00\\x9f\\x02\\x00\\x00\\x0c\\x00\\x00\\x00accounts.csv.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\ncloud_account_number, t_appid,cloud_account_name,cloud_account_root_email,master_payer_name,intake_number,account_type_name,governance_lob_name,budget_account_string,project_number,tower_name,snow_approval_group,create_date\\n000000000000,svc00000,Dark Tower Production,sauron@root.Barad-d\\xc3\\xbbr.com,222222222222,0,Production,Dark Lord,one-ring-to-rule-them-$all,000-00000-0000-0000,Barad-d\\xc3\\xbbr,e0a6bf6a-ba42-4fe9-bbe5-732bae268b94,\\n111111111111,svc11111,Light Tower Production,sarumon@root.Isengard.com,222222222222,1,Production,The Fellowship,and-bind-$them,111-11111-1111-1111,Isengard,08d065f4-58b4-4c05-b4d3-98dbdb6a688d,\\nPK\\x03\\x04\\x14\\x00\\x00\\x00\\x00\\x00\\x00\\x00!\\x00\\x10\\xf2\\xf1H\\xd5\\x01\\x00\\x00\\xd5\\x01\\x00\\x00\\x10\\x00\\x00\\x00applications.csv.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\napplication_name,t_appid,governance_lob_name,business_unit_name,application_budget_account_string,application_description,pagerduty_url,tower_name,create_date\\nDark Tower,svc00000,Dark Lord,Orcs,one-ring-to-rule-them-$all,Souron's Evil App,https://pagerduty.com/service/evilapp,Barad-d\\xc3\\xbbr,\\nLight Tower,svc11111,The Fellowship,Elves,and-bind-$them,Saruman's Orc Builder,https://pagerduty.com/service/orcbuilder,Isengard,\\nPK\\x03\\x04\\x14\\x00\\x00\\x00\\x00\\x00\\x00\\x00!\\x00\\xdb.\\x8c\\x05\\xf3\\x00\\x00\\x00\\xf3\\x00\\x00\\x00\\x18\\x00\\x00\\x00application_contacts.csv.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\nt_appid,contact_type,contact_name,contact_email\\nsvc00000,application_owner,Sauron DarkLord,dark.lord@darktower.com\\nsvc11111,application_owner,Saruman WhiteHand,saruman.whitehand@lighttower.com\\nPK\\x03\\x04\\x14\\x00\\x00\\x00\\x00\\x00\\x00\\x00!\\x00l\\xdcZ\\xde\\xf4\\x01\\x00\\x00\\xf4\\x01\\x00\\x00\\x14\\x00\\x00\\x00account_contacts.csv.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\n.,.,.,.,.\\ncloud_account_number,contact_type,contact_name,contact_email\\n000000000000,account_owner,Sauron DarkLord,dark.lord@darktower.com\\n111111111111,account_owner,Saruman WhiteHand,saruman.whitehand@lighttower.com\\n111111111111,tech_owner,Jeff Schmidt,jeffrey.schimdt@pearson.com\\n111111111111,tdm_owner,Jake Halvorsen,jake.halvorsen@pearson.com\\n000000000000,budget_vp,Ian Wright,ian.wright@pearson.com\\n111111111111,budget_vp,Ian Wright,ian.wright@pearson.com\\nPK\\x01\\x02\\x14\\x03\\x14\\x00\\x00\\x00\\x00\\x00\\x00\\x00!\\x00\\xa9\\xb0\\xda\\x0e<\\x05\\x00\\x00<\\x05\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\x01\\x00\\x00\\x00\\x00towers.csvPK\\x01\\x02\\x14\\x03\\x14\\x00\\x00\\x00\\x00\\x00\\x00\\x00!\\x00H\\xf0\\xe48\\x9f\\x02\\x00\\x00\\x9f\\x02\\x00\\x00\\x0c\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\x01d\\x05\\x00\\x00accounts.csvPK\\x01\\x02\\x14\\x03\\x14\\x00\\x00\\x00\\x00\\x00\\x00\\x00!\\x00\\x10\\xf2\\xf1H\\xd5\\x01\\x00\\x00\\xd5\\x01\\x00\\x00\\x10\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\x01-\\x08\\x00\\x00applications.csvPK\\x01\\x02\\x14\\x03\\x14\\x00\\x00\\x00\\x00\\x00\\x00\\x00!\\x00\\xdb.\\x8c\\x05\\xf3\\x00\\x00\\x00\\xf3\\x00\\x00\\x00\\x18\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\x010\\n\\x00\\x00application_contacts.csvPK\\x01\\x02\\x14\\x03\\x14\\x00\\x00\\x00\\x00\\x00\\x00\\x00!\\x00l\\xdcZ\\xde\\xf4\\x01\\x00\\x00\\xf4\\x01\\x00\\x00\\x14\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\x01Y\\x0b\\x00\\x00account_contacts.csvPK\\x05\\x06\\x00\\x00\\x00\\x00\\x05\\x00\\x05\\x008\\x01\\x00\\x00\\x7f\\r\\x00\\x00\\x00\\x00") # noqa: E501

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> test_CreateBatchImport.CreateBatchImport(group_batch_zip=batch_file_data, group_batch_uuid=uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}'), slater_user=request.get('submitter').get('name'))["group_batch_uuid"]
        '00010203-0405-0607-0809-0a0b0c0d0e0f'

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        if not group_batch_uuid:
            group_batch_uuid = uuid.uuid4()

        params = MultipartEncoder(
            fields={
                'group_batch_uuid': str(group_batch_uuid),
                'group_batch_zip': (f'{str(group_batch_uuid)}.zip', group_batch_zip, 'application/zip'),
            }
        )
        url: str = self.config['SLater']['batch_api']
        self._log.debug(f"url: {url}")

        headers: Dict[str, str] = {
            "Authorization": f"Bearer {self._creds['access_token']}",
            "Content-Type": params.content_type,
            "slater_user": slater_user,
            "db_version": self.db_version
        }
        self._log.debug(f"headers {headers}")
        response: Dict = self.client.post(url, headers=headers, data=params)

        if response.status_code not in [200, 202]:
            self._log.error(
                f'Status: {response.status_code}, Headers: {response.headers}, Error Response: {response.content}')
            return None

        self._log.debug(
            f'batch import completed with: {response.status_code}, batch uuid: {group_batch_uuid}')
        return response.json()

    @xray.capture()
    @logger.log
    def CreateBusinessUnit(self, api: str = "businessunits_api", request=None, **kwargs) -> BusinessUnit:
        """Create a account object and save it to SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_CreateBusinessUnit = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate account_type create
        >>> import random, string
        >>> business_unit_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> business_unit = test_CreateBusinessUnit.CreateBusinessUnit(business_unit_name=business_unit_name,request=request)
        >>> assert(business_unit.get('business_unit_name') == business_unit_name), f"expected name to equal {business_unit_name}, got {business_unit.get('business_unit_name')}"
        >>> search = test_CreateBusinessUnit.GetBusinessUnitByID(business_unit.get('business_unit_id'))
        >>> assert(search.get('business_unit_name') == business_unit_name), f"expected name to equal {business_unit_name}, got {search.get('business_unit_name')}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Create(api=api,
                           object_type='BusinessUnit',
                           list_type='BusinessUnits',
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def CreateContact(self, api: str = "contacts_api", request=None, **kwargs) -> Contact:
        """Create a contact object and save it to SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_CreateContact = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate contact create
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> contact = test_CreateContact.CreateContact(contact_name="Jonald Duck",contact_email="jonald.duck@pearson.com",request=request)
        >>> assert(contact.get('contact_name') == 'Jonald Duck'), f"expected name to equal 'Jonald Duck', got {contact.get('contact_name')}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Create(api=api,
                           object_type='Contact',
                           list_type='Contacts',
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def CreateContactType(self, api: str = "contact_types_api", request=None, **kwargs) -> ContactType:
        """Create a contacttype object and save it to SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_CreateContactType = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate contact_type create
        >>> import random, string
        >>> contact_type_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> contact_type = test_CreateContactType.CreateContactType(contact_type=contact_type_name,request=request)
        >>> assert(contact_type.get('contact_type') == contact_type_name), f"expected name to equal {contact_type_name}, got {contact_type.get('contact_type')}"
        >>> search = test_CreateContactType.GetContactTypeByID(contact_type.get('contact_type_id'))
        >>> assert(search.get('contact_type') == contact_type_name), f"expected name to equal {contact_type_name}, got {search.get('contact_type')}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Create(api=api,
                           object_type='ContactType',
                           list_type='ContactTypes',
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def CreateLOB(self, api: str = "lobs_api", request=None, **kwargs) -> LOB:
        """Create a lob object and save it to SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_CreateLOB = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate lob create
        >>> import random, string
        >>> governance_lob_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> lob = test_CreateLOB.CreateLOB(governance_lob_name=governance_lob_name,request=request)
        >>> assert(lob.get('governance_lob_name') == governance_lob_name), f"expected name to equal {governance_lob_name}, got {lob.get('governance_lob_name')}"
        >>> search = test_CreateLOB.GetLOBByID(lob.get('lob_id'))
        >>> assert(search.get('governance_lob_name') == governance_lob_name), f"expected name to equal {governance_lob_name}, got {search.get('governance_lob_name')}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Create(api=api,
                           object_type='LOB',
                           list_type='LOBs',
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def CreateMasterPayer(self, api: str = "masterpayers_api", request=None, **kwargs) -> MasterPayer:
        """Create a masterpayer object and save it to SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_CreateMasterPayer = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate master_payer create
        >>> import random, string

        >>> master_payer_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> master_payer_ident: str = ''.join(random.choice(string.digits) for i in range(12))
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> master_payer = test_CreateMasterPayer.CreateMasterPayer(master_payer_name=master_payer_name,master_payer_ident=master_payer_ident,request=request)

        >>> assert(master_payer.get('master_payer_name') == master_payer_name), f"expected name to equal {master_payer_name}, got {master_payer.get('master_payer_name')}"

        >>> search = test_CreateMasterPayer.GetMasterPayerByID(master_payer.get('master_payer_id'))

        >>> assert(search.get('master_payer_name') == master_payer_name), f"expected name to equal {master_payer_name}, got {search.get('master_payer_name')}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Create(api=api,
                           object_type='MasterPayer',
                           list_type='MasterPayers',
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def CreateTower(self, api: str = "towers_api", request=None, **kwargs) -> Tower:
        """Create a tower object and save it to SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_CreateTower = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate tower create
        >>> import random, string
        >>> tower_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> tower = test_CreateTower.CreateTower(tower_name=tower_name,contact_id=1,request=request)
        >>> assert(tower.get('tower_name') == tower_name), f"expected name to equal {tower_name}, got {tower.get('tower_name')}"
        >>> search = test_CreateTower.GetTowerByID(tower.get('tower_id'))
        >>> assert(search.get('tower_name') == tower_name), f"expected name to equal {tower_name}, got {search.get('tower_name')}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Create(api=api,
                           object_type='Tower',
                           list_type='Towers',
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def Delete(self,
               api: str = '',
               object_type: str = 'IOBotObject',
               id: str = '',
               db_version: str = '',
               request=None,
               **kwargs) -> IOBotObject:
        """Delete a tower object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_DeleteTower = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_ADMIN)

        Validate tower delete
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> tower_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> tower = test_DeleteTower.CreateTower(tower_name=tower_name, contact_id='1',request=request)

        >>> assert(tower.get('tower_name') == tower_name), f"expected name to equal '{tower_name}', got {tower.get('tower_name')}"

        >>> deleted_tower = test_DeleteTower.Delete(api='towers_api', object_type='Tower', id=tower.get('tower_id'),request=request)

        >>> deleted_tower is not None
        True

        >>> tower.get('tower_id') == deleted_tower.get('tower_id')
        True

        >>> assert(deleted_tower.get('next_link') is None), f"expected {deleted_tower.get('next_link')} to equal None"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        role_entitlement: Enum = SLaterRoleEntitlementLevel.FULL_ADMIN

        if not self.IsAuthorized(required_role_level=role_entitlement):
            self._log.warning(
                f'user is not authorized to make this call requires: {role_entitlement.name}, user has role: {self.GetUserRole().name}')
            raise SLaterUnAuthorizedSlackUserGroupError(self.GetUnauthorizedMessage(role_entitlement))

        if not id:
            raise SLaterClientMissingRequiredParameters(
                f'SLater Object ID required for delete {object_type} operation')

        url = f"{self.config['SLater'][api]}/{id}"
        self._log.debug(f"url: {url}")

        headers: Dict[str, str] = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self._creds['access_token']}",
            "db_version": self.db_version
        }

        if request:
            headers['slater_user'] = request.get('submitter', {}).get('name')

        self._log.debug(f"headers {headers}")
        delete_response: Dict = self.client.delete(url, headers=headers)

        delete_json = delete_response.json()
        self._log.debug(f"{delete_json}")

        if delete_response.status_code not in (200, 202, 204):
            self._log.error(
                f'Status: {delete_response.status_code}, Headers: {delete_response.headers}, Error Response: {delete_response.content}')
            return None
        else:
            self._log.debug(
                f'Status: {delete_response.status_code}, Headers: {delete_response.headers}, Delete Response: {delete_response.content}')

            row = delete_json
            self._log.debug(f'deleted record: {row}')
            slater_object = globals()[object_type](**row)

            slater_object.initialize_clients(
                self._creds, self._log, self._secrets, self)
            return slater_object

    @xray.capture()
    @logger.log
    def DeleteAccount(self,
                      api: str = 'accounts_api',
                      cloud_account_id: str = '',
                      request: dict = None,
                      **kwargs) -> Account:
        """Delete a account object from SLater

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_DeleteAccount = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_ADMIN)

        Validate account delete
        >>> import random, string
        >>> account_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> account_number: str = ''.join(random.choice(string.digits) for i in range(12))
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> account = test_DeleteAccount.CreateAccount(cloud_account_name=account_name, cloud_account_number=account_number, cloud_account_description='slater.client test account', master_payer_id=1, account_type_id=1,environment_type_id=1,environment_type_name='non-production', cloud_account_root_email=f"{account_name}@pearson.com",governance_lob_name="lob a", lob_id=1,budget_account_string="foo$1",decommissioned=0,provisioner="cinch",project_number="apifoo",snow_approval_group="slater snow",budget_alert_monthly_threshold=500, cloud_organization_id=1, cloud_organization_name="Assessments",next_link=f"https://localhost/accounts/{account_number}",request=request)
        >>> search = test_DeleteAccount.GetAccountByID(account_number)
        >>> assert(search.get('cloud_account_name') == account_name), f"expected name to equal {account_name}, got {search.get('cloud_account_name')}"
        >>> deleted_account = test_DeleteAccount.DeleteAccount(cloud_account_id=account.get('cloud_account_id'),request=request)
        >>> assert(account.get('cloud_account_id') == deleted_account.get('cloud_account_id')), f"expected {account.get('cloud_account_id')} to equal {deleted_account.get('cloud_account_id')}"
        >>> assert(deleted_account.get('next_link') is None), f"expected {deleted_account.get('next_link')} to equal None"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Delete(api=api,
                           object_type='Account',
                           id=cloud_account_id,
                           request=request)

    @xray.capture()
    @logger.log
    def DeleteAccountType(self,
                          api: str = 'accounttypes_api',
                          account_type_id: str = '',
                          request: dict = None,
                          **kwargs) -> AccountType:
        """Delete a account_type object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_DeleteAccountType = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_ADMIN)

        Validate account delete
        >>> import random, string
        >>> account_type_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> account_type = test_DeleteAccountType.CreateAccountType(account_type_name=account_type_name,request=request)
        >>> search = test_DeleteAccountType.GetAccountTypeByID(account_type.get('account_type_id'))
        >>> assert(search.get('account_type_name') == account_type_name), f"expected name to equal {account_type_name}, got {search.get('account_type_name')}"
        >>> deleted_account_type = test_DeleteAccountType.DeleteAccountType(account_type_id=account_type.get('account_type_id'),request=request)
        >>> assert(account_type.get('account_type_id') == deleted_account_type.get('account_type_id')), f"expected {account_type.get('account_type_id')} to equal {deleted_account.get('cloud_account_id')}"
        >>> assert(deleted_account_type.get('next_link') is None), f"expected {deleted_account_type.get('next_link')} to equal None"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Delete(api=api,
                           object_type='AccountType',
                           id=account_type_id,
                           request=request)

    @xray.capture()
    @logger.log
    def DeleteApplication(self,
                          api: str = 'applications_api',
                          application_id: str = '',
                          request: dict = None,
                          **kwargs) -> Application:
        """Delete a application object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_DeleteApplication = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_ADMIN)

        Validate application delete
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> application_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> t_appid: str = ''.join(random.choice(string.digits) for i in range(4))

        >>> application = test_DeleteApplication.CreateApplication(application_name=application_name, t_appid=f'SVC{t_appid}', application_description='Ride a Ostrich', lob='foo', create_date='1970-01-01', update_date='1970-01-01', budget='budget$1',request=request)

        >>> assert(application.get('application_name') == application_name), f"expected name to equal '{application_name}', got {application.get('application_name')}"

        >>> deleted_application = test_DeleteApplication.DeleteApplication(application_id=application.get('application_id'),request=request)

        >>> deleted_application is not None
        True

        >>> assert(application.get('application_id') == deleted_application.get('application_id')), f"expected {application.get('application_id')} to equal {deleted_application.get('application_id')}"

        >>> assert(deleted_application.get('next_link') is None), f"expected {deleted_application.get('next_link')} to equal None"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Delete(api=api,
                           object_type='Application',
                           id=application_id,
                           request=request)

    @xray.capture()
    @logger.log
    def DeleteApplicationContact(self,
                                 api: str = 'applicationcontacts_api',
                                 application_contact_id: str = '',
                                 request: dict = None,
                                 **kwargs) -> ApplicationContact:
        """Delete a application contact object from SLater
        >>> skip('need to sort patch to avoid foreign keys')
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_DeleteApplicationContact = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_ADMIN)

        Validate application contact delete
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> contact_type_id: str = ''.join(random.choice(string.digits) for i in range(12))
        >>> application_id: str = ''.join(random.choice(string.digits) for i in range(12))
        >>> contact_id: str = ''.join(random.choice(string.digits) for i in range(12))

        >>> application_contact = test_DeleteApplicationContact.CreateApplicationContact(contact_type_id=contact_type_id, application_id=application_id, contact_id=contact_id,request=request)

        >>> assert(application_contact.get('contact_type_id') == contact_type_id), f"expected name to equal '{contact_type_id}', got {application_contact.get('contact_type_id')}"

        >>> deleted_application_contact = test_DeleteApplicationContact.DeleteApplication(contact_type_id=application_contact.get('contact_type_id'),request=request)

        >>> deleted_application_contact is not None
        True

        >>> assert(application_contact.get('contact_type_id') == deleted_application_contact.get('contact_type_id')), f"expected {application_contact.get('contact_type_id')} to equal {deleted_application_contact.get('contact_type_id')}"

        >>> assert(deleted_application_contact.get('next_link') is None), f"expected {deleted_application_contact.get('next_link')} to equal None"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Delete(api=api,
                           object_type='ApplicationContact',
                           id=application_contact_id,
                           request=request)

    @xray.capture()
    @logger.log
    def DeleteBusinessUnit(self,
                           api: str = 'businessunits_api',
                           business_unit_id: str = '',
                           request=None,
                           **kwargs) -> BusinessUnit:
        """Delete a business unit object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_DeleteBusinessUnit = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_ADMIN)

        Validate business unit delete
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> business_unit_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> business_unit = test_DeleteBusinessUnit.CreateBusinessUnit(business_unit_name=business_unit_name,request=request)

        >>> assert(business_unit.get('business_unit_name') == business_unit_name), f"expected name to equal '{business_unit_name}', got {business_unit.get('business_unit_name')}"

        >>> deleted_business_unit = test_DeleteBusinessUnit.DeleteBusinessUnit(business_unit_id=business_unit.get('business_unit_id'),request=request)

        >>> deleted_business_unit is not None
        True

        >>> assert(business_unit.get('business_unit_id') == deleted_business_unit.get('business_unit_id')), f"expected {business_unit.get('business_unit_id')} to equal {deleted_business_unit.get('business_unit_id')}"

        >>> assert(deleted_business_unit.get('next_link') is None), f"expected {deleted_business_unit.get('next_link')} to equal None"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Delete(api=api,
                           object_type='BusinessUnit',
                           id=business_unit_id,
                           request=request)

    @xray.capture()
    @logger.log
    def DeleteContact(self,
                      api: str = 'contacts_api',
                      id: str = '',
                      contact_id: str = '',
                      request: dict = None,
                      **kwargs) -> Contact:
        """Delete a contact object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_DeleteContact = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_ADMIN)

        Validate contact delete
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import string, random

        >>> contact_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> contact_email: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))}.{''.join(random.choice(string.ascii_letters) for i in range(10))}@pearson.com"


        >>> contact = test_DeleteContact.CreateContact(contact_name=contact_name,contact_email=contact_email,request=request)

        >>> contact.get('contact_name') == contact_name
        True

        >>> deleted_contact = test_DeleteContact.DeleteContact(contact_id=contact.get('contact_id'),request=request)

        >>> contact.get('contact_id') == deleted_contact.get('contact_id')
        True

        >>> deleted_contact.get('next_link') is None
        True

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        if id:
            contact_id = id

        return self.Delete(api=api,
                           object_type='Contact',
                           id=contact_id,
                           request=request)

    @xray.capture()
    @logger.log
    def DeleteContactType(self,
                          api: str = "contact_types_api",
                          contact_type_id: str = "",
                          request=None,
                          **kwargs) -> ContactType:
        """Delete a contact type object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_DeleteContactType = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_ADMIN)

        Validate contact type delete
        >>> import random, string

        >>> contact_type_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> contact_type = test_DeleteContactType.CreateContactType(contact_type=contact_type_name,request=request)

        >>> contact_type.get('contact_type') == contact_type_name
        True

        >>> deleted_contact_type = test_DeleteContactType.DeleteContactType(contact_type_id=contact_type.get('contact_type_id'),request=request)

        >>> contact_type.get('contact_type_id') == deleted_contact_type.get('contact_type_id')
        True

        >>> assert(deleted_contact_type.get('next_link') is None), f"expected {deleted_contact_type.get('next_link')} to equal None"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Delete(api=api,
                           object_type='ContactType',
                           id=contact_type_id,
                           request=request)

    @xray.capture()
    @logger.log
    def DeleteLOB(self,
                  api: str = "lobs_api",
                  lob_id: str = '',
                  request=None,
                  **kwargs) -> LOB:
        """Delete a lob object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_DeleteLOB = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_ADMIN)

        Validate lob delete
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> lob_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> lob = test_DeleteLOB.CreateLOB(governance_lob_name=lob_name,request=request)

        >>> lob.get('governance_lob_name') == lob_name
        True

        >>> deleted_lob = test_DeleteLOB.DeleteLOB(lob_id=lob.get('lob_id'),request=request)

        >>> deleted_lob is not None
        True

        >>> lob.get('lob_id') == deleted_lob.get('lob_id')
        True

        >>> assert(deleted_lob.get('next_link') is None), f"expected {deleted_lob.get('next_link')} to equal None"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Delete(api=api,
                           object_type='LOB',
                           id=lob_id,
                           request=request)

    @xray.capture()
    @logger.log
    def DeleteMasterPayer(self,
                          api: str = "masterpayers_api",
                          master_payer_id: str = '',
                          request=None,
                          **kwargs) -> MasterPayer:
        """Delete a master_payer object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_DeleteMasterPayer = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_ADMIN)

        Validate master_payer delete
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> master_payer_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> master_payer_ident: str = ''.join(random.choice(string.digits) for i in range(12))

        >>> master_payer = test_DeleteMasterPayer.CreateMasterPayer(master_payer_name=master_payer_name, master_payer_ident=master_payer_ident,request=request)

        >>> assert(master_payer.get('master_payer_name') == master_payer_name), f"expected name to equal '{master_payer_name}', got {master_payer.get('master_payer_name')}"

        >>> deleted_master_payer = test_DeleteMasterPayer.DeleteMasterPayer(master_payer_id=master_payer.get('master_payer_id'),request=request)

        >>> deleted_master_payer is not None
        True

        >>> assert(master_payer.get('master_payer_id') == deleted_master_payer.get('master_payer_id')), f"expected {master_payer.get('master_payer_id')} to equal {deleted_master_payer.get('master_payer_id')}"

        >>> assert(deleted_master_payer.get('next_link') is None), f"expected {deleted_master_payer.get('next_link')} to equal None"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Delete(api=api,
                           object_type='MasterPayer',
                           id=master_payer_id,
                           request=request)

    @xray.capture()
    @logger.log
    def DeleteTower(self,
                    api: str = "towers_api",
                    tower_id: str = '',
                    request=None,
                    **kwargs) -> Tower:
        """Delete a tower object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_DeleteTower = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_ADMIN)

        Validate tower delete
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> tower_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> tower = test_DeleteTower.CreateTower(tower_name=tower_name, contact_id='1',request=request)

        >>> assert(tower.get('tower_name') == tower_name), f"expected name to equal '{tower_name}', got {tower.get('tower_name')}"

        >>> deleted_tower = test_DeleteTower.DeleteTower(tower=tower.get('tower_id'),request=request)
        Traceback (most recent call last):
        ...
        slater.exceptions.SLaterClientMissingRequiredParameters: SLater Object ID required for delete Tower operation

        >>> deleted_tower = test_DeleteTower.DeleteTower(tower_id=tower.get('tower_id'),request=request)

        >>> deleted_tower is not None
        True

        >>> assert(tower.get('tower_id') == deleted_tower.get('tower_id')), f"expected {tower.get('tower_id')} to equal {deleted_tower.get('tower_id')}"

        >>> assert(deleted_tower.get('next_link') is None), f"expected {deleted_tower.get('next_link')} to equal None"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Delete(api=api,
                           object_type='Tower',
                           id=tower_id,
                           request=request)

    @xray.capture()
    @logger.log
    def Get(self,
            api: str = '',
            object_type: str = 'IOBotObject',
            list_type: str = 'IOBotList',
            field_name: str = '',
            search_input: str = '',
            fuzzy: bool = False,
            limit: int = 10,
            next_token: int = -1,
            db_version: str = '',
            request: dict = None,
            **kwargs) -> IOBotList:
        """
        Use this API to search SLater. By default you need to specify
        an api that it should target as that will determine the object type
        returned.

        You can get a pageable list without any additional arguments.

        Should you wish to refine your results, specify any of the
        additional parameters.

        All parameters passed to SLater will be automatically URL Encoded.

        Parameters
        ----------

        api: str (required)
          Required SLater api endpoint to read data from.
          Reference the SLater section of the iobot.ini config.
          Only mapped values will be accepted.

        object_type: str (required)
          The object type that should be returned

        list_type: str (required)
          The list object type that should be returned

        field_name: str
          Optional name of field to search slater object by. Used by both
          literal_match and loose_match.

        search_input: str
          Optional exact value you want to search for. You must also
          provide the 'field_name' parameter.

        fuzzy: bool
          Note: Defaults to False.

          Optional parameter to indicate that 'search_input' is a partial
          value match you want to search for.
          It turns on SLater's use_like_comparator option for fuzzy searching.
          You must also provide the 'field_name' parameter.

        limit: str
          Maximum number of records to return. By default SLater returns
          lists of 20. This implementation limits to 10 to leave real-estate
          for slack which limits message blocks to 50 and modal blocks to 100.

        next_token: str | none
          Pagination token used in conjunction with 'limit'. It will be return
          as None if no more pages are available.

        db_version: str
          Alternative version of SLater DB Schema. If you set to 'swa', SLater
          will use the DB schema 'slater_swa' instead of its default 'slater'.
          Use this for fixed, or staged testing data sets.

        request: dict
          Slack Request User interacting with SLater (for auditing). It must
          contain both an ID and a submit timestamp.

          `{'submitter':{'name':'john.torres'},'submitted':1595314865}`

        **kwargs:
          K/V parameters (reserved, not currently used)

        Returns
        -------

        IOBotList[IOBotObject]:
          A list of objects populated from SLater based on the provided
          parameters.

        Throws
        ------
        SLaterDeprecatedFeatureException:
          Limit = -1 is no longer supported to return all records

        SLaterClientInvalidRequestParameters:
          When required parameters are not supplied

        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_ReadObjects = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Raise an exception without an api argument
        >>> slater_object = test_ReadObjects.ReadObjects()
        Traceback (most recent call last):
        ...
        slater.exceptions.SLaterClientMissingRequiredParameters: 'api' requires a parameter

        Validate read data without arguments
        >>> slater_objects = test_ReadObjects.ReadObjects(api='applications_api')

        >>> assert (len(slater_objects) == 10), f"expected 10 applications, got {len(slater_objects)}"
        >>> assert (slater_objects.next_token == 11), f"expected next_token to equal elev, got {slater_objects.next_token}"
        >>> del slater_objects

        Validate we fail when setting limit=-1
        >>> applications = test_ReadObjects.ReadObjects(api='applications_api', limit=-1)
        Traceback (most recent call last):
        ...
        slater.exceptions.SLaterDeprecatedFeatureException: limit=-1 has been deprecated, only signed values permitted

        Validate get applications with a limit argument of one should give us pages to navigate
        >>> first_page = test_ReadObjects.ReadObjects(api='applications_api', limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 applications, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal two, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_ReadObjects.ReadObjects(api='applications_api', limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal four, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two applications, got {len(next_page)}"

        Step foward again
        >>> next_next_page = test_ReadObjects.ReadObjects(api='applications_api', limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal six, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two applications, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_ReadObjects.ReadObjects(api='applications_api', limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two applications, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal four, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        role_entitlement: Enum = SLaterRoleEntitlementLevel.READ_ONLY

        if not self.IsAuthorized(required_role_level=role_entitlement):
            self._log.warning(
                f'user is not authorized to make this call requires: {role_entitlement.name}, user has role: {self.GetUserRole().name}')
            raise SLaterUnAuthorizedSlackUserGroupError(self.GetUnauthorizedMessage(role_entitlement))

        if not api:
            raise SLaterClientMissingRequiredParameters(
                "'api' requires a parameter")

        if int(limit) < 0:
            raise SLaterDeprecatedFeatureException(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if field_name:
            if not search_input:
                raise SLaterClientMissingRequiredParameters(
                    "search_input must also be specified when using 'field_name'")

        if search_input:
            if not field_name:
                raise SLaterClientMissingRequiredParameters(
                    "field_name must also be specified when using 'search_input'")

            params[field_name] = search_input

        if fuzzy:
            if not field_name:
                raise SLaterClientMissingRequiredParameters(
                    "field_name must also be specified when using 'fuzzy'")
            if not search_input:
                raise SLaterClientMissingRequiredParameters(
                    "search_input must also be specified when using 'fuzzy'")

            params['use_like_comparator'] = 'true'

        url = self.config['SLater'][api]
        if params:
            url = f'{url}?{parser.urlencode(params)}'

        self._log.debug(f"url: {url}")

        headers: Dict[str, str] = {
            "Content-Type": "application/x-www-form-urlencoded",
            "Authorization": f"Bearer {self._creds['access_token']}",
            "db_version": self.db_version
        }

        if not request:
            request = self.GetSLaterRequest()

        self._log.debug(f"headers {headers}")
        response: Dict = self.client.get(url, headers=headers)

        if response.status_code != 200:
            self._log.error(
                f'Status: {response.status_code}, Headers: {response.headers}, Error Response: {response.content}')
            return tuple()

        data = response.json()

        self._log.debug(
            f'settings: locals (next: {next_token}, limit: {limit}), incoming (next: {data["paginationcontrol"]["next_token"]}, limit: {data["paginationcontrol"]["limit"]})')

        slater_list = globals()[list_type]
        slater_object = globals()[object_type]

        self._log.debug(f'data is {data}')
        if not data:
            objects = slater_list([],
                                  limit=-1,
                                  next_token=-1)
        else:
            next_token = data["paginationcontrol"]["next_token"]
            if not next_token:
                next_token = -1

            objects = slater_list((
                slater_object(**record) for record in data['results']),
                limit=data["paginationcontrol"]["limit"],
                next_token=next_token)

        objects.initialize_clients(
            self._creds, self._log, self._secrets, self)

        return objects

    @xray.capture()
    @logger.log
    def GetAccounts(self, id: str = "", name: str = "", cloud_account_number: str = "", cloud_account_id: int = 0, cloud_account_name: str = "", application_name: str = "", t_appid: str = "", application_id: str = "", root_email: str = "", master_payer_ident: str = "", filter_prefix: str = "", filter_field_name: str = "cloud_account_name", limit: int = 10, next_token: int = -1, api: str = "accounts_api") -> Accounts:
        """Get Accounts list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetAccounts = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get accounts without arguments
        Normally this should return 10 per page because our default is limit: int=10, However we only have 7
        >>> accounts = test_GetAccounts.GetAccounts()
        >>> assert (len(accounts) >= 7), f"expected at least seven accounts, got {len(accounts)}"
        >>> assert (accounts.next_token >= 7), f"expected next_token to equal at least seven, got {accounts.next_token}"
        >>> del accounts

        Validate get accounts with explicit all records limit of -1
        >>> accounts = test_GetAccounts.GetAccounts(limit=-1)
        Traceback (most recent call last):
        ...
        Exception: limit=-1 has been deprecated, only signed values permitted

        Validate get accounts with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetAccounts.GetAccounts(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 accounts, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal two, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetAccounts.GetAccounts(limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal four, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two accounts, got {len(next_page)}"

        Step foward again
        >>> next_next_page = test_GetAccounts.GetAccounts(limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal six, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two accounts, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetAccounts.GetAccounts(limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two accounts, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal four, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        if int(limit) < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if root_email:
            params['field_name'] = 'cloud_account_root_email'
            params['search_input'] = root_email

        # TODO: Deprecate this call....
        if id:
            params['field_name'] = 'cloud_account_number'
            params['search_input'] = id

        if cloud_account_id:
            params['field_name'] = 'cloud_account_id'
            params['search_input'] = cloud_account_id

        if cloud_account_number:
            params['field_name'] = 'cloud_account_number'
            params['search_input'] = cloud_account_number

        if master_payer_ident:
            params['field_name'] = 'master_payer_ident'
            params['search_input'] = master_payer_ident

        if name:
            params['field_name'] = 'cloud_account_name'
            params['search_input'] = name

        if cloud_account_name:
            params['field_name'] = 'cloud_account_name'
            params['search_input'] = cloud_account_name

        if application_name:
            params['field_name'] = 'application_name'
            params['search_input'] = application_name

        if application_id:
            params['field_name'] = 'application_id'
            params['search_input'] = application_id

        if t_appid:
            params['field_name'] = 't_appid'
            params['search_input'] = t_appid

        if filter_prefix:
            params['fuzzy'] = True
            params['field_name'] = filter_field_name
            params['search_input'] = filter_prefix

        accounts = self.ReadObjects(api=api,
                                    object_type='Account',
                                    list_type='Accounts',
                                    **params)
        return accounts

    @xray.capture()
    @logger.log
    def GetAccountByID(self, id: str) -> Account:
        """Get Account Details from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetAccountByID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved account
        >>> atari_name = test_GetAccountByID.GetAccountByName("atari")
        >>> atari = test_GetAccountByID.GetAccountByID(atari_name.get('cloud_account_number'))
        >>> assert (atari.get('cloud_account_name').title() == "Atari"), f"expected 'Atari', got {atari.get('cloud_account_name').title()}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        account: Account = None

        accounts = self.GetAccounts(id=id, limit=1)
        self._log.debug(f'raw accounts: {accounts}')
        if len(accounts) == 1:
            account = accounts[0]
            self._log.debug(f'fetched account: {account}')
            account.initialize_clients(
                self._creds, self._log, self._secrets, self)

        return account

    @xray.capture()
    @logger.log
    def GetAccountByCloudAccountID(self, cloud_account_id: int) -> Account:
        """Get Account Details from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetAccountByCloudAccountID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved account
        >>> atari_name = test_GetAccountByCloudAccountID.GetAccountByName("atari")
        >>> atari = test_GetAccountByCloudAccountID.GetAccountByCloudAccountID(atari_name.get('cloud_account_id'))
        >>> assert (atari.get('cloud_account_name').title() == "Atari"), f"expected 'Atari', got {atari.get('cloud_account_name').title()}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        account: Account = None

        accounts = self.GetAccounts(cloud_account_id=cloud_account_id)
        self._log.debug(f'raw accounts: {accounts}')
        if len(accounts) == 1:
            account = accounts[0]
            self._log.debug(f'fetched account: {account}')
            account.initialize_clients(
                self._creds, self._log, self._secrets, self)

        return account

    @xray.capture()
    @logger.log
    def GetAccountByCloudAccountNumber(self, cloud_account_number: str) -> Account:
        """Get Account Details from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetAccountByID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved account
        >>> atari_name = test_GetAccountByID.GetAccountByName("atari")
        >>> atari = test_GetAccountByID.GetAccountByCloudAccountNumber(atari_name.get("cloud_account_number"))
        >>> assert (atari.get('cloud_account_name').title() == "Atari"), f"expected 'Atari', got {atari.get('cloud_account_name').title()}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        account: Account = None

        accounts = self.GetAccounts(cloud_account_number=cloud_account_number)
        self._log.debug(f'raw accounts: {accounts}')
        if len(accounts) == 1:
            account = accounts[0]
            self._log.debug(f'fetched account: {account}')
            account.initialize_clients(
                self._creds, self._log, self._secrets, self)

        return account

    @xray.capture()
    @logger.log
    def GetAccountsByIDPrefix(self, id_prefix: str, limit: int = 100) -> Accounts:
        """Get Account Details from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetAccountsByIDPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved account
        >>> accounts = test_GetAccountsByIDPrefix.GetAccountsByIDPrefix("042")
        >>> assert(len(accounts) >= 1), f'expected greater or equal to one, got {len(accounts)}'
        >>> atari = accounts[0]
        >>> assert (atari.get('cloud_account_name').title() == "Atari"), f"expected 'Atari', got {atari.get('cloud_account_name').title()}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        accounts = self.GetAccounts(
            filter_field_name='cloud_account_number', filter_prefix=id_prefix, limit=limit)
        self._log.debug(f'raw accounts: {accounts}')
        return accounts

    @xray.capture()
    @logger.log
    def GetAccountByName(self, name: str) -> Account:
        """Get Account Details from SLater by Account Name
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetAccountByName = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved account by uppercase
        >>> atari = test_GetAccountByName.GetAccountByName("Atari")
        >>> assert (atari.get('cloud_account_name').title() == "Atari"), f"expected 'Atari', got {atari.get('cloud_account_name').title()}"
        >>> del atari

        Valiate retrieved account by lowercase
        >>> atari = test_GetAccountByName.GetAccountByName("atari")
        >>> assert (atari.get('cloud_account_name').title() == "Atari"), f"expected 'Atari', got {atari.get('cloud_account_name').title()}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        account: Account = None

        accounts = self.GetAccounts(name=name)
        self._log.debug(f'raw accounts: {accounts}')
        if len(accounts) == 1:
            account = accounts[0]
            self._log.debug(f'fetched account: {account}')
            account.initialize_clients(
                self._creds, self._log, self._secrets, self)
        else:
            self._log.debug(f'too many records: {len(accounts)}')

        return account

    @xray.capture()
    @logger.log
    def GetAccountsByApplicationName(self, name: str) -> Accounts:
        """Get Account Details from SLater by Application Name
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetAccountsByApplicationName = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved accounts
        >>> accounts = test_GetAccountsByApplicationName.GetAccountsByApplicationName("Choplifter")
        >>> assert (len(accounts) >= 1), f"expected two, got {len(accounts)}"
        >>> matches = list(acct for acct in accounts if acct['cloud_account_name'].title() == 'Atari')
        >>> assert (len(matches) == 1), f"expected to find one match, found: {len(matches)}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        accounts = self.GetAccounts(
            application_name=name, api='accountapplications_api')
        self._log.debug(f'raw accounts: {accounts}')
        return accounts

    @xray.capture()
    @logger.log
    def GetAccountsByTAppID(self, t_appid: str) -> Accounts:
        """Get Account Details from SLater by Application Name
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetAccountsByTAppID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved accounts
        >>> accounts = test_GetAccountsByTAppID.GetAccountsByTAppID("SVCD005")

        >>> assert (len(accounts) == 1), f"expected two, got {len(accounts)}"

        >>> atari = accounts[0]

        >>> assert (atari.get('cloud_account_name').title() == "Atari"), f"expected 'Atari', got {atari.get('cloud_account_name').title()}"

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        accounts = self.GetAccounts(
            t_appid=t_appid, api='accountapplications_api')
        self._log.debug(f'raw accounts: {accounts}')
        return accounts

    @xray.capture()
    @logger.log
    def GetAccountsByRootEmail(self, email: str, limit: int = 10) -> Accounts:
        """Get Account Details from SLater by Contact Email

        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetAccountsByRootEmail = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved accounts
        >>> accounts = test_GetAccountsByRootEmail.GetAccountsByRootEmail("atari@root.net")
        >>> assert (len(accounts) == 1), f"expected 1, got {len(accounts)}"
        >>> atari = accounts[0]
        >>> assert (atari.get('cloud_account_name').title() == "Atari"), f"expected 'Atari', got {atari.get('cloud_account_name').title()}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        accounts = self.GetAccounts(root_email=email, limit=limit)
        self._log.debug(f'raw accounts: {accounts}')
        return accounts

    @xray.capture()
    @logger.log
    def GetAccountsByRootEmailPrefix(self, email_prefix: str, limit: int = 100) -> Accounts:
        """Get Account Details from SLater by Contact Email Prefix

        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetAccountsByRootEmailPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved accounts
        >>> accounts = test_GetAccountsByRootEmailPrefix.GetAccountsByRootEmailPrefix("atari@")
        >>> len(accounts) >= 1
        True

        >>> matches = list(acct for acct in accounts if acct['cloud_account_name'].title() == 'Atari')
        >>> len(matches) >= 2
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        accounts = self.GetAccounts(filter_field_name='cloud_account_root_email',
                                    filter_prefix=email_prefix, limit=limit, api="accountcontacts_api")
        self._log.debug(f'raw accounts: {accounts}')
        return accounts

    @xray.capture()
    @logger.log
    def GetAccountTypes(self, id: str = "", name: str = "", account_type_name: str = "", account_type_id: str = "", filter_prefix: str = "", filter_field_name: str = "account_type_name", limit: int = 10, next_token: int = -1, api: str = "accounttypes_api") -> AccountTypes:
        """Get AccountTypes list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetAccountTypes = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get account_types without arguments
        >>> account_types = test_GetAccountTypes.GetAccountTypes(limit=1)
        >>> len(account_types) == 1
        True


        >>> assert (account_types.next_token == 2), f"expected next_token to equal ten, got {account_types.next_token}"
        >>> account_types[0].get('account_type_name').title()
        '?'

        >>> del account_types

        Validate we fail when setting limit=-1
        >>> account_types = test_GetAccountTypes.GetAccountTypes(limit=-1)
        Traceback (most recent call last):
        ...
        Exception: limit=-1 has been deprecated, only signed values permitted

        Validate get business_units with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetAccountTypes.GetAccountTypes(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 record, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal two, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetAccountTypes.GetAccountTypes(limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal four, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two records, got {len(next_page)}"

        Step foward again
        >>> next_next_page = test_GetAccountTypes.GetAccountTypes(limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal six, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two records, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetAccountTypes.GetAccountTypes(limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two account_types, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal two, got {prev_page.next_token}, {current_next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        if int(limit) < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if name:
            params['field_name'] = 'account_type_name'
            params['search_input'] = name

        if account_type_name:
            params['field_name'] = 'account_type_name'
            params['search_input'] = account_type_name

        if id:
            params['field_name'] = 'account_type_id'
            params['search_input'] = id

        if account_type_id:
            params['field_name'] = 'account_type_id'
            params['search_input'] = account_type_id

        if filter_prefix:
            params['fuzzy'] = True
            params['field_name'] = filter_field_name
            params['search_input'] = filter_prefix

        account_types = self.ReadObjects(api=api,
                                         object_type='AccountType',
                                         list_type='AccountTypes',
                                         **params)
        return account_types

    @xray.capture()
    @logger.log
    def GetAccountTypeByID(self, id: str) -> AccountType:
        """Get Account Type Detail from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns('iobots-stack')

        >>> test_GetAccountTypeByID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved business unit
        >>> account_type = test_GetAccountTypeByID.GetAccountTypeByID("1")

        >>> account_type.get('account_type_name').title() is not None
        True

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        account_types = self.GetAccountTypes(id=id, limit=1)

        self._log.debug(f'single record matches: {len(account_types)}')
        account_type = account_types[0]
        self._log.debug(f'fetched account type: {account_type}')
        account_type.initialize_clients(
            self._creds, self._log, self._secrets, self)

        return account_type

    @xray.capture()
    @logger.log
    def GetAccountTypesByIDPrefix(self, id_prefix: str, limit: int = 100) -> AccountTypes:
        """Get AccountTypes from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetAccountTypesByIDPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved lob
        >>> account_types = test_GetAccountTypesByIDPrefix.GetAccountTypesByIDPrefix("1", limit=2)

        >>> len(account_types) >= 1
        True

        >>> account_types[0].get('account_type_name') is not None
        True

        >>> # matches = list(account_types for account_type in account_types if account_type['account_type_name'] == 'Non-Production')

        >>> # len(matches)
        >>> # 1

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        account_types = self.GetAccountTypes(
            filter_field_name='account_type_id',
            filter_prefix=id_prefix,
            limit=limit)
        self._log.debug(f'raw account types: {len(account_types)}')
        return account_types

    @xray.capture()
    @logger.log
    def GetAccountTypeByName(self, name: str) -> AccountType:
        """Get AccountTypes Detail from SLater by AccountType Name
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns('iobots-stack')

        >>> test_GetAccountTypeByName = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Get the first instance we find and search by that record
        >>> search = test_GetAccountTypeByName.GetAccountTypes(limit=1)[0]

        Validate retrieved Business Unit
        >>> account_type = test_GetAccountTypeByName.GetAccountTypeByName(search.get("account_type_name"))

        >>> account_type.get('account_type_name').title() == search.get('account_type_name').title()
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        account_type: AccountType = None

        account_types = self.GetAccountTypes(name=name, limit=1)
        if len(account_types) == 1:
            self._log.debug(f'single record matches: {len(account_types)}')
            account_type = account_types[0]
            self._log.debug(f'fetched account type: {account_type}')
            account_type._log = self._log
            account_type._creds = self._creds
            account_type._secrets = self._secrets
            account_type.client = self.client
        else:
            self._log.debug(f'too many records: {len(account_types)}')

        return account_type

    @xray.capture()
    @logger.log
    def GetAccountTypesByNamePrefix(self, name_prefix: str, limit: int = 100) -> AccountTypes:
        """Get AccountTypes from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetAccountTypesByIDPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved account type
        >>> search = test_GetAccountTypesByIDPrefix.GetAccountTypes(limit=1)[0]
        >>> account_types = test_GetAccountTypesByIDPrefix.GetAccountTypesByNamePrefix(search.get('account_type_name')[0:1])

        >>> len(account_types) >= 1
        True

        >>> matches = list(account_types for account_type in account_types if account_type['account_type_name'] == search.get('account_type_name'))

        >>> len(matches)
        1

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        account_types = self.GetAccountTypes(
            filter_field_name='account_type_name',
            filter_prefix=name_prefix,
            limit=limit)
        self._log.debug(f'raw account types: {len(account_types)}')
        return account_types

    @xray.capture()
    @logger.log
    def GetApplications(self, id: str = "", name: str = "", application_name: str = "", t_appid: str = "", account_name: str = "", cloud_account_number: str = "", contact_email: str = "", filter_prefix: str = "", filter_field_name: str = "application_name", limit: int = 10, next_token: int = -1, api: str = "applications_api") -> Applications:
        """Get Applications list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetApplications = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get applications without arguments
        >>> applications = test_GetApplications.GetApplications()
        >>> assert (len(applications) == 10), f"expected 10 applications, got {len(applications)}"
        >>> assert (applications.next_token == 11), f"expected next_token to equal elev, got {applications.next_token}"
        >>> del applications

        Validate we fail when setting limit=-1
        >>> applications = test_GetApplications.GetApplications(limit=-1)
        Traceback (most recent call last):
        ...
        slater.exceptions.SLaterDeprecatedFeatureException: limit=-1 has been deprecated, only signed values permitted

        Validate get applications with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetApplications.GetApplications(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 applications, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal two, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetApplications.GetApplications(limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal four, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two applications, got {len(next_page)}"

        Step foward again
        >>> next_next_page = test_GetApplications.GetApplications(limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal six, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two applications, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetApplications.GetApplications(limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two applications, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal four, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if name:
            params['field_name'] = 'application_name'
            params['search_input'] = name

        if application_name:
            params['field_name'] = 'application_name'
            params['search_input'] = application_name

        if account_name:
            params['field_name'] = 'cloud_account_name'
            params['search_input'] = account_name

        if cloud_account_number:
            params['field_name'] = 'cloud_account_number'
            params['search_input'] = cloud_account_number

        if contact_email:
            params['field_name'] = 'contact_email'
            params['search_input'] = contact_email

        if id:
            params['field_name'] = 'application_id'
            params['search_input'] = id

        if t_appid:
            params['field_name'] = 't_appid'
            params['search_input'] = t_appid

        if filter_prefix:
            params['fuzzy'] = True
            params['field_name'] = filter_field_name
            params['search_input'] = filter_prefix

        applications = self.ReadObjects(api=api,
                                        object_type='Application',
                                        list_type='Applications',
                                        **params)
        return applications

    @xray.capture()
    @logger.log
    def GetApplicationByID(self, id: str) -> Application:
        """Get Application Detail from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns('iobots-stack')
        >>> test_GetApplicationByID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved application
        >>> application = test_GetApplicationByID.GetApplicationByID("1")

        >>> application.get('application_name').title() is not None
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        applications = self.GetApplications(id=id, limit=1)

        self._log.debug(f'single record matches: {len(applications)}')
        application = applications[0]
        self._log.debug(f'fetched application: {application}')
        application.initialize_clients(
            self._creds, self._log, self._secrets, self)

        return application

    @xray.capture()
    @logger.log
    def GetApplicationsByIDPrefix(self, id_prefix: str, limit: int = 100) -> Applications:
        """Get Applications from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetApplicationsByIDPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved account
        >>> applications = test_GetApplicationsByIDPrefix.GetApplicationsByIDPrefix("12")

        >>> len(applications) >= 1
        True

        >>> matches = list(app for app in applications if app['application_name'] == 'Asteroids')

        >>> len(matches)
        1

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        applications = self.GetApplications(
            filter_field_name='application_id', filter_prefix=id_prefix, limit=limit)
        self._log.debug(f'raw applications: {len(applications)}')
        return applications

    @xray.capture()
    @logger.log
    def GetApplicationByName(self, name: str) -> Application:
        """Get Application Detail from SLater by Application Name
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns('iobots-stack')
        >>> test_GetApplicationByName = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved application
        >>> duck_hunt = test_GetApplicationByName.GetApplicationByName("Choplifter")

        >>> duck_hunt.get('application_name').title() is not None
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        application: Application = None

        applications = self.GetApplications(name=name, limit=1)
        if len(applications) == 1:
            self._log.debug(f'single record matches: {len(applications)}')
            application = applications[0]
            self._log.debug(f'fetched application: {application}')
        else:
            self._log.debug(f'too many records: {len(applications)}')

        return application

    @xray.capture()
    @logger.log
    def GetApplicationsByAccountName(self, name: str) -> Applications:
        """Get Applications Details from SLater by Account Name
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetApplicationsByAccountName = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved applications
        >>> applications = test_GetApplicationsByAccountName.GetApplicationsByAccountName("atari")

        >>> len(applications) >= 1
        True

        >>> matches = list(app for app in applications if app['application_name'] == 'Choplifter')

        >>> assert (len(matches) == 1), f"expected to find one match, found: {len(matches)}"

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """

        applications = self.GetApplications(
            account_name=name, api='accountapplications_api')
        self._log.debug(f'raw applications: {applications}')
        return applications

    @xray.capture()
    @logger.log
    def GetApplicationsByCloudAccountNumber(self, account_number: str) -> Applications:
        """Get Applications Details from SLater by Account Name
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetApplicationsByCloudAccountNumber = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved applications
        >>> applications = test_GetApplicationsByCloudAccountNumber.GetApplicationsByCloudAccountNumber("0723456789")
        >>> assert (len(applications) == 9), f"expected six, got {len(applications)}"
        >>> matches = list(app for app in applications if app['application_name'] == 'Choplifter')
        >>> assert (len(matches) == 1), f"expected to find one match, found: {len(matches)}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        applications = self.GetApplications(
            cloud_account_number=account_number, api='accountapplications_api')
        self._log.debug(f'raw applications: {applications}')
        return applications

    @xray.capture()
    @logger.log
    def GetApplicationsByContactEmail(self, email: str) -> Applications:
        """Get Applications Details from SLater by Contact Email

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetApplicationsByContactEmail = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> results = test_GetApplicationsByContactEmail.GetApplications(limit=1,api='applicationcontacts_api')

        >>> search = list(result for result in results if result.get('contact_email'))

        >>> len(search) == 1
        True

        Validate get applications
        >>> applications = test_GetApplicationsByContactEmail.GetApplicationsByContactEmail(search[0].get('contact_email'))

        >>> len(applications) >= 1
        True

        >>> applications[0].get('contact_email') == search[0].get('contact_email')
        True

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """

        applications = self.GetApplications(
            contact_email=email, api='applicationcontacts_api')
        self._log.debug(f'raw accounts: {applications}')
        return applications

    @xray.capture()
    @logger.log
    def GetApplicationsByTAppID(self, t_appid: str) -> Applications:
        """Get Application Details from SLater by t_appid
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetApplicationsByTAppID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved applications
        >>> applications = test_GetApplicationsByTAppID.GetApplicationsByTAppID("SVCD015")
        >>> assert (len(applications) == 1), f"expected one, got {len(applications)}"
        >>> application = applications[0]
        >>> assert (application.get('application_name').title() == "Choplifter"), f"expected 'Choplifter', got {application.get('application_name').title()}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        applications = self.GetApplications(
            t_appid=t_appid, api='applications_api')
        self._log.debug(f'raw applications: {applications}')
        return applications

    @xray.capture()
    @logger.log
    def GetApplicationsByTAppIDPrefix(self, id_prefix: str, limit: int = 100) -> Applications:
        """Get Applications from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetApplicationsByTAppIDPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved application
        >>> applications = test_GetApplicationsByTAppIDPrefix.GetApplicationsByTAppIDPrefix("SVCD01")
        >>> assert(len(applications) == 10), f'expected 10 records, got {len(applications)}'
        >>> matches = list(app for app in applications if app['application_name'] == 'Zaxxon')
        >>> assert (len(matches) == 1), f"expected to find one match, found: {len(matches)}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        applications = self.GetApplications(
            filter_field_name='t_appid', filter_prefix=id_prefix, limit=limit, api='applications_api')
        self._log.debug(f'applications count: {len(applications)}')
        return applications

    @xray.capture()
    @logger.log
    def GetApplicationContacts(self, id: str = "", name: str = "", contact_email: str = "", contact_id: str = "", t_appid: str = "", application_id: str = "", application_name: str = "", lob: str = "", lob_id: str = "", filter_prefix: str = "", filter_field_name: str = "application_name", limit: int = 10, next_token: int = -1, api: str = "applicationcontacts_api") -> ApplicationContacts:
        """Get Application Contacts list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetApplicationContacts = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get application contacts without arguments
        >>> app_contacts = test_GetApplicationContacts.GetApplicationContacts()
        >>> assert (len(app_contacts) == 10), f"expected 10 application contacts, got {len(app_contacts)}"
        >>> assert (app_contacts.next_token == 11), f"expected next_token to equal eleven, got {app_contacts.next_token}"
        >>> del app_contacts

        Validate we fail when setting limit=-1
        >>> app_contacts = test_GetApplicationContacts.GetApplicationContacts(limit=-1)
        Traceback (most recent call last):
        ...
        Exception: limit=-1 has been deprecated, only signed values permitted

        Validate get application contacts with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetApplicationContacts.GetApplicationContacts(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 application contact, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal two, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetApplicationContacts.GetApplicationContacts(limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal four, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two application contacts, got {len(next_page)}"

        Step foward again
        >>> next_next_page = test_GetApplicationContacts.GetApplicationContacts(limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal six, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two application contacts, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetApplicationContacts.GetApplicationContacts(limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two application contacts, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal four, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        if int(limit) < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if id:
            params['field_name'] = 'application_contact_id'
            params['search_input'] = id

        if name:
            params['field_name'] = 'contact_name'
            params['search_input'] = name

        if contact_email:
            params['field_name'] = 'contact_email'
            params['search_input'] = contact_email

        if contact_id:
            params['field_name'] = 'contact_id'
            params['search_input'] = contact_id

        if t_appid:
            params['field_name'] = 't_appid'
            params['search_input'] = t_appid

        if application_id:
            params['field_name'] = 'application_id'
            params['search_input'] = application_id

        if application_name:
            params['field_name'] = 'application_name'
            params['search_input'] = application_name

        if lob:
            params['field_name'] = 'governance_lob_name'
            params['search_input'] = lob

        if lob_id:
            params['field_name'] = 'lob_id'
            params['search_input'] = lob_id

        if filter_prefix:
            params['fuzzy'] = True
            params['field_name'] = filter_field_name
            params['search_input'] = filter_prefix

        app_contacts = self.ReadObjects(api=api,
                                        object_type='ApplicationContact',
                                        list_type='ApplicationContacts',
                                        **params)
        return app_contacts

    @xray.capture()
    @logger.log
    def GetApplicationContactByID(self, id: str) -> ApplicationContact:
        """Get Application Contact Detail from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns('iobots-stack')
        >>> test_GetApplicationContactByID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved application
        >>> search = test_GetApplicationContactByID.GetApplicationContacts(limit=1)
        >>> app_contact = test_GetApplicationContactByID.GetApplicationContactByID(search[0].get('application_contact_id'))
        >>> app_contact.get('application_name').title() == search[0].get('application_name').title()
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        app_contacts = self.GetApplicationContacts(id=id, limit=1)

        self._log.debug(f'single record matches: {len(app_contacts)}')
        app_contact = app_contacts[0]
        self._log.debug(f'fetched application contact: {app_contact}')
        app_contact.initialize_clients(
            self._creds, self._log, self._secrets, self)

        return app_contact

    @xray.capture()
    @logger.log
    def GetApplicationContactsByApplicationID(self, id: str) -> ApplicationContacts:
        """Get Contact Details from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetApplicationContactsByApplicationID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved contact by id
        Step 1. Get an existing contact:
        >>> search = test_GetApplicationContactsByApplicationID.GetApplicationContacts(limit=1)

        >>> len(search) == 1
        True

        Step 2. Try and find that record using the first record's id we retrieved
        >>> contacts = test_GetApplicationContactsByApplicationID.GetApplicationContactsByApplicationID(search[0].get('application_id'))

        >>> len(contacts) >= 1
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        app_contacts: ApplicationContacts = self.GetApplicationContacts(
            application_id=id, api="applicationcontacts_api")
        self._log.debug(f'raw contacts: {app_contacts}')
        return app_contacts

    @xray.capture()
    @logger.log
    def GetApplicationContactsByIDPrefix(self, id_prefix: str, limit: int = 100) -> ApplicationContacts:
        """Get ApplicationContacts from SLater by prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetApplicationContactsByIDPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved account type
        >>> application_contacts = test_GetApplicationContactsByIDPrefix.GetApplicationContactsByIDPrefix("1", limit=2)

        >>> len(application_contacts) >= 1
        True

        >>> application_contacts[0].get('contact_type') is not None
        True

        >>> # matches = list(application_contact for application_contact in application_contacts if application_contact['contacty_type'] == 'primary business owner')

        >>> # len(matches)
        >>> # 1

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        application_contacts = self.GetApplicationContacts(
            filter_field_name='application_contact_id',
            filter_prefix=id_prefix,
            limit=limit)
        self._log.debug(f'raw types: {len(application_contacts)}')
        return application_contacts

    @xray.capture()
    @logger.log
    def GetApplicationContactsByNamePrefix(self, name_prefix: str, limit: int = 100) -> ApplicationContacts:
        """Get ApplicationContacts from SLater by prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetApplicationContactsByNamePrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved account type
        >>> application_contacts = test_GetApplicationContactsByNamePrefix.GetApplicationContactsByNamePrefix("primary", limit=2)

        >>> len(application_contacts) >= 1
        True

        >>> application_contacts[0].get('contact_type') is not None
        True

        >>> # matches = list(application_contact for application_contact in application_contacts if application_contact['contacty_type'] == 'primary business owner')

        >>> # len(matches)
        >>> # 1

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        application_contacts = self.GetApplicationContacts(
            filter_field_name='contact_type',
            filter_prefix=name_prefix,
            limit=limit)
        self._log.debug(f'raw types: {len(application_contacts)}')
        return application_contacts

    @xray.capture()
    @logger.log
    def GetBatchImportStatus(self, group_batch_uuid: uuid.UUID = None, group_batch_load_id: int = -1, slater_user: str = '', limit: int = 10, next_token: int = -1, api: str = "batch_api") -> BatchImport:
        """Get SLater Batch Processing Import Status
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetBatchImportStatus = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        # status = test_GetBatchImportStatus.GetBatchImportStatus(group_batch_uuid=uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}'), slater_user=request.get('submitter').get('name'))
        >>>

        >>> status = test_GetBatchImportStatus.GetBatchImportStatus(group_batch_uuid=uuid.UUID('{54701b1b-c2a5-441a-931f-5e1156a6402a}'), slater_user=request.get('submitter').get('name'))

        >>> group_batch_load_id = status[0].get('group_batch_load_id')

        >>> group_batch_load_id
        440

        >>> status = test_GetBatchImportStatus.GetBatchImportStatus(group_batch_load_id = group_batch_load_id, slater_user=request.get('submitter').get('name'))

        >>> status[0].get('group_batch_uuid')
        '54701b1b-c2a5-441a-931f-5e1156a6402a'

        >>> status[0].get('status')
        'Complete'

        >>> status[0].get('num_errors')
        13736

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        if limit < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if group_batch_uuid:
            params['group_batch_uuid'] = group_batch_uuid

        if int(next_token) > -1:
            params['next_token'] = next_token

        if int(group_batch_load_id) > -1:
            params['group_batch_load_id'] = group_batch_load_id

        url: str = self.config['SLater'][api]
        if params:
            url = f'{url}?{parser.urlencode(params)}'

        self._log.debug(f"url: {url}")

        headers: Dict[str, str] = {
            "Authorization": f"Bearer {self._creds['access_token']}",
            "Content-Type": "application/x-www-form-urlencoded",
            "slater_user": slater_user,
            "db_version": self.db_version
        }

        self._log.debug(f"headers {headers}")
        response: Dict = self.client.get(url, headers=headers)

        if response.status_code not in [200, 202]:
            self._log.error(
                f'Status: {response.status_code}, Headers: {response.headers}, Error Response: {response.content}')
            return None

        self._log.debug(
            f'batch status: {response.status_code}, content: {response.json()}')

        data = response.json()
        # self._log.debug(
        #     f'settings: locals (next: {next_token}, limit: {limit}), incoming (next: {data["paginationcontrol"]["next_token"]}, limit: {data["paginationcontrol"]["limit"]})')

        self._log.debug(f'data: {data}')
        batches = BatchImports((
            BatchImport(**record) for record in data['results']),
            limit=limit,
            next_token=next_token)

        # limit=data["paginationcontrol"]['limit'],
        # next_token=data["paginationcontrol"]['next_token'])

        self._log.debug(f'processed batches: {batches}')
        batches.initialize_clients(
            self._creds, self._log, self._secrets, self)

        return batches

    @xray.capture()
    @logger.log
    def GetBatchImportErrors(self, batch_load_id: int = -1, id: int = -1, slater_user: str = '', limit: int = 10, next_token: int = -1, api: str = 'batcherrors_api') -> BatchErrors:
        """Get SLater Batch Processing Import Errors
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetBatchImportErrors = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> errors = test_GetBatchImportErrors.GetBatchImportErrors(batch_load_id=719, slater_user=request.get('submitter').get('name'))

        >>> # errors

        >>> errors[0].get('cannot_process')
        1

        >>> errors[0].get('errors')
        '*cloud_account_number not found in cloud_accounts '

        >>> errors[0].get('entity_type')
        'account_contact_error'


        Validate get exports without arguments
        >>> errors = test_GetBatchImportErrors.GetBatchImportErrors(batch_load_id=719, slater_user=request.get('submitter').get('name'),)
        >>> assert (len(errors) == 6), f"expected 6 exports, got {len(errors)}"
        >>> assert (errors.next_token == None), f"expected next_token to equal None, got {errors.next_token}"
        >>> del errors

        Validate get errors with explicit all records limit of -1 goes boom
        >>> errors = test_GetBatchImportErrors.GetBatchImportErrors(batch_load_id=719, slater_user=request.get('submitter').get('name'),limit=-1)
        Traceback (most recent call last):
        ...
        Exception: limit=-1 has been deprecated, only signed values permitted

        Validate get exports with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetBatchImportErrors.GetBatchImportErrors(batch_load_id=719, slater_user=request.get('submitter').get('name'), limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 exports, got {len(first_page)}"
        >>> assert (first_page.next_token == 294), f"expected next_token to equal 294, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetBatchImportErrors.GetBatchImportErrors(batch_load_id=719, slater_user=request.get('submitter').get('name'), limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 296), f"expected next_token to equal 296, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two exports, got {len(exports)}"

        Step foward again
        >>> next_next_page = test_GetBatchImportErrors.GetBatchImportErrors(batch_load_id=719, slater_user=request.get('submitter').get('name'), limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 298), f"expected next_token to equal 298, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two exports, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetBatchImportErrors.GetBatchImportErrors(batch_load_id=719, slater_user=request.get('submitter').get('name'), limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two exports, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 296), f"expected next_token to equal 296, got {prev_page.next_token}"


        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """

        if int(id) > 0:
            batch_load_id = id

        if int(limit) < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if int(batch_load_id) > -1:
            params['batch_load_id'] = batch_load_id

        url: str = self.config['SLater'][api]
        if params:
            url = f'{url}?{parser.urlencode(params)}'

        self._log.debug(f"url: {url}")

        headers: Dict[str, str] = {
            "Authorization": f"Bearer {self._creds['access_token']}",
            "Content-Type": "application/x-www-form-urlencoded",
            "slater_user": slater_user,
            "db_version": self.db_version
        }

        self._log.debug(f"headers {headers}")
        response: Dict = self.client.get(url, headers=headers)

        if response.status_code not in [200, 202]:
            self._log.error(
                f'Status: {response.status_code}, Headers: {response.headers}, Error Response: {response.content}')
            return None

        self._log.debug(
            f'batcherrors status: {response.status_code}, content: {response.json()}')

        data = response.json()
        # self._log.debug(
        #      f'settings: locals (next: {next_token}, limit: {limit}), incoming (next: {data["paginationcontrol"]["next_token"]}, limit: {data["paginationcontrol"]["limit"]})')

        self._log.debug(f'data: {data}')
        errors = BatchErrors((
            BatchError(**record) for record in data['results']),
            limit=data["paginationcontrol"]['limit'],
            next_token=data["paginationcontrol"]['next_token'],
            batch_load_id=batch_load_id)

        self._log.debug(f'processed errors: {errors}')
        errors.initialize_clients(
            self._creds, self._log, self._secrets, self)

        return errors

    @xray.capture()
    @logger.log
    def GetBusinessUnits(self, id: str = "", name: str = "", business_unit_name: str = "", business_unit_id: str = "", filter_prefix: str = "", filter_field_name: str = "business_unit_name", limit: int = 10, next_token: int = -1, api: str = "businessunits_api") -> BusinessUnits:
        """Get BusinessUnits list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetBusinessUnits = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get business_unit without arguments
        >>> business_units = test_GetBusinessUnits.GetBusinessUnits()
        >>> assert (len(business_units) == 10), f"expected 10 records, got {len(business_units)}"
        >>> assert (business_units.next_token >= 10), f"expected next_token to equal ten, got {business_units.next_token}"
        >>> del business_units

        Validate we fail when setting limit=-1
        >>> business_units = test_GetBusinessUnits.GetBusinessUnits(limit=-1)
        Traceback (most recent call last):
        ...
        Exception: limit=-1 has been deprecated, only signed values permitted

        Validate get business_units with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetBusinessUnits.GetBusinessUnits(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 record, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal two, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetBusinessUnits.GetBusinessUnits(limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal four, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two records, got {len(next_page)}"

        Step foward again
        >>> next_next_page = test_GetBusinessUnits.GetBusinessUnits(limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal six, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two records, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetBusinessUnits.GetBusinessUnits(limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two lobs, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal two, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        if int(limit) < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if name:
            params['field_name'] = 'business_unit_name'
            params['search_input'] = name

        if business_unit_name:
            params['field_name'] = 'business_unit_name'
            params['search_input'] = business_unit_name

        if id:
            params['field_name'] = 'business_unit_id'
            params['search_input'] = id

        if business_unit_id:
            params['field_name'] = 'business_unit_id'
            params['search_input'] = business_unit_id

        if filter_prefix:
            params['fuzzy'] = True
            params['field_name'] = filter_field_name
            params['search_input'] = filter_prefix

        business_units = self.ReadObjects(api=api,
                                          object_type='BusinessUnit',
                                          list_type='BusinessUnits',
                                          **params)

        return business_units

    @xray.capture()
    @logger.log
    def GetBusinessUnitByID(self, id: str) -> BusinessUnit:
        """Get BusinessUnit Detail from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns('iobots-stack')
        >>> test_GetBusinessUnitByID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved business unit
        >>> business_unit = test_GetBusinessUnitByID.GetBusinessUnitByID("1")
        >>> business_unit.get('business_unit_name').title()
        'Api Test Business Unit, Updated'
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        business_units = self.GetBusinessUnits(id=id, limit=1)

        self._log.debug(f'single record matches: {len(business_units)}')
        business_unit = business_units[0]
        self._log.debug(f'fetched business_unit: {business_unit}')
        business_unit.initialize_clients(
            self._creds, self._log, self._secrets, self)

        return business_unit

    @xray.capture()
    @logger.log
    def GetBusinessUnitsByIDPrefix(self, id_prefix: str, limit: int = 100) -> BusinessUnits:
        """Get Business Units from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetBusinessUnitsByIDPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved lob
        >>> business_units = test_GetBusinessUnitsByIDPrefix.GetBusinessUnitsByIDPrefix("1")

        >>> len(business_units) >= 1
        True

        >>> matches = list(business_units for business_unit in business_units if business_unit['business_unit_name'] == 'API test business unit, updated')

        >>> len(matches)
        1

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        business_units = self.GetBusinessUnits(
            filter_field_name='business_unit_id',
            filter_prefix=id_prefix,
            limit=limit)
        self._log.debug(f'raw business units: {len(business_units)}')
        return business_units

    @xray.capture()
    @logger.log
    def GetBusinessUnitsByNamePrefix(self, id_prefix: str, limit: int = 100) -> BusinessUnits:
        """Get Business Units from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetBusinessUnitsByNamePrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> search = test_GetBusinessUnitsByNamePrefix.GetBusinessUnits(limit=1)

        >>> len(search) == 1
        True

        Validate retrieved lob
        >>> business_units = test_GetBusinessUnitsByNamePrefix.GetBusinessUnitsByNamePrefix(search[0].get('business_unit_name'))

        >>> len(business_units) >= 1
        True

        >>> matches = list(business_units for business_unit in business_units if business_unit['business_unit_name'] == search[0].get('business_unit_name'))

        >>> len(matches)
        1

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        business_units = self.GetBusinessUnits(
            filter_field_name='business_unit_name',
            filter_prefix=id_prefix,
            limit=limit)
        self._log.debug(f'raw business units: {len(business_units)}')
        return business_units

    @xray.capture()
    @logger.log
    def GetBusinessUnitByName(self, name: str) -> BusinessUnit:
        """Get Business Unit Detail from SLater by Business Unit Name
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns('iobots-stack')
        >>> test_GetBusinessUnitByName = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved Business Unit
        >>> business_unit = test_GetBusinessUnitByName.GetBusinessUnitByName('Api Test Business Unit, Updated')

        >>> business_unit.get('business_unit_name').title()
        'Api Test Business Unit, Updated'

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        business_unit: BusinessUnit = None

        business_units = self.GetBusinessUnits(name=name)
        if len(business_units) == 1:
            self._log.debug(f'single record matches: {len(business_units)}')
            business_unit = business_units[0]
            self._log.debug(f'fetched business unit: {business_unit}')
            business_unit._log = self._log
            business_unit._creds = self._creds
            business_unit._secrets = self._secrets
            business_unit.client = self.client
        else:
            self._log.debug(f'too many records: {len(business_units)}')

        return business_unit

    @xray.capture()
    @logger.log
    def GetContacts(self, id: str = "", name: str = "", email: str = "", type: str = "", account_name: str = "", account_number: str = "", cloud_account_number: str = "", cloud_account_id: str = "", application_name: str = "", application_id: str = "", t_appid: str = "", filter_prefix: str = "", filter_field_name: str = "contact_name", limit: int = 10, next_token: int = -1, api: str = "contacts_api") -> Contacts:
        """Get Contacts list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetContacts = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contacts without arguments
        >>> contacts = test_GetContacts.GetContacts()
        >>> assert (len(contacts) == 10), f"expected 10 contacts, got {len(contacts)}"
        >>> assert (contacts.next_token == 11), f"expected next_token to equal eleven, got {contacts.next_token}"
        >>> del contacts

        Validate get contacts with explicit all records limit of -1
        >>> contacts = test_GetContacts.GetContacts(limit=-1)
        Traceback (most recent call last):
        ...
        Exception: limit=-1 has been deprecated, only signed values permitted

        Validate get contacts with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetContacts.GetContacts(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 contacts, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal two, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetContacts.GetContacts(limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal four, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two contacts, got {len(contacts)}"

        Step foward again
        >>> next_next_page = test_GetContacts.GetContacts(limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal six, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two contacts, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetContacts.GetContacts(limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two contacts, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal four, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        if int(limit) < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if name:
            params['field_name'] = 'contact_name'
            params['search_input'] = name

        if account_name:
            params['field_name'] = 'cloud_account_name'
            params['search_input'] = account_name

        if account_number:
            params['field_name'] = 'cloud_account_number'
            params['search_input'] = account_number

        if cloud_account_number:
            params['field_name'] = 'cloud_account_number'
            params['search_input'] = cloud_account_number

        if cloud_account_id:
            params['field_name'] = 'cloud_account_id'
            params['search_input'] = cloud_account_id

        if email:
            params['field_name'] = 'contact_email'
            params['search_input'] = email

        if id:
            params['field_name'] = 'contact_id'
            params['search_input'] = id

        if application_name:
            params['field_name'] = 'application_name'
            params['search_input'] = application_name

        if application_id:
            params['field_name'] = 'application_id'
            params['search_input'] = application_id

        if t_appid:
            params['field_name'] = 't_appid'
            params['search_input'] = t_appid

        if filter_prefix:
            params['fuzzy'] = True
            params['field_name'] = filter_field_name
            params['search_input'] = filter_prefix

        contacts = self.ReadObjects(api=api,
                                    object_type='Contact',
                                    list_type='Contacts',
                                    **params)

        return contacts

    @xray.capture()
    @logger.log
    def GetContactByID(self, id: str) -> Contact:
        """Get Contact Details from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetContactByID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved contact
        >>> speed_racer = test_GetContactByID.GetContactByID("1")
        >>> assert ("Speed Racer" in speed_racer.get('contact_name').title()), f"expected 'Speed Racer', got {speed_racer.get('contact_name').title()}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        contact: Contact = None

        contacts = self.GetContacts(id=id)
        self._log.debug(f'raw contacts: {contacts}')
        if len(contacts) == 1:
            contact = contacts[0]
            self._log.debug(f'fetched contact: {contact}')
            contact.initialize_clients(
                self._creds, self._log, self._secrets, self)
        return contact

    @xray.capture()
    @logger.log
    def GetContactsByApplicationID(self, id: str) -> Contacts:
        """Get Contact Details from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetContactsByApplicationID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved contact
        >>> contacts = test_GetContactsByApplicationID.GetContactsByApplicationID("1")
        >>> len(contacts) >= 1
        True
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        contacts: Contacts = self.GetContacts(
            application_id=id, api="applicationcontacts_api")
        self._log.debug(f'raw contacts: {contacts}')
        return contacts

    @xray.capture()
    @logger.log
    def GetContactsByCloudAccountNumber(self, id: str) -> Contacts:
        """Get Contact Details from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetContactsByCloudAccountNumber = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved contact
        >>> atari_name = test_GetContactsByCloudAccountNumber.GetAccountByName("atari")
        >>> contacts = test_GetContactsByCloudAccountNumber.GetContactsByCloudAccountNumber(atari_name.get('cloud_account_number'))
        >>> len(contacts) > 1
        True
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        contacts: Contacts = self.GetContacts(
            cloud_account_number=id, api="accountcontacts_api")
        self._log.debug(f'raw contacts: {contacts}')
        return contacts

    @xray.capture()
    @logger.log
    def GetAccountContactsByCloudAccountID(self, id: str) -> Contacts:
        """Get Account Contacts from SLater by Cloud Account ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetAccountContactsByCloudAccountID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved contacts
        >>> atari_id = test_GetAccountContactsByCloudAccountID.GetAccountByName("atari")
        >>> contacts = test_GetAccountContactsByCloudAccountID.GetAccountContactsByCloudAccountID(atari_id.get('cloud_account_id'))
        >>> len(contacts) > 1
        True
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        contacts: Contacts = self.GetContacts(
            cloud_account_id=id, api="accountcontacts_api")
        self._log.debug(f'raw contacts: {contacts}')
        return contacts

    @xray.capture()
    @logger.log
    def GetContactsByEmailPrefix(self, email_prefix: str, limit: int = 100) -> Contacts:
        """Get Contact Details from SLater by Contact Email Prefix

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetContactsByEmailPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved contacts
        >>> contacts = test_GetContactsByEmailPrefix.GetContactsByEmailPrefix("speed")

        >>> len(contacts) >= 3
        True

        >>> matches = list(contact for contact in contacts if 'Speed Racer' in contact['contact_name'].title())

        >>> len(matches) >= 1
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        contacts = self.GetContacts(filter_field_name='contact_email',
                                    filter_prefix=email_prefix, limit=limit, api="contacts_api")
        self._log.debug(f'raw contacts: {contacts}')
        return contacts

    @xray.capture()
    @logger.log
    def GetContactsByNamePrefix(self, name_prefix: str, limit: int = 100) -> Contacts:
        """Get Contact Details from SLater by Contact Name Prefix

        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetContactsByNamePrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved contacts
        >>> contacts = test_GetContactsByNamePrefix.GetContactsByNamePrefix("speed")
        >>> assert (len(contacts) >= 3), f"expected at least three, got {len(contacts)}"
        >>> matches = list(contact for contact in contacts if 'Speed Racer' in contact['contact_name'].title())
        >>> assert (len(matches) >= 1), f"expected at least one, got {len(matches)}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        contacts = self.GetContacts(
            filter_field_name='contact_name', filter_prefix=name_prefix, limit=limit, api="contacts_api")
        self._log.debug(f'raw contacts: {contacts}')
        return contacts

    @xray.capture()
    @logger.log
    def GetContactTypes(self, id: str = "", contact_type_id: str = "", contact_type: str = "", filter_prefix: str = "", filter_field_name: str = "contact_type", limit: int = 10, next_token: int = -1, api: str = "contact_types_api") -> ContactTypes:
        """Get Contact Types list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetContactTypes = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact types without arguments
        >>> contact_types = test_GetContactTypes.GetContactTypes(limit=1)

        >>> assert (len(contact_types) == 1), f"expected 10 contact types, got {len(contact_types)}"
        >>> assert (contact_types.next_token == 2), f"expected next_token to equal eleven, got {contact_types.next_token}"
        >>> for contact_type in contact_types:
        ...     assert(type(contact_type) == ContactType), f"Expected all results to be of type ContactType, got a {type(contact_type)}"

        Validate get contact_types with explicit all records limit of -1
        >>> contact_types = test_GetContactTypes.GetContactTypes(limit=-1)
        Traceback (most recent call last):
        ...
        Exception: limit=-1 has been deprecated, only signed values permitted

        Validate get contact types with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetContactTypes.GetContactTypes(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 contact type, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal two, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetContactTypes.GetContactTypes(limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal four, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two contact types, got {len(contacts)}"

        Step foward again
        >>> next_next_page = test_GetContactTypes.GetContactTypes(limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal six, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two contact types, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetContactTypes.GetContactTypes(limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two contacts, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal four, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        if int(limit) < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if contact_type:
            params['field_name'] = 'contact_type'
            params['search_input'] = contact_type

        if id:
            params['field_name'] = 'contact_type_id'
            params['search_input'] = id

        if contact_type_id:
            params['field_name'] = 'contact_type_id'
            params['search_input'] = contact_type_id

        if filter_prefix:
            params['fuzzy'] = True
            params['field_name'] = filter_field_name
            params['search_input'] = filter_prefix

        contact_types = self.ReadObjects(api=api,
                                         object_type='ContactType',
                                         list_type='ContactTypes',
                                         **params)
        return contact_types

    @xray.capture()
    @logger.log
    def GetContactTypeByID(self, id: str) -> ContactType:
        """Get Contact Details from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetContactTypeByID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved contact
        >>> data = test_GetContactTypeByID.GetContactTypeByID("1")

        >>> data.get('contact_type').title() is not None
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        contact_type: Contact = None

        contact_types = self.GetContactTypes(id=id)
        self._log.debug(f'raw record: {contact_types}')
        if len(contact_types) == 1:
            contact_type = contact_types[0]
            self._log.debug(f'fetched record: {contact_type}')
            contact_type.initialize_clients(
                self._creds, self._log, self._secrets, self)
        return contact_type

    @xray.capture()
    @logger.log
    def GetContactTypesByContactTypePrefix(self, contact_type_prefix: str, limit: int = 100) -> ContactTypes:
        """Get Contact Types from SLater by contact_type prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetContactTypesByContactTypePrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved application
        >>> contact_types = test_GetContactTypesByContactTypePrefix.GetContactTypesByContactTypePrefix("account")
        >>> assert(len(contact_types) >= 1), f'expected at least one record, got {len(contact_types)}'
        >>> matches = list(contact_type for contact_type in contact_types if contact_type['contact_type'] == 'account owner')
        >>> assert (len(matches) == 1), f"expected to find one match, found: {len(matches)}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        contact_types = self.GetContactTypes(
            filter_field_name='contact_type', filter_prefix=contact_type_prefix, limit=limit, api='contact_types_api')
        return contact_types

    @xray.capture()
    @logger.log
    def GetContactTypesByIDPrefix(self, id_prefix: str, limit: int = 100) -> ContactTypes:
        """Get Contact Types from SLater by contact_type prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetContactTypesByIDPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved application
        >>> contact_types = test_GetContactTypesByIDPrefix.GetContactTypesByIDPrefix("1")
        >>> assert(len(contact_types) >= 1), f'expected at least one record, got {len(contact_types)}'
        >>> # matches = list(contact_type for contact_type in contact_types if contact_type['contact_type'] == 'account owner')
        >>> # assert (len(matches) == 1), f"expected to find one match, found: {len(matches)}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        contact_types = self.GetContactTypes(
            filter_field_name='contact_type_id', filter_prefix=id_prefix, limit=limit, api='contact_types_api')
        return contact_types

    @xray.capture()
    @logger.log
    def GetCustomFields(self, id: str = "", name: str = "", cloud_account_id: str = "", cloud_account_name: str = "", cloud_account_number: str = "", application_id: str = "", application_name: str = "", t_appid: str = "", filter_prefix: str = "", filter_field_name: str = "custom_field_name", limit: int = 5, next_token: int = -1, api: str = "customfields_api") -> CustomFields:
        """Get Customfields list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetCustomFields = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get Customfields without arguments
        >>> custom_fields = test_GetCustomFields.GetCustomFields()
        >>> assert (len(custom_fields) == 5), f"expected 5 Customfields, got {len(custom_fields)}"
        >>> assert (custom_fields.next_token == 6), f"expected next_token to equal six, got {custom_fields.next_token}"
        >>> del custom_fields

        Validate we fail when setting limit=-1
        >>> custom_fields = test_GetCustomFields.GetCustomFields(limit=-1)
        Traceback (most recent call last):
        ...
        slater.exceptions.SLaterDeprecatedFeatureException: limit=-1 has been deprecated, only signed values permitted

        Validate get Customfields with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetCustomFields.GetCustomFields(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 Customfields, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal two, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetCustomFields.GetCustomFields(limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal four, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two Customfields, got {len(next_page)}"

        Step foward again
        >>> next_next_page = test_GetCustomFields.GetCustomFields(limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal six, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two Customfields, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetCustomFields.GetCustomFields(limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two Customfields, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal four, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if name:
            params['field_name'] = 'custom_field_name'
            params['search_input'] = name

        if id:
            params['field_name'] = 'custom_field_name_id'
            params['search_input'] = id

        if cloud_account_id:
            params['field_name'] = 'cloud_account_id'
            params['search_input'] = cloud_account_id

        if cloud_account_name:
            params['field_name'] = 'cloud_account_name'
            params['search_input'] = cloud_account_name

        if cloud_account_number:
            params['field_name'] = 'cloud_account_number'
            params['search_input'] = cloud_account_number

        if application_id:
            params['field_name'] = 'application_id'
            params['search_input'] = application_id

        if application_name:
            params['field_name'] = 'application_name'
            params['search_input'] = application_name

        if t_appid:
            params['field_name'] = 't_appid'
            params['search_input'] = t_appid

        if filter_prefix:
            params['fuzzy'] = True
            params['field_name'] = filter_field_name
            params['search_input'] = filter_prefix

        custom_fields = self.ReadObjects(api=api,
                                         object_type='CustomField',
                                         list_type='CustomFields',
                                         **params)
        return custom_fields

    @xray.capture()
    @logger.log
    def GetCustomFieldsByCloudAccountID(self, cloud_account_id: str, custom_field_name: str = "", limit: int = 10, next_token: int = -1) -> CustomFields:
        """Get Account custom fields from SLater by Cloud Account ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetCustomFieldsByCloudAccountID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved contacts
        >>> atari_number = test_GetCustomFieldsByCloudAccountID.GetAccountByName("atari")
        >>> custom_fields = test_GetCustomFieldsByCloudAccountID.GetCustomFieldsByCloudAccountID(atari_number.get('cloud_account_id'))
        >>> len(custom_fields) > 1
        True
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        custom_fields: CustomFields = self.GetCustomFields(
            cloud_account_id=cloud_account_id, limit=limit,
            name=custom_field_name, api="accountcustomfields_api")
        self._log.debug(f'raw custom_fields: {custom_fields}')
        return custom_fields


    @xray.capture()
    @logger.log
    def GetCustomFieldsByCloudAccountNumber(self, cloud_account_number: str, custom_field_name: str = "", limit: int = 10, next_token: int = -1) -> CustomFields:
        """Get Account custom fields from SLater by Cloud Account number
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetCustomFieldsByCloudAccountNumber = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved contacts
        >>> atari_number = test_GetCustomFieldsByCloudAccountNumber.GetAccountByName("atari")
        >>> custom_fields = test_GetCustomFieldsByCloudAccountNumber.GetCustomFieldsByCloudAccountNumber(atari_number.get('cloud_account_number'))
        >>> len(custom_fields) > 1
        True
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        custom_fields: CustomFields = self.GetCustomFields(
            cloud_account_number=cloud_account_number, limit=limit,
            api="accountcustomfields_api")
        self._log.debug(f'raw custom_fields: {custom_fields}')
        return custom_fields

    @xray.capture()
    @logger.log
    def GetCustomFieldsByApplicationID(self, application_id: str, custom_field_name: str = "", limit: int = 10, next_token: int = -1) -> CustomFields:
        """Get Account custom fields from SLater by Application ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetCustomFieldsByApplicationID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved contacts
        >>> choplifter = test_GetCustomFieldsByApplicationID.GetApplicationByName("Choplifter")
        >>> custom_fields = test_GetCustomFieldsByApplicationID.GetCustomFieldsByApplicationID(choplifter.get('application_id'))
        >>> len(custom_fields) > 1
        True
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        custom_fields: CustomFields = self.GetCustomFields(
            application_id=application_id, limit=limit,
            api="applicationcustomfields_api")
        self._log.debug(f'raw custom_fields: {custom_fields}')
        return custom_fields

    @xray.capture()
    @logger.log
    def GetExports(self, export_format: str = 'all', limit: int = 500, next_token: int = -1, api: str = "exports_api") -> Exports:
        """Get Exports list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetExports = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get exports without arguments
        >>> exports = test_GetExports.GetExports(limit=1)
        >>> assert (len(exports) >= 1), f"expected at least one record in exports, got {len(exports)}"
        >>> assert (exports.next_token >= 2), f"expected next_token to be equal or greater than two, got {exports.next_token}"
        >>> del exports

        Validate get exports with explicit all records limit of -1 goes boom
        >>> exports = test_GetExports.GetExports(limit=-1)
        Traceback (most recent call last):
        ...
        Exception: limit=-1 has been deprecated, only signed values permitted

        Validate get exports with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetExports.GetExports(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 exports, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal two, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetExports.GetExports(limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal four, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two exports, got {len(exports)}"

        Step foward again
        >>> next_next_page = test_GetExports.GetExports(limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal six, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two exports, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetExports.GetExports(limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two exports, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal four, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        if int(limit) < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        url = self.config['SLater'][api]

        supported_formats = {
            'all': 'all',
            'applicationcontacts': 'applicationcontacts',
            'businessmapping': 'businessmapping',
            'default': 'all'
        }

        if export_format in supported_formats:
            url = f'{url}/{supported_formats.get(export_format)}'
        else:
            url = f'{url}/{supported_formats.get("default")}'

        if params:
            url = f'{url}?{parser.urlencode(params)}'

        self._log.debug(f"url: {url}")

        headers: Dict[str, str] = {
            "Content-Type": "application/x-www-form-urlencoded",
            "Authorization": f"Bearer {self._creds['access_token']}",
            "db_version": self.db_version
        }
        self._log.debug(f"headers {headers}")
        response: Dict = self.client.get(url, headers=headers)

        # self._log.debug(f"{response.json()}")
        if response.status_code != 200:
            self._log.error(
                f'Status: {response.status_code}, Headers: {response.headers}, Error Response: {response.content}')
            return Exports([], next_token=None)

        data = response.json()

        self._log.debug(
            f'settings: locals (next: {next_token}, limit: {limit}), incoming (next: {data["paginationcontrol"]["next_token"]}, limit: {data["paginationcontrol"]["limit"]})')
        exports = Exports((
            Export(**record) for record in data['results']),
            limit=data["paginationcontrol"]["limit"],
            next_token=data["paginationcontrol"]["next_token"])

        # exports.initialize_clients(
        #     self._creds, self._log, self._secrets, self)

        return exports

    @xray.capture()
    @logger.log
    def GetExportsAll(self, limit: int = 1000, api: str = "exports_api") -> Exports:
        """Get Exports Details from SLater by export_type all
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetExportsAll = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved exports
        >>> exports = test_GetExportsAll.GetExportsAll()
        >>> assert (len(exports) >= 1), f"expected to equal at least 1, got {len(exports)}"

        >>> exports[1].get('t_appid').startswith('SVC')
        True

        >>> exports[1].get('account_type') is not None
        True

        >>> matches = list(export for export in exports if 'tari' in export['account_name'])
        >>> assert (len(matches) >= 1), f"expected at least one, got {len(matches)}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        exports: Exports = Exports([])
        cursor: Exports = Exports([])

        while cursor.next_token and cursor.next_token:
            cursor = self.GetExports(
                export_format='all', limit=limit, api=api, next_token=cursor.next_token)

            self._log.debug(f'current cursor length: {len(cursor)}')
            for item in cursor:
                self._log.debug(f'looping cursor: {item}')
                exports.append(item)

            self._log.debug(f'current records tally: {len(exports)}')

        return exports

    @xray.capture()
    @logger.log
    def GetExportsAccounts(self, limit: int = 1000, api: str = "exports_api") -> Exports:
        """Get Exports Details from SLater by export_type all
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetExportsAccounts = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved exports
        >>> exports = test_GetExportsAccounts.GetExportsAccounts()
        >>> assert (len(exports) >= 1), f"expected to equal at least 1, got {len(exports)}"

        >>> matches = list(export for export in exports if 'tari' in export['cloud_account_name'])
        >>> assert (len(matches) >= 1), f"expected at least one, got {len(matches)}"


        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        exports: Exports = Exports([])
        cursor: Exports = Exports([])

        while cursor.next_token and cursor.next_token:
            cursor = self.GetExports(
                export_format='businessmapping', limit=limit, api=api, next_token=cursor.next_token)

            self._log.debug(f'current cursor length: {len(cursor)}')
            for item in cursor:
                # self._log.debug(f'looping cursor: {item}')
                exports.append(item)

            self._log.debug(f'current records tally: {len(exports)}')

        return exports

    @xray.capture()
    @logger.log
    def GetExportsApplications(self, limit: int = 1000, api: str = "exports_api") -> Exports:
        """Get Exports Details from SLater by export_type all
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetExportsApplications = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved exports
        >>> exports = test_GetExportsApplications.GetExportsApplications()
        >>> assert (len(exports) >= 1), f"expected to equal at least 1, got {len(exports)}"
        >>> exports[1].get('t_appid').startswith('SVC')
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        exports: Exports = Exports([])
        cursor: Exports = Exports([])

        while cursor.next_token and cursor.next_token:
            cursor = self.GetExports(
                export_format='applicationcontacts', limit=limit, api=api, next_token=cursor.next_token)

            self._log.debug(f'current cursor length: {len(cursor)}')
            for item in cursor:
                # self._log.debug(f'looping cursor: {item}')
                exports.append(item)

            self._log.debug(f'current records tally: {len(exports)}')

        return exports

    @xray.capture()
    @logger.log
    def GetLOBs(self, id: str = "", name: str = "", lob_id: str = "", filter_prefix: str = "", filter_field_name: str = "governance_lob_name", limit: int = 10, next_token: int = -1, api: str = "lobs_api") -> LOBs:
        """Get LOBs list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetLOBs = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get lobs without arguments
        >>> lobs = test_GetLOBs.GetLOBs(limit=1)
        >>> assert (len(lobs) == 1), f"expected one lob, got {len(lobs)}"
        >>> assert (lobs.next_token == 2), f"expected next_token to equal one, got {lobs.next_token}"
        >>> del lobs

        Validate we fail when setting limit=-1
        >>> lobs = test_GetLOBs.GetLOBs(limit=-1)
        Traceback (most recent call last):
        ...
        Exception: limit=-1 has been deprecated, only signed values permitted

        Validate get lobs with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetLOBs.GetLOBs(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 record, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal one, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetLOBs.GetLOBs(limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal two, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two lobs, got {len(next_page)}"

        Step foward again
        >>> next_next_page = test_GetLOBs.GetLOBs(limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal four, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two records, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetLOBs.GetLOBs(limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two records, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal two, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        if int(limit) < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if name:
            params['field_name'] = 'governance_lob_name'
            params['search_input'] = name

        if id:
            params['field_name'] = 'lob_id'
            params['search_input'] = id

        if lob_id:
            params['field_name'] = 'lob_id'
            params['search_input'] = lob_id

        if filter_prefix:
            params['fuzzy'] = True
            params['field_name'] = filter_field_name
            params['search_input'] = filter_prefix

        lobs = self.ReadObjects(api=api,
                                object_type='LOB',
                                list_type='LOBs',
                                **params)

        return lobs

    @xray.capture()
    @logger.log
    def GetLOBByID(self, id: str) -> LOB:
        """Get LOB Detail from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns('iobots-stack')

        >>> test_GetLOBByID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved lob
        >>> lob = test_GetLOBByID.GetLOBByID("1")

        >>> lob.get('governance_lob_name').title()
        'Lob A'

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        lobs = self.GetLOBs(id=id, limit=1)

        self._log.debug(f'single record matches: {len(lobs)}')
        lob = lobs[0]
        self._log.debug(f'fetched lob: {lob}')
        lob.initialize_clients(
            self._creds, self._log, self._secrets, self)

        return lob

    @xray.capture()
    @logger.log
    def GetLOBsByIDPrefix(self, id_prefix: str, limit: int = 100) -> LOBs:
        """Get LOBs from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetLOBsByIDPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved lob
        >>> lobs = test_GetLOBsByIDPrefix.GetLOBsByIDPrefix("1")

        >>> len(lobs) >= 1
        True

        >>> matches = list(lob for lob in lobs if lob['governance_lob_name'] == 'lob a')

        >>> len(matches)
        1

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        lobs = self.GetLOBs(
            filter_field_name='lob_id', filter_prefix=id_prefix, limit=limit)
        self._log.debug(f'raw lobs: {len(lobs)}')
        return lobs

    @xray.capture()
    @logger.log
    def GetLOBByName(self, name: str) -> LOB:
        """Get LOB Detail from SLater by LOB Governance Name
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns('iobots-stack')
        >>> test_GetLOBByName = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved LOB
        >>> lob = test_GetLOBByName.GetLOBByName("lob a")
        >>> assert (lob.get('governance_lob_name').title() == "Lob A"), f"expected 'lob a', got {lob.get('governance_lob_name')}"
        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        lob: LOB = None

        lobs = self.GetLOBs(name=name)
        if len(lobs) == 1:
            self._log.debug(f'single record matches: {len(lobs)}')
            lob = lobs[0]
            self._log.debug(f'fetched application: {lob}')
            lob._log = self._log
            lob._creds = self._creds
            lob._secrets = self._secrets
            lob.client = self.client
        else:
            self._log.debug(f'too many records: {len(lobs)}')

        return lob

    @xray.capture()
    @logger.log
    def GetLOBsByNamePrefix(self, id_prefix: str, limit: int = 100) -> LOBs:
        """Get LOBs from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetLOBsByNamePrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved lob
        >>> lobs = test_GetLOBsByNamePrefix.GetLOBsByNamePrefix("lob")

        >>> len(lobs) >= 1
        True

        >>> matches = list(lob for lob in lobs if 'lob' in lob['governance_lob_name'].lower())

        >>> len(matches) >= 1
        1

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        lobs = self.GetLOBs(
            filter_field_name='governance_lob_name', filter_prefix=id_prefix, limit=limit)
        self._log.debug(f'raw lobs: {len(lobs)}')
        return lobs

    @xray.capture()
    @logger.log
    def GetMasterPayers(self, id: str = "", name: str = "", master_payer_name: str = "", master_payer_id: str = "", master_payer_ident: str = "", filter_prefix: str = "", filter_field_name: str = "master_payer_id", limit: int = 10, next_token: int = -1, api: str = "masterpayers_api") -> MasterPayers:
        """Get MasterPayers list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetMasterPayers = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get account_type without arguments
        >>> master_payers = test_GetMasterPayers.GetMasterPayers(limit=1)
        >>> assert (len(master_payers) == 1), f"expected five records, got {len(master_payers)}"
        >>> assert (master_payers.next_token == 2), f"expected next_token to equal two, got {master_payers.next_token}"
        >>> del master_payers

        Validate we fail when setting limit=-1
        >>> master_payers = test_GetMasterPayers.GetMasterPayers(limit=-1)
        Traceback (most recent call last):
        ...
        Exception: limit=-1 has been deprecated, only signed values permitted

        Validate get business_units with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetMasterPayers.GetMasterPayers(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 record, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal one, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetMasterPayers.GetMasterPayers(limit=1, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 3), f"expected next_token to equal two, got {next_page.next_token}"
        >>> assert (len(next_page) == 1), f"expected two records, got {len(next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_page.next_token - 1) - next_page.limit) < 0 else (next_page.next_token - 1) - next_page.limit - 1
        >>> prev_page = test_GetMasterPayers.GetMasterPayers(limit=next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 1), f"expected two lobs, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 2), f"expected next_token to equal two, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        if int(limit) < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if name:
            params['field_name'] = 'master_payer_name'
            params['search_input'] = name

        if master_payer_name:
            params['field_name'] = 'master_payer_name'
            params['search_input'] = master_payer_name

        if id:
            params['field_name'] = 'master_payer_id'
            params['search_input'] = id

        if master_payer_id:
            params['field_name'] = 'master_payer_id'
            params['search_input'] = master_payer_id

        if master_payer_ident:
            params['field_name'] = 'master_payer_ident'
            params['search_input'] = master_payer_ident

        if filter_prefix:
            params['fuzzy'] = True
            params['field_name'] = filter_field_name
            params['search_input'] = filter_prefix

        master_payers = self.ReadObjects(api=api,
                                         object_type='MasterPayer',
                                         list_type='MasterPayers',
                                         **params)
        return master_payers

    @xray.capture()
    @logger.log
    def GetMasterPayerByID(self, id: str) -> MasterPayer:
        """Get MasterPayer Detail from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns('iobots-stack')

        >>> test_GetMasterPayerByID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved business unit
        >>> test_GetMasterPayerByID.GetMasterPayerByID("1").get("master_payer_name") is not None
        True

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        master_payers = self.GetMasterPayers(id=id, limit=1)

        self._log.debug(f'single record matches: {len(master_payers)}')
        master_payer = master_payers[0]
        self._log.debug(f'fetched master_payer: {master_payer}')
        master_payer.initialize_clients(
            self._creds, self._log, self._secrets, self)

        return master_payer

    @xray.capture()
    @logger.log
    def GetMasterPayersByIDPrefix(self, id_prefix: str, limit: int = 100) -> MasterPayers:
        """Get MasterPayers from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetMasterPayersByIDPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved master payer
        >>> search = test_GetMasterPayersByIDPrefix.GetMasterPayers(limit=1)

        >>> len(search) >= 1
        True

        Validate retrieved account type
        >>> master_payers = test_GetMasterPayersByIDPrefix.GetMasterPayersByIDPrefix(search[0].get('master_payer_id'))

        >>> len(master_payers) >= 1
        True

        >>> matches = list(master_payer for master_payer in master_payers if master_payer['master_payer_name'].lower() == search[0].get('master_payer_name').lower())

        >>> len(matches) >= 1
        True


        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        master_payers = self.GetMasterPayers(
            filter_field_name='master_payer_id',
            filter_prefix=id_prefix,
            limit=limit)
        self._log.debug(f'raw types: {len(master_payers)}')
        return master_payers

    @xray.capture()
    @logger.log
    def GetMasterPayersByIdentPrefix(self, id_prefix: str, limit: int = 100) -> MasterPayers:
        """Get MasterPayers from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetMasterPayersByIdentPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> search = test_GetMasterPayersByIdentPrefix.GetMasterPayers(limit=1)

        >>> len(search) >= 1
        True

        Validate retrieved account type
        >>> master_payers = test_GetMasterPayersByIdentPrefix.GetMasterPayersByIdentPrefix(search[0].get('master_payer_ident')[0:1])

        >>> len(master_payers) >= 1
        True

        >>> matches = list(master_payer for master_payer in master_payers if master_payer['master_payer_name'].lower() == search[0].get('master_payer_name').lower())

        >>> len(matches) >= 1
        True

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        master_payers = self.GetMasterPayers(
            filter_field_name='master_payer_ident',
            filter_prefix=id_prefix,
            limit=limit)
        self._log.debug(f'raw types: {len(master_payers)}')
        return master_payers

    @xray.capture()
    @logger.log
    def GetMasterPayerByName(self, name: str) -> MasterPayer:
        """Get MasterPayer from SLater by MasterPayer Name
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns('iobots-stack')

        >>> test_GetMasterPayerByName = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved Business Unit
        >>> search = test_GetMasterPayerByName.GetMasterPayers(limit=1)

        >>> len(search) >= 1
        True

        Validate retrieved account type
        >>> master_payer = test_GetMasterPayerByName.GetMasterPayerByName(search[0].get('master_payer_name'))

        >>> master_payer.get('master_payer_name') == search[0].get('master_payer_name')
        True

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        master_payer: MasterPayer = None

        master_payers = self.GetMasterPayers(name=name, limit=1)
        if len(master_payers) == 1:
            self._log.debug(f'single record matches: {len(master_payers)}')
            master_payer = master_payers[0]
            self._log.debug(f'fetched account type: {master_payer}')
            master_payer._log = self._log
            master_payer._creds = self._creds
            master_payer._secrets = self._secrets
            master_payer.client = self.client
        else:
            self._log.debug(f'too many records: {len(master_payers)}')

        return master_payer

    @xray.capture()
    @logger.log
    def GetMasterPayersByNamePrefix(self, name_prefix: str, limit: int = 100) -> MasterPayers:
        """Get AccountTypes from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetMasterPayersByNamePrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> search = test_GetMasterPayersByNamePrefix.GetMasterPayers(limit=1)

        >>> len(search) >= 1
        True

        Validate retrieved account type
        >>> master_payers = test_GetMasterPayersByNamePrefix.GetMasterPayersByNamePrefix(search[0].get('master_payer_name')[0:1])

        >>> len(master_payers) >= 1
        True

        >>> matches = list(master_payer for master_payer in master_payers if master_payer['master_payer_name'].lower() == search[0].get('master_payer_name').lower())

        >>> len(matches) >= 1
        True

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        master_payers = self.GetMasterPayers(
            filter_field_name='master_payer_name',
            filter_prefix=name_prefix,
            limit=limit)
        self._log.debug(f'raw types: {len(master_payers)}')
        return master_payers

    @xray.capture()
    @logger.log
    def GetTowers(self, id: str = "", name: str = "", tower_name: str = "", tower_id: str = "", filter_prefix: str = "", filter_field_name: str = "tower_name", limit: int = 10, next_token: int = -1, api: str = "towers_api") -> Towers:
        """Get Towers list from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_GetTowers = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get towers without arguments
        >>> towers = test_GetTowers.GetTowers()
        >>> assert (len(towers) == 10), f"expected 10 records, got {len(towers)}"
        >>> assert (towers.next_token >= 10), f"expected next_token to equal at least 10, got {towers.next_token}"
        >>> del towers

        Validate we fail when setting limit=-1
        >>> towers = test_GetTowers.GetTowers(limit=-1)
        Traceback (most recent call last):
        ...
        Exception: limit=-1 has been deprecated, only signed values permitted

        Validate get lobs with a limit argument of one should give us pages to navigate
        >>> first_page = test_GetTowers.GetTowers(limit=1)
        >>> assert (len(first_page) == 1), f"expected 1 towers, got {len(first_page)}"
        >>> assert (first_page.next_token == 2), f"expected next_token to equal two, got {first_page.next_token}"

        Step forward and validate pagination
        >>> next_page = test_GetTowers.GetTowers(limit=2, next_token=first_page.next_token)
        >>> assert (next_page.next_token == 4), f"expected next_token to equal four, got {next_page.next_token}"
        >>> assert (len(next_page) == 2), f"expected two records, got {len(next_page)}"

        Step foward again
        >>> next_next_page = test_GetTowers.GetTowers(limit=next_page.limit, next_token=next_page.next_token)
        >>> assert (next_next_page.next_token == 6), f"expected next_token to equal six, got {next_next_page.next_token}"
        >>> assert (len(next_next_page) == 2), f"expected two records, got {len(next_next_page)}"

        Step backward and validate pagination
        >>> inclusive_bound = 0 if ((next_next_page.next_token - 1) - next_next_page.limit) < 0 else (next_next_page.next_token - 1) - next_next_page.limit - 1
        >>> prev_page = test_GetTowers.GetTowers(limit=next_next_page.limit, next_token=inclusive_bound)
        >>> assert (len(prev_page) == 2), f"expected two towers, got {len(prev_page)}"
        >>> assert (prev_page.next_token == 4), f"expected next_token to equal two, got {prev_page.next_token}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        if int(limit) < 0:
            raise Exception(
                'limit=-1 has been deprecated, only signed values permitted')

        params: Dict[str, str] = {
            'limit': limit
        }

        if int(next_token) > -1:
            params['next_token'] = next_token

        if name:
            params['field_name'] = 'tower_name'
            params['search_input'] = name

        if tower_name:
            params['field_name'] = 'tower_name'
            params['search_input'] = tower_name

        if id:
            params['field_name'] = 'tower_id'
            params['search_input'] = id

        if tower_id:
            params['field_name'] = 'tower_id'
            params['search_input'] = tower_id

        if filter_prefix:
            params['fuzzy'] = True
            params['field_name'] = filter_field_name
            params['search_input'] = filter_prefix

        towers = self.ReadObjects(api=api,
                                  object_type='Tower',
                                  list_type='Towers',
                                  **params)
        return towers

    @xray.capture()
    @logger.log
    def GetTowerByID(self, id: str) -> Tower:
        """Get Tower Detail from SLater by ID
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns('iobots-stack')
        >>> test_GetTowerByID = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved tower
        >>> tower = test_GetTowerByID.GetTowerByID("1")
        >>> tower.get('tower_name').title()
        'Eiffel Tower'

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        towers = self.GetTowers(id=id, limit=1)

        self._log.debug(f'single record matches: {len(towers)}')
        tower = towers[0]
        self._log.debug(f'fetched lob: {tower}')
        tower.initialize_clients(
            self._creds, self._log, self._secrets, self)

        return tower

    @xray.capture()
    @logger.log
    def GetTowersByIDPrefix(self, id_prefix: str, limit: int = 100) -> Towers:
        """Get LOBs from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetTowersByIDPrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved lob
        >>> towers = test_GetTowersByIDPrefix.GetTowersByIDPrefix("1")

        >>> len(towers) >= 1
        True

        >>> matches = list(towers for tower in towers if tower['tower_name'] == 'Eiffel Tower')

        >>> len(matches)
        1

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        towers = self.GetTowers(
            filter_field_name='tower_id', filter_prefix=id_prefix, limit=limit)
        self._log.debug(f'raw towers: {len(towers)}')
        return towers

    @xray.capture()
    @logger.log
    def GetTowersByNamePrefix(self, id_prefix: str, limit: int = 100) -> Towers:
        """Get LOBs from SLater by ID prefix
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_GetTowersByNamePrefix = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> search = test_GetTowersByNamePrefix.GetTowers(limit=1)

        >>> len(search) == 1
        True

        Validate retrieved lob
        >>> towers = test_GetTowersByNamePrefix.GetTowersByNamePrefix(search[0].get('tower_name'))

        >>> len(towers) >= 1
        True

        >>> matches = list(towers for tower in towers if tower['tower_name'] == search[0].get('tower_name'))

        >>> len(matches)
        1

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        towers = self.GetTowers(
            filter_field_name='tower_name', filter_prefix=id_prefix, limit=limit)
        self._log.debug(f'raw towers: {len(towers)}')
        return towers

    @xray.capture()
    @logger.log
    def GetTowerByName(self, name: str) -> Tower:
        """Get Tower Detail from SLater by Tower Name
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns('iobots-stack')
        >>> test_GetTowerByName = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate retrieved Tower
        >>> tower = test_GetTowerByName.GetTowerByName("Eiffel Tower")
        >>> tower.get('tower_name').title()
        'Eiffel Tower'

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """
        tower: Tower = None

        towers = self.GetTowers(name=name)
        if len(towers) == 1:
            self._log.debug(f'single record matches: {len(towers)}')
            tower = towers[0]
            self._log.debug(f'fetched application: {tower}')
            tower._log = self._log
            tower._creds = self._creds
            tower._secrets = self._secrets
            tower.client = self.client
        else:
            self._log.debug(f'too many records: {len(tower)}')

        return tower

    @xray.capture()
    @logger.log
    def GetSLaterAccountsListView(self, log, slack_client, data: Accounts = [], metadata: dict = None, pages: list = [], request: dict = None) -> SLaterAccountsListView:
        """Validate that we can render a modal view from an account list generated by SLater.GetAccounts()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterAccountsListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get accounts with a limit argument of one should give us pages to navigate
        >>> accounts = test_GetSLaterAccountsListView.GetAccounts(limit=2)
        >>> assert (len(accounts) == 2), f"expected to find two, found: {len(accounts)}"
        >>> acct_list_modal = test_GetSLaterAccountsListView.GetSLaterAccountsListView(log, agent, data=accounts)
        >>> assert (len(acct_list_modal.data) == 5), f"expected to find five, found: {len(acct_list_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterAccountsListView(
                log, slack_client, accounts=data, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterAccountTypesListView(self, log, slack_client, data: AccountTypes = [], metadata: dict = None, pages: list = [], request: dict = None) -> SLaterAccountTypesListView:
        """Validate that we can render a modal view from an account list generated by SLater.GetAccountTypes()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

        >>> test_GetSLaterAccountTypesListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get account types with a limit argument of one should give us pages to navigate
        >>> account_types = test_GetSLaterAccountTypesListView.GetAccountTypes(limit=2)

        >>> len(account_types)
        2

        >>> acct_type_list_modal = test_GetSLaterAccountTypesListView.GetSLaterAccountTypesListView(log, agent, data=account_types)

        >>> len(acct_type_list_modal.data)
        5

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterAccountTypesListView(
                log, slack_client, data=data, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterApplicationsListView(self, log, slack_client, data: Applications = [], metadata: dict = None, pages: list = [],  request: dict = None) -> SLaterApplicationsListView:
        """Validate that we can render a modal view from an application list generated by SLater.GetApplications()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterApplicationsListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get applications with a limit argument of one should give us pages to navigate
        >>> applications = test_GetSLaterApplicationsListView.GetApplications(limit=2)
        >>> assert (len(applications) == 2), f"expected to find two, found: {len(applications)}"
        >>> app_list_modal = test_GetSLaterApplicationsListView.GetSLaterApplicationsListView(log, agent, data=applications)
        >>> assert (len(app_list_modal.data) == 5), f"expected to find five, found: {len(app_list_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterApplicationsListView(
                log, slack_client, applications=data, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterApplicationContactsListView(self, log, slack_client, data: ApplicationContacts = [], metadata: dict = None, pages: list = [], request: dict = None) -> SLaterApplicationContactsListView:
        """Validate that we can render a modal view from an application contact list generated by SLater.GetApplicationContacts()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterApplicationContactsListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get application contacts with a limit argument of one should give us pages to navigate
        >>> application_contacts = test_GetSLaterApplicationContactsListView.GetApplicationContacts(limit=2)
        >>> assert (len(application_contacts) == 2), f"expected to find two, found: {len(application_contacts)}"
        >>> app_contact_list_modal = test_GetSLaterApplicationContactsListView.GetSLaterApplicationContactsListView(log, agent, data=application_contacts)
        >>> assert (len(app_contact_list_modal.data) == 5), f"expected to find five, found: {len(app_contact_list_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterApplicationContactsListView(
                log, slack_client, data=data, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterBatchErrorsListView(self, log, slack_client, data: BatchErrors = [], metadata: dict = None, pages: list = [], request: dict = None) -> SLaterBatchErrorsListView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterBatchErrorsListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> errors = test_GetSLaterBatchErrorsListView.GetBatchImportErrors(batch_load_id=719, slater_user=request.get('submitter').get('name'))
        >>> assert (len(errors) == 6), f"expected to find two, found: {len(errors)}"
        >>> batch_errors_modal = test_GetSLaterBatchErrorsListView.GetSLaterBatchErrorsListView(log, agent, data=errors)
        >>> assert (len(batch_errors_modal.data) == 5), f"expected to find five, found: {len(batch_errors_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterBatchErrorsListView(
                log, slack_client, batch_errors=data, pages=pages, metadata=metadata)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterBatchLoadsListView(self, log, slack_client, data: BatchLoads = [], metadata: dict = None, pages: list = [], request: dict = None) -> SLaterBatchLoadsListView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterBatchLoadsListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        # status = test_GetSLaterBatchLoadsListView.GetBatchImportStatus(group_batch_uuid=uuid.UUID('{54701b1b-c2a5-441a-931f-5e1156a6402a}'), slater_user=request.get('submitter').get('name'))
        >>>

        >>> # batch_loads = status[0].get('batch_loads')

        >>> batch_loads = BatchLoads([ \
          BatchLoad(batch_load_id=1709, group_batch_load_id=440, s3_key='inbound/54701b1b-c2a5-441a-931f-5e1156a6402a/applications.csv', status='Complete', slater_update_user='U0EKDSZE2', load_date='2020-10-09', processed_date='2020-10-09', num_errors=2412, next_link='https=//api-dev.pearsoncloudmanagement.com/slater/batcherrors?batch_load_id=1709'), \
          BatchLoad(batch_load_id=1708, group_batch_load_id=440, s3_key='inbound/54701b1b-c2a5-441a-931f-5e1156a6402a/application_contacts.csv', status='Complete', slater_update_user='U0EKDSZE2', load_date='2020-10-09', processed_date='2020-10-09', num_errors=7236, next_link='https=//api-dev.pearsoncloudmanagement.com/slater/batcherrors?batch_load_id=1708'), \
          BatchLoad(batch_load_id=1707, group_batch_load_id=440, s3_key='inbound/54701b1b-c2a5-441a-931f-5e1156a6402a/accounts.csv', status='Complete', slater_update_user='U0EKDSZE2', load_date='2020-10-09', processed_date='2020-10-09', num_errors=584, next_link='https=//api-dev.pearsoncloudmanagement.com/slater/batcherrors?batch_load_id=1707'), \
          BatchLoad(batch_load_id=1710, group_batch_load_id=440, s3_key='inbound/54701b1b-c2a5-441a-931f-5e1156a6402a/account_contacts.csv', status='Complete', slater_update_user='U0EKDSZE2', load_date='2020-10-09', processed_date='2020-10-09', num_errors=3504, next_link='https=//api-dev.pearsoncloudmanagement.com/slater/batcherrors?batch_load_id=1710')])

        >>> batch_loads[0].get('s3_key')
        'inbound/54701b1b-c2a5-441a-931f-5e1156a6402a/applications.csv'

        >>> len(batch_loads)
        4

        >>> batch_loads_modal = test_GetSLaterBatchLoadsListView.GetSLaterBatchLoadsListView(log, agent, data=batch_loads)
        >>> assert (len(batch_loads_modal.data) == 5), f"expected to find five, found: {len(batch_loads_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterBatchLoadsListView(
                log, slack_client, batch_loads=data, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterBusinessUnitsListView(self, log, slack_client, data: BusinessUnits = [], metadata: dict = None, pages: list = [], request: dict = None) -> SLaterBusinessUnitsListView:
        """Validate that we can render a modal view from an BusinessUnits list generated by SLater.GetBusinessUnits()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

        >>> test_GetSLaterBusinessUnitsListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get business units with a limit argument of one should give us pages to navigate
        >>> business_units = test_GetSLaterBusinessUnitsListView.GetBusinessUnits(limit=2)

        >>> len(business_units)
        2

        >>> business_unit_list_modal = test_GetSLaterBusinessUnitsListView.GetSLaterBusinessUnitsListView(log, agent, data=business_units)

        >>> len(business_unit_list_modal.data)
        5
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterBusinessUnitsListView(
                log, slack_client, data=data, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactsListView(self, log, slack_client, data: Contacts = [], metadata: dict = None, pages: list = [], request: dict = None) -> SLaterContactsListView:
        """Validate that we can render a modal view from an contact list generated by SLater.GetContacts()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterContactsListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contacts with a limit argument of one should give us pages to navigate
        >>> contacts = test_GetSLaterContactsListView.GetContacts(limit=2)
        >>> assert (len(contacts) == 2), f"expected to find two, found: {len(contacts)}"
        >>> contact_list_modal = test_GetSLaterContactsListView.GetSLaterContactsListView(log, agent, data=contacts)
        >>> assert (len(contact_list_modal.data) == 5), f"expected to find five, found: {len(contact_list_modal.data)}"

        Return this ModalView as a MessageView
        >>> msg = contact_list_modal.to_slack_message()
        >>> assert(len(msg['blocks']) == 11), f"expected 11 blocks, got {len(msg['blocks'])}"

        We should just have blocks, no data
        >>> msg['data'] is None
        True

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactsListView(
                log, slack_client, contacts=data, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactTypesListView(self, log, slack_client, data: ContactTypes = [], metadata: dict = None, pages: list = [], request: dict = None) -> SLaterContactTypesListView:
        """Validate that we can render a modal view from an contact type list generated by SLater.GetContactTypes()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterContactTypesListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact_types with a limit argument of one should give us pages to navigate
        >>> contact_types = test_GetSLaterContactTypesListView.GetContactTypes(limit=2)
        >>> assert (len(contact_types) == 2), f"expected to find two, found: {len(contact_types)}"
        >>> contact_type_list_modal = test_GetSLaterContactTypesListView.GetSLaterContactTypesListView(log, agent, data=contact_types)
        >>> len(contact_type_list_modal.data)
        5

        Return this ModalView as a MessageView
        >>> msg = contact_type_list_modal.to_slack_message()
        >>> len(msg['blocks'])
        11

        >>> msg['data'] is None
        True

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactTypesListView(
                log, slack_client, data=data, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterCustomFieldsListView(self, log, slack_client, data: CustomFields = [], account: Account = None, application: Application = None, metadata: dict = None, pages: list = [], request: dict = None) -> SLaterCustomFieldsListView:
        """Validate that we can render a modal view from an account list generated by SLater.GetAccounts()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterCustomFieldsListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get accounts with a limit argument of one should give us pages to navigate
        >>> custom_fields = test_GetSLaterCustomFieldsListView.GetCustomFieldsByCloudAccountID(cloud_account_id=1)
        >>> assert (len(custom_fields) > 0), f"expected to customfields, found: {len(custom_fields)}"
        >>> custom_fields_list_modal = test_GetSLaterCustomFieldsListView.GetSLaterCustomFieldsListView(log, agent, data=custom_fields)
        >>> assert (len(custom_fields_list_modal.data) == 5), f"expected to find five, found: {len(custom_fields_list_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterCustomFieldsListView(
                log, slack_client, custom_fields=data, account=account, application=application, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterHomePageView(self, log, slack_client) -> SLaterHome:
        """Validate that we can render a modal view from an application list generated by SLater.GetApplications()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterHomePageView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get the structure of home page we expect
        >>> app_home = test_GetSLaterHomePageView.GetSLaterHomePageView(log, agent)
        >>> assert (len(app_home.data) == 2), f"expected to find two, found: {len(app_home.data)}"
        """
        return SLaterHome(log, slack_client)

    @xray.capture()
    @logger.log
    def GetSLaterLOBsListView(self, log, slack_client, data: LOBs = [], metadata: dict = None, pages: list = [], request: dict = None) -> SLaterLOBsListView:
        """Validate that we can render a modal view from an lob list generated by SLater.GetLOBs()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

        >>> test_GetSLaterLOBsListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact_types with a limit argument of one should give us pages to navigate
        >>> lobs = test_GetSLaterLOBsListView.GetLOBs(limit=2)

        >>> len(lobs)
        2

        >>> lob_list_modal = test_GetSLaterLOBsListView.GetSLaterLOBsListView(log, agent, data=lobs)

        >>> len(lob_list_modal.data)
        5

        Return this ModalView as a MessageView
        >>> msg = lob_list_modal.to_slack_message()

        >>> len(msg['blocks'])
        11

        >>> msg['data'] is None
        True
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterLOBsListView(
                log, slack_client, data=data, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterMasterPayersListView(self, log, slack_client, data: MasterPayers = [], metadata: dict = None, pages: list = [], request: dict = None) -> SLaterMasterPayersListView:
        """Validate that we can render a modal view from an MasterPayer list generated by SLater.GetMasterPayers()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

        >>> test_GetSLaterMasterPayersListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get MasterPayer with a limit argument of one should give us pages to navigate
        >>> master_payers = test_GetSLaterMasterPayersListView.GetMasterPayers(limit=2)

        >>> len(master_payers)
        2

        >>> master_payer_list_modal = test_GetSLaterMasterPayersListView.GetSLaterMasterPayersListView(log, agent, data=master_payers)

        >>> len(master_payer_list_modal.data)
        5

        Return this ModalView as a MessageView
        >>> msg = master_payer_list_modal.to_slack_message()

        >>> len(msg['blocks'])
        11

        >>> msg['data']is None
        True
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterMasterPayersListView(
                log, slack_client, data=data, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterTowersListView(self, log, slack_client, data: Towers = [], metadata: dict = None, pages: list = [], request: dict = None) -> SLaterTowersListView:
        """Validate that we can render a modal view from an tower list generated by SLater.GetTowers()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

        >>> test_GetSLaterTowersListView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact_types with a limit argument of one should give us pages to navigate
        >>> towers = test_GetSLaterTowersListView.GetTowers(limit=2)

        >>> len(towers)
        2

        >>> tower_list_modal = test_GetSLaterTowersListView.GetSLaterTowersListView(log, agent, data=towers)

        >>> len(tower_list_modal.data)
        5

        Return this ModalView as a MessageView
        >>> msg = tower_list_modal.to_slack_message()

        >>> len(msg['blocks'])
        11

        >>> msg['data'] is None
        True
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterTowersListView(
                log, slack_client, data=data, metadata=metadata, pages=pages, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterAccountCreateView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterAccountCreateView:
        """Validate that we can render a modal view from an account list generated by SLater.GetAccounts()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterAccountCreateView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get account with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> account_create_modal = test_GetSLaterAccountCreateView.GetSLaterAccountCreateView(log, client, request=request)

        >>> len(account_create_modal.data['blocks'])
        17
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterAccountCreateView(
                log, slack_client, slater_object=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterAccountTypeCreateView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterAccountTypeCreateView:
        """Validate that we can render a modal view from an account_type list generated by SLater.GetAccountTypes()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterAccountTypeCreateView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get account_type with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> account_type_create_modal = test_GetSLaterAccountTypeCreateView.GetSLaterAccountTypeCreateView(log, client, request=request)

        >>> len(account_type_create_modal.data['blocks'])
        3

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterAccountTypeCreateView(
                log, slack_client, slater_object=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterApplicationCreateView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterApplicationCreateView:
        """Validate that we can render a modal view from an application list generated by SLater.GetApplications()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterApplicationCreateView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get application with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> application_create_modal = test_GetSLaterApplicationCreateView.GetSLaterApplicationCreateView(log, client, request=request)

        >>> len(application_create_modal.data['blocks'])
        11
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterApplicationCreateView(
                log, slack_client, slater_object=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterBusinessUnitCreateView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterBusinessUnitCreateView:
        """Validate that we can render a modal view from an business unit list generated by SLater.GetBusinessUnits()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterBusinessUnitCreateView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get business_unit with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> business_unit_create_modal = test_GetSLaterBusinessUnitCreateView.GetSLaterBusinessUnitCreateView(log, client, request=request)

        >>> len(business_unit_create_modal.data['blocks'])
        3
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterBusinessUnitCreateView(
                log, slack_client, slater_object=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactCreateView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterContactCreateView:
        """Validate that we can render a modal view from an contact list generated by SLater.GetContacts()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterContactCreateView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> contact_create_modal = test_GetSLaterContactCreateView.GetSLaterContactCreateView(log, client, request=request)

        >>> len(contact_create_modal.data['blocks'])
        4

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactCreateView(
                log, slack_client, slater_object=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactTypeCreateView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterContactTypeCreateView:
        """Validate that we can render a modal view from an contact_type list generated by SLater.GetContactTypes()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterContactTypeCreateView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact type with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> contact_type_create_modal = test_GetSLaterContactTypeCreateView.GetSLaterContactTypeCreateView(log, client, request=request)

        >>> len(contact_type_create_modal.data['blocks'])
        3

        >>> contact_type_create_modal.data['blocks'][2]['block_id']
        'contact_type'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactTypeCreateView(
                log, slack_client, slater_object=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterLOBCreateView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterLOBCreateView:
        """Validate that we can render a modal view from an lob list generated by SLater.GetLOBs()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterLOBCreateView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get lob with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> lob_create_modal = test_GetSLaterLOBCreateView.GetSLaterLOBCreateView(log, client, request=request)

        >>> len(lob_create_modal.data['blocks'])
        3

        >>> lob_create_modal.data['blocks'][2]['block_id']
        'governance_lob_name'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterLOBCreateView(
                log, slack_client, slater_object=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterMasterPayerCreateView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterMasterPayerCreateView:
        """Validate that we can render a modal view from an master_payer list generated by SLater.GetMasterPayers()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterMasterPayerCreateView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get master_payer with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> master_payer_create_modal = test_GetSLaterMasterPayerCreateView.GetSLaterMasterPayerCreateView(log, client, request=request)

        >>> len(master_payer_create_modal.data['blocks'])
        3

        >>> master_payer_create_modal.data['blocks'][2]['block_id']
        'master_payer_name'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterMasterPayerCreateView(
                log, slack_client, slater_object=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterTowerCreateView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterTowerCreateView:
        """Validate that we can render a modal view from an tower list generated by SLater.GetTowers()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterTowerCreateView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get tower with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> tower_create_modal = test_GetSLaterTowerCreateView.GetSLaterTowerCreateView(log, client, request=request)
        >>> len(tower_create_modal.data['blocks'])
        4
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterTowerCreateView(
                log, slack_client, slater_object=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetCloudDeliveryServicesAccountDetailView(self, log, slack_client, data=None, contacts=None, applications=None, request=None) -> CloudDeliveryServicesAccountDetailView:
        """Validate that we can render a modal view from an application list generated by SLater.GetApplications()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetCloudDeliveryServicesAccountDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get application with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> atari_name = test_GetCloudDeliveryServicesAccountDetailView.GetAccountByName("atari")
        >>> account = test_GetCloudDeliveryServicesAccountDetailView.GetAccountByCloudAccountNumber(atari_name.get('cloud_account_number'))

        >>> '23456789' in account.get('cloud_account_number')
        True

        >>> contacts = test_GetCloudDeliveryServicesAccountDetailView.GetContactsByCloudAccountNumber(account.get('cloud_account_number'))

        >>> applications = test_GetCloudDeliveryServicesAccountDetailView.GetApplicationsByCloudAccountNumber(account.get('cloud_account_number'))

        >>> acct_detail_modal = test_GetCloudDeliveryServicesAccountDetailView.GetCloudDeliveryServicesAccountDetailView(log, client, data=account, contacts=contacts, applications=applications, request=request)

        >>> len(acct_detail_modal.data['blocks']) >= 15
        True

        Negative test with broken data
        >>> del contacts[0]['contact_type']

        >>> acct_detail_modal = test_GetCloudDeliveryServicesAccountDetailView.GetCloudDeliveryServicesAccountDetailView(log, client, data=account, contacts=contacts, applications=applications, request=request)

        >>> blocks = acct_detail_modal.to_slack_message().Blocks()

        >>> len(acct_detail_modal.data['blocks']) >= 14
        True

        """
        if not request:
            request = self.GetSLaterRequest()
        view: ModalView = None
        try:
            log.debug(f'generating cds view for request {request}')
            view = CloudDeliveryServicesAccountDetailView(
                log, slack_client, account=data, contacts=contacts, applications=applications, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating cds view for request {err}')
            view = SLaterSlashCommandErrorView(
                log, slack_client, data=f'encountered error {err} while retrieving cds view', request=request)

        return view

    @xray.capture()
    @logger.log
    def GetCloudDeliveryServicesApplicationDetailView(self, log, slack_client, data=None, contacts=None, accounts=None, request=None) -> CloudDeliveryServicesApplicationDetailView:
        """Validate that we can render a modal view from an application list generated by SLater.GetApplications()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetCloudDeliveryServicesApplicationDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get application with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> application = test_GetCloudDeliveryServicesApplicationDetailView.GetApplicationByID(id=1)

        >>> application.get('application_id')
        1

        >>> contacts = test_GetCloudDeliveryServicesApplicationDetailView.GetContactsByApplicationID(application.get('application_id'))

        >>> contacts[0]['contact_type'] = 'Service VP'
        >>> contacts[1]['contact_type'] = 'Business Contact'

        >>> accounts = test_GetCloudDeliveryServicesApplicationDetailView.GetAccountsByTAppID(application.get('t_appid'))

        >>> app_detail_modal = test_GetCloudDeliveryServicesApplicationDetailView.GetCloudDeliveryServicesApplicationDetailView(log, client, data=application, contacts=contacts, accounts=accounts, request=request)

        >>> blocks = app_detail_modal.to_slack_message().Blocks()

        >>> len(blocks)
        11

        >>> 'Service' in blocks[5]['text']['text']
        True

        Negative test with broken data
        >>> del contacts[0]['contact_type']

        >>> app_detail_modal = test_GetCloudDeliveryServicesApplicationDetailView.GetCloudDeliveryServicesApplicationDetailView(log, client, data=application, contacts=contacts, accounts=accounts, request=request)

        >>> blocks = app_detail_modal.to_slack_message().Blocks()

        >>> 'Service' in blocks[5]['text']['text']
        False

        >>> contacts[0] = None
        >>> contacts[1] = None

        >>> app_detail_modal = test_GetCloudDeliveryServicesApplicationDetailView.GetCloudDeliveryServicesApplicationDetailView(log, client, data=application, contacts=contacts, accounts=accounts, request=request)

        >>> blocks = app_detail_modal.to_slack_message().Blocks()

        >>> blocks[4]
         {'type': 'header', 'text': {'type': 'plain_text', 'text': 'Accounts', 'emoji': True}}

        >>> len(blocks)
        7

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating cds view for request {request}')
            view = CloudDeliveryServicesApplicationDetailView(
                log, slack_client, application=data, contacts=contacts, accounts=accounts, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating cds view for request {err}')
            view = SLaterSlashCommandErrorView(
                log, slack_client, data=f'encountered error: {err} while retrieving cds view', request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterAccountDeleteView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterAccountDeleteView:
        """Validate we can delete, grab the first record from a Get() and test
           we get a delete view when we pass it.

        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterDeleteView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> data = test_GetSLaterDeleteView.GetAccounts(limit=1)

        >>> assert (len(data) == 1), f"expected to find one, found: {len(data)}"

        >>> delete_modal = test_GetSLaterDeleteView.GetSLaterAccountDeleteView(log, client, data=data[0], request=request)

        >>> len(delete_modal.data['blocks'])
        4

        >>> delete_modal.data['blocks'][3]['element']['options'][0]['text']['text']
        '*Yes*, _I understand that this will *delete* the record and this action *cannot* be undone._'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterAccountDeleteView(log,
                                           slack_client,
                                           data=data,
                                           request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterAccountTypeDeleteView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterAccountTypeDeleteView:
        """Validate we can delete, grab the first record from a Get() and test
           we get a delete view when we pass it.

        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterDeleteView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> data = test_GetSLaterDeleteView.GetAccountTypes(limit=1)

        >>> assert (len(data) == 1), f"expected to find one, found: {len(data)}"

        >>> delete_modal = test_GetSLaterDeleteView.GetSLaterAccountTypeDeleteView(log, client, data=data[0], request=request)

        >>> len(delete_modal.data['blocks'])
        4

        >>> delete_modal.data['blocks'][3]['element']['options'][0]['text']['text']
        '*Yes*, _I understand that this will *delete* the record and this action *cannot* be undone._'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterAccountTypeDeleteView(log,
                                               slack_client,
                                               data=data,
                                               request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterApplicationDeleteView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterApplicationDeleteView:
        """Validate we can delete, grab the first record from a Get() and test
           we get a delete view when we pass it.

        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterDeleteView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> data = test_GetSLaterDeleteView.GetApplications(limit=1)

        >>> assert (len(data) == 1), f"expected to find one, found: {len(data)}"

        >>> delete_modal = test_GetSLaterDeleteView.GetSLaterApplicationDeleteView(log, client, data=data[0], request=request)

        >>> len(delete_modal.data['blocks'])
        4

        >>> delete_modal.data['blocks'][3]['element']['options'][0]['text']['text']
        '*Yes*, _I understand that this will *delete* the record and this action *cannot* be undone._'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterApplicationDeleteView(log,
                                               slack_client,
                                               data=data,
                                               request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterApplicationContactDeleteView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterApplicationContactDeleteView:
        """Validate we can delete, grab the first record from a Get() and test
           we get a delete view when we pass it.

        >>> skip('talk with jonathan')
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterDeleteView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> data = test_GetSLaterDeleteView.GetApplicationContacts(limit=1)

        >>> assert (len(data) == 1), f"expected to find one, found: {len(data)}"

        >>> delete_modal = test_GetSLaterDeleteView.GetSLaterApplicationContactDeleteView(log, client, data=data[0], request=request)

        >>> len(delete_modal.data['blocks'])
        4

        >>> delete_modal.data['blocks'][3]['element']['options'][0]['text']['text']
        '*Yes*, _I understand that this will *delete* the record and this action *cannot* be undone._'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterApplicationContactDeleteView(log,
                                                      slack_client,
                                                      data=data,
                                                      request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactDeleteView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterContactDeleteView:
        """Validate we can delete, grab the first record from a Get() and test
           we get a delete view when we pass it.

        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterDeleteView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> data = test_GetSLaterDeleteView.GetContacts(limit=1)

        >>> assert (len(data) == 1), f"expected to find one, found: {len(data)}"

        >>> delete_modal = test_GetSLaterDeleteView.GetSLaterContactDeleteView(log, client, data=data[0], request=request)

        >>> len(delete_modal.data['blocks'])
        4

        >>> delete_modal.data['blocks'][3]['element']['options'][0]['text']['text']
        '*Yes*, _I understand that this will *delete* the record and this action *cannot* be undone._'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactDeleteView(log,
                                           slack_client,
                                           data=data,
                                           request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactTypeDeleteView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterContactTypeDeleteView:
        """Validate we can delete, grab the first record from a Get() and test
           we get a delete view when we pass it.

        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterDeleteView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> data = test_GetSLaterDeleteView.GetContactTypes(limit=1)

        >>> assert (len(data) == 1), f"expected to find one, found: {len(data)}"

        >>> delete_modal = test_GetSLaterDeleteView.GetSLaterContactTypeDeleteView(log, client, data=data[0], request=request)

        >>> len(delete_modal.data['blocks'])
        4

        >>> delete_modal.data['blocks'][3]['element']['options'][0]['text']['text']
        '*Yes*, _I understand that this will *delete* the record and this action *cannot* be undone._'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactTypeDeleteView(log,
                                               slack_client,
                                               data=data,
                                               request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterLOBDeleteView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterLOBDeleteView:
        """Validate we can delete, grab the first record from a Get() and test
           we get a delete view when we pass it.

        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterDeleteView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> data = test_GetSLaterDeleteView.GetLOBs(limit=1)

        >>> assert (len(data) == 1), f"expected to find one, found: {len(data)}"

        >>> delete_modal = test_GetSLaterDeleteView.GetSLaterLOBDeleteView(log, client, data=data[0], request=request)

        >>> len(delete_modal.data['blocks'])
        4

        >>> delete_modal.data['blocks'][3]['element']['options'][0]['text']['text']
        '*Yes*, _I understand that this will *delete* the record and this action *cannot* be undone._'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterLOBDeleteView(log,
                                       slack_client,
                                       data=data,
                                       request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterMasterPayerDeleteView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterMasterPayerDeleteView:
        """Validate we can delete, grab the first record from a Get() and test
           we get a delete view when we pass it.

        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterDeleteView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> data = test_GetSLaterDeleteView.GetMasterPayers(limit=1)

        >>> assert (len(data) == 1), f"expected to find one, found: {len(data)}"

        >>> delete_modal = test_GetSLaterDeleteView.GetSLaterMasterPayerDeleteView(log, client, data=data[0], request=request)

        >>> len(delete_modal.data['blocks'])
        4

        >>> delete_modal.data['blocks'][3]['element']['options'][0]['text']['text']
        '*Yes*, _I understand that this will *delete* the record and this action *cannot* be undone._'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterMasterPayerDeleteView(log,
                                               slack_client,
                                               data=data,
                                               request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterTowerDeleteView(self, log, slack_client, data: dict = None, request: dict = None) -> SLaterTowerDeleteView:
        """Validate we can delete, grab the first record from a Get() and test
           we get a delete view when we pass it.

        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterDeleteView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> data = test_GetSLaterDeleteView.GetTowers(limit=1)

        >>> assert (len(data) == 1), f"expected to find one, found: {len(data)}"

        >>> delete_modal = test_GetSLaterDeleteView.GetSLaterTowerDeleteView(log, client, data=data[0], request=request)

        >>> len(delete_modal.data['blocks'])
        4

        >>> delete_modal.data['blocks'][3]['element']['options'][0]['text']['text']
        '*Yes*, _I understand that this will *delete* the record and this action *cannot* be undone._'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterTowerDeleteView(log,
                                         slack_client,
                                         data=data,
                                         request=request)

        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterAccountDetailView(self, log, slack_client, data=None, request=None) -> SLaterAccountDetailView:
        """Validate that we can render a modal view from an account list generated by SLater.GetAccounts()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterAccountDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get account with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> accounts = test_GetSLaterAccountDetailView.GetAccounts(limit=1)
        >>> assert (len(accounts) == 1), f"expected to find one, found: {len(accounts)}"
        >>> acct_detail_modal = test_GetSLaterAccountDetailView.GetSLaterAccountDetailView(log, client, data=accounts[0], request=request)
        >>> len(acct_detail_modal.data['blocks'])
        3
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterAccountDetailView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterAccountTypeDetailView(self, log, slack_client, data=None, request=None) -> SLaterAccountTypeDetailView:
        """Validate that we can render a modal view from an account type list generated by SLater.GetAccounts()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterAccountTypeDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get account with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> account_types = test_GetSLaterAccountTypeDetailView.GetAccountTypes(limit=1)

        >>> len(account_types)
        1
        >>> acct_type_detail_modal = test_GetSLaterAccountTypeDetailView.GetSLaterAccountTypeDetailView(log, client, data=account_types[0], request=request)

        >>> len(acct_type_detail_modal.data['blocks'])
        3
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterAccountTypeDetailView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterApplicationDetailView(self, log, slack_client, data=None, request=None) -> SLaterApplicationDetailView:
        """Validate that we can render a modal view from an application list generated by SLater.GetApplications()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterApplicationDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get application with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> applications = test_GetSLaterApplicationDetailView.GetApplications(limit=1)

        >>> len(applications)
        1
        >>> app_detail_modal = test_GetSLaterApplicationDetailView.GetSLaterApplicationDetailView(log, client, data=applications[0], request=request)

        >>> len(app_detail_modal.data['blocks'])
        3
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterApplicationDetailView(
                log, slack_client, application=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterApplicationContactDetailView(self, log, slack_client, data=None, request=None) -> SLaterApplicationContactDetailView:
        """Validate that we can render a modal view from an application contact list generated by SLater.GetApplicationContacts()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterApplicationContactDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get application contact with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> application_contacts = test_GetSLaterApplicationContactDetailView.GetApplicationContacts(limit=1)

        >>> assert (len(application_contacts) == 1), f"expected to find one, found: {len(application_contacts)}"

        >>> app_contact_detail_modal = test_GetSLaterApplicationContactDetailView.GetSLaterApplicationContactDetailView(log, client, data=application_contacts[0], request=request)

        >>> len(app_contact_detail_modal.data['blocks'])
        3
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterApplicationContactDetailView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterBusinessUnitDetailView(self, log, slack_client, data=None, request=None) -> SLaterBusinessUnitDetailView:
        """Validate that we can render a modal view from an business unit list generated by SLater.GetBusinesUnits()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterBusinessUnitDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get business unit with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> business_units = test_GetSLaterBusinessUnitDetailView.GetBusinessUnits(limit=1)

        >>> assert (len(business_units) == 1), f"expected to find one, found: {len(business_units)}"

        >>> business_unit_detail_modal = test_GetSLaterBusinessUnitDetailView.GetSLaterBusinessUnitDetailView(log, client, data=business_units[0], request=request)

        >>> len(business_unit_detail_modal.data['blocks'])
        3
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterBusinessUnitDetailView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactDetailView(self, log, slack_client, data=None, request=None) -> SLaterContactDetailView:
        """Validate that we can render a modal view from an contact list generated by SLater.GetContacts()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterContactDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> contacts = test_GetSLaterContactDetailView.GetContacts(limit=1)

        >>> len(contacts)
        1

        >>> contact_detail_modal = test_GetSLaterContactDetailView.GetSLaterContactDetailView(log, client, data=contacts[0], request=request)

        >>> len(contact_detail_modal.data['blocks'])
        3
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactDetailView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactTypeDetailView(self, log, slack_client, data=None, request=None) -> SLaterContactTypeDetailView:
        """Validate that we can render a modal view from an contact type list generated by SLater.GetContactTypes()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterContactTypeDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact type with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> contact_types = test_GetSLaterContactTypeDetailView.GetContactTypes(limit=1)
        >>> assert (len(contact_types) == 1), f"expected to find one, found: {len(contact_types)}"
        >>> contact_type_detail_modal = test_GetSLaterContactTypeDetailView.GetSLaterContactTypeDetailView(log, client, data=contact_types[0], request=request)
        >>> len(contact_type_detail_modal.data['blocks'])
        3
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactTypeDetailView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterLOBDetailView(self, log, slack_client, data=None, request=None) -> SLaterLOBDetailView:
        """Validate that we can render a modal view from an lob list generated by SLater.GetLOBs()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterLOBDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get lob with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> lobs = test_GetSLaterLOBDetailView.GetLOBs(limit=1)

        >>> len(lobs)
        1

        >>> lob_detail_modal = test_GetSLaterLOBDetailView.GetSLaterLOBDetailView(log, client, data=lobs[0], request=request)

        >>> len(lob_detail_modal.data['blocks'])
        3

        >>> lob_detail_modal.data['blocks'][0]['block_id'].startswith('slater_lob_action-')
        True

        >>> lob_detail_modal.data['blocks'][0]['block_id'].split('-')[1].isdigit()
        True

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterLOBDetailView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterMasterPayerDetailView(self, log, slack_client, data=None, request=None) -> SLaterMasterPayerDetailView:
        """Validate that we can render a modal view from an master payer list generated by SLater.GetMasterPayers()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterMasterPayerDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get master_payer with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> master_payers = test_GetSLaterMasterPayerDetailView.GetMasterPayers(limit=1)

        >>> len(master_payers)
        1

        >>> master_payer_detail_modal = test_GetSLaterMasterPayerDetailView.GetSLaterMasterPayerDetailView(log, client, data=master_payers[0], request=request)

        >>> len(master_payer_detail_modal.data['blocks'])
        3
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterMasterPayerDetailView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterTowerDetailView(self, log, slack_client, data=None, request=None) -> SLaterTowerDetailView:
        """Validate that we can render a modal view from an tower list generated by SLater.GetTowers()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterTowerDetailView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get tower with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> towers = test_GetSLaterTowerDetailView.GetTowers(limit=1)
        >>> assert (len(towers) == 1), f"expected to find one, found: {len(towers)}"
        >>> tower_detail_modal = test_GetSLaterTowerDetailView.GetSLaterTowerDetailView(log, client, data=towers[0], request=request)
        >>> len(tower_detail_modal.data['blocks'])
        3
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterTowerDetailView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterAccountEditView(self, log, slack_client, data=None, request=None) -> SLaterAccountEditView:
        """Validate that we can render a modal edit view from an account generated by SLater.GetAccounts()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterAccountEditView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get account with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> accounts = test_GetSLaterAccountEditView.GetAccounts(limit=1)

        >>> assert (len(accounts) == 1), f"expected to find one, found: {len(accounts)}"

        # Test view with insufficient entitlement level
        >>> acct_edit_modal = test_GetSLaterAccountEditView.GetSLaterAccountEditView(log, client, data=accounts[0], request=request)

        >>> test_GetSLaterAccountEditView.GetUnauthorizedMessage(SLaterRoleEntitlementLevel.READ_WRITE) in [block['text']['text'] for block in acct_edit_modal.data['blocks'] if block.get('block_id') == 'slater_error']
        True

        # Test View with permissive entitlement level
        >>> test_GetSLaterAccountEditView = SLater(log,boto_session,http_session, entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER, config_file='environments/unit.ini')

        >>> acct_edit_modal = test_GetSLaterAccountEditView.GetSLaterAccountEditView(log, client, data=accounts[0], request=request)

        >>> len(acct_edit_modal.data['blocks'])
        20

        """
        role_entitlement = SLaterRoleEntitlementLevel.READ_WRITE
        if not self.IsAuthorized(required_role_level=role_entitlement):
            log.warning(
                f'user is not authorized to make this call requires: {role_entitlement.name}, user has role: {self.GetUserRole().name}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=self.GetUnauthorizedMessage(role_entitlement),
                request=request)
            return view

        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        blocks: list = []
        try:
            log.debug(f'generating view for request {request}')
            blocks = data.to_edit_form(self.GetUserRole().name)
            view = SLaterAccountEditView(
                log, slack_client, account=data, blocks=blocks, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterAccountTypeEditView(self, log, slack_client, data=None, request=None) -> SLaterAccountTypeEditView:
        """Validate that we can render a modal edit view from an account type generated by SLater.GetAccountTypes()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterAccountTypeEditView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get account type with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> account_types = test_GetSLaterAccountTypeEditView.GetAccountTypes(limit=1)

        >>> len(account_types)
        1

        >>> acct_type_edit_modal = test_GetSLaterAccountTypeEditView.GetSLaterAccountTypeEditView(log, client, data=account_types[0], request=request)

        >>> len(acct_type_edit_modal.data['blocks'])
        6

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterAccountTypeEditView(
                log, slack_client, account_type=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterApplicationEditView(self, log, slack_client, data=None, request=None) -> SLaterApplicationEditView:
        """Validate that we can render a modal view from an application list generated by SLater.GetApplications()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterApplicationEditView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get applications with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> application = test_GetSLaterApplicationEditView.GetApplicationByID(6)

        # Test view with insufficient entitlement level
        >>> app_edit_modal = test_GetSLaterApplicationEditView.GetSLaterApplicationEditView(log, client, data=application, request=request)

        >>> test_GetSLaterApplicationEditView.GetUnauthorizedMessage(SLaterRoleEntitlementLevel.READ_WRITE) in [block['text']['text'] for block in app_edit_modal.data['blocks'] if block.get('block_id') == 'slater_error']
        True

         # Test View with permissive entitlement level
        >>> test_GetSLaterApplicationEditView = SLater(log,boto_session,http_session, entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER, config_file='environments/unit.ini')

        >>> app_edit_modal = test_GetSLaterApplicationEditView.GetSLaterApplicationEditView(log, client, data=application, request=request)

        >>> len(app_edit_modal.data['blocks'])
        11
        """
        role_entitlement = SLaterRoleEntitlementLevel.READ_WRITE
        if not self.IsAuthorized(required_role_level=role_entitlement):
            log.warning(
                f'user is not authorized to make this call requires: {role_entitlement.name}, user has role: {self.GetUserRole().name}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=self.GetUnauthorizedMessage(role_entitlement),
                request=request)
            return view

        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            blocks = data.to_edit_form(self.GetUserRole().name)
            view = SLaterApplicationEditView(log, slack_client,
                application=data, blocks=blocks, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterApplicationContactEditView(self, log, slack_client, data=None, request=None) -> SLaterApplicationContactEditView:
        """Validate that we can render a modal edit view from an application_contact generated by SLater.GetApplicationContacts()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterApplicationContactEditView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get application_contact with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> application_contacts = test_GetSLaterApplicationContactEditView.GetApplicationContacts(limit=1)

        >>> assert (len(application_contacts) == 1), f"expected to find one, found: {len(application_contacts)}"

        >>> application_contact_edit_modal = test_GetSLaterApplicationContactEditView.GetSLaterApplicationContactEditView(log, client, data=application_contacts[0], request=request)

        >>> len(application_contact_edit_modal.data['blocks'])
        19
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterApplicationContactEditView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterBusinessUnitEditView(self, log, slack_client, data=None, request=None) -> SLaterBusinessUnitEditView:
        """Validate that we can render a modal edit view from an business_unit generated by SLater.GetBusinessUnits()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterBusinessUnitEditView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get business_unit with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> business_units = test_GetSLaterBusinessUnitEditView.GetBusinessUnits(limit=1)

        >>> assert (len(business_units) == 1), f"expected to find one, found: {len(business_units)}"

        >>> business_unit_edit_modal = test_GetSLaterBusinessUnitEditView.GetSLaterBusinessUnitEditView(log, client, data=business_units[0], request=request)

        >>> len(business_unit_edit_modal.data['blocks'])
        6

        >>> business_unit_edit_modal.data['blocks'][2]['element']['action_id']
        'business_unit_name'

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterBusinessUnitEditView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactEditView(self, log, slack_client, data=None, request=None) -> SLaterContactEditView:
        """Validate that we can render a modal view from an contact list generated by SLater.GetContacts()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterContactEditView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> contacts = test_GetSLaterContactEditView.GetContacts(limit=1)
        >>> assert (len(contacts) == 1), f"expected to find one, found: {len(contact)}"
        >>> contact_edit_modal = test_GetSLaterContactEditView.GetSLaterContactEditView(log, client, data=contacts[0], request=request)
        >>> len(contact_edit_modal.data['blocks'])
        7
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactEditView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactTypeEditView(self, log, slack_client, data=None, request=None) -> SLaterContactTypeEditView:
        """Validate that we can render a modal edit view from an contact_type generated by SLater.GetContactTypes()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterContactTypeEditView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact_type with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> contact_types = test_GetSLaterContactTypeEditView.GetContactTypes(limit=1)

        >>> len(contact_types)
        1

        >>> contact_type_edit_modal = test_GetSLaterContactTypeEditView.GetSLaterContactTypeEditView(log, client, data=contact_types[0], request=request)

        >>> len(contact_type_edit_modal.data['blocks'])
        6
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactTypeEditView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterLOBEditView(self, log, slack_client, data=None, request=None) -> SLaterLOBEditView:
        """Validate that we can render a modal edit view from an lob generated by SLater.GetLOBs()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterLOBEditView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get lob with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> lobs = test_GetSLaterLOBEditView.GetLOBs(limit=1)

        >>> len(lobs)
        1

        >>> lob_edit_modal = test_GetSLaterLOBEditView.GetSLaterLOBEditView(log, client, data=lobs[0], request=request)

        >>> len(lob_edit_modal.data['blocks'])
        3

        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterLOBEditView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterMasterPayerEditView(self, log, slack_client, data=None, request=None) -> SLaterMasterPayerEditView:
        """Validate that we can render a modal edit view from an master_payer generated by SLater.GetMasterPayers()
        >>> from iobot.slack import Agent

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())

        >>> test_GetSLaterMasterPayerEditView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get master_payer with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> master_payers = test_GetSLaterMasterPayerEditView.GetMasterPayers(limit=1)

        >>> len(master_payers)
        1

        >>> master_payer_edit_modal = test_GetSLaterMasterPayerEditView.GetSLaterMasterPayerEditView(log, client, data=master_payers[0], request=request)

        >>> len(master_payer_edit_modal.data['blocks'])
        6
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterMasterPayerEditView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterTowerEditView(self, log, slack_client, data=None, request=None) -> SLaterTowerEditView:
        """Validate that we can render a modal edit view from an tower generated by SLater.GetTowers()
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterTowerEditView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get tower with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}
        >>> towers = test_GetSLaterTowerEditView.GetTowers(limit=1)
        >>> assert (len(towers) == 1), f"expected to find one, found: {len(towers)}"
        >>> tower_edit_modal = test_GetSLaterTowerEditView.GetSLaterTowerEditView(log, client, data=towers[0], request=request)
        >>> assert (len(tower_edit_modal.data['blocks']) == 9), f"expected to find nine, found: {len(tower_edit_modal.data['blocks'])}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterTowerEditView(
                log, slack_client, data=data, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterAccountsSearchView(self, log, slack_client, request: dict = None) -> SLaterAccountsSearchView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterAccountsSearchView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct
        >>> acct_search_modal = test_GetSLaterAccountsSearchView.GetSLaterAccountsSearchView(log, agent)
        >>> assert (len(acct_search_modal.data) == 5), f"expected to find five, found: {len(acct_search_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterAccountsSearchView(log, slack_client, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterAccountTypesSearchView(self, log, slack_client, request: dict = None) -> SLaterAccountTypesSearchView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterAccountTypesSearchView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct
        >>> acct_type_search_modal = test_GetSLaterAccountTypesSearchView.GetSLaterAccountTypesSearchView(log, agent)
        >>> assert (len(acct_type_search_modal.data) == 5), f"expected to find five, found: {len(acct_type_search_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterAccountTypesSearchView(
                log, slack_client, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterApplicationsSearchView(self, log, slack_client, request: dict = None) -> SLaterApplicationsSearchView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterApplicationsSearchView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct
        >>> app_search_modal = test_GetSLaterApplicationsSearchView.GetSLaterApplicationsSearchView(log, agent)
        >>> assert (len(app_search_modal.data) == 5), f"expected to find five, found: {len(app_search_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterApplicationsSearchView(
                log, slack_client, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterApplicationContactsSearchView(self, log, slack_client, request: dict = None) -> SLaterApplicationContactsSearchView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterApplicationContactsSearchView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct
        >>> app_contact_search_modal = test_GetSLaterApplicationContactsSearchView.GetSLaterApplicationContactsSearchView(log, agent)
        >>> assert (len(app_contact_search_modal.data) == 5), f"expected to find five, found: {len(app_contact_search_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterApplicationContactsSearchView(
                log, slack_client, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterBusinessUnitsSearchView(self, log, slack_client, request: dict = None) -> SLaterBusinessUnitsSearchView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterBusinessUnitsSearchView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct
        >>> business_unit_search_modal = test_GetSLaterBusinessUnitsSearchView.GetSLaterBusinessUnitsSearchView(log, agent)
        >>> assert (len(business_unit_search_modal.data) == 5), f"expected to find five, found: {len(business_unit_search_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterBusinessUnitsSearchView(
                log, slack_client, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactsSearchView(self, log, slack_client, request: dict = None) -> SLaterContactsSearchView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterContactsSearchView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct
        >>> contact_search_modal = test_GetSLaterContactsSearchView.GetSLaterContactsSearchView(log, agent)
        >>> assert (len(contact_search_modal.data) == 5), f"expected to find five, found: {len(contact_search_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactsSearchView(log, slack_client, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterContactTypesSearchView(self, log, slack_client, request: dict = None) -> SLaterContactTypesSearchView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterContactTypesSearchView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct
        >>> contact_type_search_modal = test_GetSLaterContactTypesSearchView.GetSLaterContactTypesSearchView(log, agent)
        >>> assert (len(contact_type_search_modal.data) == 5), f"expected to find five, found: {len(contact_type_search_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterContactTypesSearchView(
                log, slack_client, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterLOBsSearchView(self, log, slack_client, request: dict = None) -> SLaterLOBsSearchView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterLOBsSearchView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct
        >>> lob_search_modal = test_GetSLaterLOBsSearchView.GetSLaterLOBsSearchView(log, agent)
        >>> assert (len(lob_search_modal.data) == 5), f"expected to find five, found: {len(lob_search_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterLOBsSearchView(log, slack_client, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterMasterPayersSearchView(self, log, slack_client, request: dict = None) -> SLaterMasterPayersSearchView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterMasterPayersSearchView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct
        >>> master_payer_search_modal = test_GetSLaterMasterPayersSearchView.GetSLaterMasterPayersSearchView(log, agent)
        >>> assert (len(master_payer_search_modal.data) == 5), f"expected to find five, found: {len(master_payer_search_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterMasterPayersSearchView(
                log, slack_client, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterRootContactsSearchView(self, log, slack_client, request: dict = None) -> SLaterRootContactsSearchView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterRootContactsSearchView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct
        >>> contact_search_modal = test_GetSLaterRootContactsSearchView.GetSLaterRootContactsSearchView(log, agent)
        >>> assert (len(contact_search_modal.data) == 5), f"expected to find five, found: {len(contact_search_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterRootContactsSearchView(
                log, slack_client, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    @xray.capture()
    @logger.log
    def GetSLaterSlashCommandHelpView(self, log, slack_client, data: list = []) -> SLaterSlashCommandHelpView:
        """Validate that we can return a help view

        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterSlashCommandHelpView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> modal = test_GetSLaterSlashCommandHelpView.GetSLaterSlashCommandHelpView(log, agent, data={'foo':'bar'})
        >>> assert (len(modal.data) == 5), f"expected to find five, found: {len(modal.data)}"
        """

        return SLaterSlashCommandHelpView(log, slack_client, data=data)

    @xray.capture()
    @logger.log
    def GetSLaterSlashCommandErrorView(self, log, slack_client, data: list = []) -> SLaterSlashCommandErrorView:
        """Validate that we can return a help view

        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterSlashCommandErrorView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> modal = test_GetSLaterSlashCommandErrorView.GetSLaterSlashCommandErrorView(log, agent, data='foo')
        >>> assert (len(modal.data) == 5), f"expected to find five, found: {len(modal.data)}"
        """
        return SLaterSlashCommandErrorView(log, slack_client, data=data)

    @xray.capture()
    @logger.log
    def GetSLaterSlashCommandNoResultsView(self, log, slack_client, data: str = None) -> SLaterSlashCommandNoResultsView:
        """Validate that we can return a help view

        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterSlashCommandNoResultsView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        >>> modal = test_GetSLaterSlashCommandNoResultsView.GetSLaterSlashCommandNoResultsView(log, agent, data='foo')
        >>> assert (len(modal.data) == 5), f"expected to find five, found: {len(modal.data)}"
        """
        return SLaterSlashCommandNoResultsView(log, slack_client, data=data)

    def GetSLaterSREsListView(self, log, slack_client, data: list = [], pages=[]) -> SLaterSREsListView:
        pass

    def GetSLaterSREDetailView(self, log, slack_client, data: list = []) -> SLaterSREDetailView:
        pass

    def GetSLaterSREEditView(self, log, slack_client, data: list = []) -> SLaterSREEditView:
        pass

    def GetSLaterSREsSearchView(self, log, slack_client, data: list = []) -> SLaterSREEditView:
        pass

    @xray.capture()
    @logger.log
    def GetSLaterTowersSearchView(self, log, slack_client, request: dict = None) -> SLaterTowersSearchView:
        """Validate that we can trigger a type-ahead modal from the shortcut menu
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> agent = Agent(log,boto_session,client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
        >>> test_GetSLaterTowersSearchView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate returned modal looks correct
        >>> tower_search_modal = test_GetSLaterTowersSearchView.GetSLaterTowersSearchView(log, agent)
        >>> assert (len(tower_search_modal.data) == 5), f"expected to find five, found: {len(tower_search_modal.data)}"
        """
        if not request:
            request = self.GetSLaterRequest()

        view: ModalView = None
        try:
            log.debug(f'generating view for request {request}')
            view = SLaterTowersSearchView(log, slack_client, request=request)
        except Exception as err:
            log.debug(
                f'trapped error while generating view for request {err}')
            view = SLaterSlashCommandErrorView(
                log,
                slack_client,
                data=f'encountered error: {err} while retrieving view',
                request=request)

        return view

    ReadObjects = Get  # Alias the R in CRUD

    @xray.capture()
    @logger.log
    def Update(self,
               api: str = '',
               object_type: str = '',
               id: str = '',
               db_version: str = '',
               request: dict = None,
               **kwargs) -> IOBotObject:
        """
        Use this API to update the internal state of the current object to
        the appropriate SLater table.

        Parameters
        ----------

        api: str (required)
          Required SLater api endpoint to read data from.
          Reference the SLater section of the iobot.ini config.
          Only mapped values will be accepted.

        object_type: str (required)
          Required name of SLater object you wish to patch.

          Supported types:

          - Account
          - AccountType
          - Application
          - ApplicationContact
          - BusinessUnit
          - Contact
          - ContactType
          - LOB
          - MasterPayer
          - Tower

        primary_key: str (required)
          After persisting object, this field will be used to retrieve
          back the saved state.

        db_version: str
          Alternative version of SLater DB Schema. If you set to 'swa', SLater
          will use the DB schema 'slater_swa' instead of its default 'slater'.
          Use this for fixed, or staged testing data sets.

        request: dict
          Slack Request User interacting with SLater (for auditing). It must
          contain both an ID and a submit timestamp.

          `{'submitter':{'name':'john.torres'},'submitted':1595314865}`

        **kwargs:
          K/V parameters (reserved, not currently used)


        Returns
        -------

        IOBotObject:
          A object populated from SLater based on the provided parameters.


        Testing
        =======

        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_Update = SLater(log,boto_session,http_session,config_file='environments/unit.ini', slater_user='U0EKDSZE2', entitlement_level=SLaterRoleEntitlementLevel.READ_WRITE)

        Validate object create
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> object_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> t_appid: str = ''.join(random.choice(string.digits) for i in range(4))

        >>> application = test_Update.Create(api="applications_api", object_type='Application', list_type='Applications', application_name=object_name, t_appid=f'SVC{t_appid}', application_description='Ride a Ostrich', lob='foo', create_date='1970-01-01', update_date='1970-01-01', budget='budget$1',request=request)

        >>> assert(application.get('application_name') == object_name), f"expected name to equal '{object_name}', got {application.get('application_name')}"

        >>> object_name_new: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> app_update = test_Update.Update(api="applications_api", id=application.get('application_id'), object_type='Application', application_name=object_name_new, t_appid=f'SVC{t_appid}', application_description='Ride a Ostrich', create_date='1970-01-01', request=request)

        >>> assert(app_update.get('application_name') == object_name_new), f"expected name to equal '{object_name_new}', got {application.get('application_name')}"

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        role_entitlement: Enum = SLaterRoleEntitlementLevel.READ_WRITE

        if not self.IsAuthorized(required_role_level=role_entitlement):
            self._log.warning(
                f'user is not authorized to make this call requires: {role_entitlement.name}, user has role: {self.GetUserRole().name}')
            raise SLaterUnAuthorizedSlackUserGroupError(self.GetUnauthorizedMessage(role_entitlement))

        if not id:
            raise SLaterClientMissingRequiredParameters(
                f'SLater Object ID required for patch {object_type} operation')

        url = f"{self.config['SLater'][api]}"
        self._log.debug(f"patch url: {url}")

        headers: Dict[str, str] = {
            "Content-Type": "application/x-www-form-urlencoded",
            "Authorization": f"Bearer {self._creds['access_token']}",
            "slater_user": request.get('submitter').get('name'),
            "db_version": self.db_version
        }

        primary_key: str = PRIMARY_KEYS[object_type]

        kwargs[primary_key] = id
        self._log.debug(f"headers {headers}")
        self._log.info(f"patching: {object_type}({primary_key}={id})")
        self._log.debug(f"data {kwargs}")
        patch_response: Dict = self.client.patch(
            url, headers=headers, data=json.dumps(kwargs))

        new_object = patch_response.json()
        self._log.debug(f'patched object: {new_object}')
        if patch_response.status_code not in (200, 201, 202):
            self._log.error(
                f'Status: {patch_response.status_code}, Headers: {patch_response.headers}, Error Response: {patch_response.content}')

            return None
        else:
            self._log.debug(
                f'Status: {patch_response.status_code}, Headers: {patch_response.headers}, Delete Response: {patch_response.content}')

            row = new_object
            self._log.debug(f'patched record: {row}')
            slater_object = globals()[object_type](**row)

            slater_object.initialize_clients(
                self._creds, self._log, self._secrets, self)

            return slater_object

    @xray.capture()
    @logger.log
    def UpdateAccount(self,
                      api: str = 'accounts_api',
                      cloud_account_id: str = '',
                      request: dict = None,
                      **kwargs) -> Account:
        """Delete a account object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_UpdateAccount = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate account update
        >>> import random, string

        >>> account_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> account_number: str = ''.join(random.choice(string.digits) for i in range(12))

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> account = test_UpdateAccount.CreateAccount(cloud_account_name=account_name, cloud_account_number=account_number, cloud_account_description='slater.client test account', master_payer_id=1, account_type_id=1,environment_type_id=1,environment_type_name='non-production', cloud_account_root_email=f"{account_name}@pearson.com",governance_lob_name="lob a", lob_id=1,budget_account_string="foo$1",decommissioned=0,provisioner="cinch",project_number="apifoo",snow_approval_group="slater snow",budget_alert_monthly_threshold=500, cloud_organization_id=1, cloud_organization_name="Assessments",next_link=f"https://localhost/accounts/{account_number}",request=request)

        >>> search = test_UpdateAccount.GetAccountByID(account_number)

        >>> assert(search.get('cloud_account_name') == account_name), f"expected name to equal {account_name}, got {search.get('cloud_account_name')}"

        >>> account_name_new: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> updated_account = test_UpdateAccount.UpdateAccount(cloud_account_id=account.get('cloud_account_id'),cloud_account_name=account_name_new,request=request)

        >>> assert(account.get('cloud_account_id') == updated_account.get('cloud_account_id')), f"expected {account.get('cloud_account_id')} to equal {updated_account.get('cloud_account_id')}"

        >>> assert(updated_account.get('cloud_account_name') == account_name_new), f"expected name to equal {account_name_new}, got {updated_account.get('cloud_account_name')}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Update(api=api,
                           object_type='Account',
                           id=cloud_account_id,
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def UpdateAccountType(self,
                          api: str = 'accounttypes_api',
                          account_type_id: str = '',
                          request: dict = None,
                          **kwargs) -> AccountType:
        """Update a account_type object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> test_UpdateAccountType = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate account update
        >>> import random, string

        >>> account_type_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> account_type = test_UpdateAccountType.CreateAccountType(account_type_name=account_type_name,request=request)

        >>> search = test_UpdateAccountType.GetAccountTypeByID(account_type.get('account_type_id'))

        >>> assert(search.get('account_type_name') == account_type_name), f"expected name to equal {account_type_name}, got {search.get('account_type_name')}"

        >>> account_type_name_new: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> updated_account_type = test_UpdateAccountType.UpdateAccountType(account_type_id=account_type.get('account_type_id'),account_type_name=account_type_name_new,request=request)

        >>> assert(account_type.get('account_type_id') == updated_account_type.get('account_type_id')), f"expected {account_type.get('account_type_id')} to equal {updated_account_type.get('cloud_account_id')}"

        >>> assert(updated_account_type.get('account_type_name') == account_type_name_new), f"expected name to equal {account_name_new}, got {updated_account_type.get('account_type_name')}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Update(api=api,
                           object_type='AccountType',
                           id=account_type_id,
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def UpdateApplication(self,
                          api: str = 'applications_api',
                          application_id: str = '',
                          request: dict = None,
                          **kwargs) -> Application:
        """Update a application object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_UpdateApplication = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate application update
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> application_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> t_appid: str = ''.join(random.choice(string.digits) for i in range(4))

        >>> application = test_UpdateApplication.CreateApplication(application_name=application_name, t_appid=f'SVC{t_appid}', application_description='Ride a Ostrich', lob='foo', create_date='1970-01-01', update_date='1970-01-01', budget='budget$1',request=request)

        >>> assert(application.get('application_name') == application_name), f"expected name to equal '{application_name}', got {application.get('application_name')}"

        >>> application_name_new: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> updated_application = test_UpdateApplication.UpdateApplication(application_id=application.get('application_id'),application_name=application_name_new,request=request)

        >>> assert(updated_application.get('application_name') == application_name_new), f"expected name to equal {application_name_new}, got {updated_application.get('application_name')}"

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Update(api=api,
                           object_type='Application',
                           id=application_id,
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def UpdateApplicationContact(self,
                                 api: str = 'applicationcontacts_api',
                                 application_contact_id: str = '',
                                 request: dict = None,
                                 **kwargs) -> ApplicationContact:
        """Update a application contact object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_UpdateApplicationContact = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate application contact update
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        Step 1. Create a contact type
        >>> contact_type_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> contact_type = test_UpdateApplicationContact.CreateContactType(contact_type=contact_type_name,request=request)

        Step 2. Create a contact
        >>> contact_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> contact_email: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))}.{''.join(random.choice(string.ascii_letters) for i in range(10))}@pearson.com"
        >>> contact = test_UpdateApplicationContact.CreateContact(contact_name=contact_name,contact_email=contact_email,request=request)

        Step 3. Create an application
        >>> application_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> t_appid: str = ''.join(random.choice(string.digits) for i in range(4))
        >>> application = test_UpdateApplicationContact.CreateApplication(application_name=application_name, t_appid=f'SVC{t_appid}', application_description='Ride a Ostrich', lob='foo', create_date='1970-01-01', update_date='1970-01-01', budget='budget$1',request=request)

        Step 4. associate the application contact to that application
        >>> application_contact = test_UpdateApplicationContact.CreateApplicationContact(contact_type_id=contact_type.get('contact_type_id'), application_id=application.get('application_id'), contact_id=contact.get('contact_id'), request=request)

        Step 5. Create another contact type
        >>> contact_type_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> contact_type_new = test_UpdateApplicationContact.CreateContactType(contact_type=contact_type_name,request=request)

        >>> application_contact.get('contact_type_id') == contact_type.get('contact_type_id')
        True

        Step 6. Update the application to point to the new contact instead of the old
        >>> updated_application_contact = test_UpdateApplicationContact.UpdateApplicationContact(application_contact_id=application_contact.get('application_contact_id'),contact_type_id=contact_type_new.get('contact_type_id'),request=request)

        >>> application_contact.get('application_contact_id') == updated_application_contact.get('application_contact_id')
        True

        >>> updated_application_contact.get('contact_type_id') == contact_type_new.get('contact_type_id')
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Update(api=api,
                           object_type='ApplicationContact',
                           id=application_contact_id,
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def UpdateBusinessUnit(self,
                           api: str = 'businessunits_api',
                           business_unit_id: str = '',
                           request=None,
                           **kwargs) -> BusinessUnit:
        """Update a business unit object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_UpdateBusinessUnit = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate business unit update
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> business_unit_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> business_unit = test_UpdateBusinessUnit.CreateBusinessUnit(business_unit_name=business_unit_name,request=request)

        >>> assert(business_unit.get('business_unit_name') == business_unit_name), f"expected name to equal '{business_unit_name}', got {business_unit.get('business_unit_name')}"

        >>> business_unit_name_new: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> updated_business_unit = test_UpdateBusinessUnit.UpdateBusinessUnit(business_unit_id=business_unit.get('business_unit_id'), business_unit_name=business_unit_name_new,request=request)

        >>> updated_business_unit is not None
        True

        >>> business_unit.get('business_unit_id') == updated_business_unit.get('business_unit_id')
        True

        >>> updated_business_unit.get('business_unit_name') == business_unit_name_new
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Update(api=api,
                           object_type='BusinessUnit',
                           id=business_unit_id,
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def UpdateContact(self,
                      api: str = 'contacts_api',
                      id: str = '',
                      contact_id: str = '',
                      request: dict = None,
                      **kwargs) -> Contact:
        """Update a contact object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_UpdateContact = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate contact update
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import string, random

        >>> contact_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> contact_email: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))}.{''.join(random.choice(string.ascii_letters) for i in range(10))}@pearson.com"

        >>> contact = test_UpdateContact.CreateContact(contact_name=contact_name,contact_email=contact_email,request=request)

        >>> contact.get('contact_name') == contact_name
        True

        >>> contact_name_new: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> updated_contact = test_UpdateContact.UpdateContact(contact_id=contact.get('contact_id'),contact_name=contact_name_new,request=request)

        >>> contact.get('contact_id') == updated_contact.get('contact_id')
        True

        >>> updated_contact.get('contact_name') == contact_name_new
        True

        >>> testing.unexport_secret_arns()

        >>> del os.environ['STACK_NAME']
        """
        if id:
            contact_id = id

        return self.Update(api=api,
                           object_type='Contact',
                           id=contact_id,
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def UpdateContactType(self,
                          api: str = "contact_types_api",
                          contact_type_id: str = "",
                          request=None,
                          **kwargs) -> ContactType:
        """Update a contact type object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_UpdateContactType = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate contact type update
        >>> import random, string

        >>> contact_type_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> contact_type = test_UpdateContactType.CreateContactType(contact_type=contact_type_name,request=request)

        >>> contact_type.get('contact_type') == contact_type_name
        True

        >>> contact_type_name_new: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> updated_contact_type = test_UpdateContactType.UpdateContactType(contact_type_id=contact_type.get('contact_type_id'),contact_type=contact_type_name_new,request=request)

        >>> contact_type.get('contact_type_id') == updated_contact_type.get('contact_type_id')
        True

        >>> updated_contact_type.get('contact_type') == contact_type_name_new
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Update(api=api,
                           object_type='ContactType',
                           id=contact_type_id,
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def UpdateLOB(self,
                  api: str = "lobs_api",
                  lob_id: str = '',
                  request=None,
                  **kwargs) -> LOB:
        """Update a lob object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_UpdateLOB = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate lob update
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> lob_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> lob = test_UpdateLOB.CreateLOB(governance_lob_name=lob_name,request=request)

        >>> lob.get('governance_lob_name') == lob_name
        True

        >>> lob_name_new: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> updated_lob = test_UpdateLOB.UpdateLOB(lob_id=lob.get('lob_id'),governance_lob_name=lob_name_new, request=request)

        >>> updated_lob is not None
        True

        >>> lob.get('lob_id') == updated_lob.get('lob_id')
        True

        >>> updated_lob.get('governance_lob_name') == lob_name_new
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Update(api=api,
                           object_type='LOB',
                           id=lob_id,
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def UpdateMasterPayer(self,
                          api: str = "masterpayers_api",
                          master_payer_id: str = '',
                          request=None,
                          **kwargs) -> MasterPayer:
        """Update a master_payer object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_UpdateMasterPayer = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate master_payer update
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> master_payer_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"
        >>> master_payer_ident: str = ''.join(random.choice(string.digits) for i in range(12))

        >>> master_payer = test_UpdateMasterPayer.CreateMasterPayer(master_payer_name=master_payer_name, master_payer_ident=master_payer_ident,request=request)

        >>> assert(master_payer.get('master_payer_name') == master_payer_name), f"expected name to equal '{master_payer_name}', got {master_payer.get('master_payer_name')}"

        >>> master_payer_name_new: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> updated_master_payer = test_UpdateMasterPayer.UpdateMasterPayer(master_payer_id=master_payer.get('master_payer_id'),master_payer_name=master_payer_name_new, request=request)

        >>> updated_master_payer is not None
        True

        >>> assert(master_payer.get('master_payer_id') == updated_master_payer.get('master_payer_id')), f"expected {master_payer.get('master_payer_id')} to equal {updated_master_payer.get('master_payer_id')}"

        >>> updated_master_payer.get('master_payer_name') == master_payer_name_new
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Update(api=api,
                           object_type='MasterPayer',
                           id=master_payer_id,
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def UpdateTower(self,
                    api: str = "towers_api",
                    tower_id: str = '',
                    request=None,
                    **kwargs) -> Tower:
        """Update a tower object from SLater
        >>> os.environ['STACK_NAME'] = 'iobots-stack'

        >>> testing.export_secret_arns(os.environ['STACK_NAME'])

        >>> test_UpdateTower = SLater(log,boto_session,http_session,config_file='environments/unit.ini',entitlement_level=SLaterUserEntitlementLevel.SLATER_WRITER)

        Validate tower update
        >>> request={'submitter':{'name':'john.torres'},'submitted':1595314865}

        >>> import random, string

        >>> tower_name: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> tower = test_UpdateTower.CreateTower(tower_name=tower_name, contact_id='1',request=request)

        >>> assert(tower.get('tower_name') == tower_name), f"expected name to equal '{tower_name}', got {tower.get('tower_name')}"

        >>> tower_name_new: str = f"{''.join(random.choice(string.ascii_letters) for i in range(10))} {''.join(random.choice(string.ascii_letters) for i in range(10))}"

        >>> updated_tower = test_UpdateTower.UpdateTower(tower_id=tower.get('tower_id'),tower_name=tower_name_new,request=request)

        >>> updated_tower is not None
        True

        >>> assert(tower.get('tower_id') == updated_tower.get('tower_id')), f"expected {tower.get('tower_id')} to equal {updated_tower.get('tower_id')}"

        >>> updated_tower.get('tower_name') == tower_name_new
        True

        >>> testing.unexport_secret_arns()
        >>> del os.environ['STACK_NAME']
        """

        return self.Update(api=api,
                           object_type='Tower',
                           id=tower_id,
                           request=request,
                           **kwargs)

    @xray.capture()
    @logger.log
    def GetSLaterAddApplicationContactView(self, log, slack_client, application: Application = None, contacts: Contacts = None, callback_id="SLaterAddApplicationContactView", title="SLater-Add App Contact", close="Cancel", request=None, blocks=None) -> SLaterAddContactView:
        """Validate that we can get a SLaterContactCreateView with application contacts
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterAddApplicationContactView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'jonathan.morris'},'submitted':1595314865}
        >>> application = test_GetSLaterAddApplicationContactView.GetApplicationByID('1')
        >>> contacts = test_GetSLaterAddApplicationContactView.GetApplicationContactsByApplicationID('1')
        >>> add_contact_modal = test_GetSLaterAddApplicationContactView.GetSLaterAddApplicationContactView(log, client, application=application, contacts=contacts)

        >>> assert (add_contact_modal.data['blocks'][1]['block_id'] == "slater_selected_contacts"), f"expected to find block with block_id of 'slater_selected_contacts', found: {add_contact_modal.data['blocks'][1]['block_id']}"
        """
        return SLaterAddContactView(log, slack_client, application=application, contacts=contacts, callback_id=callback_id, title=title, close=close, request=request, blocks=blocks)

    @xray.capture()
    @logger.log
    def GetSLaterAddAccountContactView(self, log, slack_client, account: Account = None, contacts: Contacts = None, callback_id="SLaterAddAccountContactView", title="SLater-Add Acct Contact", close="Cancel", request=None, blocks=None) -> SLaterAddContactView:
        """Validate that we can get a SLaterContactCreateView with application contacts
        >>> from iobot.slack import Agent
        >>> os.environ['STACK_NAME'] = 'iobots-stack'
        >>> testing.export_secret_arns(os.environ['STACK_NAME'])
        >>> client = slack_sdk.WebClient(token=testing.slack_bearer_token())
        >>> test_GetSLaterAddAccountContactView = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

        Validate get contact with a limit argument of one should give us pages to navigate
        >>> request={'submitter':{'name':'jonathan.morris'},'submitted':1595314865}
        >>> account = test_GetSLaterAddAccountContactView.GetAccountByCloudAccountID('4')
        >>> contacts = test_GetSLaterAddAccountContactView.GetAccountContactsByCloudAccountID('4')
        >>> add_contact_modal = test_GetSLaterAddAccountContactView.GetSLaterAddAccountContactView(log, client, account=account, contacts=contacts)
        >>> assert (add_contact_modal.data['blocks'][1]['block_id'] == "slater_selected_contacts"), f"expected to find block with block_id of 'slater_selected_contacts', found: {add_contact_modal.data['blocks'][1]['block_id']}"
        >>> assert (add_contact_modal.data['blocks'][1]['block_id'] == "slater_selected_contacts"), f"expected to find block with block_id of 'slater_selected_contacts', found: {add_contact_modal.data['blocks'][1]['block_id']}"
        """
        return SLaterAddContactView(log, slack_client, account=account, contacts=contacts, callback_id=callback_id, title=title, close=close, request=request, blocks=blocks)

    ReadObjects = Get  # Alias Get to the R in CRUD
