183 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| #    Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
 | |
| #
 | |
| #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | |
| #    not use this file except in compliance with the License. You may obtain
 | |
| #    a copy of the License at
 | |
| #
 | |
| #         http://www.apache.org/licenses/LICENSE-2.0
 | |
| #
 | |
| #    Unless required by applicable law or agreed to in writing, software
 | |
| #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | |
| #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | |
| #    License for the specific language governing permissions and limitations
 | |
| #    under the License.
 | |
| 
 | |
| import traceback
 | |
| 
 | |
| import six
 | |
| 
 | |
| 
 | |
| class TaskFlowException(Exception):
 | |
|     """Base class for *most* exceptions emitted from this library.
 | |
| 
 | |
|     NOTE(harlowja): in later versions of python we can likely remove the need
 | |
|     to have a cause here as PY3+ have implemented PEP 3134 which handles
 | |
|     chaining in a much more elegant manner.
 | |
|     """
 | |
|     def __init__(self, message, cause=None):
 | |
|         super(TaskFlowException, self).__init__(message)
 | |
|         self._cause = cause
 | |
| 
 | |
|     @property
 | |
|     def cause(self):
 | |
|         return self._cause
 | |
| 
 | |
|     def pformat(self, indent=2):
 | |
|         """Pretty formats a taskflow exception + any connected causes."""
 | |
|         if indent < 0:
 | |
|             raise ValueError("indent must be greater than or equal to zero")
 | |
| 
 | |
|         def _format(excp, indent_by):
 | |
|             lines = []
 | |
|             for line in traceback.format_exception_only(type(excp), excp):
 | |
|                 # We'll add our own newlines on at the end of formatting.
 | |
|                 if line.endswith("\n"):
 | |
|                     line = line[0:-1]
 | |
|                 lines.append((" " * indent_by) + line)
 | |
|             try:
 | |
|                 lines.extend(_format(excp.cause, indent_by + indent))
 | |
|             except AttributeError:
 | |
|                 pass
 | |
|             return lines
 | |
| 
 | |
|         return "\n".join(_format(self, 0))
 | |
| 
 | |
| 
 | |
| # Errors related to storage or operations on storage units.
 | |
| 
 | |
| class StorageFailure(TaskFlowException):
 | |
|     """Raised when storage backends can not be read/saved/deleted."""
 | |
| 
 | |
| 
 | |
| # Job related errors.
 | |
| 
 | |
| class JobFailure(TaskFlowException):
 | |
|     """Errors related to jobs or operations on jobs."""
 | |
| 
 | |
| 
 | |
| class UnclaimableJob(JobFailure):
 | |
|     """Raised when a job can not be claimed."""
 | |
| 
 | |
| 
 | |
| # Engine/ during execution related errors.
 | |
| 
 | |
| class ExecutionFailure(TaskFlowException):
 | |
|     """Errors related to engine execution."""
 | |
| 
 | |
| 
 | |
| class RequestTimeout(ExecutionFailure):
 | |
|     """Raised when a worker request was not finished within an allotted
 | |
|     timeout.
 | |
|     """
 | |
| 
 | |
| 
 | |
| class InvalidState(ExecutionFailure):
 | |
|     """Raised when a invalid state transition is attempted while executing."""
 | |
| 
 | |
| 
 | |
| # Other errors that do not fit the above categories (at the current time).
 | |
| 
 | |
| 
 | |
| class DependencyFailure(TaskFlowException):
 | |
|     """Raised when some type of dependency problem occurs."""
 | |
| 
 | |
| 
 | |
| class MissingDependencies(DependencyFailure):
 | |
|     """Raised when a entity has dependencies that can not be satisfied."""
 | |
|     MESSAGE_TPL = ("%(who)s requires %(requirements)s but no other entity"
 | |
|                    " produces said requirements")
 | |
| 
 | |
|     def __init__(self, who, requirements, cause=None):
 | |
|         message = self.MESSAGE_TPL % {'who': who, 'requirements': requirements}
 | |
|         super(MissingDependencies, self).__init__(message, cause=cause)
 | |
|         self.missing_requirements = requirements
 | |
| 
 | |
| 
 | |
| class IncompatibleVersion(TaskFlowException):
 | |
|     """Raised when some type of version incompatibility is found."""
 | |
| 
 | |
| 
 | |
| class Duplicate(TaskFlowException):
 | |
|     """Raised when a duplicate entry is found."""
 | |
| 
 | |
| 
 | |
| class NotFound(TaskFlowException):
 | |
|     """Raised when some entry in some object doesn't exist."""
 | |
| 
 | |
| 
 | |
| class Empty(TaskFlowException):
 | |
|     """Raised when some object is empty when it shouldn't be."""
 | |
| 
 | |
| 
 | |
| # Others.
 | |
| 
 | |
| class WrappedFailure(Exception):
 | |
|     """Wraps one or several failures.
 | |
| 
 | |
|     When exception cannot be re-raised (for example, because
 | |
|     the value and traceback is lost in serialization) or
 | |
|     there are several exceptions, we wrap corresponding Failure
 | |
|     objects into this exception class.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, causes):
 | |
|         super(WrappedFailure, self).__init__()
 | |
|         self._causes = []
 | |
|         for cause in causes:
 | |
|             if cause.check(type(self)) and cause.exception:
 | |
|                 # NOTE(imelnikov): flatten wrapped failures.
 | |
|                 self._causes.extend(cause.exception)
 | |
|             else:
 | |
|                 self._causes.append(cause)
 | |
| 
 | |
|     def __iter__(self):
 | |
|         """Iterate over failures that caused the exception."""
 | |
|         return iter(self._causes)
 | |
| 
 | |
|     def __len__(self):
 | |
|         """Return number of wrapped failures."""
 | |
|         return len(self._causes)
 | |
| 
 | |
|     def check(self, *exc_classes):
 | |
|         """Check if any of exc_classes caused (part of) the failure.
 | |
| 
 | |
|         Arguments of this method can be exception types or type names
 | |
|         (strings). If any of wrapped failures were caused by exception
 | |
|         of given type, the corresponding argument is returned. Else,
 | |
|         None is returned.
 | |
|         """
 | |
|         if not exc_classes:
 | |
|             return None
 | |
|         for cause in self:
 | |
|             result = cause.check(*exc_classes)
 | |
|             if result is not None:
 | |
|                 return result
 | |
|         return None
 | |
| 
 | |
|     def __str__(self):
 | |
|         causes = [exception_message(cause) for cause in self._causes]
 | |
|         return 'WrappedFailure: %s' % causes
 | |
| 
 | |
| 
 | |
| def exception_message(exc):
 | |
|     """Return the string representation of exception."""
 | |
|     # NOTE(imelnikov): Dealing with non-ascii data in python is difficult:
 | |
|     # https://bugs.launchpad.net/taskflow/+bug/1275895
 | |
|     # https://bugs.launchpad.net/taskflow/+bug/1276053
 | |
|     try:
 | |
|         return six.text_type(exc)
 | |
|     except UnicodeError:
 | |
|         return str(exc)
 | 
