132 lines
4.9 KiB
Python
132 lines
4.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (C) 2007 Samuel Abels
|
|
#
|
|
# This library is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
# License as published by the Free Software Foundation; either
|
|
# version 2.1 of the License, or (at your option) any later version.
|
|
#
|
|
# This library is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
# License along with this library; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
# 02110-1301 USA
|
|
import re
|
|
|
|
from SpiffWorkflow.util import levenshtein
|
|
|
|
|
|
class SpiffWorkflowException(Exception):
|
|
"""
|
|
Base class for all SpiffWorkflow-generated exceptions.
|
|
"""
|
|
def __init__(self, msg):
|
|
super().__init__(msg)
|
|
self.notes = []
|
|
|
|
def add_note(self, note):
|
|
"""add_note is a python 3.11 feature, this can be removed when we
|
|
stop supporting versions prior to 3.11"""
|
|
self.notes.append(note)
|
|
|
|
def __str__(self):
|
|
return super().__str__() + ". " + ". ".join(self.notes)
|
|
|
|
|
|
class WorkflowException(SpiffWorkflowException):
|
|
"""
|
|
Base class for all SpiffWorkflow-generated exceptions.
|
|
"""
|
|
|
|
def __init__(self, message, task_spec=None):
|
|
"""
|
|
Standard exception class.
|
|
|
|
:param task_spec: the task spec that threw the exception
|
|
:type task_spec: TaskSpec
|
|
:param error: a human-readable error message
|
|
:type error: string
|
|
"""
|
|
super().__init__(str(message))
|
|
# Points to the TaskSpec that generated the exception.
|
|
self.task_spec = task_spec
|
|
|
|
@staticmethod
|
|
def get_task_trace(task):
|
|
task_trace = [f"{task.task_spec.description} ({task.workflow.spec.file})"]
|
|
workflow = task.workflow
|
|
while workflow != workflow.outer_workflow:
|
|
caller = workflow.name
|
|
workflow = workflow.outer_workflow
|
|
task_trace.append(f"{workflow.spec.task_specs[caller].description} ({workflow.spec.file})")
|
|
return task_trace
|
|
|
|
@staticmethod
|
|
def did_you_mean_from_name_error(name_exception, options):
|
|
"""Returns a string along the lines of 'did you mean 'dog'? Given
|
|
a name_error, and a set of possible things that could have been called,
|
|
or an empty string if no valid suggestions come up. """
|
|
if isinstance(name_exception, NameError):
|
|
def_match = re.match("name '(.+)' is not defined", str(name_exception))
|
|
if def_match:
|
|
bad_variable = re.match("name '(.+)' is not defined",
|
|
str(name_exception)).group(1)
|
|
most_similar = levenshtein.most_similar(bad_variable,
|
|
options, 3)
|
|
error_msg = ""
|
|
if len(most_similar) == 1:
|
|
error_msg += f' Did you mean \'{most_similar[0]}\'?'
|
|
if len(most_similar) > 1:
|
|
error_msg += f' Did you mean one of \'{most_similar}\'?'
|
|
return error_msg
|
|
|
|
|
|
class WorkflowTaskException(WorkflowException):
|
|
"""WorkflowException that provides task_trace information."""
|
|
|
|
def __init__(self, error_msg, task=None, exception=None,
|
|
line_number=None, offset=None, error_line=None):
|
|
"""
|
|
Exception initialization.
|
|
|
|
:param task: the task that threw the exception
|
|
:type task: Task
|
|
:param error_msg: a human readable error message
|
|
:type error_msg: str
|
|
:param exception: an exception to wrap, if any
|
|
:type exception: Exception
|
|
"""
|
|
|
|
self.task = task
|
|
self.line_number = line_number
|
|
self.offset = offset
|
|
self.error_line = error_line
|
|
if exception:
|
|
self.error_type = exception.__class__.__name__
|
|
else:
|
|
self.error_type = "unknown"
|
|
super().__init__(error_msg, task_spec=task.task_spec)
|
|
|
|
if isinstance(exception, SyntaxError) and not line_number:
|
|
# Line number and offset can be recovered directly from syntax errors,
|
|
# otherwise they must be passed in.
|
|
self.line_number = exception.lineno
|
|
self.offset = exception.offset
|
|
elif isinstance(exception, NameError):
|
|
self.add_note(self.did_you_mean_from_name_error(exception, list(task.data.keys())))
|
|
|
|
# If encountered in a sub-workflow, this traces back up the stack,
|
|
# so we can tell how we got to this particular task, no matter how
|
|
# deeply nested in sub-workflows it is. Takes the form of:
|
|
# task-description (file-name)
|
|
self.task_trace = self.get_task_trace(task)
|
|
|
|
|
|
|
|
class StorageException(SpiffWorkflowException):
|
|
pass
|