Exemple #1
0
def return_handler(ret: Any, path_enum: EnumMeta, paths: dict, logger, event,
                   context, debug: bool) -> Any:
    if not ret:
        return ret
    _payloads = []
    try:
        if isinstance(ret, Payload):
            _payloads.append(ret.validate(path_enum))
        elif isinstance(ret, list):
            for r in ret:
                if isinstance(r, Payload):
                    _payloads.append(r.validate(path_enum))
    except Exception as e:
        logger.debug(utils.exception_to_str(e))
        raise lpipe.exceptions.FailButContinue(
            f"Something went wrong while extracting Payloads from a function return value: {ret}"
        ) from e

    if _payloads:
        logger.debug(f"{len(_payloads)} dynamic payloads received")
    for p in _payloads:
        logger.debug(f"Executing dynamic payload: {p}")
        try:
            ret = execute_payload(p, path_enum, paths, logger, event, context,
                                  debug)
        except Exception as e:
            logger.debug(utils.exception_to_str(e))
            raise lpipe.exceptions.FailButContinue(
                f"Failed to execute returned Payload: {p}") from e
    return ret
Exemple #2
0
def return_handler(ret: Any, state: State) -> Any:
    if not ret:
        return ret
    _payloads = []
    try:
        if isinstance(ret, Payload):
            _payloads.append(ret.validate(state.path_enum))
        elif isinstance(ret, list):
            for r in ret:
                if isinstance(r, Payload):
                    _payloads.append(r.validate(state.path_enum))
    except Exception as e:
        state.logger.debug(utils.exception_to_str(e))
        raise lpipe.exceptions.FailButContinue(
            f"Something went wrong while extracting Payloads from a function return value: {ret}"
        ) from e

    if _payloads:
        state.logger.debug(f"{len(_payloads)} dynamic payloads received")
    for p in _payloads:
        state.logger.debug(f"executing dynamic payload: {p}")
        try:
            ret = execute_payload(payload=p, state=state)
        except Exception:
            state.logger.error(f"Failed to execute returned {p}")
            raise
    return ret
Exemple #3
0
def execute_action(
    payload: Payload,
    path_enum: EnumMeta,
    paths: dict,
    action: Action,
    logger,
    event,
    context,
    debug: bool = False,
    exception_handler: FunctionType = None,
):
    """Execute functions, paths, and queues (shortcuts) in an Action.

    Args:
        payload (Payload):
        path_enum (EnumMeta): An Enum class which define the possible paths available in this lambda.
        paths (dict): Keys are path names / enums and values are a list of Action objects
        action: (Action):
        logger:
        event: https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html
        context: https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html
        debug (bool):
        exception_handler (FunctionType): A function which will be used to capture exceptions (e.g. contrib.sentry.capture)
    """
    assert isinstance(action, Action)
    ret = None

    # Build action kwargs and validate type hints
    try:
        dummy = ["logger", "event"]
        action_kwargs = build_action_kwargs(action, {
            **{k: None
               for k in dummy},
            **payload.kwargs
        })
        for k in dummy:
            action_kwargs.pop(k, None)
    except (TypeError, AssertionError) as e:
        raise lpipe.exceptions.InvalidPayloadError(
            f"Failed to run {payload.path.name} {action} due to {utils.exception_to_str(e)}"
        ) from e

    default_kwargs = {
        "logger": logger,
        "event": PayloadEvent(event=event, context=context, payload=payload),
    }

    # Run action functions
    for f in action.functions:
        assert isinstance(f, FunctionType)
        try:
            # TODO: if ret, set _last_output
            _log_context = {"path": payload.path.name, "function": f.__name__}
            with logger.context(bind={
                    **_log_context, "kwargs": action_kwargs
            }):
                logger.log("Executing function.")
            with logger.context(bind=_log_context):
                ret = f(**{**action_kwargs, **default_kwargs})
            ret = return_handler(
                ret=ret,
                path_enum=path_enum,
                paths=paths,
                logger=logger,
                event=event,
                context=context,
                debug=debug,
            )
        except lpipe.exceptions.LPBaseException:
            # CAPTURES:
            #    lpipe.exceptions.FailButContinue
            #    lpipe.exceptions.FailCatastrophically
            raise
        except Exception as e:
            logger.error(
                f"Skipped {payload.path.name} {f.__name__} due to unhandled Exception. This is very serious; please update your function to handle this. Reason: {utils.exception_to_str(e)}"
            )
            if exception_handler:
                exception_handler(e)
            if debug:
                raise lpipe.exceptions.FailCatastrophically(
                    utils.exception_to_str(e)) from e

    payloads = []
    for _path in action.paths:
        payloads.append(
            Payload(
                path=normalize.normalize_path(path_enum, _path),
                kwargs=action_kwargs,
                event_source=payload.event_source,
            ).validate(path_enum))

    for _queue in action.queues:
        payloads.append(
            Payload(queue=_queue,
                    kwargs=action_kwargs,
                    event_source=payload.event_source).validate())

    for p in payloads:
        ret = execute_payload(p, path_enum, paths, logger, event, context,
                              debug)

    return ret
