Exception Handling
koji-habitude provides a comprehensive exception hierarchy that correlates errors with their source in YAML files, templates, and koji objects. All exceptions include rich context information to help diagnose and fix issues quickly.
Exception Hierarchy
All koji-habitude exceptions inherit from HabitudeError, which provides:
filename - The YAML file where the error originated
lineno - The line number in the YAML file
trace - Template expansion chain (when applicable)
original_exception - The underlying exception (pydantic, jinja2, koji, etc.)
Exception Types
YAML Parsing Errors
YAMLErrorWraps
yaml.YAMLErrorwhen YAML files cannot be parsed.Example:
YAML Error: YAML parsing error: while parsing a block mapping Location: /data/tags/broken.yaml:4
Validation Errors
ValidationErrorWraps pydantic validation errors when object data fails schema validation.
Shows which fields failed validation and why.
Example:
Validation Error: Validation error for tag 'fedora-42-build' (2 validation errors): - arches: field required - extra_arches: Extra inputs are not permitted Location: /data/tags/fedora.yaml:42 Template trace: - build-tag-template in /templates/common.yaml:15
Template Errors
TemplateErrorBase class for all template-related errors. Wraps Jinja2 errors with template context.
TemplateSyntaxErrorWraps Jinja2 syntax errors when template content has invalid syntax.
Example:
Template Error: Error in template 'build-tag-template': unexpected '}' Location: /templates/common.yaml:15 Template file: common-content.j2 Template line: 23
TemplateRenderErrorWraps Jinja2 rendering errors such as undefined variables.
Example:
Template Error: Error in template 'build-tag-template': 'arch' is undefined Location: /templates/common.yaml:15 Template trace: - build-tag-template in /templates/common.yaml:15TemplateOutputErrorRaised when a template renders successfully but produces invalid output (invalid YAML or objects that fail validation).
Example:
Template Error: Invalid output from template 'build-tag-template': Template rendered invalid YAML Location: /templates/common.yaml:15
Namespace Errors
ExpansionErrorRaised when template expansion fails, such as when a referenced template cannot be found.
Shows which templates are available to help identify typos.
Example:
Template Error: Could not resolve template: missing-template Available templates: build-tag, release-tag, test-template Location: /data/calls.yaml:10
RedefineErrorRaised when attempting to redefine an object in the namespace (when
redefine=Redefine.ERROR).Shows both the original and new object locations.
Example:
Redefinition Error: Redefinition of tag 'fedora-42-build' Original: /data/tags.yaml:42 New: /data/duplicate.yaml:5 Location: /data/duplicate.yaml:5
Koji API Errors
KojiErrorBase class for koji API errors. All koji errors include object context.
ChangeReadErrorWraps
koji.GenericErrorexceptions during the query/read phase when fetching current koji state.Example:
Koji Error: Koji error during query for tag 'fedora-42-build' Error: ServerOffline: database outage in progress Location: /data/tags/fedora.yaml:42
ChangeApplyErrorWraps
koji.GenericErrorexceptions during the apply phase when writing changes to koji.Includes the specific change that failed and the koji method that was called.
Example:
Koji Error: Koji error during apply changes for tag 'fedora-42-build' Koji method: createTag Parameters: {'args': ('fedora-42-build',), 'kwargs': {'arches': 'x86_64'}} Error: TagAlreadyExists: tag fedora-42-build already exists Change: Create tag 'fedora-42-build' Location: /data/tags/fedora.yaml:42 Template trace: - build-tag-template in /templates/common.yaml:15
Template Trace
When errors occur in objects created from templates, the template trace shows the full expansion chain:
Template trace:
- build-tag-template in /templates/common.yaml:15
- release-tags in /templates/release.yaml:42
- platform-release in /templates/platforms.yaml:10
This shows that:
platform-releasetemplate (line 10 of platforms.yaml) was calledWhich expanded to
release-tagstemplate (line 42 of release.yaml)Which expanded to
build-tag-template(line 15 of common.yaml)Which finally produced the object that caused the error
The trace makes it easy to track down where configuration problems originate, even through multiple levels of template expansion.
Exception Chaining
All wrapped exceptions preserve the original exception via Python’s exception chaining (raise ... from e):
Access the original exception via
exception.__cause__Stack traces show both the wrapper and original exception
You can catch either the wrapper (e.g.,
TemplateError) or the original (e.g.,jinja2.TemplateSyntaxError)
Example:
from koji_habitude.exceptions import TemplateSyntaxError
from jinja2.exceptions import TemplateSyntaxError as Jinja2Error
try:
# ... code that might fail ...
except TemplateSyntaxError as e:
# Catches our wrapped exception with rich context
print(e) # Shows full context
print(e.__cause__) # Shows original jinja2 error
except Jinja2Error as e:
# This will also catch it (exception chaining)
print("Caught original jinja2 error")
CLI Error Display
The CLI catchall decorator in koji_habitude.cli.util provides user-friendly error display:
Each exception type is clearly labeled
Full context is displayed (file location, template trace)
Original error details are included for debugging
Users see exactly what to fix and where
State Machine Errors
The following exceptions are not part of the HabitudeError hierarchy, as they represent programming errors rather than user data errors:
ProcessorStateError- Invalid processor state transitionsChangeError- Invalid change state transitionsChangeReportError- Invalid change report state transitionsWorkflowStateError- Invalid workflow state transitions
These should not occur during normal operation and indicate bugs in the code.
Best Practices
When catching exceptions:
Catch specific types when you know what to expect:
try: namespace.expand() except ExpansionError as e: logger.error(f"Template expansion failed: {e}")
Catch HabitudeError to handle all user data errors:
try: workflow.run() except HabitudeError as e: # All user-facing errors print(f"Error: {e}") return 1
Access original exceptions for detailed debugging:
try: namespace.to_object(data) except ValidationError as e: # Show our formatted error print(e) # Access pydantic's detailed error if e.original_exception: for error in e.original_exception.errors(): print(f" Field: {error['loc']}") print(f" Type: {error['type']}")
Use exception attributes to programmatically handle errors:
try: processor.run() except ChangeApplyError as e: logger.error(f"Failed to apply changes to {e.typename} '{e.name}'") logger.error(f" File: {e.filename}:{e.lineno}") logger.error(f" Change: {e.change_description}") logger.error(f" Koji method: {e.method_name}")
API Reference
koji_habitude.exceptions
Exception hierarchy for wrapping third-party exceptions with context about the YAML file, object, template, or change that caused the error.
- author:
Christopher O’Brien <obriencj@gmail.com>
- license:
GNU General Public License v3
- ai-assistant:
Claude 3.5 Sonnet via Cursor
- exception koji_habitude.exceptions.ChangeApplyError(original_error: Exception, obj: Any, change_description: str | None = None, method_name: str | None = None, parameters: Dict[str, Any] | None = None)
Bases:
KojiErrorWraps koji exceptions that occur during the apply/write phase.
- Parameters:
original_error – The original koji error
obj – The object being modified
change_description – Optional description of the change
method_name – Optional koji method name
parameters – Optional parameters passed to the method
- exception koji_habitude.exceptions.ChangeReadError(original_error: Exception, obj: Any)
Bases:
KojiErrorWraps koji exceptions that occur during the query/read phase.
- Parameters:
original_error – The original koji error
obj – The object being queried
- exception koji_habitude.exceptions.ExpansionError(call: Any, available_templates: List[str] | None = None)
Bases:
HabitudeErrorIndicates an error during the template expansion process.
- Parameters:
call – Either a :class:TemplateCall object or a plain string message
available_templates – Optional list of available template names
- exception koji_habitude.exceptions.HabitudeError(message: str, filename: str | None = None, lineno: int | None = None, trace: List[Dict[str, Any]] | None = None, original_exception: Exception | None = None)
Bases:
ExceptionBase exception for all koji-habitude exceptions.
All custom exceptions include context about where the error originated.
- Parameters:
message – The error message
filename – Optional filename where the error occurred
lineno – Optional line number where the error occurred
trace – Optional template trace information
original_exception – Optional original exception that caused this error
- exception koji_habitude.exceptions.KojiError(original_error: Exception, typename: str | None = None, name: str | None = None, filename: str | None = None, lineno: int | None = None, trace: List[Dict[str, Any]] | None = None, operation: str | None = None, method_name: str | None = None, parameters: Dict[str, Any] | None = None)
Bases:
HabitudeErrorWraps generic koji exceptions with object context.
Used when koji API calls fail.
- Parameters:
original_error – The original koji error
typename – Optional type name of the object
name – Optional name of the object
filename – Optional filename
lineno – Optional line number
trace – Optional template trace
operation – Optional operation description
method_name – Optional koji method name
parameters – Optional parameters passed to the method
- exception koji_habitude.exceptions.RedefineError(key: Any, original_obj: Any, new_obj: Any)
Bases:
HabitudeErrorIndicates a redefinition of an object in the namespace.
- Parameters:
key – Either a BaseKey (tuple) or string (for templates)
original_obj – The original object
new_obj – The new object attempting to redefine
- exception koji_habitude.exceptions.TemplateError(original_error: Exception, template: Any | None = None, data: Dict[str, Any] | None = None, template_file: str | None = None)
Bases:
HabitudeErrorWraps Jinja2 template errors with template context.
Base class for all template-related errors.
- Parameters:
original_error – The original exception
template – Optional template object
data – Optional call data
template_file – Optional template filename
- exception koji_habitude.exceptions.TemplateOutputError(message: str, template: Any, data: Dict[str, Any], rendered_content: str | None = None, original_exception: Exception | None = None)
Bases:
HabitudeErrorUsed when template renders successfully but produces invalid output.
This can be either invalid YAML or valid YAML that fails validation.
- Parameters:
message – Error message describing the problem
template – The template object
data – The call data
rendered_content – Optional rendered content
original_exception – Optional original exception
- exception koji_habitude.exceptions.TemplateRenderError(original_error: Exception, template: Any, data: Dict[str, Any])
Bases:
TemplateErrorWraps Jinja2 rendering errors (undefined variables, etc.).
Used when template rendering fails due to missing variables or other runtime issues.
- Parameters:
original_error – The original rendering error
template – The template object
data – The call data
- exception koji_habitude.exceptions.TemplateSyntaxError(original_error: Exception, template: Any, template_file: str | None = None)
Bases:
TemplateErrorWraps Jinja2 syntax errors.
Used when template content has invalid Jinja2 syntax.
- Parameters:
original_error – The original Jinja2 syntax error
template – The template object
template_file – Optional template filename
- exception koji_habitude.exceptions.ValidationError(original_error: ValidationError, objdict: Dict[str, Any])
Bases:
HabitudeErrorWraps pydantic validation errors with object and file context.
Used when object data fails pydantic schema validation.
- Parameters:
original_error – The original pydantic validation error
objdict – The object data that failed validation
- exception koji_habitude.exceptions.YAMLError(original_error: YAMLError, filename: str | None = None)
Bases:
HabitudeErrorWraps YAML parsing errors with file context.
Used when YAML files cannot be parsed due to syntax errors.
- Parameters:
original_error – The original YAML error
filename – Optional filename where the error occurred