Labels: enhancement, good first issue
Context
Today, exceptions raised inside the library inherit directly from Exception. Examples in dotflow/core/exception.py:
class MissingActionDecorator(Exception): ...
class ExecutionModeNotExist(Exception): ...
class NotCallableObject(Exception): ...
There is no shared root, so consumers cannot write except DotflowError to handle library-originated failures specifically. They must catch Exception and inspect the type, which is fragile and easy to get wrong.
TaskError (in the same file) is a serializer record (it captures exception_name, traceback, message for persistence). It is intentionally not an Exception subclass and should not be touched.
Concept
Introduce a single root, DotflowError, and a small hierarchy of typed exceptions for the common failure modes the engine can raise. Existing exceptions become subclasses of DotflowError. New exceptions are added for cases the library currently signals through generic Exception (retry exhaustion, task timeout, dependency cycles, input-fingerprint mismatch).
Proposed hierarchy
# dotflow/core/exception.py
class DotflowError(Exception):
"""Root of all library-raised exceptions."""
class RetryExhausted(DotflowError):
def __init__(self, task_id, attempts, last_error):
self.task_id = task_id
self.attempts = attempts
self.last_error = last_error
super().__init__(
f"Task {task_id} failed after {attempts} attempts"
)
class TaskTimeout(DotflowError):
def __init__(self, task_id, seconds):
super().__init__(
f"Task {task_id} exceeded timeout of {seconds}s"
)
class CycleError(DotflowError):
"""Task graph contains a cycle."""
class InputChangedError(DotflowError):
"""initial_context fingerprint differs from stored checkpoint
(see #281)."""
class CompensationFailed(DotflowError):
"""Saga rollback step failed (see #277)."""
# Existing exceptions become subclasses
class MissingActionDecorator(DotflowError): ...
class ExecutionModeNotExist(DotflowError): ...
class NotCallableObject(DotflowError): ...
class ImportModuleError(DotflowError): ...
Consumer benefit
try:
workflow.start()
except RetryExhausted as e:
logger.warning("retry exhausted on task %s", e.task_id)
except DotflowError:
raise # any other dotflow-originated error
except Exception:
raise # unrelated bugs in user code
Acceptance criteria
Out of scope
- Wiring the new exceptions into engine flow (
RetryExhausted raised
on exhaustion, etc.) is a follow-up. This issue introduces the hierarchy; engine integration is tracked separately.
Labels:
enhancement,good first issueContext
Today, exceptions raised inside the library inherit directly from
Exception. Examples indotflow/core/exception.py:There is no shared root, so consumers cannot write
except DotflowErrorto handle library-originated failures specifically. They must catchExceptionand inspect the type, which is fragile and easy to get wrong.TaskError(in the same file) is a serializer record (it capturesexception_name,traceback,messagefor persistence). It is intentionally not anExceptionsubclass and should not be touched.Concept
Introduce a single root,
DotflowError, and a small hierarchy of typed exceptions for the common failure modes the engine can raise. Existing exceptions become subclasses ofDotflowError. New exceptions are added for cases the library currently signals through genericException(retry exhaustion, task timeout, dependency cycles, input-fingerprint mismatch).Proposed hierarchy
Consumer benefit
Acceptance criteria
DotflowErrorexists atdotflow/core/exception.pyRetryExhausted,TaskTimeout,CycleError,InputChangedError,CompensationFaileddefinedDotflowErrorTaskErrorserializer untouchedassert isinstance(MissingActionDecorator(), DotflowError)Out of scope
RetryExhaustedraisedon exhaustion, etc.) is a follow-up. This issue introduces the hierarchy; engine integration is tracked separately.