Skip to content

rendercv.data.models

The rendercv.data.models package contains all the Pydantic data models, validators, and computed fields that are used in RenderCV. The package is divided into several modules, each containing a different group of data models.

  • base.py: Contains RenderCVBaseModel, which is the parent class of all the data models in RenderCV.
  • computers.py: Contains all the functions that are used to compute some values of the fields in the data models. For example, converting ISO dates to human-readable dates.
  • entry_types.py: Contains all the data models that are used to represent the entries in the CV.
  • curriculum_vitae.py: Contains the CurriculumVitae data model, which is the main data model that contains all the content of the CV.
  • design.py: Contains the data model that is used to represent the design options of the CV.
  • locale_catalog.py: Contains the data model that is used to represent the locale catalog of the CV.
  • rendercv_settings.py: Contains the data model that is used to represent the settings of the RenderCV.
  • rendercv_data_model.py: Contains the RenderCVDataModel data model, which is the main data model that defines the whole input file structure.

CurriculumVitae

Bases: RenderCVBaseModelWithExtraKeys

This class is the data model of the cv field.

Source code in rendercv/data/models/curriculum_vitae.py
class CurriculumVitae(RenderCVBaseModelWithExtraKeys):
    """This class is the data model of the `cv` field."""

    name: Optional[str] = pydantic.Field(
        default=None,
        title="Name",
        description="The name of the person.",
    )
    location: Optional[str] = pydantic.Field(
        default=None,
        title="Location",
        description="The location of the person.",
    )
    email: Optional[pydantic.EmailStr] = pydantic.Field(
        default=None,
        title="Email",
        description="The email address of the person.",
    )
    phone: Optional[pydantic_phone_numbers.PhoneNumber] = pydantic.Field(
        default=None,
        title="Phone",
        description="The phone number of the person, including the country code.",
    )
    website: Optional[pydantic.HttpUrl] = pydantic.Field(
        default=None,
        title="Website",
        description="The website of the person.",
    )
    social_networks: Optional[list[SocialNetwork]] = pydantic.Field(
        default=None,
        title="Social Networks",
        description="The social networks of the person.",
    )
    sections_input: Sections = pydantic.Field(
        default=None,
        title="Sections",
        description="The sections of the CV.",
        # This is an alias to allow users to use `sections` in the YAML file:
        # `sections` key is preserved for RenderCV's internal use.
        alias="sections",
    )

    @pydantic.field_validator("name")
    @classmethod
    def update_curriculum_vitae(cls, value: str, info: pydantic.ValidationInfo) -> str:
        """Update the `curriculum_vitae` dictionary."""
        if value:
            curriculum_vitae[info.field_name] = value  # type: ignore

        return value

    @functools.cached_property
    def connections(self) -> list[dict[str, Optional[str]]]:
        """Return all the connections of the person as a list of dictionaries and cache
        `connections` as an attribute of the instance. The connections are used in the
        header of the CV.

        Returns:
            The connections of the person.
        """

        connections: list[dict[str, Optional[str]]] = []

        if self.location is not None:
            connections.append(
                {
                    "latex_icon": "\\faMapMarker*",
                    "url": None,
                    "clean_url": None,
                    "placeholder": self.location,
                }
            )

        if self.email is not None:
            connections.append(
                {
                    "latex_icon": "\\faEnvelope[regular]",
                    "url": f"mailto:{self.email}",
                    "clean_url": self.email,
                    "placeholder": self.email,
                }
            )

        if self.phone is not None:
            phone_placeholder = computers.format_phone_number(self.phone)
            connections.append(
                {
                    "latex_icon": "\\faPhone*",
                    "url": self.phone,
                    "clean_url": phone_placeholder,
                    "placeholder": phone_placeholder,
                }
            )

        if self.website is not None:
            website_placeholder = computers.make_a_url_clean(str(self.website))
            connections.append(
                {
                    "latex_icon": "\\faLink",
                    "url": str(self.website),
                    "clean_url": website_placeholder,
                    "placeholder": website_placeholder,
                }
            )

        if self.social_networks is not None:
            icon_dictionary = {
                "LinkedIn": "\\faLinkedinIn",
                "GitHub": "\\faGithub",
                "GitLab": "\\faGitlab",
                "Instagram": "\\faInstagram",
                "Mastodon": "\\faMastodon",
                "ORCID": "\\faOrcid",
                "StackOverflow": "\\faStackOverflow",
                "ResearchGate": "\\faResearchgate",
                "YouTube": "\\faYoutube",
                "Google Scholar": "\\faGraduationCap",
                "Telegram": "\\faTelegram",
            }
            for social_network in self.social_networks:
                clean_url = computers.make_a_url_clean(social_network.url)
                connection = {
                    "latex_icon": icon_dictionary[social_network.network],
                    "url": social_network.url,
                    "clean_url": clean_url,
                    "placeholder": social_network.username,
                }

                if social_network.network == "StackOverflow":
                    username = social_network.username.split("/")[1]
                    connection["placeholder"] = username
                if social_network.network == "Google Scholar":
                    connection["placeholder"] = "Google Scholar"

                connections.append(connection)  # type: ignore

        return connections

    @functools.cached_property
    def sections(self) -> list[SectionBase]:
        """Compute the sections of the CV based on the input sections.

        The original `sections` input is a dictionary where the keys are the section titles
        and the values are the list of entries in that section. This function converts the
        input sections to a list of `SectionBase` objects. This makes it easier to work with
        the sections in the rest of the code.

        Returns:
            The computed sections.
        """
        sections: list[SectionBase] = []

        if self.sections_input is not None:
            for title, entries in self.sections_input.items():
                title = computers.dictionary_key_to_proper_section_title(title)

                # The first entry can be used because all the entries in the section are
                # already validated with the `validate_a_section` function:
                entry_type_name, _ = get_entry_type_name_and_section_validator(
                    entries[0],  # type: ignore
                    entry_types=entry_types.available_entry_models,
                )

                # SectionBase is used so that entries are not validated again:
                section = SectionBase(
                    title=title,
                    entry_type=entry_type_name,
                    entries=entries,
                )
                sections.append(section)

        return sections

