Coverage for rendercv/data/generator.py: 100%
57 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-26 00:25 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-26 00:25 +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: The dictionary to be converted to YAML.
23 Returns:
24 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 return string_stream.getvalue()
35def create_a_sample_data_model(
36 name: str = "John Doe", theme: str = "classic"
37) -> models.RenderCVDataModel:
38 """Return a sample data model for new users to start with.
40 Args:
41 name: The name of the person. Defaults to "John Doe".
43 Returns:
44 A sample data model.
45 """
46 # Check if the theme is valid:
47 if theme not in models.available_theme_options:
48 available_themes_string = ", ".join(models.available_theme_options.keys())
49 message = (
50 f"The theme should be one of the following: {available_themes_string}!"
51 f' The provided theme is "{theme}".'
52 )
53 raise ValueError(message)
55 # read the sample_content.yaml file
56 sample_content = pathlib.Path(__file__).parent / "sample_content.yaml"
57 sample_content_dictionary = reader.read_a_yaml_file(sample_content)
58 cv = models.CurriculumVitae(**sample_content_dictionary)
60 # Update the name:
61 name = name.encode().decode("unicode-escape")
62 cv.name = name
64 design = models.available_theme_options[theme](theme=theme)
66 return models.RenderCVDataModel(cv=cv, design=design)
69def create_a_sample_yaml_input_file(
70 input_file_path: Optional[pathlib.Path] = None,
71 name: str = "John Doe",
72 theme: str = "classic",
73) -> str:
74 """Create a sample YAML input file and return it as a string. If the input file path
75 is provided, then also save the contents to the file.
77 Args:
78 input_file_path: The path to save the input file. Defaults to None.
79 name: The name of the person. Defaults to "John Doe".
80 theme: The theme of the CV. Defaults to "classic".
82 Returns:
83 The sample YAML input file as a string.
84 """
85 data_model = create_a_sample_data_model(name=name, theme=theme)
87 # Instead of getting the dictionary with data_model.model_dump() directly, we
88 # convert it to JSON and then to a dictionary. Because the YAML library we are
89 # using sometimes has problems with the dictionary returned by model_dump().
91 # We exclude "cv.sections" because the data model automatically generates them.
92 # The user's "cv.sections" input is actually "cv.sections_input" in the data
93 # model. It is shown as "cv.sections" in the YAML file because an alias is being
94 # used. If"cv.sections" were not excluded, the automatically generated
95 # "cv.sections" would overwrite the "cv.sections_input". "cv.sections" are
96 # automatically generated from "cv.sections_input" to make the templating
97 # process easier. "cv.sections_input" exists for the convenience of the user.
98 data_model_as_json = data_model.model_dump_json(
99 exclude_none=True, by_alias=True, exclude={"cv": {"sections"}}
100 )
101 data_model_as_dictionary = json.loads(data_model_as_json)
103 yaml_string = dictionary_to_yaml(data_model_as_dictionary)
105 if input_file_path is not None:
106 input_file_path.write_text(yaml_string, encoding="utf-8")
108 return yaml_string
111def generate_json_schema() -> dict:
112 """Generate the JSON schema of RenderCV.
114 JSON schema is generated for the users to make it easier for them to write the input
115 file. The JSON Schema of RenderCV is saved in the root directory of the repository
116 and distributed to the users with the
117 [JSON Schema Store](https://www.schemastore.org/).
119 Returns:
120 The JSON schema of RenderCV.
121 """
123 class RenderCVSchemaGenerator(pydantic.json_schema.GenerateJsonSchema):
124 def generate(self, schema, mode="validation"): # type: ignore
125 json_schema = super().generate(schema, mode=mode)
127 # Basic information about the schema:
128 json_schema["title"] = "RenderCV"
129 json_schema["description"] = "RenderCV data model."
130 json_schema["$id"] = (
131 "https://raw.githubusercontent.com/rendercv/rendercv/main/schema.json"
132 )
133 json_schema["$schema"] = "http://json-schema.org/draft-07/schema#"
135 # Loop through $defs and remove docstring descriptions and fix optional
136 # fields
137 for _, value in json_schema["$defs"].items():
138 # If a type is optional, then Pydantic sets the type to a list of two
139 # types, one of which is null. The null type can be removed since we
140 # already have the required field. Moreover, we would like to warn
141 # users if they provide null values. They can remove the fields if they
142 # don't want to provide them.
143 null_type_dict = {
144 "type": "null",
145 }
146 for _, field in value["properties"].items():
147 if "anyOf" in field:
148 if null_type_dict in field["anyOf"]:
149 field["anyOf"].remove(null_type_dict)
151 field["oneOf"] = field["anyOf"]
152 del field["anyOf"]
154 # # Currently, YAML extension in VS Code doesn't work properly with the
155 # # `ListOfEntries` objects. For the best user experience, we will update
156 # # the JSON Schema. If YAML extension in VS Code starts to work properly,
157 # # then we should remove the following code for the correct JSON Schema.
158 # ListOfEntriesForJsonSchema = list[models.Entry]
159 # list_of_entries_json_schema = pydantic.TypeAdapter(
160 # ListOfEntriesForJsonSchema
161 # ).json_schema()
162 # del list_of_entries_json_schema["$defs"]
164 # json_schema["$defs"]["CurriculumVitae"]["properties"]["sections"]["oneOf"][
165 # 0
166 # ]["additionalProperties"] = list_of_entries_json_schema
168 # # Loop through json_schema["$defs"] and update keys:
169 # # Make all ANYTHING__KEY to KEY
170 # for key in list(json_schema["$defs"]):
171 # new_key = key.split("__")[-1]
172 # json_schema["$defs"][new_key] = json_schema["$defs"][key]
174 return json_schema
176 return models.RenderCVDataModel.model_json_schema(
177 schema_generator=RenderCVSchemaGenerator
178 )
181def generate_json_schema_file(json_schema_path: pathlib.Path):
182 """Generate the JSON schema of RenderCV and save it to a file.
184 Args:
185 json_schema_path: The path to save the JSON schema.
186 """
187 schema = generate_json_schema()
188 schema_json = json.dumps(schema, indent=2, ensure_ascii=False)
189 json_schema_path.write_text(schema_json, encoding="utf-8")