Coverage for rendercv/data/generator.py: 100%
63 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-07 17:51 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-07 17:51 +0000
1"""
2The `rendercv.data.generator` module contains all the functions for generating the JSON
3Schema of the input data format and a sample YAML input file.
4"""
6import io
7import json
8import pathlib
9from typing import Optional
11import pydantic
12import ruamel.yaml
14from . import models, reader
17def dictionary_to_yaml(dictionary: dict) -> str:
18 """Converts a dictionary to a YAML string.
20 Args:
21 dictionary (dict): The dictionary to be converted to YAML.
23 Returns:
24 str: The YAML string.
25 """
26 yaml_object = ruamel.yaml.YAML()
27 yaml_object.encoding = "utf-8"
28 yaml_object.width = 60
29 yaml_object.indent(mapping=2, sequence=4, offset=2)
30 with io.StringIO() as string_stream:
31 yaml_object.dump(dictionary, string_stream)
32 yaml_string = string_stream.getvalue()
34 return yaml_string
37def create_a_sample_data_model(
38 name: str = "John Doe", theme: str = "classic"
39) -> models.RenderCVDataModel:
40 """Return a sample data model for new users to start with.
42 Args:
43 name (str, optional): The name of the person. Defaults to "John Doe".
45 Returns:
46 RenderCVDataModel: A sample data model.
47 """
48 # Check if the theme is valid:
49 if theme not in models.available_theme_options:
50 available_themes_string = ", ".join(models.available_theme_options.keys())
51 raise ValueError(
52 f"The theme should be one of the following: {available_themes_string}!"
53 f' The provided theme is "{theme}".'
54 )
56 # read the sample_content.yaml file
57 sample_content = pathlib.Path(__file__).parent / "sample_content.yaml"
58 sample_content_dictionary = reader.read_a_yaml_file(sample_content)
59 cv = models.CurriculumVitae(**sample_content_dictionary)
61 # Update the name:
62 name = name.encode().decode("unicode-escape")
63 cv.name = name
65 design = models.available_theme_options[theme](theme=theme)
67 return models.RenderCVDataModel(cv=cv, design=design)
70def create_a_sample_yaml_input_file(
71 input_file_path: Optional[pathlib.Path] = None,
72 name: str = "John Doe",
73 theme: str = "classic",
74) -> str:
75 """Create a sample YAML input file and return it as a string. If the input file path
76 is provided, then also save the contents to the file.
78 Args:
79 input_file_path (pathlib.Path, optional): The path to save the input file.
80 Defaults to None.
81 name (str, optional): The name of the person. Defaults to "John Doe".
82 theme (str, optional): The theme of the CV. Defaults to "classic".
84 Returns:
85 str: The sample YAML input file as a string.
86 """
87 data_model = create_a_sample_data_model(name=name, theme=theme)
89 # Instead of getting the dictionary with data_model.model_dump() directly, we
90 # convert it to JSON and then to a dictionary. Because the YAML library we are
91 # using sometimes has problems with the dictionary returned by model_dump().
93 # We exclude "cv.sections" because the data model automatically generates them.
94 # The user's "cv.sections" input is actually "cv.sections_input" in the data
95 # model. It is shown as "cv.sections" in the YAML file because an alias is being
96 # used. If"cv.sections" were not excluded, the automatically generated
97 # "cv.sections" would overwrite the "cv.sections_input". "cv.sections" are
98 # automatically generated from "cv.sections_input" to make the templating
99 # process easier. "cv.sections_input" exists for the convenience of the user.
100 data_model_as_json = data_model.model_dump_json(
101 exclude_none=True, by_alias=True, exclude={"cv": {"sections"}}
102 )
103 data_model_as_dictionary = json.loads(data_model_as_json)
105 yaml_string = dictionary_to_yaml(data_model_as_dictionary)
107 if input_file_path is not None:
108 input_file_path.write_text(yaml_string, encoding="utf-8")
110 return yaml_string
113def generate_json_schema() -> dict:
114 """Generate the JSON schema of RenderCV.
116 JSON schema is generated for the users to make it easier for them to write the input
117 file. The JSON Schema of RenderCV is saved in the root directory of the repository
118 and distributed to the users with the
119 [JSON Schema Store](https://www.schemastore.org/).
121 Returns:
122 dict: The JSON schema of RenderCV.
123 """
125 class RenderCVSchemaGenerator(pydantic.json_schema.GenerateJsonSchema):
126 def generate(self, schema, mode="validation"): # type: ignore
127 json_schema = super().generate(schema, mode=mode)
129 # Basic information about the schema:
130 json_schema["title"] = "RenderCV"
131 json_schema["description"] = "RenderCV data model."
132 json_schema["$id"] = (
133 "https://raw.githubusercontent.com/sinaatalay/rendercv/main/schema.json"
134 )
135 json_schema["$schema"] = "http://json-schema.org/draft-07/schema#"
137 # Loop through $defs and remove docstring descriptions and fix optional
138 # fields
139 for object_name, value in json_schema["$defs"].items():
140 # Don't allow additional properties
141 value["additionalProperties"] = False
143 # If a type is optional, then Pydantic sets the type to a list of two
144 # types, one of which is null. The null type can be removed since we
145 # already have the required field. Moreover, we would like to warn
146 # users if they provide null values. They can remove the fields if they
147 # don't want to provide them.
148 null_type_dict = {
149 "type": "null",
150 }
151 for field_name, field in value["properties"].items():
152 if "anyOf" in field:
153 if null_type_dict in field["anyOf"]:
154 field["anyOf"].remove(null_type_dict)
156 field["oneOf"] = field["anyOf"]
157 del field["anyOf"]
159 # Currently, YAML extension in VS Code doesn't work properly with the
160 # `ListOfEntries` objects. For the best user experience, we will update
161 # the JSON Schema. If YAML extension in VS Code starts to work properly,
162 # then we should remove the following code for the correct JSON Schema.
163 ListOfEntriesForJsonSchema = list[models.Entry]
164 list_of_entries_json_schema = pydantic.TypeAdapter(
165 ListOfEntriesForJsonSchema
166 ).json_schema()
167 del list_of_entries_json_schema["$defs"]
169 # Update the JSON Schema:
170 json_schema["$defs"]["CurriculumVitae"]["properties"]["sections"]["oneOf"][
171 0
172 ]["additionalProperties"] = list_of_entries_json_schema
174 return json_schema
176 schema = models.RenderCVDataModel.model_json_schema(
177 schema_generator=RenderCVSchemaGenerator
178 )
180 return schema
183def generate_json_schema_file(json_schema_path: pathlib.Path):
184 """Generate the JSON schema of RenderCV and save it to a file.
186 Args:
187 json_schema_path (pathlib.Path): The path to save the JSON schema.
188 """
189 schema = generate_json_schema()
190 schema_json = json.dumps(schema, indent=2, ensure_ascii=False)
191 json_schema_path.write_text(schema_json, encoding="utf-8")