From 0b4139a52795ffae32021175348edcf9aa9cc718 Mon Sep 17 00:00:00 2001 From: Sam Der Date: Mon, 11 Dec 2023 23:55:48 -0800 Subject: [PATCH 1/4] feat: application pydantic models --- apps/api/src/models/ApplicationData.py | 45 ++++++++++++++++++++++++++ apps/api/src/models/utils.py | 21 ++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 apps/api/src/models/ApplicationData.py create mode 100644 apps/api/src/models/utils.py diff --git a/apps/api/src/models/ApplicationData.py b/apps/api/src/models/ApplicationData.py new file mode 100644 index 00000000..478fd2a1 --- /dev/null +++ b/apps/api/src/models/ApplicationData.py @@ -0,0 +1,45 @@ +from datetime import datetime +from enum import Enum +from typing import Optional + +from pydantic import BaseModel, EmailStr, Field, HttpUrl + +from .utils import form_body + + +class Decision(str, Enum): + ACCEPTED = "ACCEPTED" + WAITLISTED = "WAITLISTED" + REJECTED = "REJECTED" + + +Review = tuple[datetime, str, Decision] + + +@form_body +class RawApplicationData(BaseModel): + first_name: str + last_name: str + email: EmailStr + gender: str + pronouns: list[str] + ethnicity: str + is_18_older: bool + university: str + education_level: str + major: str + is_first_hackathon: bool + portfolio_link: Optional[HttpUrl] + linkedin_link: Optional[HttpUrl] + collaboration_question: Optional[str] = Field() + any_job_question: str = Field() + + class Config: + anystr_strip_whitespace = True + max_anystr_length = 254 + + +class ProcessedApplicationData(RawApplicationData): + resume_url: Optional[HttpUrl] + submission_time: datetime + reviews: list[Review] = [] diff --git a/apps/api/src/models/utils.py b/apps/api/src/models/utils.py new file mode 100644 index 00000000..e031316b --- /dev/null +++ b/apps/api/src/models/utils.py @@ -0,0 +1,21 @@ +from typing import Type + +from fastapi import Form +from pydantic import BaseModel + + +def form_body(cls: Type[BaseModel]) -> Type[BaseModel]: + """Decorator allows BaseModel to be used in Form Data""" + params = [] + for arg in zip(cls.__signature__.parameters.values(), cls.model_fields.values()): + if arg[1].is_required(): + params += [arg[0].replace(default=Form())] + else: + params += [arg[0].replace(default=Form(None))] + cls.__signature__ = cls.__signature__.replace(parameters=params) + + return cls + + +# https://stackoverflow.com/questions/60127234/how-to-use-a-pydantic-model-with-form-data-in-fastapi +# Commenter: stefanlsd From dfc08825102860074d4a81b22f839adf383e623f Mon Sep 17 00:00:00 2001 From: Sam Der Date: Wed, 13 Dec 2023 01:52:27 -0800 Subject: [PATCH 2/4] update: remove gender from application data model --- apps/api/src/models/ApplicationData.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/api/src/models/ApplicationData.py b/apps/api/src/models/ApplicationData.py index 478fd2a1..20943e28 100644 --- a/apps/api/src/models/ApplicationData.py +++ b/apps/api/src/models/ApplicationData.py @@ -21,7 +21,6 @@ class RawApplicationData(BaseModel): first_name: str last_name: str email: EmailStr - gender: str pronouns: list[str] ethnicity: str is_18_older: bool From 426d26bc4d5c863d1761d2fac3421d9b4658109d Mon Sep 17 00:00:00 2001 From: Sam Der Date: Thu, 14 Dec 2023 22:08:05 -0800 Subject: [PATCH 3/4] fix: class Config deprecation --- apps/api/src/models/ApplicationData.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/api/src/models/ApplicationData.py b/apps/api/src/models/ApplicationData.py index 20943e28..7bd6b19a 100644 --- a/apps/api/src/models/ApplicationData.py +++ b/apps/api/src/models/ApplicationData.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Optional -from pydantic import BaseModel, EmailStr, Field, HttpUrl +from pydantic import BaseModel, ConfigDict, EmailStr, Field, HttpUrl from .utils import form_body @@ -18,6 +18,8 @@ class Decision(str, Enum): @form_body class RawApplicationData(BaseModel): + model_config = ConfigDict(str_strip_whitespace=True, str_max_length=254) + first_name: str last_name: str email: EmailStr @@ -33,10 +35,6 @@ class RawApplicationData(BaseModel): collaboration_question: Optional[str] = Field() any_job_question: str = Field() - class Config: - anystr_strip_whitespace = True - max_anystr_length = 254 - class ProcessedApplicationData(RawApplicationData): resume_url: Optional[HttpUrl] From 07890ac41a0275ad449a877fa5ba549b52c742af Mon Sep 17 00:00:00 2001 From: Sam Der Date: Thu, 14 Dec 2023 23:05:43 -0800 Subject: [PATCH 4/4] fix: Optional->Union, reinstated max lengths --- apps/api/src/models/ApplicationData.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/api/src/models/ApplicationData.py b/apps/api/src/models/ApplicationData.py index 7bd6b19a..8f3375f9 100644 --- a/apps/api/src/models/ApplicationData.py +++ b/apps/api/src/models/ApplicationData.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import Enum -from typing import Optional +from typing import Union from pydantic import BaseModel, ConfigDict, EmailStr, Field, HttpUrl @@ -23,20 +23,20 @@ class RawApplicationData(BaseModel): first_name: str last_name: str email: EmailStr - pronouns: list[str] + pronouns: str ethnicity: str is_18_older: bool university: str education_level: str major: str is_first_hackathon: bool - portfolio_link: Optional[HttpUrl] - linkedin_link: Optional[HttpUrl] - collaboration_question: Optional[str] = Field() - any_job_question: str = Field() + portfolio_link: Union[HttpUrl, None] + linkedin_link: Union[HttpUrl, None] + collaboration_question: Union[str, None] = Field(None, max_length=1024) + any_job_question: str = Field(max_length=1024) class ProcessedApplicationData(RawApplicationData): - resume_url: Optional[HttpUrl] + resume_url: Union[HttpUrl, None] submission_time: datetime reviews: list[Review] = []