connections: list[dict[str, Optional[str]]] cached property

Return all the connections of the person as a list of dictionaries and cache connections as an attribute of the instance. The connections are used in the header of the CV.

Returns:

  • list[dict[str, Optional[str]]]

    The connections of the person.

sections: list[SectionBase] cached property

Compute the sections of the CV based on the input sections.

The original sections input is a dictionary where the keys are the section titles and the values are the list of entries in that section. This function converts the input sections to a list of SectionBase objects. This makes it easier to work with the sections in the rest of the code.

Returns:

update_curriculum_vitae(value, info) classmethod

Update the curriculum_vitae dictionary.

Source code in rendercv/data/models/curriculum_vitae.py
@pydantic.field_validator("name")
@classmethod
def update_curriculum_vitae(cls, value: str, info: pydantic.ValidationInfo) -> str:
    """Update the `curriculum_vitae` dictionary."""
    if value:
        curriculum_vitae[info.field_name] = value  # type: ignore

    return value

SocialNetwork

Bases: RenderCVBaseModelWithoutExtraKeys

This class is the data model of a social network.

Source code in rendercv/data/models/curriculum_vitae.py
class SocialNetwork(RenderCVBaseModelWithoutExtraKeys):
    """This class is the data model of a social network."""

    network: SocialNetworkName = pydantic.Field(
        title="Social Network",
        description="Name of the social network.",
    )
    username: str = pydantic.Field(
        title="Username",
        description="The username of the social network. The link will be generated.",
    )

    @pydantic.field_validator("username")
    @classmethod
    def check_username(cls, username: str, info: pydantic.ValidationInfo) -> str:
        """Check if the username is provided correctly."""
        if "network" not in info.data:
            # the network is either not provided or not one of the available social
            # networks. In this case, don't check the username, since Pydantic will
            # raise an error for the network.
            return username

        network = info.data["network"]

        username = validate_a_social_network_username(username, network)

        return username

    @pydantic.model_validator(mode="after")  # type: ignore
    def check_url(self) -> "SocialNetwork":
        """Validate the URL of the social network."""
        if self.network == "Mastodon":
            # All the other social networks have valid URLs. Mastodon URLs contain both
            # the username and the domain. So, we need to validate if the url is valid.
            validate_url(self.url)

        return self

    @functools.cached_property
    def url(self) -> str:
        """Return the URL of the social network and cache `url` as an attribute of the
        instance.
        """
        if self.network == "Mastodon":
            # Split domain and username
            _, username, domain = self.username.split("@")
            url = f"https://{domain}/@{username}"
        else:
            url_dictionary = {
                "LinkedIn": "https://linkedin.com/in/",
                "GitHub": "https://github.com/",
                "GitLab": "https://gitlab.com/",
                "Instagram": "https://instagram.com/",
                "ORCID": "https://orcid.org/",
                "StackOverflow": "https://stackoverflow.com/users/",
                "ResearchGate": "https://researchgate.net/profile/",
                "YouTube": "https://youtube.com/@",
                "Google Scholar": "https://scholar.google.com/citations?user=",
                "Telegram": "https://t.me/",
            }
            url = url_dictionary[self.network] + self.username

        return url

