Jelajahi Sumber

updating to pydantic v2

tripeur 1 tahun lalu
induk
melakukan
06f3ce482f

+ 3 - 3
app/api/endpoints/project.py

@@ -45,7 +45,7 @@ async def create_project(
     session: Session = Depends(deps.get_session),
 ):
     """Create a new project"""
-    project = Project(**new_project.dict())
+    project = Project(**new_project.model_dump())
     session.add(project)
     try:
         session.commit()
@@ -54,7 +54,7 @@ async def create_project(
 
     session.refresh(project)
 
-    return ProjectResponse.from_orm(project)
+    return project
 
 
 @router.get("/public-project/{project_id}", response_model=ProjectResponse)
@@ -79,7 +79,7 @@ async def get_project(
     project = session.get(Project, project_id)
     if project is None:
         raise HTTPException(status_code=404, detail="Project not found")
-    return ProjectResponse.from_orm(project)
+    return project
 
 
 @router.post("/project/{project_id}", response_model=ProjectListResponse)

+ 2 - 2
app/api/endpoints/slots.py

@@ -50,7 +50,7 @@ async def create_slot(
     if p is None:
         raise HTTPException(status_code=404, detail="Project not found")
 
-    input_dict = new_slot.dict()
+    input_dict = new_slot.model_dump()
     # Extract volunteer list from input dict
     volunteers: list[UUID] = []
     if input_dict["volunteers"] is not None:
@@ -90,7 +90,7 @@ async def update_slot(
             session, [new_slot.template_id], project_id, SlotTemplate, "Invalid template id"
         )
 
-    input_dict = new_slot.dict(exclude_unset=True)
+    input_dict = new_slot.model_dump(exclude_unset=True)
     if "volunteers" in input_dict:
         volunteers: list[UUID] = input_dict["volunteers"]
         await verify_id_list(session, volunteers, project_id, Volunteer, "Invalid volunteer list")

+ 2 - 2
app/api/endpoints/sms.py

@@ -44,7 +44,7 @@ async def create_sms(
     p = session.get(Project, project_id)
     if p is None:
         raise HTTPException(status_code=404, detail="Project not found")
-    sms = Sms(project_id=project_id, **new_sms.dict())
+    sms = Sms(project_id=project_id, **new_sms.model_dump())
     session.add(sms)
     session.commit()
     return sms
@@ -62,7 +62,7 @@ async def update_sms(
     sms = session.get(Sms, sms_id)
     if (sms is None) or (sms.project_id != str(project_id)):
         raise HTTPException(status_code=404, detail="Sms not found")
-    update_object_from_payload(sms, new_sms.dict(exclude_unset=True))
+    update_object_from_payload(sms, new_sms.model_dump(exclude_unset=True))
     session.commit()
     session.refresh(sms)
     return sms

+ 1 - 1
app/api/endpoints/templates.py

@@ -45,7 +45,7 @@ async def create_template(
         raise HTTPException(status_code=404, detail="Project not found")
 
     template = SlotTemplate(project_id=p.id, title=payload.title)
-    update_object_from_payload(template, payload.dict(exclude_unset=True))
+    update_object_from_payload(template, payload.model_dump(exclude_unset=True))
     session.add(template)
     session.commit()
     return template

+ 2 - 2
app/api/endpoints/volunteers.py

@@ -40,7 +40,7 @@ async def create_volunteer(
     p = session.get(Project, project_id)
     if p is None:
         raise HTTPException(status_code=404, detail="Project not found")
-    input_dict = new_volunteer.dict()
+    input_dict = new_volunteer.model_dump()
 
     # Extract slots list from input dict
     slots: list[UUID] = []
@@ -77,7 +77,7 @@ async def update_volunteer(
     if (volunteer is None) or (volunteer.project_id != str(project_id)):
         raise HTTPException(status_code=404, detail="Volunteer not found")
 
-    input_dict = new_volunteer.dict(exclude_unset=True)
+    input_dict = new_volunteer.model_dump(exclude_unset=True)
     # Extract slots list from input dict
     if "slots" in input_dict:
         slots: list[UUID] = input_dict["slots"]

+ 32 - 33
app/core/config.py

@@ -11,11 +11,6 @@ Pydantic priority ordering:
 For project name, version, description we use pyproject.toml
 For the rest, we use file `.env` (gitignored), see `.env.example`
 
-`DEFAULT_SQLALCHEMY_DATABASE_URI` and `TEST_SQLALCHEMY_DATABASE_URI`:
-Both are ment to be validated at the runtime, do not change unless you know
-what are you doing. All the two validators do is to build full URI (TCP protocol)
-to databases to avoid typo bugs.
-
 See https://pydantic-docs.helpmanual.io/usage/settings/
 
 Note, complex types like lists are read as json-encoded strings.
@@ -25,13 +20,21 @@ import tomllib
 from pathlib import Path
 from typing import Literal
 
-from pydantic import AnyHttpUrl, BaseSettings, EmailStr, PostgresDsn, validator
+from pydantic import AnyHttpUrl, EmailStr, PostgresDsn, field_validator, validator
+from pydantic_settings import BaseSettings, SettingsConfigDict
 
 PROJECT_DIR = Path(__file__).parent.parent.parent
 with open(f"{PROJECT_DIR}/pyproject.toml", "rb") as f:
     PYPROJECT_CONTENT = tomllib.load(f)["tool"]["poetry"]
 
 
+def build_postgreuri(
+    scheme: str, username: str, password: str, host: str, port: int = 0, path: str = ""
+):
+    fullhost = host + (f":{port}" if port > 0 else "")
+    return f"{scheme}://{username}:{password}@{fullhost}{path}"
+
+
 class Settings(BaseSettings):
     # CORE SETTINGS
     SECRET_KEY: str
@@ -53,7 +56,17 @@ class Settings(BaseSettings):
     DEFAULT_DATABASE_PASSWORD: str
     DEFAULT_DATABASE_PORT: str
     DEFAULT_DATABASE_DB: str
-    DEFAULT_SQLALCHEMY_DATABASE_URI: str = ""
+
+    @property
+    def DEFAULT_SQLALCHEMY_DATABASE_URI(self) -> str:
+        return build_postgreuri(
+            scheme="postgresql",
+            username=self.DEFAULT_DATABASE_USER,
+            password=self.DEFAULT_DATABASE_PASSWORD,
+            host=self.DEFAULT_DATABASE_HOSTNAME,
+            port=int(self.DEFAULT_DATABASE_PORT),
+            path=f"/{self.DEFAULT_DATABASE_DB}",
+        )
 
     # POSTGRESQL TEST DATABASE
     TEST_DATABASE_HOSTNAME: str = "postgres"
@@ -61,7 +74,17 @@ class Settings(BaseSettings):
     TEST_DATABASE_PASSWORD: str = "postgres"
     TEST_DATABASE_PORT: str = "5432"
     TEST_DATABASE_DB: str = "postgres"
-    TEST_SQLALCHEMY_DATABASE_URI: str = ""
+
+    @property
+    def TEST_SQLALCHEMY_DATABASE_URI(self) -> str:
+        return build_postgreuri(
+            scheme="postgresql",
+            username=self.TEST_DATABASE_USER,
+            password=self.TEST_DATABASE_PASSWORD,
+            host=self.TEST_DATABASE_HOSTNAME,
+            port=int(self.TEST_DATABASE_PORT),
+            path=f"/{self.TEST_DATABASE_DB}",
+        )
 
     # SMS batch
     BATCH_SMS_PHONE_NUMBER: str = ""
@@ -70,31 +93,7 @@ class Settings(BaseSettings):
     FIRST_SUPERUSER_EMAIL: EmailStr
     FIRST_SUPERUSER_PASSWORD: str
 
-    @validator("DEFAULT_SQLALCHEMY_DATABASE_URI")
-    def _assemble_default_db_connection(cls, v: str, values: dict[str, str]) -> str:
-        return PostgresDsn.build(
-            scheme="postgresql",
-            user=values["DEFAULT_DATABASE_USER"],
-            password=values["DEFAULT_DATABASE_PASSWORD"],
-            host=values["DEFAULT_DATABASE_HOSTNAME"],
-            port=values["DEFAULT_DATABASE_PORT"],
-            path=f"/{values['DEFAULT_DATABASE_DB']}",
-        )
-
-    @validator("TEST_SQLALCHEMY_DATABASE_URI")
-    def _assemble_test_db_connection(cls, v: str, values: dict[str, str]) -> str:
-        return PostgresDsn.build(
-            scheme="postgresql",
-            user=values["TEST_DATABASE_USER"],
-            password=values["TEST_DATABASE_PASSWORD"],
-            host=values["TEST_DATABASE_HOSTNAME"],
-            port=values["TEST_DATABASE_PORT"],
-            path=f"/{values['TEST_DATABASE_DB']}",
-        )
-
-    class Config:
-        env_file = f"{PROJECT_DIR}/.env"
-        case_sensitive = True
+    model_config = SettingsConfigDict(env_file=f"{PROJECT_DIR}/.env", case_sensitive=True)
 
 
 settings: Settings = Settings()  # type: ignore

+ 1 - 1
app/schemas/requests.py

@@ -112,7 +112,7 @@ class TemplateCreateRequest(BaseRequest):
 
 
 class TemplateUpdateRequest(BaseRequest):
-    title: Optional[str]
+    title: Optional[str] = None
     description: Optional[str] = None
     place: Optional[str] = None
     responsible_contact: Optional[str] = None

+ 2 - 3
app/schemas/responses.py

@@ -1,12 +1,11 @@
 from datetime import datetime
 from typing import Optional
-from pydantic import BaseModel, EmailStr
+from pydantic import BaseModel, ConfigDict, EmailStr
 
 
 class BaseResponse(BaseModel):
     # may define additional fields or config shared across responses
-    class Config:
-        orm_mode = True
+    model_config = ConfigDict(from_attributes=True)
 
 
 class BaseObjectResponse(BaseResponse):

TEMPAT SAMPAH
requirements.txt