exceptions.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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. class BoilerplatesError(Exception):
  6. """Base exception for all boilerplates CLI errors."""
  7. pass
  8. class ConfigError(BoilerplatesError):
  9. """Raised when configuration operations fail."""
  10. pass
  11. class ConfigValidationError(ConfigError):
  12. """Raised when configuration validation fails."""
  13. pass
  14. class TemplateError(BoilerplatesError):
  15. """Base exception for template-related errors."""
  16. pass
  17. class TemplateNotFoundError(TemplateError):
  18. """Raised when a template cannot be found."""
  19. def __init__(self, template_id: str, module_name: str | None = None):
  20. self.template_id = template_id
  21. self.module_name = module_name
  22. msg = f"Template '{template_id}' not found"
  23. if module_name:
  24. msg += f" in module '{module_name}'"
  25. super().__init__(msg)
  26. class DuplicateTemplateError(TemplateError):
  27. """Raised when duplicate template IDs are found within the same library."""
  28. def __init__(self, template_id: str, library_name: str):
  29. self.template_id = template_id
  30. self.library_name = library_name
  31. super().__init__(
  32. f"Duplicate template ID '{template_id}' found in library '{library_name}'. "
  33. f"Each template within a library must have a unique ID."
  34. )
  35. class TemplateLoadError(TemplateError):
  36. """Raised when a template fails to load."""
  37. pass
  38. class TemplateSyntaxError(TemplateError):
  39. """Raised when a Jinja2 template has syntax errors."""
  40. def __init__(self, template_id: str, errors: list[str]):
  41. self.template_id = template_id
  42. self.errors = errors
  43. msg = f"Jinja2 syntax errors in template '{template_id}':\n" + "\n".join(errors)
  44. super().__init__(msg)
  45. class TemplateValidationError(TemplateError):
  46. """Raised when template validation fails."""
  47. pass
  48. class IncompatibleSchemaVersionError(TemplateError):
  49. """Raised when a template uses a schema version not supported by the module."""
  50. def __init__(
  51. self,
  52. template_id: str,
  53. template_schema: str,
  54. module_schema: str,
  55. module_name: str,
  56. ):
  57. self.template_id = template_id
  58. self.template_schema = template_schema
  59. self.module_schema = module_schema
  60. self.module_name = module_name
  61. msg = (
  62. f"Template '{template_id}' uses schema version {template_schema}, "
  63. f"but module '{module_name}' only supports up to version {module_schema}.\n\n"
  64. f"This template requires features not available in your current CLI version.\n"
  65. f"Please upgrade the boilerplates CLI.\n\n"
  66. f"Run: pip install --upgrade boilerplates"
  67. )
  68. super().__init__(msg)
  69. class TemplateRenderError(TemplateError):
  70. """Raised when template rendering fails."""
  71. def __init__(
  72. self,
  73. message: str,
  74. file_path: str | None = None,
  75. line_number: int | None = None,
  76. column: int | None = None,
  77. context_lines: list[str] | None = None,
  78. variable_context: dict[str, str] | None = None,
  79. suggestions: list[str] | None = None,
  80. original_error: Exception | None = None,
  81. ):
  82. self.file_path = file_path
  83. self.line_number = line_number
  84. self.column = column
  85. self.context_lines = context_lines or []
  86. self.variable_context = variable_context or {}
  87. self.suggestions = suggestions or []
  88. self.original_error = original_error
  89. # Build enhanced error message
  90. parts = [message]
  91. if file_path:
  92. location = f"File: {file_path}"
  93. if line_number:
  94. location += f", Line: {line_number}"
  95. if column:
  96. location += f", Column: {column}"
  97. parts.append(location)
  98. super().__init__("\n".join(parts))
  99. class VariableError(BoilerplatesError):
  100. """Base exception for variable-related errors."""
  101. pass
  102. class VariableValidationError(VariableError):
  103. """Raised when variable validation fails."""
  104. def __init__(self, variable_name: str, message: str):
  105. self.variable_name = variable_name
  106. msg = f"Validation error for variable '{variable_name}': {message}"
  107. super().__init__(msg)
  108. class VariableTypeError(VariableError):
  109. """Raised when a variable has an incorrect type."""
  110. def __init__(self, variable_name: str, expected_type: str, actual_type: str):
  111. self.variable_name = variable_name
  112. self.expected_type = expected_type
  113. self.actual_type = actual_type
  114. msg = f"Type error for variable '{variable_name}': expected {expected_type}, got {actual_type}"
  115. super().__init__(msg)
  116. class LibraryError(BoilerplatesError):
  117. """Raised when library operations fail."""
  118. pass
  119. class ModuleError(BoilerplatesError):
  120. """Raised when module operations fail."""
  121. pass
  122. class ModuleNotFoundError(ModuleError):
  123. """Raised when a module cannot be found."""
  124. def __init__(self, module_name: str):
  125. self.module_name = module_name
  126. msg = f"Module '{module_name}' not found"
  127. super().__init__(msg)
  128. class ModuleLoadError(ModuleError):
  129. """Raised when a module fails to load."""
  130. pass
  131. class FileOperationError(BoilerplatesError):
  132. """Raised when file operations fail."""
  133. pass
  134. class RenderError(BoilerplatesError):
  135. """Raised when rendering operations fail."""
  136. pass
  137. class YAMLParseError(BoilerplatesError):
  138. """Raised when YAML parsing fails."""
  139. def __init__(self, file_path: str, original_error: Exception):
  140. self.file_path = file_path
  141. self.original_error = original_error
  142. msg = f"Failed to parse YAML file '{file_path}': {original_error}"
  143. super().__init__(msg)