url: str cached property

Return the URL of the social network and cache url as an attribute of the instance.

check_username(username, info) classmethod

Check if the username is provided correctly.

Source code in rendercv/data/models/curriculum_vitae.py
@pydantic.field_validator("username")
@classmethod
def check_username(cls, username: str, info: pydantic.ValidationInfo) -> str:
    """Check if the username is provided correctly."""
    if "network" not in info.data:
        # the network is either not provided or not one of the available social
        # networks. In this case, don't check the username, since Pydantic will
        # raise an error for the network.
        return username

    network = info.data["network"]

    username = validate_a_social_network_username(username, network)

    return username

check_url()

Validate the URL of the social network.

Source code in rendercv/data/models/curriculum_vitae.py
@pydantic.model_validator(mode="after")  # type: ignore
def check_url(self) -> "SocialNetwork":
    """Validate the URL of the social network."""
    if self.network == "Mastodon":
        # All the other social networks have valid URLs. Mastodon URLs contain both
        # the username and the domain. So, we need to validate if the url is valid.
        validate_url(self.url)

    return self

BulletEntry

Bases: RenderCVBaseModelWithExtraKeys

This class is the data model of BulletEntry.

Source code in rendercv/data/models/entry_types.py
class BulletEntry(RenderCVBaseModelWithExtraKeys):
    """This class is the data model of `BulletEntry`."""

    bullet: str = pydantic.Field(
        title="Bullet",
        description="The bullet of the BulletEntry.",
    )

EducationEntry

Bases: EntryBase, EducationEntryBase

This class is the data model of EducationEntry. EducationEntry class is created by combining the EntryBase and EducationEntryBase classes to have the fields in the correct order.

Source code in rendercv/data/models/entry_types.py
class EducationEntry(EntryBase, EducationEntryBase):
    """This class is the data model of `EducationEntry`. `EducationEntry` class is
    created by combining the `EntryBase` and `EducationEntryBase` classes to have the
    fields in the correct order.
    """

    pass

ExperienceEntry

Bases: EntryBase, ExperienceEntryBase

This class is the data model of ExperienceEntry. ExperienceEntry class is created by combining the EntryBase and ExperienceEntryBase classes to have the fields in the correct order.

