mirror of https://github.com/eyhc1/rendercv.git
data_models: refactor comments (thanks to @flowrolltide)
This commit is contained in:
parent
50681949cc
commit
3696ada59d
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
This module contains all the necessary classes to store CV data. These classes are called
|
This module contains the necessary classes to store CV data. These classes are called
|
||||||
data models. The YAML input file is transformed into instances of these classes (i.e.,
|
data models. The YAML input file is transformed into instances of these classes (i.e.,
|
||||||
the input file is read) with the
|
the input file is read) with the
|
||||||
[`read_input_file`][rendercv.data_models.read_input_file] function. RenderCV utilizes
|
[`read_input_file`][rendercv.data_models.read_input_file] function. RenderCV utilizes
|
||||||
|
@ -7,7 +7,7 @@ these instances to generate a $\\LaTeX$ file which is then rendered into a PDF f
|
||||||
|
|
||||||
The data models are initialized with data validation to prevent unexpected bugs. During
|
The data models are initialized with data validation to prevent unexpected bugs. During
|
||||||
the initialization, we ensure that everything is in the correct place and that the user
|
the initialization, we ensure that everything is in the correct place and that the user
|
||||||
has provided a valid RenderCV input. This is achieved through the use of
|
has provided a valid RenderCV input. This is achieved using
|
||||||
[Pydantic](https://pypi.org/project/pydantic/). Each class method decorated with
|
[Pydantic](https://pypi.org/project/pydantic/). Each class method decorated with
|
||||||
`pydantic.model_validator` or `pydantic.field_validator` is executed automatically
|
`pydantic.model_validator` or `pydantic.field_validator` is executed automatically
|
||||||
during the data classes' initialization.
|
during the data classes' initialization.
|
||||||
|
@ -39,7 +39,7 @@ from .themes.moderncv import ModerncvThemeOptions
|
||||||
from .themes.sb2nov import Sb2novThemeOptions
|
from .themes.sb2nov import Sb2novThemeOptions
|
||||||
from .themes.engineeringresumes import EngineeringresumesThemeOptions
|
from .themes.engineeringresumes import EngineeringresumesThemeOptions
|
||||||
|
|
||||||
# disable Pydantic warnings:
|
# Disable Pydantic warnings:
|
||||||
warnings.filterwarnings("ignore")
|
warnings.filterwarnings("ignore")
|
||||||
|
|
||||||
locale_catalog = {
|
locale_catalog = {
|
||||||
|
@ -49,6 +49,7 @@ locale_catalog = {
|
||||||
"years": "years",
|
"years": "years",
|
||||||
"present": "present",
|
"present": "present",
|
||||||
"to": "to",
|
"to": "to",
|
||||||
|
# Month abbreviations are taken from https://web.library.yale.edu/cataloging/months:
|
||||||
"abbreviations_for_months": [
|
"abbreviations_for_months": [
|
||||||
"Jan.",
|
"Jan.",
|
||||||
"Feb.",
|
"Feb.",
|
||||||
|
@ -72,9 +73,9 @@ def get_date_object(date: str | int) -> Date:
|
||||||
the data models.
|
the data models.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
date (str): The date string to parse.
|
date (str | int): The date string to parse.
|
||||||
Returns:
|
Returns:
|
||||||
datetime.date: The parsed date.
|
Date: The parsed date.
|
||||||
"""
|
"""
|
||||||
if isinstance(date, int):
|
if isinstance(date, int):
|
||||||
date_object = Date.fromisoformat(f"{date}-01-01")
|
date_object = Date.fromisoformat(f"{date}-01-01")
|
||||||
|
@ -101,9 +102,6 @@ def get_date_object(date: str | int) -> Date:
|
||||||
def format_date(date: Date) -> str:
|
def format_date(date: Date) -> str:
|
||||||
"""Formats a `Date` object to a string in the following format: "Jan. 2021".
|
"""Formats a `Date` object to a string in the following format: "Jan. 2021".
|
||||||
|
|
||||||
It uses month abbreviations, taken from
|
|
||||||
[Yale University Library](https://web.library.yale.edu/cataloging/months).
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```python
|
```python
|
||||||
format_date(Date(2024, 5, 1))
|
format_date(Date(2024, 5, 1))
|
||||||
|
@ -199,7 +197,7 @@ class EntryWithDate(RenderCVBaseModel):
|
||||||
def check_date(
|
def check_date(
|
||||||
cls, date: Optional[int | RenderCVDate | str]
|
cls, date: Optional[int | RenderCVDate | str]
|
||||||
) -> Optional[int | RenderCVDate | str]:
|
) -> Optional[int | RenderCVDate | str]:
|
||||||
"""Check if the date is provided correctly."""
|
"""Check if `date` is provided correctly."""
|
||||||
date_is_provided = date is not None
|
date_is_provided = date is not None
|
||||||
|
|
||||||
if date_is_provided:
|
if date_is_provided:
|
||||||
|
@ -219,7 +217,7 @@ class EntryWithDate(RenderCVBaseModel):
|
||||||
date = int(date)
|
date = int(date)
|
||||||
elif isinstance(date, Date):
|
elif isinstance(date, Date):
|
||||||
# Pydantic parses YYYY-MM-DD dates as datetime.date objects. We need to
|
# Pydantic parses YYYY-MM-DD dates as datetime.date objects. We need to
|
||||||
# convert them to strings because that's how RenderCV uses them.
|
# convert them to strings because that is how RenderCV uses them.
|
||||||
date = date.isoformat()
|
date = date.isoformat()
|
||||||
|
|
||||||
return date
|
return date
|
||||||
|
@ -245,7 +243,7 @@ class EntryWithDate(RenderCVBaseModel):
|
||||||
|
|
||||||
class PublicationEntryBase(RenderCVBaseModel):
|
class PublicationEntryBase(RenderCVBaseModel):
|
||||||
title: str = pydantic.Field(
|
title: str = pydantic.Field(
|
||||||
title="Title of the Publication",
|
title="Publication Title",
|
||||||
description="The title of the publication.",
|
description="The title of the publication.",
|
||||||
)
|
)
|
||||||
authors: list[str] = pydantic.Field(
|
authors: list[str] = pydantic.Field(
|
||||||
|
@ -261,15 +259,15 @@ class PublicationEntryBase(RenderCVBaseModel):
|
||||||
journal: Optional[str] = pydantic.Field(
|
journal: Optional[str] = pydantic.Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="Journal",
|
title="Journal",
|
||||||
description="The journal or the conference name.",
|
description="The journal or conference name.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@pydantic.field_validator("doi")
|
@pydantic.field_validator("doi")
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_doi(cls, doi: Optional[str]) -> Optional[str]:
|
def check_doi(cls, doi: Optional[str]) -> Optional[str]:
|
||||||
"""Check if the DOI exists in the DOI System."""
|
"""Check if the DOI is valid and exists in the DOI System."""
|
||||||
if doi is not None:
|
if doi is not None:
|
||||||
# see https://stackoverflow.com/a/60671292/18840665 for the explanation of
|
# See https://stackoverflow.com/a/60671292/18840665 for the explanation of
|
||||||
# the next line:
|
# the next line:
|
||||||
ssl._create_default_https_context = ssl._create_unverified_context # type: ignore
|
ssl._create_default_https_context = ssl._create_unverified_context # type: ignore
|
||||||
|
|
||||||
|
@ -284,7 +282,7 @@ class PublicationEntryBase(RenderCVBaseModel):
|
||||||
if err.code == 404:
|
if err.code == 404:
|
||||||
raise ValueError("DOI cannot be found in the DOI System!")
|
raise ValueError("DOI cannot be found in the DOI System!")
|
||||||
except InvalidURL:
|
except InvalidURL:
|
||||||
# Unfortunately, url_validator doesn't catch all the invalid URLs.
|
# Unfortunately, url_validator does not catch all the invalid URLs.
|
||||||
raise ValueError("This DOI is invalid!")
|
raise ValueError("This DOI is invalid!")
|
||||||
except URLError:
|
except URLError:
|
||||||
# In this case, there is no internet connection, so don't raise an
|
# In this case, there is no internet connection, so don't raise an
|
||||||
|
@ -353,7 +351,7 @@ class EntryBase(EntryWithDate):
|
||||||
if date_is_provided:
|
if date_is_provided:
|
||||||
if isinstance(date, Date):
|
if isinstance(date, Date):
|
||||||
# Pydantic parses YYYY-MM-DD dates as datetime.date objects. We need to
|
# Pydantic parses YYYY-MM-DD dates as datetime.date objects. We need to
|
||||||
# convert them to strings because that's how RenderCV uses them.
|
# convert them to strings because that is how RenderCV uses them.
|
||||||
date = date.isoformat()
|
date = date.isoformat()
|
||||||
|
|
||||||
elif date != "present":
|
elif date != "present":
|
||||||
|
@ -584,7 +582,7 @@ class NormalEntryBase(RenderCVBaseModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# The following class is to make sure NormalEntryBase keys come first,
|
# The following class is to ensure NormalEntryBase keys come first,
|
||||||
# then the keys of the EntryBase class. The only way to achieve this in Pydantic is
|
# then the keys of the EntryBase class. The only way to achieve this in Pydantic is
|
||||||
# to do this.
|
# to do this.
|
||||||
class NormalEntry(EntryBase, NormalEntryBase):
|
class NormalEntry(EntryBase, NormalEntryBase):
|
||||||
|
@ -639,7 +637,7 @@ class EducationEntry(EntryBase, EducationEntryBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Create a custom type called Entry and ListOfEntries:
|
# Create custom types named Entry and ListOfEntries:
|
||||||
Entry = (
|
Entry = (
|
||||||
OneLineEntry
|
OneLineEntry
|
||||||
| NormalEntry
|
| NormalEntry
|
||||||
|
@ -728,7 +726,7 @@ def get_entry_and_section_type(
|
||||||
Returns:
|
Returns:
|
||||||
tuple[str, Type[Section]]: The entry type and the section type.
|
tuple[str, Type[Section]]: The entry type and the section type.
|
||||||
"""
|
"""
|
||||||
# Get class attributes of EntryBase class:
|
# Get the class attributes of EntryBase class:
|
||||||
common_attributes = set(EntryBase.model_fields.keys())
|
common_attributes = set(EntryBase.model_fields.keys())
|
||||||
|
|
||||||
if isinstance(entry, dict):
|
if isinstance(entry, dict):
|
||||||
|
@ -812,15 +810,15 @@ def validate_section_input(
|
||||||
new_error = ValueError(
|
new_error = ValueError(
|
||||||
"There are problems with the entries. RenderCV detected the entry type"
|
"There are problems with the entries. RenderCV detected the entry type"
|
||||||
f" of this section to be {entry_type}! The problems are shown below.",
|
f" of this section to be {entry_type}! The problems are shown below.",
|
||||||
"", # this is the location of the error
|
"", # This is the location of the error
|
||||||
"", # this is value of the error
|
"", # This is value of the error
|
||||||
)
|
)
|
||||||
raise new_error from e
|
raise new_error from e
|
||||||
|
|
||||||
return sections_input
|
return sections_input
|
||||||
|
|
||||||
|
|
||||||
# Create a custom type called SectionInput so that it can be validated with
|
# Create a custom type named SectionInput so that it can be validated with
|
||||||
# `validate_section_input` function.
|
# `validate_section_input` function.
|
||||||
SectionInput = Annotated[
|
SectionInput = Annotated[
|
||||||
ListOfEntries,
|
ListOfEntries,
|
||||||
|
@ -906,7 +904,7 @@ class SocialNetwork(RenderCVBaseModel):
|
||||||
def url(self) -> str:
|
def url(self) -> str:
|
||||||
"""Return the URL of the social network."""
|
"""Return the URL of the social network."""
|
||||||
if self.network == "Mastodon":
|
if self.network == "Mastodon":
|
||||||
# split domain and username
|
# Split domain and username
|
||||||
dummy, username, domain = self.username.split("@")
|
dummy, username, domain = self.username.split("@")
|
||||||
url = f"https://{domain}/@{username}"
|
url = f"https://{domain}/@{username}"
|
||||||
else:
|
else:
|
||||||
|
@ -948,7 +946,7 @@ class CurriculumVitae(RenderCVBaseModel):
|
||||||
email: Optional[pydantic.EmailStr] = pydantic.Field(
|
email: Optional[pydantic.EmailStr] = pydantic.Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="Email",
|
title="Email",
|
||||||
description="The email of the person.",
|
description="The email address of the person.",
|
||||||
)
|
)
|
||||||
phone: Optional[pydantic_phone_numbers.PhoneNumber] = pydantic.Field(
|
phone: Optional[pydantic_phone_numbers.PhoneNumber] = pydantic.Field(
|
||||||
default=None,
|
default=None,
|
||||||
|
@ -1153,7 +1151,7 @@ class LocaleCatalog(RenderCVBaseModel):
|
||||||
# ======================================================================================
|
# ======================================================================================
|
||||||
# ======================================================================================
|
# ======================================================================================
|
||||||
|
|
||||||
# Create a custom type called Design:
|
# Create a custom type named Design:
|
||||||
# It is a union of all the design options and the correct design option is determined by
|
# It is a union of all the design options and the correct design option is determined by
|
||||||
# the theme field, thanks to Pydantic's discriminator feature.
|
# the theme field, thanks to Pydantic's discriminator feature.
|
||||||
# See https://docs.pydantic.dev/2.5/concepts/fields/#discriminator for more information
|
# See https://docs.pydantic.dev/2.5/concepts/fields/#discriminator for more information
|
||||||
|
@ -1215,7 +1213,7 @@ class RenderCVDataModel(RenderCVBaseModel):
|
||||||
# Then it means it is a custom theme, so initialize and validate it:
|
# Then it means it is a custom theme, so initialize and validate it:
|
||||||
theme_name: str = str(design["theme"])
|
theme_name: str = str(design["theme"])
|
||||||
|
|
||||||
# check if the theme name is valid:
|
# Check if the theme name is valid:
|
||||||
if not theme_name.isalpha():
|
if not theme_name.isalpha():
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"The custom theme name should contain only letters.",
|
"The custom theme name should contain only letters.",
|
||||||
|
@ -1223,10 +1221,10 @@ class RenderCVDataModel(RenderCVBaseModel):
|
||||||
theme_name, # this is value of the error
|
theme_name, # this is value of the error
|
||||||
)
|
)
|
||||||
|
|
||||||
# then it is a custom theme
|
# Then it is a custom theme
|
||||||
custom_theme_folder = pathlib.Path(theme_name)
|
custom_theme_folder = pathlib.Path(theme_name)
|
||||||
|
|
||||||
# check if the custom theme folder exists:
|
# Check if the custom theme folder exists:
|
||||||
if not custom_theme_folder.exists():
|
if not custom_theme_folder.exists():
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"The custom theme folder `{custom_theme_folder}` does not exist."
|
f"The custom theme folder `{custom_theme_folder}` does not exist."
|
||||||
|
@ -1252,11 +1250,11 @@ class RenderCVDataModel(RenderCVBaseModel):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"You provided a custom theme, but the file `{file}` is not"
|
f"You provided a custom theme, but the file `{file}` is not"
|
||||||
f" found in the folder `{custom_theme_folder}`.",
|
f" found in the folder `{custom_theme_folder}`.",
|
||||||
"", # this is the location of the error
|
"", # This is the location of the error
|
||||||
theme_name, # this is value of the error
|
theme_name, # This is value of the error
|
||||||
)
|
)
|
||||||
|
|
||||||
# import __init__.py file from the custom theme folder if it exists:
|
# Import __init__.py file from the custom theme folder if it exists:
|
||||||
path_to_init_file = pathlib.Path(f"{theme_name}/__init__.py")
|
path_to_init_file = pathlib.Path(f"{theme_name}/__init__.py")
|
||||||
|
|
||||||
if path_to_init_file.exists():
|
if path_to_init_file.exists():
|
||||||
|
|
Loading…
Reference in New Issue