def scan(table_name: str,
         filter_attr_name: str = None,
         filter_attr_values=None,
         correlation_id=new_correlation_id()):
    try:
        logger = get_logger()
        table = get_table(table_name)

        # accept string but make it into a list for later processing
        if isinstance(filter_attr_values, str):
            filter_attr_values = [filter_attr_values]
        logger.info('dynamodb scan',
                    extra={
                        'table_name': table_name,
                        'filter_attr_name': filter_attr_name,
                        'filter_attr_value': str(filter_attr_values),
                        'correlation_id': correlation_id
                    })
        if filter_attr_name is None:
            response = table.scan()
        else:
            filter_expr = Attr(filter_attr_name).eq(filter_attr_values[0])
            for value in filter_attr_values[1:]:
                filter_expr = filter_expr | Attr(filter_attr_name).eq(value)
            response = table.scan(FilterExpression=filter_expr)
        items = response['Items']
        logger.info('dynamodb scan result',
                    extra={
                        'count': str(len(items)),
                        'correlation_id': correlation_id
                    })
        return items
    except Exception as ex:
        raise ex
def update_item_old(table_name: str,
                    key: str,
                    attr_name: str,
                    attr_value,
                    correlation_id=new_correlation_id()):
    try:
        logger = get_logger()
        table = get_table(table_name)
        key_json = {'id': key}
        update = 'SET ' + attr_name + ' = :new_value, modified = :m'
        expression_json = {':new_value': attr_value, ':m': str(now_with_tz())}

        logger.info('dynamodb update',
                    extra={
                        'table_name': table_name,
                        'key': key,
                        'attr_name': attr_name,
                        'attr_value': attr_value,
                        'correlation_id': correlation_id
                    })
        response = table.update_item(Key=key_json,
                                     UpdateExpression=update,
                                     ExpressionAttributeValues=expression_json)
    except Exception as ex:
        raise ex
def scan_old(table_name: str,
             filter_attr_name: str = None,
             filter_attr_value=None,
             correlation_id=new_correlation_id()):
    try:
        logger = get_logger()
        table = get_table(table_name)
        logger.info('dynamodb scan',
                    extra={
                        'table_name': table_name,
                        'filter_attr_name': filter_attr_name,
                        'filter_attr_value': filter_attr_value,
                        'correlation_id': correlation_id
                    })
        if filter_attr_name is None:
            response = table.scan()
        else:
            response = table.scan(
                FilterExpression=Attr(filter_attr_name).eq(filter_attr_value))
        items = response['Items']
        logger.info('dynamodb scan result',
                    extra={
                        'count': str(len(items)),
                        'correlation_id': correlation_id
                    })
        return items
    except Exception as ex:
        raise ex
def process_user_registration(notification):
    logger = get_logger()
    correlation_id = new_correlation_id()
    try:
        notification_id = notification['id']
        details = notification['details']
        user_id = details['id']
        logger.info('process_user_registration: post to hubspot',
                    extra={'notification_id': str(notification_id), 'user_id': str(user_id), 'email': details['email'], 'correlation_id': str(correlation_id)})
        hubspot_id, is_new = post_new_user_to_crm(details, correlation_id)
        logger.info('process_user_registration: hubspot details',
                    extra={'notification_id': str(notification_id), 'hubspot_id': str(hubspot_id), 'isNew': str(is_new), 'correlation_id': str(correlation_id)})

        if hubspot_id == -1:
            errorjson = {'user_id': user_id, 'correlation_id': str(correlation_id)}
            raise DetailedValueError('could not find user in HubSpot', errorjson)

        user_jsonpatch = [
            {'op': 'replace', 'path': '/crm_id', 'value': str(hubspot_id)},
        ]

        patch_user(user_id, user_jsonpatch, now_with_tz(), correlation_id)

        mark_notification_processed(notification, correlation_id)
    except Exception as ex:
        error_message = str(ex)
        mark_notification_failure(notification, error_message, correlation_id)
def process_task_signup(notification):
    logger = get_logger()
    correlation_id = new_correlation_id()

    try:
        # get basic data out of notification
        signup_details = notification['details']
        user_task_id = signup_details['id']

        # get additional data that hubspot needs from database
        extra_data = get_task_signup_data_for_crm(user_task_id, correlation_id)

        # put it all together for dispatch to HubSpot
        signup_details.update(extra_data)
        signup_details['signup_event_type'] = 'Sign-up'

        # check here that we have a hubspot id
        if signup_details['crm_id'] is None:
            errorjson = {'user_task_id': user_task_id, 'correlation_id': str(correlation_id)}
            raise DetailedValueError('user does not have crm_id', errorjson)
        else:
            post_task_signup_to_crm(signup_details, correlation_id)
            mark_notification_processed(notification, correlation_id)
    except Exception as ex:
        error_message = str(ex)
        mark_notification_failure(notification, error_message, correlation_id)
