"""Lambda fan-triggered-send-definitions function module.""" from concurrent.futures import ThreadPoolExecutor import json import time import xml.etree.ElementTree as ET from lambdacommon.common_config import logger import requests from sentry_sdk import capture_exception import config # noqa from src import marketing_cloud_messages def handler(event, context): """Lambda entry point.""" try: business_units_xml, triggered_send_definitions_xml = get_business_units_and_triggered_send_definitions() response = prepare_response(business_units_xml, triggered_send_definitions_xml) return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json' }, 'body': response } except Exception as e: logger.exception(str(e)) capture_exception(e) return { 'statusCode': 500, 'body': 'Internal Server Error' } def get_business_units_and_triggered_send_definitions(): """Get business units and triggered send definitions from the Marketing Cloud API.""" with ThreadPoolExecutor(max_workers=2) as executor: futures = [ executor.submit(send_marketing_cloud_request, marketing_cloud_messages.BUSINESS_UNITS_MESSAGE), executor.submit(send_marketing_cloud_request, marketing_cloud_messages.TRIGGERED_SENDS_MESSAGE), ] business_units = futures[0].result() logger.info('Successfully fetched business units') triggered_sends = futures[1].result() logger.info('Successfully fetched triggered sends') return business_units, triggered_sends def send_marketing_cloud_request(soap_request_message, max_attempts=3): """Get triggered send definitions from the Marketing Cloud API. Args: soap_request_message (str): The SOAP request message to send to the Marketing Cloud API. max_attempts (int): The maximum number of attempts to fetch the data from the API. Returns: str: The response from the Marketing Cloud API. Raises: RuntimeError: If request to Marketing Cloud API fails after several attempts. """ attempt = 0 while attempt < max_attempts: try: response = requests.post( config.MARKETING_CLOUD_SERVICE_API_URL, data=soap_request_message, headers={ 'SOAPAction': 'Retrieve', 'Content-Type': 'text/xml; charset=utf-8', }) response.raise_for_status() return response.text except requests.RequestException as e: attempt += 1 if attempt == max_attempts: raise RuntimeError(f'Failed to fetch token after {max_attempts} attempts') from e retry_in = (2 ** (attempt - 1)) logger.warning( f'Request to Marketing Cloud API failed. Attempt {attempt} of {max_attempts}. ' f'Failure reason: {e}. Retrying in {retry_in} seconds.') time.sleep(retry_in) def prepare_response(business_units_xml, triggered_send_definitions_xml) -> str: """Prepare the triggered send definitions response. Args: xml (str): The XML response from the Marketing Cloud API. Returns: str: The JSON response with triggered send definitions. """ bu_root = ET.fromstring(business_units_xml) namespaces = { 'ns': 'http://exacttarget.com/wsdl/partnerAPI' } business_units = {} for elem in bu_root.findall('.//ns:Results', namespaces): business_units[elem.find('ns:ID', namespaces).text] = elem.find('ns:Name', namespaces).text tsd_root = ET.fromstring(triggered_send_definitions_xml) namespaces = { 'ns': 'http://exacttarget.com/wsdl/partnerAPI' } triggered_send_definitions = [] for elem in tsd_root.findall('.//ns:Results', namespaces): mid = elem.find('ns:Client/ns:ID', namespaces).text triggered_send_definitions.append({ 'MID': mid, 'BusinessUnitName': business_units.get(mid, ''), 'TriggerSendDefinitionKey': elem.find('ns:CustomerKey', namespaces).text, 'TriggerSendDefinitionName': elem.find('ns:Name', namespaces).text }) return json.dumps({'TriggerSends': triggered_send_definitions})