exceptions.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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. class IncompatibleSchemaVersionError(TemplateError):
  51. """Raised when a template uses a schema version not supported by the module."""
  52. def __init__(
  53. self,
  54. template_id: str,
  55. template_schema: str,
  56. module_schema: str,
  57. module_name: str,
  58. ):
  59. self.template_id = template_id
  60. self.template_schema = template_schema
  61. self.module_schema = module_schema
  62. self.module_name = module_name
  63. msg = (
  64. f"Template '{template_id}' uses schema version {template_schema}, "
  65. f"but module '{module_name}' only supports up to version {module_schema}.\n\n"
  66. f"This template requires features not available in your current CLI version.\n"
  67. f"Please upgrade the boilerplates CLI.\n\n"
  68. f"Run: pip install --upgrade boilerplates"
  69. )
  70. super().__init__(msg)
  71. @dataclass
  72. class RenderErrorContext:
  73. """Context information for template rendering errors."""
  74. file_path: str | None = None
  75. line_number: int | None = None
  76. column: int | None = None
  77. context_lines: list[str] = field(default_factory=list)
  78. variable_context: dict[str, str] = field(default_factory=dict)
  79. suggestions: list[str] = field(default_factory=list)
  80. original_error: Exception | None = None
  81. class TemplateRenderError(TemplateError):
  82. """Raised when template rendering fails."""
  83. def __init__(self, message: str, context: RenderErrorContext | None = None):
  84. self.context = context or RenderErrorContext()
  85. # Expose context fields as instance attributes for backward compatibility
  86. self.file_path = self.context.file_path
  87. self.line_number = self.context.line_number
  88. self.column = self.context.column
  89. self.context_lines = self.context.context_lines
  90. self.variable_context = self.context.variable_context
  91. self.suggestions = self.context.suggestions
  92. self.original_error = self.context.original_error
  93. # Build enhanced error message
  94. parts = [message]
  95. if self.context.file_path:
  96. location = f"File: {self.context.file_path}"
  97. if self.context.line_number:
  98. location += f", Line: {self.context.line_number}"
  99. if self.context.column:
  100. location += f", Column: {self.context.column}"
  101. parts.append(location)
  102. super().__init__("\n".join(parts))
  103. class VariableError(BoilerplatesError):
  104. """Base exception for variable-related errors."""
  105. pass
  106. class VariableValidationError(VariableError):
  107. """Raised when variable validation fails."""
  108. def __init__(self, variable_name: str, message: str):
  109. self.variable_name = variable_name
  110. msg = f"Validation error for variable '{variable_name}': {message}"
  111. super().__init__(msg)
  112. class VariableTypeError(VariableError):
  113. """Raised when a variable has an incorrect type."""
  114. def __init__(self, variable_name: str, expected_type: str, actual_type: str):
  115. self.variable_name = variable_name
  116. self.expected_type = expected_type
  117. self.actual_type = actual_type
  118. msg = f"Type error for variable '{variable_name}': expected {expected_type}, got {actual_type}"
  119. super().__init__(msg)
  120. class LibraryError(BoilerplatesError):
  121. """Raised when library operations fail."""
  122. pass
  123. class ModuleError(BoilerplatesError):
  124. """Raised when module operations fail."""
  125. pass
  126. class ModuleNotFoundError(ModuleError):
  127. """Raised when a module cannot be found."""
  128. def __init__(self, module_name: str):
  129. self.module_name = module_name
  130. msg = f"Module '{module_name}' not found"
  131. super().__init__(msg)
  132. class ModuleLoadError(ModuleError):
  133. """Raised when a module fails to load."""
  134. pass
  135. class SchemaError(BoilerplatesError):
  136. """Raised when schema operations fail."""
  137. def __init__(self, message: str, details: str | None = None):
  138. self.details = details
  139. msg = message
  140. if details:
  141. msg += f" ({details})"
  142. super().__init__(msg)
  143. class FileOperationError(BoilerplatesError):
  144. """Raised when file operations fail."""
  145. pass
  146. class RenderError(BoilerplatesError):
  147. """Raised when rendering operations fail."""
  148. pass
  149. class YAMLParseError(BoilerplatesError):
  150. """Raised when YAML parsing fails."""
  151. def __init__(self, file_path: str, original_error: Exception):
  152. self.file_path = file_path
  153. self.original_error = original_error
  154. msg = f"Failed to parse YAML file '{file_path}': {original_error}"
  155. super().__init__(msg)