Skip to content

cv

email_validator = pydantic.TypeAdapter(pydantic.EmailStr) module-attribute

emails_validator = pydantic.TypeAdapter(list[pydantic.EmailStr]) module-attribute

phone_validator = pydantic.TypeAdapter(pydantic_phone_numbers.PhoneNumber) module-attribute

phones_validator = pydantic.TypeAdapter(list[pydantic_phone_numbers.PhoneNumber]) module-attribute

website_validator = pydantic.TypeAdapter(pydantic.HttpUrl) module-attribute

websites_validator = pydantic.TypeAdapter(list[pydantic.HttpUrl]) module-attribute

Cv

Bases: BaseModelWithExtraKeys

custom_connections = pydantic.Field(default=None, description='Additional header connections you define yourself. Each item has a `placeholder` (the displayed text), an optional `url`, and the Font Awesome icon name to render (from https://fontawesome.com/search).', examples=[[{'placeholder': 'Book a call', 'url': 'https://cal.com/johndoe', 'fontawesome_icon': 'calendar-days'}]]) class-attribute instance-attribute

email = pydantic.Field(default=None, description='You can provide multiple emails as a list.', examples=['john.doe@example.com', ['john.doe.1@example.com', 'john.doe.2@example.com']]) class-attribute instance-attribute

headline = pydantic.Field(default=None, examples=['Software Engineer', 'Data Scientist', 'Product Manager']) class-attribute instance-attribute

location = pydantic.Field(default=None, examples=['New York, NY', 'London, UK', 'Istanbul, Türkiye']) class-attribute instance-attribute

model_config = pydantic.ConfigDict(extra='allow', validate_default=True) class-attribute instance-attribute

name = pydantic.Field(default=None, examples=['John Doe', 'Jane Smith']) class-attribute instance-attribute

phone = pydantic.Field(default=None, description='Your phone number with country code in international format (e.g., +1 for USA, +44 for UK). The display format in the output is controlled by `design.header.connections.phone_number_format`. You can provide multiple numbers as a list.', examples=['+1-234-567-8900', ['+1-234-567-8900', '+44 20 1234 5678']]) class-attribute instance-attribute

photo = pydantic.Field(default=None, description='Photo file path, relative to the YAML file.', examples=['photo.jpg', 'images/profile.png']) class-attribute instance-attribute

rendercv_sections cached property

Transform user's section dict to list of typed section objects.

Why

Templates need sections as list with title/entry_type metadata. Cached property computes once after validation, enabling repeated template access without recomputation.

Returns:

sections = pydantic.Field(default=None, description='The sections of your CV. Keys are section titles (e.g., Experience, Education), and values are lists of entries. Entry types are automatically detected based on their fields.', examples=[{'Experience': '...', 'Education': '...', 'Projects': '...', 'Skills': '...'}]) class-attribute instance-attribute

social_networks = pydantic.Field(default=None) class-attribute instance-attribute

website = pydantic.Field(default=None, description='You can provide multiple URLs as a list.', examples=['https://johndoe.com', ['https://johndoe.com', 'https://www.janesmith.dev']]) class-attribute instance-attribute

capture_input_order(data, handler) classmethod

Preserve YAML field order for header rendering.

Why

Header fields (name, label, location, etc.) must render in user-defined order, not alphabetical. Wrap validator captures dict key order before Pydantic reorders fields.

Parameters:

  • data (Any) –

    Raw input data before validation.

  • handler (ModelWrapValidatorHandler[Self]) –

    Pydantic's validation handler.

Returns:

  • Cv

    Validated CV instance with _key_order preserved.