Source code in rendercv/data/models/entry_types.py
class ExperienceEntry(EntryBase, ExperienceEntryBase):
    """This class is the data model of `ExperienceEntry`. `ExperienceEntry` class is
    created by combining the `EntryBase` and `ExperienceEntryBase` classes to have the
    fields in the correct order.
    """

    pass

NormalEntry

Bases: EntryBase, NormalEntryBase

This class is the data model of NormalEntry. NormalEntry class is created by combining the EntryBase and NormalEntryBase classes to have the fields in the correct order.

Source code in rendercv/data/models/entry_types.py
class NormalEntry(EntryBase, NormalEntryBase):
    """This class is the data model of `NormalEntry`. `NormalEntry` class is created by
    combining the `EntryBase` and `NormalEntryBase` classes to have the fields in the
    correct order.
    """

    pass

OneLineEntry

Bases: RenderCVBaseModelWithExtraKeys

This class is the data model of OneLineEntry.

Source code in rendercv/data/models/entry_types.py
class OneLineEntry(RenderCVBaseModelWithExtraKeys):
    """This class is the data model of `OneLineEntry`."""

    label: str = pydantic.Field(
        title="Name",
        description="The label of the OneLineEntry.",
    )
    details: str = pydantic.Field(
        title="Details",
        description="The details of the OneLineEntry.",
    )

PublicationEntry

Bases: EntryWithDate, PublicationEntryBase

This class is the data model of PublicationEntry. PublicationEntry class is created by combining the EntryWithDate and PublicationEntryBase classes to have the fields in the correct order.

Source code in rendercv/data/models/entry_types.py
class PublicationEntry(EntryWithDate, PublicationEntryBase):
    """This class is the data model of `PublicationEntry`. `PublicationEntry` class is
    created by combining the `EntryWithDate` and `PublicationEntryBase` classes to have
    the fields in the correct order.
    """

    pass

LocaleCatalog

Bases: RenderCVBaseModelWithoutExtraKeys

This class is the data model of the locale catalog. The values of each field updates the locale_catalog dictionary.

Source code in rendercv/data/models/locale_catalog.py
class LocaleCatalog(RenderCVBaseModelWithoutExtraKeys):
    """This class is the data model of the locale catalog. The values of each field
    updates the `locale_catalog` dictionary.
    """

    phone_number_format: Optional[Literal["national", "international", "E164"]] = (
        pydantic.Field(
            default="national",
            title="Phone Number Format",
            description=(
                "If 'national', phone numbers are formatted without the country code."
                " If 'international', phone numbers are formatted with the country"
                " code. The default value is 'national'."
            ),
        )
    )
    date_style: Optional[str] = pydantic.Field(
        default="MONTH_ABBREVIATION YEAR",
        title="Date Style",
        description=(
            "The style of the date. The following placeholders can be used:\n-"
            " FULL_MONTH_NAME: Full name of the month\n- MONTH_ABBREVIATION:"
            " Abbreviation of the month\n- MONTH: Month as a number\n-"
            " MONTH_IN_TWO_DIGITS: Month as a number in two digits\n- YEAR: Year as a"
            " number\n- YEAR_IN_TWO_DIGITS: Year as a number in two digits\nThe default"
            ' value is "MONTH_ABBREVIATION YEAR".'
        ),
    )
    month: Optional[str] = pydantic.Field(
        default="month",
        title='Translation of "Month"',
        description='Translation of the word "month" in the locale.',
    )
    months: Optional[str] = pydantic.Field(
        default="months",
        title='Translation of "Months"',
        description='Translation of the word "months" in the locale.',
    )
    year: Optional[str] = pydantic.Field(
        default="year",
        title='Translation of "Year"',
        description='Translation of the word "year" in the locale.',
    )
    years: Optional[str] = pydantic.Field(
        default="years",
        title='Translation of "Years"',
        description='Translation of the word "years" in the locale.',
    )
    present: Optional[str] = pydantic.Field(
        default="present",
        title='Translation of "Present"',
        description='Translation of the word "present" in the locale.',
    )
    to: Optional[str] = pydantic.Field(
        default="–",  # en dash
        title='Translation of "To"',
        description=(
            "The word or character used to indicate a range in the locale (e.g.,"
            ' "2020 - 2021").'
        ),
    )
    abbreviations_for_months: Optional[
        Annotated[list[str], at.Len(min_length=12, max_length=12)]
    ] = pydantic.Field(
        # Month abbreviations are taken from
        # https://web.library.yale.edu/cataloging/months:
        default=[
            "Jan",
            "Feb",
            "Mar",
            "Apr",
            "May",
            "June",
            "July",
            "Aug",
            "Sept",
            "Oct",
            "Nov",
            "Dec",
        ],
        title="Abbreviations of Months",
        description="Abbreviations of the months in the locale.",
    )
    full_names_of_months: Optional[
        Annotated[list[str], at.Len(min_length=12, max_length=12)]
    ] = pydantic.Field(
        default=[
            "January",
            "February",
            "March",
            "April",
            "May",
            "June",
            "July",
            "August",
            "September",
            "October",
            "November",
            "December",
        ],
        title="Full Names of Months",
        description="Full names of the months in the locale.",
    )

    @pydantic.field_validator(
        "month",
        "months",
        "year",
        "years",
        "present",
        "abbreviations_for_months",
        "to",
        "full_names_of_months",
        "phone_number_format",
        "date_style",
    )
    @classmethod
    def update_locale_catalog(cls, value: str, info: pydantic.ValidationInfo) -> str:
        """Update the `locale_catalog` dictionary."""
        if value:
            LOCALE_CATALOG[info.field_name] = value  # type: ignore

        return value

