From 87c76403223c77c92ea4792da0131a06a4bfc003 Mon Sep 17 00:00:00 2001 From: Sina Atalay <79940989+sinaatalay@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:46:06 +0300 Subject: [PATCH] Fix `settings.current_date` issues --- pyproject.toml | 4 +- .../schema/models/validation_context.py | 14 +++--- .../schema/pydantic_error_handling.py | 12 +++++ src/rendercv/schema/rendercv_model_builder.py | 4 +- tests/schema/test_rendercv_model_builder.py | 45 +++++++++++++++++++ .../expected_errors.yaml | 6 +++ .../wrong_input.yaml | 2 + 7 files changed, 78 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9da0d0f7..d0d24b39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ build-backend = "uv_build" # Build-backend object for building RenderCV # Metadata about RenderCV. name = "rendercv" version = "2.7" -description = "Typst-based CV/resume generator" +description = "CV/resume generator for academics and engineers" readme = "README.md" requires-python = ">=3.12" license = "MIT" @@ -41,8 +41,6 @@ classifiers = [ "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", diff --git a/src/rendercv/schema/models/validation_context.py b/src/rendercv/schema/models/validation_context.py index f5a70199..ba16fff9 100644 --- a/src/rendercv/schema/models/validation_context.py +++ b/src/rendercv/schema/models/validation_context.py @@ -1,13 +1,15 @@ import pathlib +from dataclasses import dataclass from datetime import date as Date -from typing import Literal, cast +from typing import Any, cast import pydantic -class ValidationContext(pydantic.BaseModel): +@dataclass +class ValidationContext: input_file_path: pathlib.Path | None = None - current_date: Date | Literal["today"] | None = None + current_date: Any = None def get_input_file_path(info: pydantic.ValidationInfo) -> pathlib.Path | None: @@ -38,7 +40,8 @@ def get_current_date(info: pydantic.ValidationInfo) -> Date: Date calculations (like months of experience) must use consistent reference dates. Users can override via settings.current_date for reproducible builds, otherwise defaults to today. The ``"today"`` - keyword is resolved to the actual current date. + keyword is resolved to the actual current date. Invalid values fall + back to today so the Settings model can report the error properly. Args: info: Pydantic validation info containing context. @@ -48,7 +51,8 @@ def get_current_date(info: pydantic.ValidationInfo) -> Date: """ if isinstance(info.context, dict): context = cast(ValidationContext, info.context["context"]) + if isinstance(context.current_date, Date): + return context.current_date if context.current_date == "today": return Date.today() - return context.current_date or Date.today() return Date.today() diff --git a/src/rendercv/schema/pydantic_error_handling.py b/src/rendercv/schema/pydantic_error_handling.py index 3415e301..68cd2c9a 100644 --- a/src/rendercv/schema/pydantic_error_handling.py +++ b/src/rendercv/schema/pydantic_error_handling.py @@ -74,6 +74,18 @@ def parse_plain_pydantic_error( ' or YYYY format or "present"!' ) + # Special case for current_date: the field is typed as datetime.date | + # Literal["today"]. Pydantic appends "date" to the loc for the datetime.date + # union branch (e.g. ("settings", "current_date", "date")). Strip that suffix + # first so the field name lands at location[-1], matching the end_date pattern. + if len(location) >= 2 and location[-1] == "date" and location[-2] == "current_date": + location = location[:-1] + if location and "current_date" in location[-1]: + plain_error["msg"] = ( + "This is not a valid `current_date`! Please use YYYY-MM-DD format or" + ' "today".' + ) + for old_error_message, new_error_message in error_dictionary.items(): if old_error_message in plain_error["msg"]: plain_error["msg"] = new_error_message diff --git a/src/rendercv/schema/rendercv_model_builder.py b/src/rendercv/schema/rendercv_model_builder.py index f747b99e..2c39e7b0 100644 --- a/src/rendercv/schema/rendercv_model_builder.py +++ b/src/rendercv/schema/rendercv_model_builder.py @@ -176,7 +176,9 @@ def build_rendercv_model_from_commented_map( validation_context = { "context": ValidationContext( input_file_path=input_file_path, - current_date=commented_map.get("settings", {}).get("current_date"), + current_date=commented_map.get("settings", {}).get( + "current_date", "today" + ), ) } model = RenderCVModel.model_validate(commented_map, context=validation_context) diff --git a/tests/schema/test_rendercv_model_builder.py b/tests/schema/test_rendercv_model_builder.py index f0a1e127..23f1196c 100644 --- a/tests/schema/test_rendercv_model_builder.py +++ b/tests/schema/test_rendercv_model_builder.py @@ -389,6 +389,51 @@ class TestBuildRendercvModelFromDictionary: with pytest.raises(RenderCVUserValidationError): build_rendercv_model_from_commented_map(invalid_dict) + @pytest.mark.parametrize( + "invalid_date", + ["todady", "not-a-date", "2024-13-01", "yesterday"], + ) + def test_invalid_current_date_raises_user_validation_error( + self, minimal_input_dict, invalid_date + ): + # current_date uses datetime.date | Literal["today"]. Pydantic emits one + # error per union branch, including a "date" suffix that has no counterpart + # in the YAML. parse_plain_pydantic_error must truncate the location to + # ("settings", "current_date") or the coordinate lookup raises an error. + yaml_input = dictionary_to_yaml( + {**minimal_input_dict, "settings": {"current_date": invalid_date}} + ) + + with pytest.raises(RenderCVUserValidationError) as exc_info: + build_rendercv_dictionary_and_model(yaml_input) + + errors = exc_info.value.validation_errors + assert len(errors) >= 1 + assert any( + error.schema_location is not None + and "settings" in error.schema_location + and "current_date" in error.schema_location + for error in errors + ) + + def test_valid_current_date_string_works(self, minimal_input_dict): + yaml_input = dictionary_to_yaml( + {**minimal_input_dict, "settings": {"current_date": "2024-06-15"}} + ) + + _, model = build_rendercv_dictionary_and_model(yaml_input) + + assert model.settings.current_date == Date(2024, 6, 15) + + def test_today_keyword_in_current_date_works(self, minimal_input_dict): + yaml_input = dictionary_to_yaml( + {**minimal_input_dict, "settings": {"current_date": "today"}} + ) + + _, model = build_rendercv_dictionary_and_model(yaml_input) + + assert model.settings.current_date == "today" + class TestBuildRendercvModel: def test_basic_model_creation(self, minimal_input_dict): diff --git a/tests/schema/testdata/test_pydantic_error_handling/expected_errors.yaml b/tests/schema/testdata/test_pydantic_error_handling/expected_errors.yaml index 6b57cc7a..a6ecd819 100644 --- a/tests/schema/testdata/test_pydantic_error_handling/expected_errors.yaml +++ b/tests/schema/testdata/test_pydantic_error_handling/expected_errors.yaml @@ -143,3 +143,9 @@ expected_errors: input: not_a_valid_theme yaml_location: [[58, 3], [58, 9]] yaml_source: main_yaml_file + + - schema_location: ["settings", "current_date"] + message: 'This is not a valid `current_date`! Please use YYYY-MM-DD format or "today".' + input: todady + yaml_location: [[61, 3], [61, 16]] + yaml_source: main_yaml_file diff --git a/tests/schema/testdata/test_pydantic_error_handling/wrong_input.yaml b/tests/schema/testdata/test_pydantic_error_handling/wrong_input.yaml index e0fd77d0..220fd849 100644 --- a/tests/schema/testdata/test_pydantic_error_handling/wrong_input.yaml +++ b/tests/schema/testdata/test_pydantic_error_handling/wrong_input.yaml @@ -57,3 +57,5 @@ cv: design: theme: not_a_valid_theme extra_field_in_design: I don't think this is allowed. +settings: + current_date: todady