django-viewflow, как правильно обрабатывать исключения в обработчике? - PullRequest
0 голосов
/ 11 ноября 2019

Допустим, у меня есть следующий узел:

perform_disk_size_change = ( 
    flow.Handler(
        this.perform_proxmox_api_request
    ).Next(this.notify_iaas_success)
)

Что если perform_proxmox_api_request вызовет исключение? Могу ли я указать узел исключения, чтобы любое исключение попадало на этот узел?

perform_disk_size_change = ( 
    flow.Handler(
        this.perform_proxmox_api_request
    ).Next(this.notify_iaas_success).Except(this.notify_iaas_fail)
)

1 Ответ

0 голосов
/ 11 ноября 2019

Итак, я пришел к следующему решению, не совсем уверенный, если это будет сделано таким образом, проблема в том, что HandlerActivation выполняется немедленно, поэтому не дает мне возможности передать объект исключения для следующей активации (если этоУзел-обработчик), я до сих пор не знаю, как передать исключение для следующей активации без необходимости использования другого пользовательского узла.

from copy import copy
from viewflow.activation import Activation, STATUS, all_leading_canceled
from viewflow.nodes.handler import HandlerActivation
from viewflow.rest import flow


class HandlerActivationCatchExcept(HandlerActivation):
    def __init__(self, *args, **kwargs):
        self.caught_exception = None
        super(HandlerActivationCatchExcept, self).__init__(*args, **kwargs)

    def execute(self):
        """Run the callback."""
        try:
            self.flow_task.handler(self)
        except Exception as ex:
            self.caught_exception = ex

    @Activation.status.transition(source=STATUS.DONE, conditions=[all_leading_canceled])
    def activate_next(self):
        """Activate all outgoing edges."""
        if self.caught_exception:
            for mapping in self.flow_task.exception_mappings:
                if isinstance(self.caught_exception, mapping['cls']):
                    mapping['node'].activate(prev_activation=self, token=self.task.token)
        else:
            super(HandlerActivationCatchExcept, self).activate_next()


class CatchExceptionHandler(flow.Handler):
    """
    Custom handler node to allow catching exception and route to certain node
    usage: CatchExceptionHandler(method).Except(default_node)
           CatchExceptionHandler(method).Except(specific_node, SpecificExceptionCls).Except(default_node)
    """

    activation_class = HandlerActivationCatchExcept

    def __init__(self, *args, **kwargs):
        super(HandlerCatchExcept, self).__init__(*args, **kwargs)
        self.exception_mappings = []

    def _resolve(self, resolver):
        super(HandlerCatchExcept, self)._resolve(resolver)
        for exception_mapping in self.exception_mappings:
            exception_mapping['node'] = resolver.get_implementation(exception_mapping['node'])

    def _outgoing(self):
        if self._next:
            yield Edge(src=self, dst=self._next, edge_class='next')
        # not sure what edge_class to use for exception in diagram
        for exception_mapping in self.exception_mappings:
            yield Edge(src=self, dst=exception_mapping['node'], edge_class='next')

    def Except(self, node, exception_cls=Exception):
        self.exception_mappings.append({"cls": exception_cls, "node": node})
        return copy(self)


class HandleExceptionActivation(HandlerActivation):
    @classmethod
    def activate(cls, flow_task, prev_activation, token):
        """Instantiate new task."""
        task = flow_task.flow_class.task_class(
            process=prev_activation.process,
            flow_task=flow_task,
            token=token)

        task.save()
        task.previous.add(prev_activation.task)

        activation = cls()
        # adds previous activation ref
        activation.prev_activation = prev_activation
        activation.initialize(flow_task, task)
        activation.perform()

        return activation


class ExceptionHandler(flow.Handler):
    activation_class = HandleExceptionActivation

использование будет похоже на:

perform_disk_size_change = (
    nodes.CatchExceptionHandler(
        this.perform_proxmox_api_request
    ).Next(
        this.notify_iaas_success
    ).Except(
        this.notify_iaas_team, TechnicalExceptionCls
    ).Except(
        this.notify_customer_failed
    )
)

notify_iaas_team = ExceptionHandler(this.email_iaas)
notify_customer_decline = Handler(...)

def email_iaas(self, activation):
    # access to previous activation
    prev_activation = activation.prev_activation
    # access to previously caught exception
    exception = prev_activation.caught_exception
    ...
...