simple-resume 0.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. simple_resume/__init__.py +132 -0
  2. simple_resume/core/__init__.py +47 -0
  3. simple_resume/core/colors.py +215 -0
  4. simple_resume/core/config.py +672 -0
  5. simple_resume/core/constants/__init__.py +207 -0
  6. simple_resume/core/constants/colors.py +98 -0
  7. simple_resume/core/constants/files.py +28 -0
  8. simple_resume/core/constants/layout.py +58 -0
  9. simple_resume/core/dependencies.py +258 -0
  10. simple_resume/core/effects.py +154 -0
  11. simple_resume/core/exceptions.py +261 -0
  12. simple_resume/core/file_operations.py +68 -0
  13. simple_resume/core/generate/__init__.py +21 -0
  14. simple_resume/core/generate/exceptions.py +69 -0
  15. simple_resume/core/generate/html.py +233 -0
  16. simple_resume/core/generate/pdf.py +659 -0
  17. simple_resume/core/generate/plan.py +131 -0
  18. simple_resume/core/hydration.py +55 -0
  19. simple_resume/core/importers/__init__.py +3 -0
  20. simple_resume/core/importers/json_resume.py +284 -0
  21. simple_resume/core/latex/__init__.py +60 -0
  22. simple_resume/core/latex/context.py +56 -0
  23. simple_resume/core/latex/conversion.py +227 -0
  24. simple_resume/core/latex/escaping.py +68 -0
  25. simple_resume/core/latex/fonts.py +93 -0
  26. simple_resume/core/latex/formatting.py +81 -0
  27. simple_resume/core/latex/sections.py +218 -0
  28. simple_resume/core/latex/types.py +84 -0
  29. simple_resume/core/markdown.py +127 -0
  30. simple_resume/core/models.py +102 -0
  31. simple_resume/core/palettes/__init__.py +38 -0
  32. simple_resume/core/palettes/common.py +73 -0
  33. simple_resume/core/palettes/data/default_palettes.json +58 -0
  34. simple_resume/core/palettes/exceptions.py +33 -0
  35. simple_resume/core/palettes/fetch_types.py +52 -0
  36. simple_resume/core/palettes/generators.py +137 -0
  37. simple_resume/core/palettes/registry.py +76 -0
  38. simple_resume/core/palettes/resolution.py +123 -0
  39. simple_resume/core/palettes/sources.py +162 -0
  40. simple_resume/core/paths.py +21 -0
  41. simple_resume/core/protocols.py +134 -0
  42. simple_resume/core/py.typed +0 -0
  43. simple_resume/core/render/__init__.py +37 -0
  44. simple_resume/core/render/manage.py +199 -0
  45. simple_resume/core/render/plan.py +405 -0
  46. simple_resume/core/result.py +226 -0
  47. simple_resume/core/resume.py +609 -0
  48. simple_resume/core/skills.py +60 -0
  49. simple_resume/core/validation.py +321 -0
  50. simple_resume/py.typed +0 -0
  51. simple_resume/shell/__init__.py +3 -0
  52. simple_resume/shell/assets/static/css/README.md +213 -0
  53. simple_resume/shell/assets/static/css/common.css +641 -0
  54. simple_resume/shell/assets/static/css/fonts.css +42 -0
  55. simple_resume/shell/assets/static/css/preview.css +82 -0
  56. simple_resume/shell/assets/static/css/print.css +99 -0
  57. simple_resume/shell/assets/static/fonts/AvenirLTStd-Book.otf +0 -0
  58. simple_resume/shell/assets/static/fonts/AvenirLTStd-Light.otf +0 -0
  59. simple_resume/shell/assets/static/fonts/AvenirLTStd-Medium.otf +0 -0
  60. simple_resume/shell/assets/static/fonts/AvenirLTStd-Oblique.otf +0 -0
  61. simple_resume/shell/assets/static/fonts/AvenirLTStd-Roman.otf +0 -0
  62. simple_resume/shell/assets/static/fonts/fontawesome/Font Awesome 6 Brands-Regular-400.otf +0 -0
  63. simple_resume/shell/assets/static/fonts/fontawesome/Font Awesome 6 Free-Solid-900.otf +0 -0
  64. simple_resume/shell/assets/static/images/default_profile_1.jpg +0 -0
  65. simple_resume/shell/assets/static/images/default_profile_2.png +0 -0
  66. simple_resume/shell/assets/static/schema.json +236 -0
  67. simple_resume/shell/assets/static/themes/README.md +208 -0
  68. simple_resume/shell/assets/static/themes/bold.yaml +64 -0
  69. simple_resume/shell/assets/static/themes/classic.yaml +64 -0
  70. simple_resume/shell/assets/static/themes/executive.yaml +64 -0
  71. simple_resume/shell/assets/static/themes/minimal.yaml +64 -0
  72. simple_resume/shell/assets/static/themes/modern.yaml +64 -0
  73. simple_resume/shell/assets/templates/html/cover.html +129 -0
  74. simple_resume/shell/assets/templates/html/demo.html +13 -0
  75. simple_resume/shell/assets/templates/html/resume_base.html +453 -0
  76. simple_resume/shell/assets/templates/html/resume_no_bars.html +316 -0
  77. simple_resume/shell/assets/templates/html/resume_with_bars.html +362 -0
  78. simple_resume/shell/cli/__init__.py +35 -0
  79. simple_resume/shell/cli/main.py +975 -0
  80. simple_resume/shell/cli/palette.py +75 -0
  81. simple_resume/shell/cli/random_palette_demo.py +407 -0
  82. simple_resume/shell/config.py +96 -0
  83. simple_resume/shell/effect_executor.py +211 -0
  84. simple_resume/shell/file_opener.py +308 -0
  85. simple_resume/shell/generate/__init__.py +37 -0
  86. simple_resume/shell/generate/core.py +650 -0
  87. simple_resume/shell/generate/lazy.py +284 -0
  88. simple_resume/shell/io_utils.py +199 -0
  89. simple_resume/shell/palettes/__init__.py +1 -0
  90. simple_resume/shell/palettes/fetch.py +63 -0
  91. simple_resume/shell/palettes/loader.py +321 -0
  92. simple_resume/shell/palettes/remote.py +179 -0
  93. simple_resume/shell/pdf_executor.py +52 -0
  94. simple_resume/shell/py.typed +0 -0
  95. simple_resume/shell/render/__init__.py +1 -0
  96. simple_resume/shell/render/latex.py +308 -0
  97. simple_resume/shell/render/operations.py +240 -0
  98. simple_resume/shell/resume_extensions.py +737 -0
  99. simple_resume/shell/runtime/__init__.py +7 -0
  100. simple_resume/shell/runtime/content.py +190 -0
  101. simple_resume/shell/runtime/generate.py +497 -0
  102. simple_resume/shell/runtime/lazy.py +138 -0
  103. simple_resume/shell/runtime/lazy_import.py +173 -0
  104. simple_resume/shell/service_locator.py +80 -0
  105. simple_resume/shell/services.py +256 -0
  106. simple_resume/shell/session/__init__.py +6 -0
  107. simple_resume/shell/session/config.py +35 -0
  108. simple_resume/shell/session/manage.py +386 -0
  109. simple_resume/shell/strategies.py +181 -0
  110. simple_resume/shell/themes/__init__.py +35 -0
  111. simple_resume/shell/themes/loader.py +230 -0
  112. simple_resume-0.1.9.dist-info/METADATA +201 -0
  113. simple_resume-0.1.9.dist-info/RECORD +116 -0
  114. simple_resume-0.1.9.dist-info/WHEEL +4 -0
  115. simple_resume-0.1.9.dist-info/entry_points.txt +5 -0
  116. simple_resume-0.1.9.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,154 @@