update_locale_catalog(value, info) classmethod

Update the locale_catalog dictionary.

Source code in rendercv/data/models/locale_catalog.py
@pydantic.field_validator(
    "month",
    "months",
    "year",
    "years",
    "present",
    "abbreviations_for_months",
    "to",
    "full_names_of_months",
    "phone_number_format",
    "date_style",
)
@classmethod
def update_locale_catalog(cls, value: str, info: pydantic.ValidationInfo) -> str:
    """Update the `locale_catalog` dictionary."""
    if value:
        LOCALE_CATALOG[info.field_name] = value  # type: ignore

    return value

RenderCVDataModel

Bases: RenderCVBaseModelWithoutExtraKeys

This class binds both the CV and the design information together.

Source code in rendercv/data/models/rendercv_data_model.py
class RenderCVDataModel(RenderCVBaseModelWithoutExtraKeys):
    """This class binds both the CV and the design information together."""

    cv: CurriculumVitae = pydantic.Field(
        title="Curriculum Vitae",
        description="The data of the CV.",
    )
    design: RenderCVDesign = pydantic.Field(
        default=ClassicThemeOptions(theme="classic"),
        title="Design",
        description=(
            "The design information of the CV. The default is the classic theme."
        ),
    )
    locale_catalog: Optional[LocaleCatalog] = pydantic.Field(
        default=None,
        title="Locale Catalog",
        description=(
            "The locale catalog of the CV to allow the support of multiple languages."
        ),
    )
    rendercv_settings: Optional[RenderCVSettings] = pydantic.Field(
        default=None,
        title="RenderCV Settings",
        description="The settings of the RenderCV.",
    )

    @pydantic.field_validator("locale_catalog")
    @classmethod
    def initialize_locale_catalog(
        cls, locale_catalog: Optional[LocaleCatalog]
    ) -> Optional[LocaleCatalog]:
        """Even if the locale catalog is not provided, initialize it with the default
        values."""
        if locale_catalog is None:
            LocaleCatalog()

        return locale_catalog

initialize_locale_catalog(locale_catalog) classmethod

Even if the locale catalog is not provided, initialize it with the default values.