Source code in src/rendercv/schema/models/cv/cv.py
@pydantic.model_validator(mode="wrap")
@classmethod
def capture_input_order(
    cls, data: Any, handler: pydantic.ModelWrapValidatorHandler[Self]
) -> "Cv":
    """Preserve YAML field order for header rendering.

    Why:
        Header fields (name, label, location, etc.) must render in user-defined
        order, not alphabetical. Wrap validator captures dict key order before
        Pydantic reorders fields.

    Args:
        data: Raw input data before validation.
        handler: Pydantic's validation handler.

    Returns:
        Validated CV instance with _key_order preserved.
    """
    # If data is already a Cv instance, preserve its _key_order
    if isinstance(data, cls):
        return data

    # Capture the input order before validation
    key_order = list(data.keys()) if isinstance(data, dict) else []

    # Let Pydantic do its validation
    instance = handler(data)

    # Set the private attribute on the instance:
    # If the values of those keys are None, remove the key from the key_order
    instance._key_order = [key for key in key_order if data.get(key) is not None]

    return instance

serialize_phone(phone)

Remove tel: prefix from phone number for clean serialization.

Why

phone number library adds "tel:" URI scheme for validation. Serialization strips prefix so templates render plain numbers.

Parameters:

  • phone (PhoneNumber | None) –

    Validated phone number with tel: prefix.

Returns:

  • str | None

    Phone string without tel: prefix, or None.

Source code in src/rendercv/schema/models/cv/cv.py
@pydantic.field_serializer("phone")
def serialize_phone(
    self, phone: pydantic_phone_numbers.PhoneNumber | None
) -> str | None:
    """Remove tel: prefix from phone number for clean serialization.

    Why:
        phone number library adds "tel:" URI scheme for validation.
        Serialization strips prefix so templates render plain numbers.

    Args:
        phone: Validated phone number with tel: prefix.

    Returns:
        Phone string without tel: prefix, or None.
    """
    if phone is not None:
        return phone.replace("tel:", "")

    return phone

validate_list_or_scalar_fields(value, info) classmethod

Validate fields that accept single value or list with type-specific errors.

Why

Users provide either email: "x@y.com" or email: ["x@y.com", "a@b.com"]. Plain mode validator detects list vs scalar first, enabling specific error messages like "invalid email in list" instead of generic validation errors.

Parameters:

  • value (Any) –

    Single value or list to validate.

  • info (ValidationInfo) –

    Validation context containing field name.

Returns:

  • EmailStr | HttpUrl | PhoneNumber | list[EmailStr] | list[HttpUrl] | list[PhoneNumber] | None

    Validated single value or list.

Source code in src/rendercv/schema/models/cv/cv.py
@pydantic.field_validator("website", "email", "phone", mode="plain")
@classmethod
def validate_list_or_scalar_fields(
    cls, value: Any, info: pydantic.ValidationInfo
) -> (
    pydantic.EmailStr
    | pydantic.HttpUrl
    | pydantic_phone_numbers.PhoneNumber
    | list[pydantic.EmailStr]
    | list[pydantic.HttpUrl]
    | list[pydantic_phone_numbers.PhoneNumber]
    | None
):
    """Validate fields that accept single value or list with type-specific errors.

    Why:
        Users provide either `email: "x@y.com"` or `email: ["x@y.com", "a@b.com"]`.
        Plain mode validator detects list vs scalar first, enabling specific error
        messages like "invalid email in list" instead of generic validation errors.

    Args:
        value: Single value or list to validate.
        info: Validation context containing field name.

    Returns:
        Validated single value or list.
    """
    # Allow None values since these fields are optional
    if value is None:
        return None

    if info.field_name is None:
        raise RenderCVInternalError("field_name is None in validator")

    validators: tuple[
        pydantic.TypeAdapter[pydantic.EmailStr]
        | pydantic.TypeAdapter[pydantic.HttpUrl]
        | pydantic.TypeAdapter[pydantic_phone_numbers.PhoneNumber],
        (
            pydantic.TypeAdapter[list[pydantic.EmailStr]]
            | pydantic.TypeAdapter[list[pydantic.HttpUrl]]
            | pydantic.TypeAdapter[list[pydantic_phone_numbers.PhoneNumber]]
        ),
    ] = {
        "website": (website_validator, websites_validator),
        "email": (email_validator, emails_validator),
        "phone": (phone_validator, phones_validator),
    }[info.field_name]

    if isinstance(value, list):
        return validators[1].validate_python(value)

    return validators[0].validate_python(value)