data_models: add a new function `set_or_update_a_value`

This commit is contained in:
Sina Atalay 2024-04-30 19:00:46 +03:00
parent 6864bb8508
commit 70d9589d9a
2 changed files with 114 additions and 0 deletions

View File

@ -25,6 +25,7 @@ import json
import re
import ssl
import pathlib
import warnings
import pydantic
import pydantic_extra_types.phone_numbers as pydantic_phone_numbers
@ -35,6 +36,9 @@ from .themes.moderncv import ModerncvThemeOptions
from .themes.sb2nov import Sb2novThemeOptions
from .themes.engineeringresumes import EngineeringresumesThemeOptions
# disable Pydantic warnings:
warnings.filterwarnings("ignore")
# Create a custom type called RenderCVDate that accepts only strings in YYYY-MM-DD or
# YYYY-MM format:
# This type is used to validate the date fields in the data.
@ -1126,6 +1130,69 @@ class RenderCVDataModel(RenderCVBaseModel):
return theme_data_model
def set_or_update_a_value(
data_model: pydantic.BaseModel | dict | list,
key: str,
value: Any,
sub_model: pydantic.BaseModel | dict | list = None,
):
"""Set or update a value in a data model for a specific key. For example, a key can
be `cv.sections.education.3.institution` and the value can be "Bogazici University".
Args:
data_model (pydantic.BaseModel | dict | list): The data model to set or update
the value.
key (str): The key to set or update the value.
value (Any): The value to set or update.
sub_model (pydantic.BaseModel | dict | list, optional): The sub model to set or
update the value. This is used for recursive calls. When the value is set
to a sub model, the original data model is validated. Defaults to None.
"""
# recursively call this function until the last key is reached:
# rename `sections` with `sections_input` since the key is `sections` is an alias:
key = key.replace("sections.", "sections_input.")
keys = key.split(".")
if sub_model is not None:
model = sub_model
else:
model = data_model
if len(keys) == 1:
# set the value:
if isinstance(model, pydantic.BaseModel):
setattr(model, key, value)
elif isinstance(model, dict):
model[key] = value
elif isinstance(model, list):
model[int(key)] = value
else:
raise ValueError(
"The data model should be either a Pydantic data model, dictionary, or"
" list.",
)
data_model.model_validate(data_model.model_dump(by_alias=True))
else:
# get the first key and call the function with remaining keys:
first_key = keys[0]
key = ".".join(keys[1:])
if isinstance(model, pydantic.BaseModel):
sub_model = getattr(model, first_key)
elif isinstance(model, dict):
sub_model = model[first_key]
elif isinstance(model, list):
sub_model = model[int(first_key)]
else:
raise ValueError(
"The data model should be either a Pydantic data model, dictionary, or"
" list.",
)
set_or_update_a_value(data_model, key, value, sub_model)
def read_input_file(
file_path: pathlib.Path,
) -> RenderCVDataModel:

View File

@ -2,6 +2,7 @@ from datetime import date as Date
import json
import pathlib
import os
import re
import shutil
import pydantic
@ -59,6 +60,52 @@ def test_format_date(date, expected_date_string):
assert dm.format_date(date) == expected_date_string
@pytest.mark.parametrize(
"key, value",
[
("cv.phone", "+905555555555"),
("cv.email", "test@example.com"),
("cv.sections.education.0.degree", "PhD"),
("design.page_size", "a4paper"),
],
)
def test_set_or_update_a_value(rendercv_data_model, key, value):
dm.set_or_update_a_value(rendercv_data_model, key, value)
# replace with regex pattern:
key = re.sub(r"sections\.([^\.]*?)\.(\d+)", 'sections_input["\\1"][\\2]', key)
assert eval(f"rendercv_data_model.{key}") == value
@pytest.mark.parametrize(
"key, value",
[
("cv.phones", "+905555555555"),
("cv.emssdsail", ""),
("cv.sections.education.99.degree", "PhD"),
("dessssign.page_size", "a4paper"),
],
)
def test_set_or_update_a_value_invalid_keys(rendercv_data_model, key, value):
with pytest.raises((ValueError, KeyError, IndexError, AttributeError)):
dm.set_or_update_a_value(rendercv_data_model, key, value)
@pytest.mark.parametrize(
"key, value",
[
("cv.phone", "+9999995555555555"),
("cv.email", "notanemail***"),
("cv.sections.education.0.highlights", "this is not a list"),
("design.page_size", "invalid_page_size"),
],
)
def test_set_or_update_a_value_invalid_values(rendercv_data_model, key, value):
with pytest.raises(pydantic.ValidationError):
dm.set_or_update_a_value(rendercv_data_model, key, value)
def test_read_input_file(input_file_path):
# Update the auxiliary files if update_testdata is True
if update_testdata: