Source code for nick_derobertis_site.api.routers.skills
import datetime
from typing import Dict, List, Optional, Sequence, Union, cast
from derobertis_cv.models.skill import SkillModel as CVSkillModel
from derobertis_cv.pldata.cover_letters.models import (
ApplicationFocus,
SpecificApplicationFocus,
)
from derobertis_cv.pldata.skills import CV_SKILL_SECTION_ORDER, get_skills
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
router = APIRouter()
[docs]
class APISkillModel(BaseModel):
title: str
level: int
direct_parent_title: Optional[str]
hours: Optional[float] = None
first_used: Optional[datetime.date] = None
experience_length_str: Optional[str] = None
priorities: Dict[Union[SpecificApplicationFocus, ApplicationFocus], int] = Field(
default_factory=lambda: {}
)
# TODO: maybe need a PR into pydantic2ts as it does not support enums as dictionary keys
#
# It currently casts them to strings and does not bring the enums through as ts types.
# It should bring over the enum as a ts type and then have [k in EnumName] as the key
# type, e.g. priorities?: {
# [k in ApplicationFocus]: number;
# }
#
# The current code is a hack to get the enums coming through
unused_for_pydantic2ts: Optional[
Union[SpecificApplicationFocus, ApplicationFocus]
] = None
[docs]
@classmethod
def from_cv_skill_model(cls, model: CVSkillModel):
params = dict(title=model.to_title_case_str(), level=model.level)
# Uncomment to send priorities once they are to be used on the frontend. Disabled to reduce response size
# params['priorities'] = model.priority.levels
if not model.parents:
params["direct_parent_title"] = None
else:
first_parent = cast(CVSkillModel, model.category)
if first_parent == model:
params["direct_parent_title"] = None
elif (
first_parent.to_lower_case_str() == "programming"
and model.to_lower_case_str() == "frameworks"
):
# TODO: come up with a better way of modifying skill parents
#
# Currently added an explicit condition to check for the frameworks skill and remove the parent,
# but should have a more general system for this
params["direct_parent_title"] = None
else:
params["direct_parent_title"] = first_parent.to_title_case_str()
if model.experience is not None:
params["hours"] = model.experience.hours
params["first_used"] = model.experience.begin_date
params["experience_length_str"] = model.experience.experience_length_str
return cls(**params)
[docs]
@classmethod
def list_from_cv_skills(cls, models: Sequence[CVSkillModel]):
unique_mods: List[CVSkillModel] = []
for mod in models:
if mod not in unique_mods:
unique_mods.append(mod)
return [cls.from_cv_skill_model(mod) for mod in unique_mods]
[docs]
class APISkillStatisticsModel(BaseModel):
count: int
parent_count: int
[docs]
def get_recursive_child_skills(mod: CVSkillModel) -> List[CVSkillModel]:
models: List[CVSkillModel] = []
models.extend(mod.children)
for child in mod.children:
models.extend(get_recursive_child_skills(child))
return models
EXCLUDE_SKILLS = ["research", "soft skills"]
ALL_SKILL_CV_MODELS = get_skills(
exclude_skills=EXCLUDE_SKILLS, exclude_skill_children=False
)
PARENT_SKILL_CV_MODELS = []
for model in ALL_SKILL_CV_MODELS:
parent = model.category
if parent.to_lower_case_str() in EXCLUDE_SKILLS:
continue
if parent not in PARENT_SKILL_CV_MODELS:
PARENT_SKILL_CV_MODELS.append(parent)
if parent not in ALL_SKILL_CV_MODELS:
ALL_SKILL_CV_MODELS.append(parent)
ALL_SKILL_CV_MODELS.sort(key=lambda skill: skill.level, reverse=True)
orig_category_names = CV_SKILL_SECTION_ORDER.copy()
PARENT_SKILL_CV_MODELS.sort(
key=lambda skill: CV_SKILL_SECTION_ORDER.index(skill.to_title_case_str())
if skill.to_title_case_str() in CV_SKILL_SECTION_ORDER
else 1000
)
PARENT_TO_CHILD_SKILL_CV_MODELS: List[CVSkillModel] = PARENT_SKILL_CV_MODELS.copy()
for mod in PARENT_SKILL_CV_MODELS:
for child in get_recursive_child_skills(mod):
if child in PARENT_TO_CHILD_SKILL_CV_MODELS:
continue
if child.category not in PARENT_SKILL_CV_MODELS:
continue
PARENT_TO_CHILD_SKILL_CV_MODELS.append(child)
ALL_SKILL_MODELS = APISkillModel.list_from_cv_skills(ALL_SKILL_CV_MODELS)
PARENT_SKILL_MODELS = APISkillModel.list_from_cv_skills(PARENT_SKILL_CV_MODELS)
PARENT_TO_CHILD_SKILL_MODELS = APISkillModel.list_from_cv_skills(
PARENT_TO_CHILD_SKILL_CV_MODELS
)
SKILL_COUNT = len(ALL_SKILL_MODELS)
PARENT_SKILL_COUNT = len(PARENT_SKILL_MODELS)
[docs]
@router.get("/", tags=["skills"], response_model=List[APISkillModel])
async def read_parent_skills():
return PARENT_SKILL_MODELS
[docs]
@router.get("/all", tags=["skills"], response_model=List[APISkillModel])
async def read_all_skills():
return PARENT_TO_CHILD_SKILL_MODELS
[docs]
@router.get("/children", tags=["skills"], response_model=List[APISkillModel])
async def read_child_skills(title: str):
for skill in ALL_SKILL_CV_MODELS:
if skill.to_title_case_str() == title:
children = list(skill.children)
children.sort(key=lambda skill: skill.level, reverse=True)
return APISkillModel.list_from_cv_skills(children)
raise HTTPException(status_code=404, detail=f"Skill with title {title} not found")
[docs]
@router.get("/stats", tags=["skills"], response_model=APISkillStatisticsModel)
async def read_skill_stats():
mod = APISkillStatisticsModel(count=SKILL_COUNT, parent_count=PARENT_SKILL_COUNT)
return mod