"""Lambda function module.""" import json import uuid from typing import Dict from ddex_ingester_common.constants.ddex_providers import SOM_LIVRE_VENDOR_ID from ddex_ingester_common.constants.release_type import VIDEO_RELEASE_TYPES from ddex_ingester_common.constants.status import (IN_CONTENT, SUBMITTED, TRANSFER_TO_CONTENT) from ddex_ingester_common.lambda_exceptions import \ SubmitProductGraphQLException from ddex_ingester_common.logging import utils as logging_utils from ddex_ingester_common.models.state_machine import \ body as StateMachineContext from ddex_ingester_common.schemas.state_machine_schema import \ StateMachineSchema from lambdacommon.graphql import graphql from marshmallow.utils import get_value import config from config import graphql_gateway from constants.queries import (DELETE_ERROR_CORRECTION, GET_PRODUCT, SUBMIT_PRODUCT) logger = logging_utils.get_logger(config.app_logger) def handler(event, context): """Submit product handler.""" logger.info(f'Triggered submit_product: {event}') # We receive a list of items from the parallel task prior to this, # grab the last item. context = StateMachineSchema().load(event) correlation_id = event.get('correlation_id') or str(uuid.uuid4()) logging_utils.update_logger_correlation_id(logger, correlation_id) logging_utils.update_logger_with_message_ids( logger, context.message_id, context.message_thread_id, context.execution_name ) graphql_gateway.set_headers( { 'Orchard-User-Id': config.OA_USER, 'Correlation-Id': correlation_id, } ) product_id = context.product.product_id handle_video(event, context) if not should_submit(context): return skip_content_submit(context) # We should have an unsubmitted product requiring validation. try: submit_product(context, product_id) except graphql.GraphQLError as graphql_error: status_code = graphql_error.get_response_status() # If 400, assume validation issue and pass details to error handling. if status_code == 400: handle_validation_results_error(graphql_error) # Retry on any 5XX status code # We raise this exception instead of the GraphQLError as Terraform # catches this particular exception and runs the lambda again. elif (status_code // 100) == 5: raise SubmitProductGraphQLException(graphql_error) else: raise graphql_error return StateMachineSchema().dump(context) def submit_product(context, product_id): """Submit a product.""" vendor_id = context.product.vendor_id subaccount_id = context.product.subaccount_id logger.info( f'Running submit product with vendor_id: {vendor_id}' f' and product: {product_id}') result = graphql_gateway.execute( SUBMIT_PRODUCT, { 'productId': product_id, 'vendorId': vendor_id, 'subaccountId': subaccount_id } )['data']['submitProduct'] return json.dumps(result) def handle_validation_results_error(graphql_error): """Raise only error body.""" raise SubmitProductValidationException(graphql_error.get_response_body()) def skip_content_submit(context): """Skip submit product when processing an in_content DDEX.""" return StateMachineSchema().dump(context) def should_submit(context): """Determine if we should submit the product.""" if context.product.vendor_id == SOM_LIVRE_VENDOR_ID: return False if context.product.release_type in VIDEO_RELEASE_TYPES: if (context.product.status == IN_CONTENT or context.product.status == TRANSFER_TO_CONTENT): return False if (context.product.status == IN_CONTENT and context.product.display_status == SUBMITTED): logger.info('Skipping submit because Product already submitted') return False if context.product.status == IN_CONTENT and not context.error_correction: # Skip submisssion if product is IN_CONTENT and has no error correction logger.info( ('Skipping submit because Product already' ' in_content with no error corrections') ) return False if context.error_correction: # We do not want to submit error corrections with no items logger.info('Checking error correction for items') # Check Orchard for ec items as there may be items there orchard_product = graphql_gateway.execute( GET_PRODUCT, {'productId': str(context.product.product_id)} )['data']['product'] ec_items = get_value(orchard_product, 'releaseCorrection.items', []) if not ec_items: # Remove correction as we have no items release_correction_id =\ context.error_correction.release_correction_id logger.info( f'Deleting error correction with id {release_correction_id}' ' as no correction items are attached') delete_error_correction(context) return False return True def valid_product(context): """Query the Product via graphql for validation issues.""" result = graphql_gateway.execute( GET_PRODUCT, { 'productId': str(context.product.product_id), } )['data']['product'] logger.info(f'Product Validation result: {result}') return result.get('validation', {}).get('isValid') def handle_video(event, context): """Video product specific submit logic.""" if context.product.release_type not in VIDEO_RELEASE_TYPES: return logger.info('Running video specific submit logic') # Separate logic as we don't get the video validation failure details if not valid_product(context): msg = 'Video Product invalid' logger.error('Raising validation exception for video product') raise SubmitVideoProductValidationException(msg) def delete_error_correction(context: StateMachineContext) -> Dict: """Delete error correction via GraphQL.""" payload = { 'productId': context.product.product_id, 'releaseCorrectionId': context.error_correction.release_correction_id } result = graphql_gateway.execute( DELETE_ERROR_CORRECTION, payload )['data']['deleteProductCorrection'] logger.info(f'Delete release correction result: {result}') return result class SubmitProductValidationException(Exception): """Validation Results lambda exception.""" class SubmitVideoProductValidationException(Exception): """Video Validation Results lambda exception."""