Source code in rendercv/data/models/rendercv_data_model.py
@pydantic.field_validator("locale_catalog")
@classmethod
def initialize_locale_catalog(
    cls, locale_catalog: Optional[LocaleCatalog]
) -> Optional[LocaleCatalog]:
    """Even if the locale catalog is not provided, initialize it with the default
    values."""
    if locale_catalog is None:
        LocaleCatalog()

    return locale_catalog

RenderCommandSettings

Bases: RenderCVBaseModelWithoutExtraKeys

This class is the data model of the render command's settings.

Source code in rendercv/data/models/rendercv_settings.py
class RenderCommandSettings(RenderCVBaseModelWithoutExtraKeys):
    """This class is the data model of the `render` command's settings."""

    output_folder_name: str = pydantic.Field(
        default="rendercv_output",
        title="Output Folder Name",
        description=(
            "The name of the folder where the output files will be saved."
            f" {file_path_placeholder_description_without_default}\nThe default value"
            ' is "rendercv_output".'
        ),
    )

    use_local_latex_command: Optional[str] = pydantic.Field(
        default=None,
        title="Local LaTeX Command",
        description=(
            "The command to compile the LaTeX file to a PDF file. The default value is"
            ' "pdflatex".'
        ),
    )

    pdf_path: Optional[pathlib.Path] = pydantic.Field(
        default=None,
        title="PDF Path",
        description=(
            "The path of the PDF file. If it is not provided, the PDF file will not be"
            f" generated. {file_path_placeholder_description}"
        ),
    )

    latex_path: Optional[pathlib.Path] = pydantic.Field(
        default=None,
        title="LaTeX Path",
        description=(
            "The path of the LaTeX file. If it is not provided, the LaTeX file will not"
            f" be generated. {file_path_placeholder_description}"
        ),
    )

    html_path: Optional[pathlib.Path] = pydantic.Field(
        default=None,
        title="HTML Path",
        description=(
            "The path of the HTML file. If it is not provided, the HTML file will not"
            f" be generated. {file_path_placeholder_description}"
        ),
    )

    png_path: Optional[pathlib.Path] = pydantic.Field(
        default=None,
        title="PNG Path",
        description=(
            "The path of the PNG file. If it is not provided, the PNG file will not be"
            f" generated. {file_path_placeholder_description}"
        ),
    )

    markdown_path: Optional[pathlib.Path] = pydantic.Field(
        default=None,
        title="Markdown Path",
        description=(
            "The path of the Markdown file. If it is not provided, the Markdown file"
            f" will not be generated. {file_path_placeholder_description}"
        ),
    )

    dont_generate_html: bool = pydantic.Field(
        default=False,
        title="Generate HTML Flag",
        description=(
            "A boolean value to determine whether the HTML file will be generated. The"
            " default value is False."
        ),
    )

    dont_generate_markdown: bool = pydantic.Field(
        default=False,
        title="Generate Markdown Flag",
        description=(
            "A boolean value to determine whether the Markdown file will be generated."
            " The default value is False."
        ),
    )

    dont_generate_png: bool = pydantic.Field(
        default=False,
        title="Generate PNG Flag",
        description=(
            "A boolean value to determine whether the PNG file will be generated. The"
            " default value is False."
        ),
    )

    @pydantic.field_validator(
        "output_folder_name",
        mode="before",
    )
    @classmethod
    def replace_placeholders(cls, value: str) -> str:
        """Replaces the placeholders in a string with the corresponding values."""
        return replace_placeholders(value)

    @pydantic.field_validator(
        "pdf_path",
        "latex_path",
        "html_path",
        "png_path",
        "markdown_path",
        mode="before",
    )
    @classmethod
    def convert_string_to_path(cls, value: Optional[str]) -> Optional[pathlib.Path]:
        """Converts a string to a `pathlib.Path` object by replacing the placeholders
        with the corresponding values. If the path is not an absolute path, it is
        converted to an absolute path by prepending the current working directory.
        """
        if value is None:
            return None

        return convert_string_to_path(value)

