def save_error(self, context, exception, swallowed): """Append step's exception information to the context. Append the[on_error] dictionary to the context. This will append to existing `runErrors` values if `runErrors` are already in there. Args: context: (pypyr.context.Context) The pypyr context. This arg will mutate - after method execution will contain the new updated context. exception: (Exception) The error detected during step execution. swallowed: (bool) Whether exception was swallowed or not. """ failure = { 'name': get_error_name(exception), 'description': str(exception), 'customError': context.get_formatted_iterable(self.on_error) if self.on_error else {}, 'line': self.line_no, 'col': self.line_col, 'step': self.name, 'exception': exception, 'swallowed': swallowed, } run_errors = context.setdefault('runErrors', []) run_errors.append(failure)
def exec_iteration(self, counter, context, step_method): """Run a single retry iteration. This method abides by the signature invoked by poll.while_until_true, which is to say (counter, *args, **kwargs). In a normal execution chain, this method's args passed by self.retry_loop where context and step_method set. while_until_true injects counter as a 1st arg. Args: counter. int. loop counter, which number of iteration is this. context: (pypyr.context.Context) The pypyr context. This arg will mutate - after method execution will contain the new updated context. step_method: (method/function) This is the method/function that will execute on every loop iteration. Signature is: function(context) Returns: bool. True if step execution completed without error. False if error occured during step execution. """ logger.debug("starting") context['retryCounter'] = counter self.retry_counter = counter logger.info("retry: running step with counter %s", counter) try: step_method(context) result = True except (ControlOfFlowInstruction, Stop): # Control-of-Flow/Stop are instructions to go somewhere # else, not errors per se. raise except Exception as ex_info: if self.max: if counter == self.max: logger.debug( "retry: max %s retries exhausted. " "raising error.", counter) # arguably shouldn't be using errs for control of flow. # but would lose the err info if not, so lesser of 2 evils. raise if isinstance(ex_info, HandledError): ex_info = ex_info.__cause__ if self.stop_on or self.retry_on: error_name = get_error_name(ex_info) if self.stop_on: formatted_stop_list = context.get_formatted_iterable( self.stop_on) if error_name in formatted_stop_list: logger.error( "%s in stopOn. Raising error " "and exiting retry.", error_name) raise else: logger.debug("%s not in stopOn. Continue.", error_name) if self.retry_on: formatted_retry_list = context.get_formatted_iterable( self.retry_on) if error_name not in formatted_retry_list: logger.error( "%s not in retryOn. Raising " "error and exiting retry.", error_name) raise else: logger.debug("%s in retryOn. Retry again.", error_name) result = False logger.error( "retry: ignoring error because retryCounter < max.\n" "%s: %s", type(ex_info).__name__, ex_info) logger.debug("retry: done step with counter %s", counter) logger.debug("done") return result
def run_conditional_decorators(self, context): """Evaluate the step decorators to decide whether to run step or not. Use pypyr.dsl.Step.run_step if you intend on executing the step the same way pypyr does. Args: context: (pypyr.context.Context) The pypyr context. This arg will mutate. """ logger.debug("starting") # The decorator attributes might contain formatting expressions that # change whether they evaluate True or False, thus apply formatting at # last possible instant. run_me = context.get_formatted_as_type(self.run_me, out_type=bool) skip_me = context.get_formatted_as_type(self.skip_me, out_type=bool) swallow_me = context.get_formatted_as_type(self.swallow_me, out_type=bool) if run_me: if not skip_me: try: if self.retry_decorator: self.retry_decorator.retry_loop( context, self.invoke_step) else: self.invoke_step(context=context) except (ControlOfFlowInstruction, Stop): # Control-of-Flow/Stop are instructions to go somewhere # else, not errors per se. raise except Exception as exc_info: if isinstance(exc_info, HandledError): exc_info = exc_info.__cause__ else: # prevent already logged err logging twice. self.save_error(context=context, exception=exc_info, swallowed=swallow_me) if swallow_me: logger.error( "%s Ignoring error because swallow " "is True for this step.\n" "%s: %s", self.name, get_error_name(exc_info), exc_info) else: if self.line_no: logger.error( "Error while running step %s " "at pipeline yaml line: %d, col: %d", self.name, self.line_no, self.line_col) else: logger.error("Error while running step %s", self.name) raise exc_info else: logger.info("%s not running because skip is True.", self.name) else: logger.info("%s not running because run is False.", self.name) logger.debug("done")
def test_get_error_name_canonical(): """Other error returns modulename.name on get_error_name.""" assert get_error_name(ContextError('blah')) == 'pypyr.errors.ContextError'
def test_get_error_name_builtin(): """Builtin returns bare name on get_error_name.""" assert get_error_name(ValueError('blah')) == 'ValueError'