From 71298ca060ec07fae477400a26fc644bf44abbeb Mon Sep 17 00:00:00 2001 From: Alvin De Cruz Date: Thu, 25 Dec 2025 17:42:03 +0800 Subject: [PATCH] Replace asserts with explicit error handling (#589) --- src/rendercv/renderer/pdf_png.py | 4 +++- src/rendercv/renderer/templater/connections.py | 17 +++++++++++++---- .../templater/entry_templates_from_input.py | 12 ++++++++---- src/rendercv/schema/models/cv/cv.py | 5 ++++- .../entries/bases/entry_with_complex_fields.py | 5 ++++- src/rendercv/schema/models/design/design.py | 10 ++++++++-- src/rendercv/schema/pydantic_error_handling.py | 6 ++++-- 7 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/rendercv/renderer/pdf_png.py b/src/rendercv/renderer/pdf_png.py index 5864f4af..d5406c05 100644 --- a/src/rendercv/renderer/pdf_png.py +++ b/src/rendercv/renderer/pdf_png.py @@ -5,6 +5,7 @@ import shutil import rendercv_fonts import typst +from rendercv.exception import RenderCVInternalError from rendercv.schema.models.rendercv_model import RenderCVModel from .path_resolver import resolve_rendercv_file_path @@ -69,7 +70,8 @@ def generate_png( png_files = [] for i, png_file_bytes in enumerate(png_files_bytes): - assert png_file_bytes is not None + if png_file_bytes is None: + raise RenderCVInternalError("Typst compiler returned None for PNG bytes") png_file = png_path.parent / (png_path.stem + f"_{i + 1}.png") png_file.write_bytes(png_file_bytes) png_files.append(png_file) diff --git a/src/rendercv/renderer/templater/connections.py b/src/rendercv/renderer/templater/connections.py index 428eb2cf..8c40dc92 100644 --- a/src/rendercv/renderer/templater/connections.py +++ b/src/rendercv/renderer/templater/connections.py @@ -3,6 +3,7 @@ from typing import Literal import phonenumbers +from rendercv.exception import RenderCVInternalError from rendercv.schema.models.rendercv_model import RenderCVModel from .markdown_parser import markdown_to_typst @@ -90,7 +91,8 @@ def parse_connections(rendercv_model: RenderCVModel) -> list[Connection]: case "phone": phones = rendercv_model.cv.phone - assert phones is not None + if phones is None: + raise RenderCVInternalError("phone key present but value is None") if not isinstance(phones, list): phones = [phones] @@ -111,7 +113,8 @@ def parse_connections(rendercv_model: RenderCVModel) -> list[Connection]: case "website": websites = rendercv_model.cv.website - assert websites + if not websites: + raise RenderCVInternalError("website key present but value is None") if not isinstance(websites, list): websites = [websites] @@ -134,7 +137,10 @@ def parse_connections(rendercv_model: RenderCVModel) -> list[Connection]: ) case "social_networks": - assert rendercv_model.cv.social_networks is not None + if rendercv_model.cv.social_networks is None: + raise RenderCVInternalError( + "social_networks key present but value is None" + ) for social_network in rendercv_model.cv.social_networks: url = social_network.url if rendercv_model.design.header.connections.display_urls_instead_of_usernames: @@ -154,7 +160,10 @@ def parse_connections(rendercv_model: RenderCVModel) -> list[Connection]: ) case "custom_connections": - assert rendercv_model.cv.custom_connections is not None + if rendercv_model.cv.custom_connections is None: + raise RenderCVInternalError( + "custom_connections key present but value is None" + ) for custom_connection in rendercv_model.cv.custom_connections: url = ( str(custom_connection.url) diff --git a/src/rendercv/renderer/templater/entry_templates_from_input.py b/src/rendercv/renderer/templater/entry_templates_from_input.py index ecc846e3..ebeaf4ee 100644 --- a/src/rendercv/renderer/templater/entry_templates_from_input.py +++ b/src/rendercv/renderer/templater/entry_templates_from_input.py @@ -54,12 +54,14 @@ def render_entry_templates[EntryType: Entry]( # Handle special placeholders: if "HIGHLIGHTS" in entry_fields: highlights = getattr(entry, "highlights", None) - assert highlights is not None + if highlights is None: + raise RenderCVInternalError("HIGHLIGHTS in fields but highlights is None") entry_fields["HIGHLIGHTS"] = process_highlights(highlights) if "AUTHORS" in entry_fields: authors = getattr(entry, "authors", None) - assert authors is not None + if authors is None: + raise RenderCVInternalError("AUTHORS in fields but authors is None") entry_fields["AUTHORS"] = process_authors(authors) if ( @@ -81,7 +83,8 @@ def render_entry_templates[EntryType: Entry]( if "START_DATE" in entry_fields: start_date = getattr(entry, "start_date", None) - assert start_date is not None + if start_date is None: + raise RenderCVInternalError("START_DATE in fields but start_date is None") entry_fields["START_DATE"] = format_single_date( start_date, locale=locale, @@ -90,7 +93,8 @@ def render_entry_templates[EntryType: Entry]( if "END_DATE" in entry_fields: end_date = getattr(entry, "end_date", None) - assert end_date is not None + if end_date is None: + raise RenderCVInternalError("END_DATE in fields but end_date is None") entry_fields["END_DATE"] = format_single_date( end_date, locale=locale, diff --git a/src/rendercv/schema/models/cv/cv.py b/src/rendercv/schema/models/cv/cv.py index 3c9e7d27..a305fa7e 100644 --- a/src/rendercv/schema/models/cv/cv.py +++ b/src/rendercv/schema/models/cv/cv.py @@ -4,6 +4,8 @@ from typing import Any, Self import pydantic import pydantic_extra_types.phone_numbers as pydantic_phone_numbers +from rendercv.exception import RenderCVInternalError + from ..base import BaseModelWithExtraKeys from ..path import ExistingPathRelativeToInput from .custom_connection import CustomConnection @@ -190,7 +192,8 @@ class Cv(BaseModelWithExtraKeys): if value is None: return None - assert info.field_name is not None + if info.field_name is None: + raise RenderCVInternalError("field_name is None in validator") validators: tuple[ pydantic.TypeAdapter[pydantic.EmailStr] diff --git a/src/rendercv/schema/models/cv/entries/bases/entry_with_complex_fields.py b/src/rendercv/schema/models/cv/entries/bases/entry_with_complex_fields.py index ce1ab093..a45a0a12 100644 --- a/src/rendercv/schema/models/cv/entries/bases/entry_with_complex_fields.py +++ b/src/rendercv/schema/models/cv/entries/bases/entry_with_complex_fields.py @@ -76,7 +76,10 @@ def get_date_object(date: str | int, current_date: Date | None = None) -> Date: # Then it is in YYYY format date_object = Date.fromisoformat(f"{date}-01-01") elif date == "present": - assert current_date is not None + if current_date is None: + raise RenderCVInternalError( + "current_date is None when processing 'present' date" + ) date_object = current_date else: raise RenderCVInternalError("This is not a valid date!") diff --git a/src/rendercv/schema/models/design/design.py b/src/rendercv/schema/models/design/design.py index 7b8804a2..6e56cff4 100644 --- a/src/rendercv/schema/models/design/design.py +++ b/src/rendercv/schema/models/design/design.py @@ -7,6 +7,8 @@ from typing import Annotated, Any import pydantic import pydantic_core +from rendercv.exception import RenderCVInternalError + from ...pydantic_error_handling import CustomPydanticErrorTypes from ..validation_context import get_input_file_path from .built_in_design import BuiltInDesign, built_in_design_adapter @@ -92,11 +94,15 @@ def validate_design(design: Any, info: pydantic.ValidationInfo) -> Any: "theme", path_to_init_file, ) - assert spec is not None + if spec is None: + msg = f"Failed to load spec from {path_to_init_file}" + raise RenderCVInternalError(msg) theme_module = importlib.util.module_from_spec(spec) try: - assert spec.loader is not None + if spec.loader is None: + msg = f"spec.loader is None for {path_to_init_file}" + raise RenderCVInternalError(msg) spec.loader.exec_module(theme_module) except SyntaxError as e: raise pydantic_core.PydanticCustomError( diff --git a/src/rendercv/schema/pydantic_error_handling.py b/src/rendercv/schema/pydantic_error_handling.py index a528e7f6..94108691 100644 --- a/src/rendercv/schema/pydantic_error_handling.py +++ b/src/rendercv/schema/pydantic_error_handling.py @@ -126,8 +126,10 @@ def parse_validation_errors( ) if plain_error["type"] == CustomPydanticErrorTypes.entry_validation.value: - assert "ctx" in plain_error - assert "caused_by" in plain_error["ctx"] + if "ctx" not in plain_error or "caused_by" not in plain_error["ctx"]: + raise RenderCVInternalError( + "entry_validation error missing ctx or caused_by" + ) for plain_cause_error in plain_error["ctx"]["caused_by"]: loc = plain_cause_error["loc"][1:] # Omit `entries` location plain_cause_error["loc"] = plain_error["loc"] + loc