def process_user_login(notification):
    logger = get_logger()
    correlation_id = new_correlation_id()

    try:
        # get basic data out of notification
        login_details = notification['details']
        posting_result = post_user_login_to_crm(login_details, correlation_id)
        marking_result = mark_notification_processed(notification, correlation_id)
        return posting_result, marking_result

    except Exception as ex:
        error_message = str(ex)
        mark_notification_failure(notification, error_message, correlation_id)
def update_item(table_name: str,
                key: str,
                name_value_pairs: dict,
                correlation_id=new_correlation_id()):
    try:
        logger = get_logger()
        table = get_table(table_name)
        key_json = {'id': key}
        update_expr = 'SET #modified = :m '
        values_expr = {':m': str(now_with_tz())}
        attr_names_expr = {
            '#modified': 'modified'
        }  # not strictly necessary, but allows easy addition of names later
        param_count = 1
        for name, value in name_value_pairs.items():
            param_name = ':p' + str(param_count)
            map_name = None
            if name == 'status':  # todo generalise this to other reserved words, and ensure it only catches whole words
                if '.' in name:
                    map_name = name.split('.')[0]
                attr_name = '#a' + str(param_count)
                attr_names_expr[attr_name] = 'status'
            else:
                attr_name = name
            if map_name is not None:
                attr_name = map_name + '.' + attr_name
            update_expr += ', ' + attr_name + ' = ' + param_name

            values_expr[param_name] = str(value)

            param_count += 1

        logger.info('dynamodb update',
                    extra={
                        'table_name': table_name,
                        'key': key,
                        'update_expr': update_expr,
                        'values_expr': values_expr,
                        'correlation_id': correlation_id
                    })
        response = table.update_item(
            Key=key_json,
            UpdateExpression=update_expr,
            ExpressionAttributeValues=values_expr,
            ExpressionAttributeNames=attr_names_expr,
        )
        return response
    except Exception as ex:
        raise ex
def delete_item(table_name: str, key: str,
                correlation_id=new_correlation_id()):
    try:
        logger = get_logger()
        table = get_table(table_name)
        key_json = {'id': key}
        logger.info('dynamodb delete',
                    extra={
                        'table_name': table_name,
                        'key': key,
                        'correlation_id': correlation_id
                    })
        response = table.delete_item(Key=key_json)
    except Exception as err:
        raise err
def delete_all(table_name: str, correlation_id=new_correlation_id()):
    try:
        logger = get_logger()
        table = get_table(table_name)
        items = scan(table_name)
        for item in items:
            key = item['id']
            key_json = {'id': key}
            logger.info('dynamodb delete_all',
                        extra={
                            'table_name': table_name,
                            'key': key,
                            'correlation_id': correlation_id
                        })
            table.delete_item(Key=key_json)
    except Exception as err:
        raise err
def get_item(table_name: str, key: str, correlation_id=new_correlation_id()):
    try:
        logger = get_logger()
        table = get_table(table_name)
        key_json = {'id': key}
        logger.info('dynamodb get',
                    extra={
                        'table_name': table_name,
                        'key': key,
                        'correlation_id': correlation_id
                    })
        response = table.get_item(Key=key_json)
        if 'Item' in response:
            return response['Item']
        else:
            # not found
            return None
    except Exception as ex:
        raise ex
def put_item(table_name: str,
             key,
             item_type: str,
             item_details,
             item: dict,
             update_allowed=False,
             correlation_id=new_correlation_id()):
    try:
        logger = get_logger()
        table = get_table(table_name)

        item['id'] = str(key)
        item['type'] = item_type
        item['details'] = item_details
        now = str(now_with_tz())
        item['created'] = now
        item['modified'] = now

        logger.info('dynamodb put',
                    extra={
                        'table_name': table_name,
                        'item': item,
                        'correlation_id': correlation_id
                    })
        if update_allowed:
            response = table.put_item(Item=item)
        else:
            response = table.put_item(
                Item=item, ConditionExpression='attribute_not_exists(id)')
    except ClientError as ex:
        error_code = ex.response['Error']['Code']
        errorjson = {
            'error_code': error_code,
            'table_name': table_name,
            'item_type': item_type,
            'id': str(key),
            'correlation_id': correlation_id
        }
        raise DuplicateInsertError('item already exists', errorjson)