1
+ """Effect types for the functional core.
2
+
3
+ Effects represent side effects (I/O operations) that should be
4
+ executed by the shell layer. Core functions return effects instead
5
+ of performing I/O directly, enabling pure testing.
6
+
7
+ This implements the "Effect System" pattern where:
8
+ - Core functions are pure and return descriptions of side effects (Effects)
9
+ - Shell layer executes these effects, performing actual I/O
10
+
11
+ All effects are immutable (frozen dataclasses) and hashable.
12
+ """
13
+
14
+ from abc import ABC, abstractmethod
15
+ from dataclasses import dataclass
16
+ from pathlib import Path
17
+
18
+
19
+ class Effect(ABC):
20
+ """Base class for all side effects.
21
+
22
+ Effects describe I/O operations without performing them.
23
+ They are created by core logic and executed by the shell layer.
24
+ """
25
+
26
+ @abstractmethod
27
+ def describe(self) -> str:
28
+ """Return human-readable description of this effect."""
29
+ pass
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class WriteFile(Effect):
34
+ """Effect: Write content to a file.
35
+
36
+ Attributes:
37
+ path: Target file path
38
+ content: Content to write (string or bytes)
39
+ encoding: Text encoding (used only for string content)
40
+
41
+ """
42
+
43
+ path: Path
44
+ content: str | bytes
45
+ encoding: str = "utf-8"
46
+
47
+ def describe(self) -> str:
48
+ """Return human-readable description."""
49
+ return f"Write file: {self.path}"
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class MakeDirectory(Effect):
54
+ """Effect: Create a directory.
55
+
56
+ Attributes:
57
+ path: Directory path to create
58
+ parents: If True, create parent directories as needed
59
+
60
+ """
61
+
62
+ path: Path
63
+ parents: bool = True
64
+
65
+ def describe(self) -> str:
66
+ """Return human-readable description."""
67
+ return f"Create directory: {self.path}"
68
+
69
+
70
+ @dataclass(frozen=True)
71
+ class DeleteFile(Effect):
72
+ """Effect: Delete a file.
73
+
74
+ Attributes:
75
+ path: File path to delete
76
+
77
+ """
78
+
79
+ path: Path
80
+
81
+ def describe(self) -> str:
82
+ """Return human-readable description."""
83
+ return f"Delete file: {self.path}"
84
+
85
+
86
+ @dataclass(frozen=True)
87
+ class OpenBrowser(Effect):
88
+ """Effect: Open a URL in the default web browser.
89
+
90
+ Attributes:
91
+ url: URL to open (can be http://, https://, or file://)
92
+
93
+ """
94
+
95
+ url: str
96
+
97
+ def describe(self) -> str:
98
+ """Return human-readable description."""
99
+ return f"Open browser: {self.url}"
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class RunCommand(Effect):
104
+ """Effect: Execute a shell command.
105
+
106
+ Attributes:
107
+ command: Command to run as a list of arguments
108
+ cwd: Working directory for command execution (None for current dir)
109
+
110
+ """
111
+
112
+ command: list[str]
113
+ cwd: Path | None = None
114
+
115
+ def describe(self) -> str:
116
+ """Return human-readable description."""
117
+ command_str = " ".join(self.command)
118
+ return f"Run command: {command_str}"
119
+
120
+
121
+ @dataclass(frozen=True)
122
+ class CopyFile(Effect):
123
+ """Effect: Copy a file from source to destination.
124
+
125
+ Attributes:
126
+ source: Source file path
127
+ destination: Destination file path
128
+
129
+ """
130
+
131
+ source: Path
132
+ destination: Path
133
+
134
+ def describe(self) -> str:
135
+ """Return human-readable description."""
136
+ return f"Copy file: {self.source} -> {self.destination}"
137
+
138
+
139
+ @dataclass(frozen=True)
140
+ class RenderPdf(Effect):
141
+ """Effect: Render HTML+CSS to PDF at the target path.
142
+
143
+ The shell layer is responsible for providing the rendering engine
144
+ (e.g., WeasyPrint).
145
+ """
146
+
147
+ html: str
148
+ css: str
149
+ output_path: Path
150
+ base_url: str | None = None
151
+
152
+ def describe(self) -> str:
153
+ """Return human-readable description."""
154
+ return f"Render PDF to: {self.output_path}"
@@ -0,0 +1,261 @@
1
+ """Core exception types for simple-resume."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class SimpleResumeError(Exception):
9
+ """Raise for any simple-resume specific error."""
10
+
11
+ def __init__(
12
+ self,
13
+ message: str,
14
+ *,
15
+ context: dict[str, Any] | None = None,
16
+ filename: str | None = None,
17
+ ) -> None:
18
+ """Initialize the exception.
19
+
20
+ Args:
21
+ message: The error message.
22
+ context: Optional context for the error.
23
+ filename: The name of the file being processed.
24
+
25
+ """
26
+ super().__init__(message)
27
+ self.message = message
28
+ self.context = context or {}
29
+ self.filename = filename
30
+
31
+ def __str__(self) -> str:
32
+ """Return a formatted error message."""
33
+ base_msg = self.message
34
+ if self.filename:
35
+ base_msg = f"{self.filename}: {base_msg}"
36
+ if self.context:
37
+ context_str = ", ".join(f"{k}={v}" for k, v in self.context.items())
38
+ base_msg = f"{base_msg} (context: {context_str})"
39
+ return base_msg
40
+
41
+
42
+ class ValidationError(SimpleResumeError, ValueError):
43
+ """Raise when resume data validation fails."""
44
+
45
+ def __init__(
46
+ self,
47
+ message: str,
48
+ *,
49
+ errors: list[str] | None = None,
50
+ warnings: list[str] | None = None,
51
+ context: dict[str, Any] | None = None,
52
+ filename: str | None = None,
53
+ **kwargs: Any,
54
+ ) -> None:
55
+ """Initialize the exception.
56
+
57
+ Args:
58
+ message: The error message.
59
+ errors: A list of validation errors.
60
+ warnings: A list of validation warnings.
61
+ context: Optional context for the error.
62
+ filename: The name of the file being processed.
63
+ **kwargs: Additional context (will be filtered).
64
+
65
+ """
66
+ # Filter out parameters that should be passed to parent
67
+ filtered_kwargs = {
68
+ k: v for k, v in kwargs.items() if k not in ["context", "filename"]
69
+ }
70
+
71
+ super().__init__(message, context=context, filename=filename, **filtered_kwargs)
72
+ self.errors = errors or []
73
+ self.warnings = warnings or []
74
+
75
+
76
+ class ConfigurationError(SimpleResumeError):
77
+ """Raise when configuration is invalid."""
78
+
79
+ def __init__(
80
+ self,
81
+ message: str,
82
+ *,
83
+ config_key: str | None = None,
84
+ config_value: Any | None = None,
85
+ context: dict[str, Any] | None = None,
86
+ filename: str | None = None,
87
+ **kwargs: Any,
88
+ ) -> None:
89
+ """Initialize the exception.
90
+
91
+ Args:
92
+ message: The error message.
93
+ config_key: The configuration key that caused the error.
94
+ config_value: The value of the configuration key.
95
+ context: Optional context for the error.
96
+ filename: The name of the file being processed.
97
+ **kwargs: Additional context (will be filtered).
98
+
99
+ """
100
+ # Filter out parameters that should be passed to parent
101
+ filtered_kwargs = {
102
+ k: v for k, v in kwargs.items() if k not in ["context", "filename"]
103
+ }
104
+
105
+ super().__init__(message, context=context, filename=filename, **filtered_kwargs)
106
+ self.config_key = config_key
107
+ self.config_value = config_value
108
+
109
+ def __str__(self) -> str:
110
+ """Return a formatted error message."""
111
+ base_msg = super().__str__()
112
+ if self.config_key:
113
+ base_msg = f"{base_msg} (config_key={self.config_key})"
114
+ return base_msg
115
+
116
+
117
+ class PaletteError(SimpleResumeError):
118
+ """Raise when color palette operations fail."""
119
+
120
+ def __init__(
121
+ self,
122
+ message: str,
123
+ *,
124
+ palette_name: str | None = None,
125
+ color_values: list[str] | None = None,
126
+ context: dict[str, Any] | None = None,
127
+ filename: str | None = None,
128
+ **kwargs: Any,
129
+ ) -> None:
130
+ """Initialize the exception.
131
+
132
+ Args:
133
+ message: The error message.
134
+ palette_name: The name of the palette.
135
+ color_values: The color values that caused the error.
136
+ context: Optional context for the error.
137
+ filename: The name of the file being processed.
138
+ **kwargs: Additional context (will be filtered).
139
+
140
+ """
141
+ # Filter out parameters that should be passed to parent
142
+ filtered_kwargs = {
143
+ k: v for k, v in kwargs.items() if k not in ["context", "filename"]
144
+ }
145
+
146
+ super().__init__(message, context=context, filename=filename, **filtered_kwargs)
147
+ self.palette_name = palette_name
148
+ self.color_values = color_values
149
+
150
+
151
+ class FileSystemError(SimpleResumeError):
152
+ """Raise when file system operations fail."""
153
+
154
+ def __init__(
155
+ self,
156
+ message: str,
157
+ *,
158
+ path: str | None = None,
159
+ operation: str | None = None,
160
+ **kwargs: Any,
161
+ ):
162
+ """Initialize the exception.
163
+
164
+ Args:
165
+ message: The error message.
166
+ path: The file path.
167
+ operation: The file system operation that failed.
168
+ **kwargs: Additional context.
169
+
170
+ """
171
+ super().__init__(message, **kwargs)
172
+ self.path = path
173
+ self.operation = operation
174
+
175
+
176
+ class SessionError(SimpleResumeError):
177
+ """Raise when session operations fail."""
178
+
179
+ def __init__(
180
+ self, message: str, *, session_id: str | None = None, **kwargs: Any
181
+ ) -> None:
182
+ """Initialize the exception.
183
+
184
+ Args:
185
+ message: The error message.
186
+ session_id: The ID of the session.
187
+ **kwargs: Additional context.
188
+
189
+ """
190
+ super().__init__(message, **kwargs)
191
+ self.session_id = session_id
192
+
193
+
194
+ class GenerationError(SimpleResumeError):
195
+ """Raise when PDF/HTML generation fails."""
196
+
197
+ def __init__(self, message: str, **metadata: Any) -> None:
198
+ """Initialize the exception with optional metadata.
199
+
200
+ Args:
201
+ message: The error message.
202
+ **metadata: Optional metadata such as ``output_path``, ``format_type``,
203
+ ``resume_name``, ``context`` and ``filename``.
204
+
205
+ """
206
+ output_path = metadata.pop("output_path", None)
207
+ format_type = metadata.pop("format_type", None)
208
+ resume_name = metadata.pop("resume_name", None)
209
+ context = metadata.pop("context", None)
210
+ filename = metadata.pop("filename", None)
211
+
212
+ if filename is None and resume_name is not None:
213
+ filename = resume_name
214
+
215
+ super().__init__(message, context=context, filename=filename, **metadata)
216
+ self.output_path = str(output_path) if output_path is not None else None
217
+ self.format_type = format_type
218
+ self.resume_name = resume_name
219
+
220
+ def __str__(self) -> str:
221
+ """Return a formatted error message."""
222
+ base_msg = super().__str__()
223
+ if self.format_type:
224
+ base_msg = f"{base_msg} (format={self.format_type})"
225
+ return base_msg
226
+
227
+
228
+ class TemplateError(SimpleResumeError):
229
+ """Raise when template processing fails."""
230
+
231
+ def __init__(self, message: str, **metadata: Any) -> None:
232
+ """Initialize the exception with optional metadata.
233
+
234
+ Args:
235
+ message: The error message.
236
+ **metadata: Optional metadata such as ``template_name``,
237
+ ``template_path``, ``context`` and ``filename``.
238
+
239
+ """
240
+ template_name = metadata.pop("template_name", None)
241
+ template_path = metadata.pop("template_path", None)
242
+ context = metadata.pop("context", None)
243
+ filename = metadata.pop("filename", None)
244
+
245
+ super().__init__(message, context=context, filename=filename, **metadata)
246
+ self.template_name = template_name
247
+ self.template_path = template_path
248
+
249
+
250
+ __all__ = [
251
+ # Base exception
252
+ "SimpleResumeError",
253
+ # Specific exception types
254
+ "ConfigurationError",
255
+ "FileSystemError",
256
+ "GenerationError",
257
+ "PaletteError",
258
+ "SessionError",
259
+ "TemplateError",
260
+ "ValidationError",
261
+ ]
@@ -0,0 +1,68 @@
1
+ """Core file operations for resume management.
2
+
3
+ Pure functions for file discovery and path operations without external dependencies.
4
+ """
5
+
6
+ from collections.abc import Generator
7
+ from pathlib import Path
8
+
9
+
10
+ def find_yaml_files(input_dir: Path, pattern: str = "*") -> list[Path]:
11
+ """Find resume input files matching the given pattern.
12
+
13
+ Args:
14
+ input_dir: Directory to search for YAML files.
15
+ pattern: Glob pattern for matching files.
16
+
17
+ Returns:
18
+ List of matching YAML file paths.
19
+
20
+ """
21
+ if not input_dir.exists():
22
+ return []
23
+
24
+ yaml_files = []
25
+
26
+ # Find files matching pattern with .yaml/.yml/.json extension
27
+ for ext in ("yaml", "yml", "json"):
28
+ for file_path in input_dir.glob(f"{pattern}.{ext}"):
29
+ if file_path.is_file():
30
+ yaml_files.append(file_path)
31
+
32
+ return sorted(yaml_files)
33
+
34
+
35
+ def iterate_yaml_files(
36
+ input_dir: Path, pattern: str = "*"
37
+ ) -> Generator[Path, None, None]:
38
+ """Iterate over YAML files matching the given pattern.
39
+
40
+ Args:
41
+ input_dir: Directory to search for YAML files.
42
+ pattern: Glob pattern for matching files.
43
+
44
+ Yields:
45
+ YAML file paths.
46
+
47
+ """
48
+ yield from find_yaml_files(input_dir, pattern)
49
+
50
+
51
+ def get_resume_name_from_path(file_path: Path) -> str:
52
+ """Extract resume name from file path.
53
+
54
+ Args:
55
+ file_path: Path to YAML file.
56
+
57
+ Returns:
58
+ Resume name (filename without extension).
59
+
60
+ """
61
+ return file_path.stem
62
+
63
+
64
+ __all__ = [
65
+ "find_yaml_files",
66
+ "iterate_yaml_files",
67
+ "get_resume_name_from_path",
68
+ ]
@@ -0,0 +1,21 @@
1
+ """Core generation functionality for resumes.
2
+
3
+ This module provides pure functions for generating different output formats
4
+ from resume data without any I/O side effects.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from simple_resume.core.generate.html import create_html_generator_factory
10
+ from simple_resume.core.generate.pdf import (
11
+ prepare_pdf_with_latex,
12
+ prepare_pdf_with_weasyprint,
13
+ )
14
+ from simple_resume.core.generate.plan import build_generation_plan
15
+
16
+ __all__ = [
17
+ "build_generation_plan",
18
+ "create_html_generator_factory",
19
+ "prepare_pdf_with_latex",
20
+ "prepare_pdf_with_weasyprint",
21
+ ]
@@ -0,0 +1,69 @@
1
+ """Exception types used by the generation subsystem."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from simple_resume.core.exceptions import SimpleResumeError
8
+
9
+
10
+ class GenerationError(SimpleResumeError):
11
+ """Raise when PDF/HTML generation fails."""
12
+
13
+ def __init__(self, message: str, **metadata: Any) -> None:
14
+ """Initialize the exception with optional metadata.
15
+
16
+ Args:
17
+ message: The error message.
18
+ **metadata: Optional metadata such as ``output_path``, ``format_type``,
19
+ ``resume_name``, ``context`` and ``filename``.
20
+
21
+ """
22
+ output_path = metadata.pop("output_path", None)
23
+ format_type = metadata.pop("format_type", None)
24
+ resume_name = metadata.pop("resume_name", None)
25
+ context = metadata.pop("context", None)
26
+ filename = metadata.pop("filename", None)
27
+
28
+ if filename is None and resume_name is not None:
29
+ filename = resume_name
30
+
31
+ super().__init__(message, context=context, filename=filename, **metadata)
32
+ self.output_path = str(output_path) if output_path is not None else None
33
+ self.format_type = format_type
34
+ self.resume_name = resume_name
35
+
36
+ def __str__(self) -> str:
37
+ """Return a formatted error message."""
38
+ base_msg = super().__str__()
39
+ if self.format_type:
40
+ base_msg = f"{base_msg} (format={self.format_type})"
41
+ return base_msg
42
+
43
+
44
+ class TemplateError(SimpleResumeError):
45
+ """Raise when template processing fails."""
46
+
47
+ def __init__(self, message: str, **metadata: Any) -> None:
48
+ """Initialize the exception with optional metadata.
49
+
50
+ Args:
51
+ message: The error message.
52
+ **metadata: Optional metadata such as ``template_name``,
53
+ ``template_path``, ``context`` and ``filename``.
54
+
55
+ """
56
+ template_name = metadata.pop("template_name", None)
57
+ template_path = metadata.pop("template_path", None)
58
+ context = metadata.pop("context", None)
59
+ filename = metadata.pop("filename", None)
60
+
61
+ super().__init__(message, context=context, filename=filename, **metadata)
62
+ self.template_name = template_name
63
+ self.template_path = template_path
64
+
65
+
66
+ __all__ = [
67
+ "GenerationError",
68
+ "TemplateError",
69
+ ]