Skip to content

model_processor

download_photo_from_url(rendercv_model)

Download photo from URL to output directory and update model to local path.

Why

Templates and Typst compiler require cv.photo to be a local pathlib.Path. When user provides a URL, this downloads the image before template rendering, preserving the local-path invariant for all downstream code.

Parameters:

  • rendercv_model (RenderCVModel) –

    CV model whose photo URL will be downloaded in-place.

Source code in src/rendercv/renderer/templater/model_processor.py
def download_photo_from_url(rendercv_model: RenderCVModel) -> None:
    """Download photo from URL to output directory and update model to local path.

    Why:
        Templates and Typst compiler require cv.photo to be a local pathlib.Path.
        When user provides a URL, this downloads the image before template
        rendering, preserving the local-path invariant for all downstream code.

    Args:
        rendercv_model: CV model whose photo URL will be downloaded in-place.
    """
    if rendercv_model.cv.photo is None or isinstance(
        rendercv_model.cv.photo, pathlib.Path
    ):
        return

    url_str = str(rendercv_model.cv.photo)

    parsed = urllib.parse.urlparse(url_str)
    filename = pathlib.PurePosixPath(parsed.path).name
    if not filename or "." not in filename:
        filename = "photo.jpg"

    output_dir = rendercv_model.settings.render_command.output_folder
    output_dir.mkdir(parents=True, exist_ok=True)
    destination = output_dir / filename

    if not destination.exists():
        try:
            urllib.request.urlretrieve(url_str, destination)
        except Exception as e:
            raise RenderCVUserError(
                message=f"Failed to download photo from {url_str}: {e}"
            ) from e

    rendercv_model.cv.photo = destination

process_fields(entry, string_processors)

Apply string processors to all entry fields except skipped technical fields.

Why

Entry fields need markdown parsing and formatting, but dates, DOIs, and URLs must remain unprocessed for correct linking and formatting. Field- level processing enables selective transformation.

Parameters:

  • entry (Entry) –

    Entry to process (model or string).

  • string_processors (list[Callable[[str], str]]) –

    Transformation functions to apply.

Returns:

  • Entry

    Entry with processed fields.

Source code in src/rendercv/renderer/templater/model_processor.py
def process_fields(
    entry: Entry, string_processors: list[Callable[[str], str]]
) -> Entry:
    """Apply string processors to all entry fields except skipped technical fields.

    Why:
        Entry fields need markdown parsing and formatting, but dates, DOIs, and
        URLs must remain unprocessed for correct linking and formatting. Field-
        level processing enables selective transformation.

    Args:
        entry: Entry to process (model or string).
        string_processors: Transformation functions to apply.

    Returns:
        Entry with processed fields.
    """
    skipped = {"start_date", "end_date", "doi", "url"}

    if isinstance(entry, str):
        return apply_string_processors(entry, string_processors)

    data = entry.model_dump(exclude_none=True)
    for field, value in data.items():
        if field in skipped or field.startswith("_"):
            continue

        if isinstance(value, str):
            setattr(entry, field, apply_string_processors(value, string_processors))
        elif isinstance(value, list):
            setattr(
                entry,
                field,
                [apply_string_processors(v, string_processors) for v in value],
            )
        else:
            setattr(
                entry, field, apply_string_processors(str(value), string_processors)
            )

    return entry

process_model(rendercv_model, file_type)

Pre-process CV model for template rendering with format-specific transformations.

Why

Templates need processed data, not raw model. This applies markdown parsing, keyword bolding, connection formatting, date rendering, and entry template expansion before templates execute.

Parameters:

  • rendercv_model (RenderCVModel) –

    Validated CV model.

  • file_type (Literal['typst', 'markdown']) –

    Target format for format-specific processors.

Returns:

Source code in src/rendercv/renderer/templater/model_processor.py
def process_model(
    rendercv_model: RenderCVModel, file_type: Literal["typst", "markdown"]
) -> RenderCVModel:
    """Pre-process CV model for template rendering with format-specific transformations.

    Why:
        Templates need processed data, not raw model. This applies markdown
        parsing, keyword bolding, connection formatting, date rendering, and
        entry template expansion before templates execute.

    Args:
        rendercv_model: Validated CV model.
        file_type: Target format for format-specific processors.

    Returns:
        Processed model ready for templates.
    """
    rendercv_model = rendercv_model.model_copy(deep=True)

    string_processors: list[Callable[[str], str]] = [
        lambda string: make_keywords_bold(string, rendercv_model.settings.bold_keywords)
    ]
    if file_type == "typst":
        string_processors.extend([markdown_to_typst])

    rendercv_model.cv._plain_name = rendercv_model.cv.name
    rendercv_model.cv.name = apply_string_processors(
        rendercv_model.cv.name, string_processors
    )
    rendercv_model.cv.headline = apply_string_processors(
        rendercv_model.cv.headline, string_processors
    )
    rendercv_model.cv._connections = compute_connections(rendercv_model, file_type)
    rendercv_model.cv._top_note = render_top_note_template(
        rendercv_model.design.templates.top_note,
        locale=rendercv_model.locale,
        current_date=rendercv_model.settings._resolved_current_date,
        name=rendercv_model.cv.name,
        single_date_template=rendercv_model.design.templates.single_date,
        string_processors=string_processors,
    )

    rendercv_model.cv._footer = render_footer_template(
        rendercv_model.design.templates.footer,
        locale=rendercv_model.locale,
        current_date=rendercv_model.settings._resolved_current_date,
        name=rendercv_model.cv.name,
        single_date_template=rendercv_model.design.templates.single_date,
        string_processors=string_processors,
    )

    pdf_title_placeholders: dict[str, str] = {
        "CURRENT_DATE": date_object_to_string(
            rendercv_model.settings._resolved_current_date,
            locale=rendercv_model.locale,
            single_date_template=rendercv_model.design.templates.single_date,
        ),
        "NAME": rendercv_model.cv._plain_name or "",
        **build_date_placeholders(
            rendercv_model.settings._resolved_current_date, locale=rendercv_model.locale
        ),
    }
    rendercv_model.settings.pdf_title = substitute_placeholders(
        rendercv_model.settings.pdf_title, pdf_title_placeholders
    )

    if rendercv_model.cv.sections is None:
        return rendercv_model

    for section in rendercv_model.cv.rendercv_sections:
        section.title = apply_string_processors(section.title, string_processors)
        show_time_span = (
            section.snake_case_title
            in rendercv_model.design.sections.show_time_spans_in
        )
        for i, entry in enumerate(section.entries):
            entry = render_entry_templates(  # NOQA: PLW2901
                entry,
                templates=rendercv_model.design.templates,
                locale=rendercv_model.locale,
                show_time_span=show_time_span,
                current_date=rendercv_model.settings._resolved_current_date,
            )
            section.entries[i] = process_fields(entry, string_processors)

    return rendercv_model