Merge pull request #80 from flowrolltide/boilerplate

ENH: improve boilerplate content
This commit is contained in:
Sina Atalay 2024-05-31 19:32:01 +03:00 committed by GitHub
commit e25095ff05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 97 additions and 103 deletions

View File

@ -143,7 +143,7 @@ class RenderCVBaseModel(pydantic.BaseModel):
# Entry models: ======================================================================== # Entry models: ========================================================================
# ====================================================================================== # ======================================================================================
# Create an URL validator to validate social network URLs: # Create a URL validator to validate social network URLs:
url_validator = pydantic.TypeAdapter(pydantic.HttpUrl) # type: ignore url_validator = pydantic.TypeAdapter(pydantic.HttpUrl) # type: ignore
@ -210,7 +210,7 @@ class EntryWithDate(RenderCVBaseModel):
# Check if it is a valid date: # Check if it is a valid date:
get_date_object(date) get_date_object(date)
# check if it is in YYYY format, and if so, convert it to an # Check if it is in YYYY format, and if so, convert it to an
# integer: # integer:
if re.fullmatch(r"\d{4}", date): if re.fullmatch(r"\d{4}", date):
# This is not required for start_date and end_date because they # This is not required for start_date and end_date because they
@ -281,7 +281,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, URLError): except (InvalidURL, URLError):
raise ValueError("This DOI is not valid!") raise ValueError("This DOI is invalid!")
return doi return doi
@ -359,7 +359,7 @@ class EntryBase(EntryWithDate):
) )
def check_and_adjust_dates(self) -> "EntryBase": def check_and_adjust_dates(self) -> "EntryBase":
""" """
Check if the dates are provided correctly and do the necessary adjustments. Check if the dates are provided correctly and make the necessary adjustments.
""" """
date_is_provided = self.date is not None date_is_provided = self.date is not None
start_date_is_provided = self.start_date is not None start_date_is_provided = self.start_date is not None
@ -388,8 +388,8 @@ class EntryBase(EntryWithDate):
if start_date > end_date: if start_date > end_date:
raise ValueError( raise ValueError(
'"start_date" can not be after "end_date"!', '"start_date" can not be after "end_date"!',
"start_date", # this is the location of the error "start_date", # This is the location of the error
str(start_date), # this is value of the error str(start_date), # This is value of the error
) )
return self return self
@ -435,7 +435,7 @@ class EntryBase(EntryWithDate):
date_string = f"{start_date} {locale_catalog['to']} {end_date}" date_string = f"{start_date} {locale_catalog['to']} {end_date}"
else: else:
# Neither date, start_date, nor end_date is provided, so return an empty # Neither date, start_date, nor end_date are provided, so return an empty
# string: # string:
date_string = "" date_string = ""
@ -488,7 +488,7 @@ class EntryBase(EntryWithDate):
date_string = f"{start_date} {locale_catalog['to']} {end_date}" date_string = f"{start_date} {locale_catalog['to']} {end_date}"
else: else:
# Neither date, start_date, nor end_date is provided, so return an empty # Neither date, start_date, nor end_date are provided, so return an empty
# string: # string:
date_string = "" date_string = ""
@ -541,10 +541,10 @@ class EntryBase(EntryWithDate):
end_date = get_date_object(self.end_date) # type: ignore end_date = get_date_object(self.end_date) # type: ignore
start_date = get_date_object(self.start_date) # type: ignore start_date = get_date_object(self.start_date) # type: ignore
# calculate the number of days between start_date and end_date: # Calculate the number of days between start_date and end_date:
timespan_in_days = (end_date - start_date).days # type: ignore timespan_in_days = (end_date - start_date).days # type: ignore
# calculate the number of years between start_date and end_date: # Calculate the number of years between start_date and end_date:
how_many_years = timespan_in_days // 365 how_many_years = timespan_in_days // 365
if how_many_years == 0: if how_many_years == 0:
how_many_years_string = None how_many_years_string = None
@ -553,14 +553,14 @@ class EntryBase(EntryWithDate):
else: else:
how_many_years_string = f"{how_many_years} {locale_catalog['years']}" how_many_years_string = f"{how_many_years} {locale_catalog['years']}"
# calculate the number of months between start_date and end_date: # Calculate the number of months between start_date and end_date:
how_many_months = round((timespan_in_days % 365) / 30) how_many_months = round((timespan_in_days % 365) / 30)
if how_many_months <= 1: if how_many_months <= 1:
how_many_months_string = f"1 {locale_catalog['month']}" how_many_months_string = f"1 {locale_catalog['month']}"
else: else:
how_many_months_string = f"{how_many_months} {locale_catalog['months']}" how_many_months_string = f"{how_many_months} {locale_catalog['months']}"
# combine howManyYearsString and howManyMonthsString: # Combine howManyYearsString and howManyMonthsString:
if how_many_years_string is None: if how_many_years_string is None:
time_span_string = how_many_months_string time_span_string = how_many_months_string
else: else:
@ -576,7 +576,7 @@ class NormalEntryBase(RenderCVBaseModel):
) )
# The following class is to make sure NormalEntryBase keys comes first, # The following class is to make sure 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):
@ -596,7 +596,7 @@ class ExperienceEntryBase(RenderCVBaseModel):
) )
# The following class is to make sure ExperienceEntryBase keys comes first, # The following class is to make sure ExperienceEntryBase 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 ExperienceEntry(EntryBase, ExperienceEntryBase): class ExperienceEntry(EntryBase, ExperienceEntryBase):
@ -622,7 +622,7 @@ class EducationEntryBase(RenderCVBaseModel):
) )
# The following class is to make sure EducationEntryBase keys comes first, # The following class is to make sure EducationEntryBase 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 EducationEntry(EntryBase, EducationEntryBase): class EducationEntry(EntryBase, EducationEntryBase):
@ -765,7 +765,7 @@ def validate_section_input(
entries. Since there are multiple entry types, it is not possible to validate it entries. Since there are multiple entry types, it is not possible to validate it
directly. This function looks at the entry list's first element and determines the directly. This function looks at the entry list's first element and determines the
section's entry type based on the first element. Then, it validates the rest of the section's entry type based on the first element. Then, it validates the rest of the
list based on the determined entry type. If it is a `Section` object, then it list based on the determined entry type. If it is a `Section` object, it then
validates it directly. validates it directly.
Args: Args:
@ -774,7 +774,7 @@ def validate_section_input(
SectionBase | list[Any]: The validated sections input. SectionBase | list[Any]: The validated sections input.
""" """
if isinstance(sections_input, list): if isinstance(sections_input, list):
# find the entry type based on the first identifiable entry: # Find the entry type based on the first identifiable entry:
entry_type = None entry_type = None
section_type = None section_type = None
for entry in sections_input: for entry in sections_input:
@ -786,10 +786,10 @@ def validate_section_input(
if entry_type is None or section_type is None: if entry_type is None or section_type is None:
raise ValueError( raise ValueError(
"RenderCV couldn't match this section with any entry type! Please check" "RenderCV couldn't match this section with any entry types! Please"
" the entries and make sure they are provided correctly.", " check the entries and make sure they are provided correctly.",
"", # 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
) )
test_section = { test_section = {
@ -832,7 +832,7 @@ SocialNetworkName = Literal[
"GitHub", "GitHub",
"GitLab", "GitLab",
"Instagram", "Instagram",
"Orcid", "ORCID",
"Mastodon", "Mastodon",
"Twitter", "Twitter",
"StackOverflow", "StackOverflow",
@ -904,12 +904,13 @@ class SocialNetwork(RenderCVBaseModel):
"GitHub": "https://github.com/", "GitHub": "https://github.com/",
"GitLab": "https://gitlab.com/", "GitLab": "https://gitlab.com/",
"Instagram": "https://instagram.com/", "Instagram": "https://instagram.com/",
"Orcid": "https://orcid.org/", "ORCID": "https://orcid.org/",
"Twitter": "https://twitter.com/", "Twitter": "https://twitter.com/",
"StackOverflow": "https://stackoverflow.com/users/", "StackOverflow": "https://stackoverflow.com/users/",
"ResearchGate": "https://researchgate.net/profile/", "ResearchGate": "https://researchgate.net/profile/",
"YouTube": "https://youtube.com/", "YouTube": "https://youtube.com/",
"Google Scholar": "https://scholar.google.com/citations?user=", "Google Scholar": "https://scholar.google.com/citations?user=",
"Google Scholar": "https://scholar.google.com/citations?user=",
} }
url = url_dictionary[self.network] + self.username url = url_dictionary[self.network] + self.username
@ -1015,7 +1016,7 @@ class CurriculumVitae(RenderCVBaseModel):
"GitLab": "\\faGitlab", "GitLab": "\\faGitlab",
"Instagram": "\\faInstagram", "Instagram": "\\faInstagram",
"Mastodon": "\\faMastodon", "Mastodon": "\\faMastodon",
"Orcid": "\\faOrcid", "ORCID": "\\faOrcid",
"StackOverflow": "\\faStackOverflow", "StackOverflow": "\\faStackOverflow",
"Twitter": "\\faTwitter", "Twitter": "\\faTwitter",
"ResearchGate": "\\faResearchgate", "ResearchGate": "\\faResearchgate",
@ -1072,37 +1073,37 @@ class LocaleCatalog(RenderCVBaseModel):
default="month", default="month",
title='Translation of "Month"', title='Translation of "Month"',
description='Translation of the word "month" in the locale.', description='Translation of the word "month" in the locale.',
validate_default=True, # to initialize the locale catalog with the default values validate_default=True, # To initialize the locale catalog with the default values
) )
months: Optional[str] = pydantic.Field( months: Optional[str] = pydantic.Field(
default="months", default="months",
title='Translation of "Months"', title='Translation of "Months"',
description='Translation of the word "months" in the locale.', description='Translation of the word "months" in the locale.',
validate_default=True, # to initialize the locale catalog with the default values validate_default=True, # To initialize the locale catalog with the default values
) )
year: Optional[str] = pydantic.Field( year: Optional[str] = pydantic.Field(
default="year", default="year",
title='Translation of "Year"', title='Translation of "Year"',
description='Translation of the word "year" in the locale.', description='Translation of the word "year" in the locale.',
validate_default=True, # to initialize the locale catalog with the default values validate_default=True, # To initialize the locale catalog with the default values
) )
years: Optional[str] = pydantic.Field( years: Optional[str] = pydantic.Field(
default="years", default="years",
title='Translation of "Years"', title='Translation of "Years"',
description='Translation of the word "years" in the locale.', description='Translation of the word "years" in the locale.',
validate_default=True, # to initialize the locale catalog with the default values validate_default=True, # To initialize the locale catalog with the default values
) )
present: Optional[str] = pydantic.Field( present: Optional[str] = pydantic.Field(
default="present", default="present",
title='Translation of "Present"', title='Translation of "Present"',
description='Translation of the word "present" in the locale.', description='Translation of the word "present" in the locale.',
validate_default=True, # to initialize the locale catalog with the default values validate_default=True, # To initialize the locale catalog with the default values
) )
to: Optional[str] = pydantic.Field( to: Optional[str] = pydantic.Field(
default="to", default="to",
title='Translation of "To"', title='Translation of "To"',
description='Translation of the word "to" in the locale.', description='Translation of the word "to" in the locale.',
validate_default=True, # to initialize the locale catalog with the default values validate_default=True, # To initialize the locale catalog with the default values
) )
abbreviations_for_months: Optional[ abbreviations_for_months: Optional[
Annotated[list[str], at.Len(min_length=12, max_length=12)] Annotated[list[str], at.Len(min_length=12, max_length=12)]
@ -1144,9 +1145,8 @@ class LocaleCatalog(RenderCVBaseModel):
# Create a custom type called Design: # Create a custom type called 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 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
# about discriminators.
RenderCVDesign = Annotated[ RenderCVDesign = Annotated[
ClassicThemeOptions ClassicThemeOptions
| ModerncvThemeOptions | ModerncvThemeOptions
@ -1194,15 +1194,15 @@ class RenderCVDataModel(RenderCVBaseModel):
theme_data_model_types = get_args(RenderCVDesign)[0] theme_data_model_types = get_args(RenderCVDesign)[0]
if isinstance(design, theme_data_model_types): if isinstance(design, theme_data_model_types):
# then it means RenderCVDataModel is already initialized with a design, so # Then it means RenderCVDataModel is already initialized with a design, so
# return it as is: # return it as is:
return design return design
elif design["theme"] in available_themes: # type: ignore elif design["theme"] in available_themes: # type: ignore
# then it means it's a built-in theme, but it is not initialized (validated) # Then it means it's a built-in theme, but it is not initialized (validated)
# yet. So, validate and return it: # yet. So, validate and return it:
return rendercv_design_validator.validate_python(design) return rendercv_design_validator.validate_python(design)
else: else:
# 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:
@ -1268,11 +1268,11 @@ class RenderCVDataModel(RenderCVBaseModel):
theme_module, f"{theme_name.capitalize()}ThemeOptions" # type: ignore theme_module, f"{theme_name.capitalize()}ThemeOptions" # type: ignore
) )
# initialize and validate the custom theme data model: # Initialize and validate the custom theme data model:
theme_data_model = ThemeDataModel(**design) theme_data_model = ThemeDataModel(**design)
else: else:
# Then it means there is no __init__.py file in the custom theme folder. # Then it means there is no __init__.py file in the custom theme folder.
# So, create a dummy data model and use that instead. # Create a dummy data model and use that instead.
class ThemeOptionsAreNotProvided(RenderCVBaseModel): class ThemeOptionsAreNotProvided(RenderCVBaseModel):
theme: str = theme_name theme: str = theme_name
@ -1309,9 +1309,9 @@ def set_or_update_a_value(
update the value. This is used for recursive calls. When the value is set 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. to a sub model, the original data model is validated. Defaults to None.
""" """
# recursively call this function until the last key is reached: # Recursively call this function until the last key is reached:
# rename `sections` with `sections_input` since the key is `sections` is an alias: # Rename `sections` with `sections_input` since the key is `sections` is an alias:
key = key.replace("sections.", "sections_input.") key = key.replace("sections.", "sections_input.")
keys = key.split(".") keys = key.split(".")
@ -1321,12 +1321,12 @@ def set_or_update_a_value(
model = data_model model = data_model
if len(keys) == 1: if len(keys) == 1:
# set the value: # Set the value:
if value.startswith("{") and value.endswith("}"): if value.startswith("{") and value.endswith("}"):
# allow users to assign dictionaries: # Allow users to assign dictionaries:
value = eval(value) value = eval(value)
elif value.startswith("[") and value.endswith("]"): elif value.startswith("[") and value.endswith("]"):
# allow users to assign lists: # Allow users to assign lists:
value = eval(value) value = eval(value)
if isinstance(model, pydantic.BaseModel): if isinstance(model, pydantic.BaseModel):
@ -1380,14 +1380,14 @@ def read_input_file(
RenderCVDataModel: The data models with $\\LaTeX$ and markdown strings. RenderCVDataModel: The data models with $\\LaTeX$ and markdown strings.
""" """
if isinstance(file_path_or_contents, pathlib.Path): if isinstance(file_path_or_contents, pathlib.Path):
# check if the file exists: # Check if the file exists:
if not file_path_or_contents.exists(): if not file_path_or_contents.exists():
raise FileNotFoundError( raise FileNotFoundError(
f"The input file [magenta]{file_path_or_contents}[/magenta] doesn't" f"The input file [magenta]{file_path_or_contents}[/magenta] doesn't"
" exist!" " exist!"
) )
# check the file extension: # Check the file extension:
accepted_extensions = [".yaml", ".yml", ".json", ".json5"] accepted_extensions = [".yaml", ".yml", ".json", ".json5"]
if file_path_or_contents.suffix not in accepted_extensions: if file_path_or_contents.suffix not in accepted_extensions:
user_friendly_accepted_extensions = [ user_friendly_accepted_extensions = [
@ -1408,7 +1408,7 @@ def read_input_file(
input_as_dictionary: dict[str, Any] = ruamel.yaml.YAML().load(file_content) # type: ignore input_as_dictionary: dict[str, Any] = ruamel.yaml.YAML().load(file_content) # type: ignore
# validate the parsed dictionary by creating an instance of RenderCVDataModel: # Validate the parsed dictionary by creating an instance of RenderCVDataModel:
rendercv_data_model = RenderCVDataModel(**input_as_dictionary) rendercv_data_model = RenderCVDataModel(**input_as_dictionary)
return rendercv_data_model return rendercv_data_model
@ -1424,7 +1424,7 @@ def get_a_sample_data_model(
Returns: Returns:
RenderCVDataModel: A sample data model. RenderCVDataModel: A sample data model.
""" """
# check if the theme is valid: # Check if the theme is valid:
if theme not in available_themes: if theme not in available_themes:
available_themes_string = ", ".join(available_themes) available_themes_string = ", ".join(available_themes)
raise ValueError( raise ValueError(
@ -1437,16 +1437,16 @@ def get_a_sample_data_model(
sections = { sections = {
"welcome_to_rendercv!": [ "welcome_to_rendercv!": [
( (
"[RenderCV](https://github.com/sinaatalay/rendercv) is a LaTeX" "[RenderCV](https://github.com/sinaatalay/rendercv) is a LaTeX-based"
" CV/resume framework. It allows you to create a high-quality CV as" " CV/resume framework. It allows you to create a high-quality CV or"
" a PDF from a YAML file with **full Markdown syntax support** and" " resume as a PDF file from a YAML file, with **full Markdown syntax"
" **complete control over the LaTeX code**." " support** and **complete control over the LaTeX code**."
), ),
( (
"A substantial" "The boilerplate content is taken from"
" part of the content is taken from" " [here](https://github.com/dnl-blkv/mcdowell-cv), where a"
" [here](https://www.careercup.com/resume), where a *clean and tidy CV*" " *clean and tidy CV* pattern is proposed by"
" pattern is proposed by **Gayle L. McDowell**." " **[Gayle Laakmann McDowell](https://www.gayle.com/)**."
), ),
], ],
"quick_guide": [ "quick_guide": [
@ -1458,7 +1458,7 @@ def get_a_sample_data_model(
), ),
BulletEntry( BulletEntry(
bullet=( bullet=(
"There are seven different entry types: *BulletEntry*, *TextEntry*," "There are 7 unique entry types: *BulletEntry*, *TextEntry*,"
" *EducationEntry*, *ExperienceEntry*, *NormalEntry*," " *EducationEntry*, *ExperienceEntry*, *NormalEntry*,"
" *PublicationEntry*, and *OneLineEntry*." " *PublicationEntry*, and *OneLineEntry*."
), ),
@ -1472,7 +1472,7 @@ def get_a_sample_data_model(
BulletEntry( BulletEntry(
bullet=( bullet=(
"[Here](https://docs.rendercv.com/user_guide/), you can find a" "[Here](https://docs.rendercv.com/user_guide/), you can find a"
" comprehensive user guide." " comprehensive user guide for RenderCV."
) )
), ),
], ],
@ -1486,51 +1486,51 @@ def get_a_sample_data_model(
highlights=[ highlights=[
"GPA: 3.9/4.0 ([Transcript](https://example.com))", "GPA: 3.9/4.0 ([Transcript](https://example.com))",
( (
"**Coursework:** Software Foundations, Computer" "**Coursework:** Computer Architecture, Artificial"
" Architecture, Algorithms, Artificial Intelligence, Comparison" " Intelligence, Comparison of Learning Algorithms,"
" of Learning Algorithms, Computational Theory." " Computational Theory"
), ),
], ],
), ),
], ],
"experience": [ "experience": [
ExperienceEntry( ExperienceEntry(
company="Apple Computer", company="Apple",
position="Software Engineer, Intern", position="Software Engineer, Intern",
start_date="2004-06", start_date="2004-06",
end_date="2004-08", end_date="2004-08",
location="CA, USA", location="Cupertino, CA",
highlights=[ highlights=[
( (
"Reduced time to render the user's buddy list by 75% by" "Reduced time to render the user's buddy list by 75% by"
" implementing a prediction algorithm." " implementing a prediction algorithm"
), ),
( (
"Implemented iChat integration with OS X Spotlight Search by" "Implemented iChat integration with OS X Spotlight Search by"
" creating a tool that extracts metadata from saved chat" " creating a tool to extract metadata from saved chat"
" transcripts and provides metadata to a system-wide search" " transcripts and provide metadata to a system-wide search"
" database." " database"
), ),
( (
"Redesigned chat file format and implemented backward" "Redesigned chat file format and implemented backward"
" compatibility for search." " compatibility for search"
), ),
], ],
), ),
ExperienceEntry( ExperienceEntry(
company="Microsoft Corporation", company="Microsoft",
position="Lead Student Ambassador", position="Lead Student Ambassador",
start_date="2003-09", start_date="2003-09",
end_date="2005-04", end_date="2005-04",
location="WA, USA", location="Redmond, WA",
highlights=[ highlights=[
( (
"Promoted to Lead Student Ambassador in the Fall of 2004," "Promoted to Lead Student Ambassador in the Fall of 2004,"
" supervised 10 - 15 Student Ambassadors." " supervised 10-15 Student Ambassadors"
), ),
( (
"Created and taught a computer science course, CSE 099:" "Created and taught a computer science course, CSE 099:"
" Software Design and Development." " Software Design and Development"
), ),
], ],
), ),
@ -1539,40 +1539,34 @@ def get_a_sample_data_model(
position="Head Teaching Assistant", position="Head Teaching Assistant",
start_date="2001-10", start_date="2001-10",
end_date="2005-05", end_date="2005-05",
location="PA, USA", location="Philadelphia, PA",
highlights=[ highlights=[
( (
"Implemented a user interface for the VS open file switcher" "Implemented a user interface for the VS open file switcher"
" (ctrl-tab) and extended it to tool windows." " (ctrl-tab) and extended it to tool windows"
), ),
( (
"Created a service to provide gradient across VS and VS" "Created a service to provide gradient across VS and VS"
" add-ins. Optimized service via caching." " add-ins, optimized its performance via caching"
), ),
"Programmer Productivity Research Center (Summers 2001, 2002)", "Programmer Productivity Research Center (Summers 2001, 2002)",
( (
"Built app to compute the similarity of all methods in a code" "Built an app to compute the similarity of all methods in a"
" base, reduced time from $\\mathcal{O}(n^2)$ to" " code base, reducing the time from $\\mathcal{O}(n^2)$ to"
" $\\mathcal{O}(n \\log n)$. " " $\\mathcal{O}(n \\log n)$"
), ),
( (
"Created a test case generation tool that creates random XML" "Created a test case generation tool that creates random XML"
" docs from XML Schema." " docs from XML Schema"
), ),
], ],
), ),
ExperienceEntry( ExperienceEntry(
company="Microsoft Corporation", company="Microsoft",
position="Software Design Engineer, Intern", position="Software Design Engineer, Intern",
start_date="2003-06", start_date="2003-06",
end_date="2003-08", end_date="2003-08",
location="WA, USA", location="Redmond, WA",
highlights=[
(
"Promoted to Lead Student Ambassador in the Fall of 2004,"
" supervised 10 - 15 Student Ambassadors."
),
],
), ),
], ],
"publications": [ "publications": [
@ -1595,65 +1589,65 @@ def get_a_sample_data_model(
"projects": [ "projects": [
NormalEntry( NormalEntry(
name="Multi-User Drawing Tool", name="Multi-User Drawing Tool",
date="2004", date="2004", # I would omit start/end dates and replace with a project link as the date is largely irrelevant
# project_link = "github.com/username/repo"
highlights=[ highlights=[
( (
"Developed an electronic classroom where multiple users can" "Developed an electronic classroom where multiple users can"
' view and simultaneously draw on a "chalkboard" with each' ' view and simultaneously draw on a "chalkboard" with each'
" person's edits synchronized." " person's edits synchronized"
), ),
"Used C++ and MFC.", "Tools Used: C++, MFC",
], ],
), ),
NormalEntry( NormalEntry(
name="Synchronized Calendar", name="Synchronized Calendar",
start_date="2003", start_date="2003",
end_date="2004", end_date="2004",
# project_link = "github.com/username/repo",
highlights=[ highlights=[
( (
"Developed a desktop calendar with globally shared and" "Developed a desktop calendar with globally shared and"
" synchronized calendars, allowing users to schedule meetings" " synchronized calendars, allowing users to schedule meetings"
" with other users." " with other users"
), ),
"Used C#.NET, SQL, and XML.", "Tools Used: C#, .NET, SQL, XML",
], ],
), ),
NormalEntry( NormalEntry(
name="Operating System", name="Operating System",
date="2002", date="2002", # would replace project dates with a project link as the date is largely irrelevant
# project_link = "github.com/username/repo",
highlights=[ highlights=[
( (
"Developed a UNIX-style OS with a scheduler, file system, text" "Developed a UNIX-style OS with a scheduler, file system, text"
" editor, and calculator." " editor, and calculator"
), ),
"Used C.", "Tools Used: C",
], ],
), ),
], ],
"additional_experience_and_awards": [ "additional_experience_and_awards": [
OneLineEntry( OneLineEntry(
label="Instructor (2003 - 2005)", label="Instructor (2003-2005)",
details="Taught two full-credit Computer Science courses.", details="Taught 2 full-credit computer science courses",
), ),
OneLineEntry( OneLineEntry(
label="Third Prize, Senior Design Projects", label="Third Prize, Senior Design Project",
details=( details=(
"Awarded 3rd prize for a synchronized calendar project out of 100" "Awarded 3rd prize for a synchronized calendar project out of 100"
" projects." " entries"
), ),
), ),
], ],
"technologies": [ "technologies": [
OneLineEntry( OneLineEntry(
label="Languages", label="Languages",
details="C++, C, Java, Objective-C, C#.NET, SQL, JavaScript", details="C++, C, Java, Objective-C, C#, SQL, JavaScript",
), ),
OneLineEntry( OneLineEntry(
label="Software", label="Software",
details=( details=".NET, Microsoft SQL Server, XCode, Interface Builder",
"Visual Studio, Microsoft SQL Server, Eclipse, XCode, Interface"
" Builder"
),
), ),
], ],
} }

View File

@ -427,7 +427,7 @@ def test_invalid_social_networks(network, username):
("LinkedIn", "myusername", "https://linkedin.com/in/myusername"), ("LinkedIn", "myusername", "https://linkedin.com/in/myusername"),
("GitHub", "myusername", "https://github.com/myusername"), ("GitHub", "myusername", "https://github.com/myusername"),
("Instagram", "myusername", "https://instagram.com/myusername"), ("Instagram", "myusername", "https://instagram.com/myusername"),
("Orcid", "myusername", "https://orcid.org/myusername"), ("ORCID", "myusername", "https://orcid.org/myusername"),
("Twitter", "myusername", "https://twitter.com/myusername"), ("Twitter", "myusername", "https://twitter.com/myusername"),
("Mastodon", "@myusername@test.org", "https://test.org/@myusername"), ("Mastodon", "@myusername@test.org", "https://test.org/@myusername"),
( (