replace_placeholders(value) classmethod

Replaces the placeholders in a string with the corresponding values.

Source code in rendercv/data/models/rendercv_settings.py
@pydantic.field_validator(
    "output_folder_name",
    mode="before",
)
@classmethod
def replace_placeholders(cls, value: str) -> str:
    """Replaces the placeholders in a string with the corresponding values."""
    return replace_placeholders(value)

convert_string_to_path(value) classmethod

Converts a string to a pathlib.Path object by replacing the placeholders with the corresponding values. If the path is not an absolute path, it is converted to an absolute path by prepending the current working directory.

Source code in rendercv/data/models/rendercv_settings.py
@pydantic.field_validator(
    "pdf_path",
    "latex_path",
    "html_path",
    "png_path",
    "markdown_path",
    mode="before",
)
@classmethod
def convert_string_to_path(cls, value: Optional[str]) -> Optional[pathlib.Path]:
    """Converts a string to a `pathlib.Path` object by replacing the placeholders
    with the corresponding values. If the path is not an absolute path, it is
    converted to an absolute path by prepending the current working directory.
    """
    if value is None:
        return None

    return convert_string_to_path(value)

RenderCVSettings

Bases: RenderCVBaseModelWithoutExtraKeys

This class is the data model of the RenderCV settings.

Source code in rendercv/data/models/rendercv_settings.py
class RenderCVSettings(RenderCVBaseModelWithoutExtraKeys):
    """This class is the data model of the RenderCV settings."""

    render_command: Optional[RenderCommandSettings] = pydantic.Field(
        default=None,
        title="Render Command Settings",
        description=(
            "RenderCV's `render` command settings. They are the same as the command"
            " line arguments. CLI arguments have higher priority than the settings in"
            " the input file."
        ),
    )

format_date(date, date_style=None)

Formats a Date object to a string in the following format: "Jan 2021". The month names are taken from the locale_catalog dictionary from the rendercv.data_models.models module.

Example

format_date(Date(2024, 5, 1))
will return

"May 2024"

Parameters:

  • date (date) –

    The date to format.

  • date_style (Optional[str], default: None ) –

    The style of the date string. If not provided, the default date style from the locale_catalog dictionary will be used.

Returns:

  • str

    The formatted date.

Source code in rendercv/data/models/computers.py
def format_date(date: Date, date_style: Optional[str] = None) -> str:
    """Formats a `Date` object to a string in the following format: "Jan 2021". The
    month names are taken from the `locale_catalog` dictionary from the
    `rendercv.data_models.models` module.

    Example:
        ```python
        format_date(Date(2024, 5, 1))
        ```
        will return

        `"May 2024"`

    Args:
        date: The date to format.
        date_style: The style of the date string. If not provided, the default date
            style from the `locale_catalog` dictionary will be used.

    Returns:
        The formatted date.
    """
    full_month_names = LOCALE_CATALOG["full_names_of_months"]
    short_month_names = LOCALE_CATALOG["abbreviations_for_months"]

    month = int(date.strftime("%m"))
    year = date.strftime(format="%Y")

    placeholders = {
        "FULL_MONTH_NAME": full_month_names[month - 1],
        "MONTH_ABBREVIATION": short_month_names[month - 1],
        "MONTH_IN_TWO_DIGITS": f"{month:02d}",
        "YEAR_IN_TWO_DIGITS": str(year[-2:]),
        "MONTH": str(month),
        "YEAR": str(year),
    }
    if date_style is None:
        date_style = LOCALE_CATALOG["date_style"]  # type: ignore

    for placeholder, value in placeholders.items():
        date_style = date_style.replace(placeholder, value)  # type: ignore

    date_string = date_style

    return date_string  # type: ignore