Exemple #4
0
def execute_payload(
    payload: Payload,
    path_enum: EnumMeta,
    paths: dict,
    logger,
    event,
    context,
    debug=False,
):
    """Execute functions, paths, and shortcuts in a Path.

    Args:
        payload (Payload):
        path_enum (EnumMeta): An Enum class which define the possible paths available in this lambda.
        paths (dict): Keys are path names / enums and values are a list of Action objects
        logger:
        event: https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html
        context: https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html
    """
    if not logger:
        logger = ServerlessLogger()

    ret = None

    if not isinstance(payload.path, path_enum):
        payload.path = clean_path(path_enum, payload.path)

    if isinstance(payload.path, Enum):  # PATH
        # Allow someone to simplify their definition of a Path to a list of functions.
        if all([isinstance(f, FunctionType) for f in paths[payload.path]]):
            paths[payload.path] = [Action(functions=action)]

        for action in paths[payload.path]:
            assert isinstance(action, Action)

            # Build action kwargs and validate type hints
            try:
                dummy = ["logger", "event"]
                action_kwargs = build_action_kwargs(action, {
                    **{k: None
                       for k in dummy},
                    **payload.kwargs
                })
                for k in dummy:
                    action_kwargs.pop(k, None)
            except (TypeError, AssertionError) as e:
                raise InvalidPayloadError(
                    f"Failed to run {payload.path.name} {action} due to {exception_to_str(e)}"
                ) from e

            # Run action functions
            for f in action.functions:
                assert isinstance(f, FunctionType)
                try:
                    # TODO: if ret, set _last_output
                    _log_context = {
                        "path": payload.path.name,
                        "function": f.__name__
                    }
                    with logger.context(bind={
                            **_log_context, "kwargs": action_kwargs
                    }):
                        logger.log("Executing function.")
                    with logger.context(bind=_log_context):
                        ret = f(
                            **{
                                **action_kwargs,
                                "logger": logger,
                                "event": {
                                    "event": event,
                                    "context": context,
                                    "payload": payload,
                                },
                            })

                    if ret:
                        _payloads = []
                        try:
                            if isinstance(ret, Payload):
                                _payloads.append(ret.validate(path_enum))
                            elif isinstance(ret, list):
                                for r in ret:
                                    if isinstance(r, Payload):
                                        _payloads.append(r.validate(path_enum))
                        except Exception as e:
                            logger.debug(exception_to_str(e))
                            raise FailButContinue(
                                f"Something went wrong while extracting Payloads from a function return value. {ret}"
                            ) from e

                        for p in _payloads:
                            logger.debug(
                                f"Function returned a Payload. Executing. {p}")
                            try:
                                ret = execute_payload(p, path_enum, paths,
                                                      logger, event, context,
                                                      debug)
                            except Exception as e:
                                logger.debug(exception_to_str(e))
                                raise FailButContinue(
                                    f"Failed to execute returned Payload. {p}"
                                ) from e
                except LpipeBaseException:
                    # CAPTURES:
                    #    FailButContinue
                    #    FailCatastrophically
                    raise
                except Exception as e:
                    logger.error(
                        f"Skipped {payload.path.name} {f.__name__} due to unhandled Exception. This is very serious; please update your function to handle this. Reason: {exception_to_str(e)}"
                    )
                    sentry.capture(e)
                    if debug:
                        raise FailCatastrophically() from e

            # Run action paths / shortcuts
            for path_descriptor in action.paths:
                _payload = Payload(
                    clean_path(path_enum, path_descriptor),
                    action_kwargs,
                    payload.event_source,
                ).validate(path_enum)
                ret = execute_payload(_payload, path_enum, paths, logger,
                                      event, context, debug)
    elif isinstance(payload.path, Queue):  # SHORTCUT
        queue = payload.path
        assert isinstance(queue.type, QueueType)
        with logger.context(
                bind={
                    "path": queue.path,
                    "queue_type": queue.type,
                    "queue_name": queue.name,
                    "kwargs": payload.kwargs,
                }):
            logger.log("Pushing record.")
        put_record(queue=queue,
                   record={
                       "path": queue.path,
                       "kwargs": payload.kwargs
                   })
    else:
        logger.info(
            f"Path should be a string (path name), Path (path Enum), or Queue: {payload.path})"
        )

    return ret
Exemple #5
0
def log_exception(state: State, e: BaseException):
    state.logger.error(utils.exception_to_str(e))
    if state.exception_handler:
        state.exception_handler(e)