exceptions.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. """Custom exception classes for the boilerplates CLI.
  2. This module defines specific exception types for better error handling
  3. and diagnostics throughout the application.
  4. """
  5. from __future__ import annotations
  6. from dataclasses import dataclass, field
  7. class BoilerplatesError(Exception):
  8. """Base exception for all boilerplates CLI errors."""
  9. pass
  10. class ConfigError(BoilerplatesError):
  11. """Raised when configuration operations fail."""
  12. pass
  13. class ConfigValidationError(ConfigError):
  14. """Raised when configuration validation fails."""
  15. pass
  16. class TemplateError(BoilerplatesError):
  17. """Base exception for template-related errors."""
  18. pass
  19. class TemplateNotFoundError(TemplateError):
  20. """Raised when a template cannot be found."""
  21. def __init__(self, template_id: str, module_name: str | None = None):
  22. self.template_id = template_id
  23. self.module_name = module_name
  24. msg = f"Template '{template_id}' not found"
  25. if module_name:
  26. msg += f" in module '{module_name}'"
  27. super().__init__(msg)
  28. class DuplicateTemplateError(TemplateError):
  29. """Raised when duplicate template IDs are found within the same library."""
  30. def __init__(self, template_id: str, library_name: str):
  31. self.template_id = template_id
  32. self.library_name = library_name
  33. super().__init__(
  34. f"Duplicate template ID '{template_id}' found in library '{library_name}'. "
  35. f"Each template within a library must have a unique ID."
  36. )
  37. class TemplateLoadError(TemplateError):
  38. """Raised when a template fails to load."""
  39. pass
  40. class TemplateSyntaxError(TemplateError):
  41. """Raised when a Jinja2 template has syntax errors."""
  42. def __init__(self, template_id: str, errors: list[str]):
  43. self.template_id = template_id
  44. self.errors = errors
  45. msg = f"Jinja2 syntax errors in template '{template_id}':\n" + "\n".join(errors)
  46. super().__init__(msg)
  47. class TemplateValidationError(TemplateError):
  48. """Raised when template validation fails."""
  49. pass
  50. @dataclass
  51. class RenderErrorContext:
  52. """Context information for template rendering errors."""
  53. file_path: str | None = None
  54. line_number: int | None = None
  55. column: int | None = None
  56. context_lines: list[str] = field(default_factory=list)
  57. variable_context: dict[str, str] = field(default_factory=dict)
  58. suggestions: list[str] = field(default_factory=list)
  59. original_error: Exception | None = None
  60. class TemplateRenderError(TemplateError):
  61. """Raised when template rendering fails."""
  62. def __init__(self, message: str, context: RenderErrorContext | None = None):
  63. self.context = context or RenderErrorContext()
  64. # Expose context fields as instance attributes for backward compatibility
  65. self.file_path = self.context.file_path
  66. self.line_number = self.context.line_number
  67. self.column = self.context.column
  68. self.context_lines = self.context.context_lines
  69. self.variable_context = self.context.variable_context
  70. self.suggestions = self.context.suggestions
  71. self.original_error = self.context.original_error
  72. # Build enhanced error message
  73. parts = [message]
  74. if self.context.file_path:
  75. location = f"File: {self.context.file_path}"
  76. if self.context.line_number:
  77. location += f", Line: {self.context.line_number}"
  78. if self.context.column:
  79. location += f", Column: {self.context.column}"
  80. parts.append(location)
  81. super().__init__("\n".join(parts))
  82. class VariableError(BoilerplatesError):
  83. """Base exception for variable-related errors."""
  84. pass
  85. class VariableValidationError(VariableError):
  86. """Raised when variable validation fails."""
  87. def __init__(self, variable_name: str, message: str):
  88. self.variable_name = variable_name
  89. msg = f"Validation error for variable '{variable_name}': {message}"
  90. super().__init__(msg)
  91. class VariableTypeError(VariableError):
  92. """Raised when a variable has an incorrect type."""
  93. def __init__(self, variable_name: str, expected_type: str, actual_type: str):
  94. self.variable_name = variable_name
  95. self.expected_type = expected_type
  96. self.actual_type = actual_type
  97. msg = f"Type error for variable '{variable_name}': expected {expected_type}, got {actual_type}"
  98. super().__init__(msg)
  99. class LibraryError(BoilerplatesError):
  100. """Raised when library operations fail."""
  101. pass
  102. class ModuleError(BoilerplatesError):
  103. """Raised when module operations fail."""
  104. pass
  105. class ModuleNotFoundError(ModuleError):
  106. """Raised when a module cannot be found."""
  107. def __init__(self, module_name: str):
  108. self.module_name = module_name
  109. msg = f"Module '{module_name}' not found"
  110. super().__init__(msg)
  111. class ModuleLoadError(ModuleError):
  112. """Raised when a module fails to load."""
  113. pass
  114. class FileOperationError(BoilerplatesError):
  115. """Raised when file operations fail."""
  116. pass
  117. class RenderError(BoilerplatesError):
  118. """Raised when rendering operations fail."""
  119. pass
  120. class YAMLParseError(BoilerplatesError):
  121. """Raised when YAML parsing fails."""
  122. def __init__(self, file_path: str, original_error: Exception):
  123. self.file_path = file_path
  124. self.original_error = original_error
  125. msg = f"Failed to parse YAML file '{file_path}': {original_error}"
  126. super().__init__(msg)