From 3070092a2be73387f039289dbf474a12fd6ae9cf Mon Sep 17 00:00:00 2001 From: Sina Atalay Date: Fri, 3 Nov 2023 20:52:14 +0100 Subject: [PATCH] change data model design for a better JSON schema --- rendercv/data_model.py | 115 ++++++++++++++----- schema.json | 255 +++++++++++++++++++++++++++++++++++------ 2 files changed, 309 insertions(+), 61 deletions(-) diff --git a/rendercv/data_model.py b/rendercv/data_model.py index 9b321d0..0e5cb1c 100644 --- a/rendercv/data_model.py +++ b/rendercv/data_model.py @@ -323,7 +323,7 @@ def generate_json_schema(output_directory: str) -> str: # ====================================================================================== -# CUSTOM DATA TYPES ==================================================================== +# DESIGN MODELS ======================================================================== # ====================================================================================== # To understand how to create custom data types, see: @@ -337,14 +337,6 @@ LaTeXDimension = Annotated[ LaTeXString = Annotated[str, AfterValidator(escape_latex_characters)] SpellCheckedString = Annotated[LaTeXString, AfterValidator(check_spelling)] -# ====================================================================================== -# ====================================================================================== -# ====================================================================================== - -# ====================================================================================== -# DESIGN MODELS ======================================================================== -# ====================================================================================== - class ClassicThemePageMargins(BaseModel): """This class stores the margins of pages for the classic theme.""" @@ -1034,24 +1026,20 @@ class Connection(BaseModel): return url -class Section(BaseModel): - """This class stores a section information.""" +class SectionBase(BaseModel): + """This class stores a section information. + + It is the parent class of all the section classes like + `#!python SectionWithEducationEntries`, `#!python SectionWithExperienceEntries`, + `#!python SectionWithNormalEntries`, `#!python SectionWithOneLineEntries`, and + `#!python SectionWithPublicationEntries`. + """ title: LaTeXString = Field( title="Section Title", description="The title of the section.", examples=["My Custom Section"], ) - entry_type: Literal[ - "OneLineEntry", - "NormalEntry", - "ExperienceEntry", - "EducationEntry", - "PublicationEntry", - ] = Field( - title="Entry Type", - description="The type of the entries in the section.", - ) link_text: Optional[LaTeXString] = Field( default=None, title="Link Text", @@ -1062,12 +1050,6 @@ class Section(BaseModel): ), examples=["view on GitHub", "view on LinkedIn"], ) - entries: list[ - OneLineEntry | NormalEntry | ExperienceEntry | EducationEntry | PublicationEntry - ] = Field( - title="Entries", - description="The entries of the section. The format depends on the entry type.", - ) @field_validator("title") @classmethod @@ -1076,6 +1058,73 @@ class Section(BaseModel): return title.title() +entry_type_field = Field( + title="Entry Type", + description="The type of the entries in the section.", +) +entries_field = Field( + title="Entries", + description="The entries of the section. The format depends on the entry type.", +) + + +class SectionWithEducationEntries(SectionBase): + """This class stores a section with + [EducationEntry](../user_guide.md#educationentry)s. + """ + + entry_type: Literal["EducationEntry"] = entry_type_field + entries: list[EducationEntry] = entries_field + + +class SectionWithExperienceEntries(SectionBase): + """This class stores a section with + [ExperienceEntry](../user_guide.md#experienceentry)s. + """ + + entry_type: Literal["ExperienceEntry"] = entry_type_field + entries: list[ExperienceEntry] = entries_field + + +class SectionWithNormalEntries(SectionBase): + """This class stores a section with + [NormalEntry](../user_guide.md#normalentry)s. + """ + + entry_type: Literal["NormalEntry"] = entry_type_field + entries: list[NormalEntry] = entries_field + + +class SectionWithOneLineEntries(SectionBase): + """This class stores a section with + [OneLineEntry](../user_guide.md#onelineentry)s. + """ + + entry_type: Literal["OneLineEntry"] = entry_type_field + entries: list[OneLineEntry] = entries_field + + +class SectionWithPublicationEntries(SectionBase): + """This class stores a section with + [PublicationEntry](../user_guide.md#publicationentry)s. + """ + + entry_type: Literal["PublicationEntry"] = entry_type_field + entries: list[PublicationEntry] = entries_field + + +Section = Annotated[ + SectionWithEducationEntries + | SectionWithExperienceEntries + | SectionWithNormalEntries + | SectionWithOneLineEntries + | SectionWithPublicationEntries, + Field( + discriminator="entry_type", + ), +] + + class CurriculumVitae(BaseModel): """This class bindes all the information of a CV together.""" @@ -1284,7 +1333,7 @@ class CurriculumVitae(BaseModel): @computed_field @cached_property - def sections(self) -> list[Section]: + def sections(self) -> list[SectionBase]: sections = [] # Pre-defined sections (i.e. sections that are not custom)): @@ -1368,7 +1417,15 @@ class CurriculumVitae(BaseModel): " order 😷" ) - section = Section( + object_map = { + "EducationEntry": SectionWithEducationEntries, + "ExperienceEntry": SectionWithExperienceEntries, + "NormalEntry": SectionWithNormalEntries, + "OneLineEntry": SectionWithOneLineEntries, + "PublicationEntry": SectionWithPublicationEntries, + } + + section = object_map[entry_type]( title=section_name, entry_type=entry_type, # type: ignore entries=entries, diff --git a/schema.json b/schema.json index af4a377..e20d492 100644 --- a/schema.json +++ b/schema.json @@ -534,7 +534,33 @@ "allOf": [ { "items": { - "$ref": "#/$defs/Section" + "discriminator": { + "mapping": { + "EducationEntry": "#/$defs/SectionWithEducationEntries", + "ExperienceEntry": "#/$defs/SectionWithExperienceEntries", + "NormalEntry": "#/$defs/SectionWithNormalEntries", + "OneLineEntry": "#/$defs/SectionWithOneLineEntries", + "PublicationEntry": "#/$defs/SectionWithPublicationEntries" + }, + "propertyName": "entry_type" + }, + "oneOf": [ + { + "$ref": "#/$defs/SectionWithEducationEntries" + }, + { + "$ref": "#/$defs/SectionWithExperienceEntries" + }, + { + "$ref": "#/$defs/SectionWithNormalEntries" + }, + { + "$ref": "#/$defs/SectionWithOneLineEntries" + }, + { + "$ref": "#/$defs/SectionWithPublicationEntries" + } + ] }, "type": "array" } @@ -1263,7 +1289,7 @@ "type": "object", "additionalProperties": false }, - "Section": { + "SectionWithEducationEntries": { "properties": { "title": { "description": "The title of the section.", @@ -1273,18 +1299,6 @@ "title": "Section Title", "type": "string" }, - "entry_type": { - "description": "The type of the entries in the section.", - "enum": [ - "OneLineEntry", - "NormalEntry", - "ExperienceEntry", - "EducationEntry", - "PublicationEntry" - ], - "title": "Entry Type", - "type": "string" - }, "link_text": { "default": null, "description": "If the section has a link, then what should be the text of the link? If this field is not provided, then the link text will be generated automatically based on the URL.", @@ -1299,26 +1313,15 @@ } ] }, + "entry_type": { + "const": "EducationEntry", + "description": "The type of the entries in the section.", + "title": "Entry Type" + }, "entries": { "description": "The entries of the section. The format depends on the entry type.", "items": { - "oneOf": [ - { - "$ref": "#/$defs/OneLineEntry" - }, - { - "$ref": "#/$defs/NormalEntry" - }, - { - "$ref": "#/$defs/ExperienceEntry" - }, - { - "$ref": "#/$defs/EducationEntry" - }, - { - "$ref": "#/$defs/PublicationEntry" - } - ] + "$ref": "#/$defs/EducationEntry" }, "title": "Entries", "type": "array" @@ -1329,7 +1332,195 @@ "entry_type", "entries" ], - "title": "Section", + "title": "SectionWithEducationEntries", + "type": "object", + "additionalProperties": false + }, + "SectionWithExperienceEntries": { + "properties": { + "title": { + "description": "The title of the section.", + "examples": [ + "My Custom Section" + ], + "title": "Section Title", + "type": "string" + }, + "link_text": { + "default": null, + "description": "If the section has a link, then what should be the text of the link? If this field is not provided, then the link text will be generated automatically based on the URL.", + "examples": [ + "view on GitHub", + "view on LinkedIn" + ], + "title": "Link Text", + "allOf": [ + { + "type": "string" + } + ] + }, + "entry_type": { + "const": "ExperienceEntry", + "description": "The type of the entries in the section.", + "title": "Entry Type" + }, + "entries": { + "description": "The entries of the section. The format depends on the entry type.", + "items": { + "$ref": "#/$defs/ExperienceEntry" + }, + "title": "Entries", + "type": "array" + } + }, + "required": [ + "title", + "entry_type", + "entries" + ], + "title": "SectionWithExperienceEntries", + "type": "object", + "additionalProperties": false + }, + "SectionWithNormalEntries": { + "properties": { + "title": { + "description": "The title of the section.", + "examples": [ + "My Custom Section" + ], + "title": "Section Title", + "type": "string" + }, + "link_text": { + "default": null, + "description": "If the section has a link, then what should be the text of the link? If this field is not provided, then the link text will be generated automatically based on the URL.", + "examples": [ + "view on GitHub", + "view on LinkedIn" + ], + "title": "Link Text", + "allOf": [ + { + "type": "string" + } + ] + }, + "entry_type": { + "const": "NormalEntry", + "description": "The type of the entries in the section.", + "title": "Entry Type" + }, + "entries": { + "description": "The entries of the section. The format depends on the entry type.", + "items": { + "$ref": "#/$defs/NormalEntry" + }, + "title": "Entries", + "type": "array" + } + }, + "required": [ + "title", + "entry_type", + "entries" + ], + "title": "SectionWithNormalEntries", + "type": "object", + "additionalProperties": false + }, + "SectionWithOneLineEntries": { + "properties": { + "title": { + "description": "The title of the section.", + "examples": [ + "My Custom Section" + ], + "title": "Section Title", + "type": "string" + }, + "link_text": { + "default": null, + "description": "If the section has a link, then what should be the text of the link? If this field is not provided, then the link text will be generated automatically based on the URL.", + "examples": [ + "view on GitHub", + "view on LinkedIn" + ], + "title": "Link Text", + "allOf": [ + { + "type": "string" + } + ] + }, + "entry_type": { + "const": "OneLineEntry", + "description": "The type of the entries in the section.", + "title": "Entry Type" + }, + "entries": { + "description": "The entries of the section. The format depends on the entry type.", + "items": { + "$ref": "#/$defs/OneLineEntry" + }, + "title": "Entries", + "type": "array" + } + }, + "required": [ + "title", + "entry_type", + "entries" + ], + "title": "SectionWithOneLineEntries", + "type": "object", + "additionalProperties": false + }, + "SectionWithPublicationEntries": { + "properties": { + "title": { + "description": "The title of the section.", + "examples": [ + "My Custom Section" + ], + "title": "Section Title", + "type": "string" + }, + "link_text": { + "default": null, + "description": "If the section has a link, then what should be the text of the link? If this field is not provided, then the link text will be generated automatically based on the URL.", + "examples": [ + "view on GitHub", + "view on LinkedIn" + ], + "title": "Link Text", + "allOf": [ + { + "type": "string" + } + ] + }, + "entry_type": { + "const": "PublicationEntry", + "description": "The type of the entries in the section.", + "title": "Entry Type" + }, + "entries": { + "description": "The entries of the section. The format depends on the entry type.", + "items": { + "$ref": "#/$defs/PublicationEntry" + }, + "title": "Entries", + "type": "array" + } + }, + "required": [ + "title", + "entry_type", + "entries" + ], + "title": "SectionWithPublicationEntries", "type": "object", "additionalProperties": false },