From 56552b6dd9fbabef9add6cf7babb803d8319790f Mon Sep 17 00:00:00 2001 From: Cizz22 Date: Mon, 9 Feb 2026 10:30:47 +0700 Subject: [PATCH] feat: Add Pydantic field validation with max_length and pattern constraints to various schemas and models. --- src/aeros_equipment/schema.py | 18 +++++++++--------- src/aeros_project/schema.py | 6 +++--- src/aeros_simulation/schema.py | 32 ++++++++++++++++---------------- src/api.py | 4 ++-- src/auth/model.py | 8 ++++---- src/database/schema.py | 6 +++--- src/exceptions.py | 10 +++++----- src/models.py | 2 +- tests/test_validation.py | 0 9 files changed, 43 insertions(+), 43 deletions(-) create mode 100644 tests/test_validation.py diff --git a/src/aeros_equipment/schema.py b/src/aeros_equipment/schema.py index 47771b6..e32d6fe 100644 --- a/src/aeros_equipment/schema.py +++ b/src/aeros_equipment/schema.py @@ -42,15 +42,15 @@ class EquipmentBase(DefultBase): # remark: Optional[str] class MasterEquipment(DefultBase): - name: str + name: str = Field(..., max_length=100) class CustomParameter(DefultBase): - level: str + level: str = Field(..., max_length=50) failure_rates: List[float] mttr: float class Equipment(EquipmentBase): - location_tag: str + location_tag: str = Field(..., max_length=50) master_equipment: MasterEquipment class EquipmentWithCustomParameters(Equipment): @@ -88,7 +88,7 @@ class EquipmentConfiguration(EquipmentBase): """ equipment_name: str = Field( - ..., alias="equipmentName", description="Name of the equipment" + ..., alias="equipmentName", description="Name of the equipment", max_length=100 ) max_flowrate: float = Field( ..., alias="maxFlowrate", ge=0, description="Maximum flow rate" @@ -102,7 +102,7 @@ class EquipmentConfiguration(EquipmentBase): # Reliability Distribution Parameters rel_dis_type: str = Field( - ..., alias="relDisType", description="Reliability distribution type" + ..., alias="relDisType", description="Reliability distribution type", max_length=50 ) rel_dis_p1: float = Field( ..., alias="relDisP1", description="Reliability distribution parameter 1" @@ -119,7 +119,7 @@ class EquipmentConfiguration(EquipmentBase): # Corrective Maintenance Distribution Parameters cm_dis_type: str = Field( - ..., alias="cmDisType", description="Corrective maintenance distribution type" + ..., alias="cmDisType", description="Corrective maintenance distribution type", max_length=50 ) cm_dis_p1: float = Field( ..., @@ -144,7 +144,7 @@ class EquipmentConfiguration(EquipmentBase): # Inspection Distribution Parameters ip_dis_type: str = Field( - ..., alias="ipDisType", description="Inspection distribution type" + ..., alias="ipDisType", description="Inspection distribution type", max_length=50 ) ip_dis_p1: float = Field( ..., alias="ipDisP1", description="Inspection distribution parameter 1" @@ -161,7 +161,7 @@ class EquipmentConfiguration(EquipmentBase): # Preventive Maintenance Distribution Parameters pm_dis_type: str = Field( - ..., alias="pmDisType", description="Preventive maintenance distribution type" + ..., alias="pmDisType", description="Preventive maintenance distribution type", max_length=50 ) pm_dis_p1: float = Field( ..., @@ -186,7 +186,7 @@ class EquipmentConfiguration(EquipmentBase): # Overhaul Distribution Parameters oh_dis_type: str = Field( - ..., alias="ohDisType", description="Overhaul distribution type" + ..., alias="ohDisType", description="Overhaul distribution type", max_length=50 ) oh_dis_p1: float = Field( ..., alias="ohDisP1", description="Overhaul distribution parameter 1" diff --git a/src/aeros_project/schema.py b/src/aeros_project/schema.py index 81c6869..cacd4c7 100644 --- a/src/aeros_project/schema.py +++ b/src/aeros_project/schema.py @@ -16,12 +16,12 @@ class AerosProjectBase(DefultBase): class AerosProjectInput(AerosProjectBase): - schematic_name: str + schematic_name: str = Field(..., max_length=100) aro_file: UploadFile = File(..., description="ARO file") class AerosMetadata(AerosProjectBase): - project_name: str - aro_file_path: str + project_name: str = Field(..., max_length=100) + aro_file_path: str = Field(..., max_length=255) updated_at: datetime diff --git a/src/aeros_simulation/schema.py b/src/aeros_simulation/schema.py index 0629264..d0c37d2 100644 --- a/src/aeros_simulation/schema.py +++ b/src/aeros_simulation/schema.py @@ -11,12 +11,12 @@ from src.aeros_equipment.schema import MasterEquipment, EquipmentWithCustomParam # Pydantic models for request/response validation class SimulationInput(DefultBase): - SchematicName: str = "- TJB - Unit 3 -" + SchematicName: str = Field("- TJB - Unit 3 -", max_length=100) SimSeed: int = 1 SimDuration: int = 3 - DurationUnit: str = "UYear" + DurationUnit: str = Field("UYear", max_length=20) SimNumRun: int = 1 - SimulationName: str = "DefaultSimulation" + SimulationName: str = Field("DefaultSimulation", max_length=100, pattern=r"^[a-zA-Z0-9_\-\s]+$") CustomInput: dict = {} IsDefault:bool = False Konkin_offset: Optional[int] = 0 @@ -25,15 +25,15 @@ class SimulationInput(DefultBase): PlannedOutages: Optional[int] = 0 OverhaulInterval: Optional[int] = Field(0) OverhaulDuration: Optional[int] = Field(1200) - AhmJobId: Optional[str] = Field(None) + AhmJobId: Optional[str] = Field(None, max_length=50) class SimulationNode(DefultBase): id: UUID - node_type: Optional[str] + node_type: Optional[str] = Field(None, max_length=50) node_id: Optional[int] - node_name: Optional[str] - structure_name: Optional[str] - schematic_name: Optional[str] + node_name: Optional[str] = Field(None, max_length=100) + structure_name: Optional[str] = Field(None, max_length=100) + schematic_name: Optional[str] = Field(None, max_length=100) schematic_id: Optional[UUID] model_image: Optional[list] = Field(None) equipment:Optional[MasterEquipment] = None @@ -103,9 +103,9 @@ class SimulationPlotResult(DefultBase): class SimulationData(DefultBase): id: UUID - simulation_name: str - status: str - schematic_name: str + simulation_name: str = Field(..., max_length=100) + status: str = Field(..., max_length=20) + schematic_name: str = Field(..., max_length=100) created_at: datetime started_at: datetime duration: Optional[int]= 0 @@ -120,17 +120,17 @@ class SimulationPagination(Pagination): class AhmMetricInput(DefultBase): - target_simulation_id: str - baseline_simulation_id: Optional[str] = Field(None) + target_simulation_id: str = Field(..., max_length=50) + baseline_simulation_id: Optional[str] = Field(None, max_length=50) class YearlySimulationInput(DefultBase): year: int class SimulationQueryModel(CommonParams): - status: Optional[str] = Field(None) + status: Optional[str] = Field(None, max_length=20) class SimulationCalcResultQuery(DefultBase): - schematic_name: Optional[str] = None - node_type: Optional[str] = Field(None, alias="nodetype") \ No newline at end of file + schematic_name: Optional[str] = Field(None, max_length=100) + node_type: Optional[str] = Field(None, alias="nodetype", max_length=50) \ No newline at end of file diff --git a/src/api.py b/src/api.py index d3549e5..16b2baf 100644 --- a/src/api.py +++ b/src/api.py @@ -2,7 +2,7 @@ from typing import List, Optional from fastapi import APIRouter, Depends from fastapi.responses import JSONResponse -from pydantic import BaseModel +from pydantic import BaseModel, Field from src.aeros_project.router import router as aeros_project_router from src.aeros_simulation.router import router as aeros_simulation_router, airflow_router @@ -12,7 +12,7 @@ from src.aeros_equipment.router import router as aeros_equipment_router from src.aeros_contribution.router import router as aeros_contribution_router class ErrorMessage(BaseModel): - msg: str + msg: str = Field(..., max_length=255) class ErrorResponse(BaseModel): diff --git a/src/auth/model.py b/src/auth/model.py index 1171466..49350c6 100644 --- a/src/auth/model.py +++ b/src/auth/model.py @@ -1,7 +1,7 @@ -from pydantic import BaseModel +from pydantic import BaseModel, Field class UserBase(BaseModel): - name: str - role: str - user_id: str + name: str = Field(..., max_length=100) + role: str = Field(..., max_length=50) + user_id: str = Field(..., max_length=50) diff --git a/src/database/schema.py b/src/database/schema.py index 37f6671..84bac92 100644 --- a/src/database/schema.py +++ b/src/database/schema.py @@ -6,11 +6,11 @@ from src.models import DefultBase class CommonParams(DefultBase): # This ensures no extra query params are allowed - current_user: Optional[str] = Field(None, alias="currentUser") + current_user: Optional[str] = Field(None, alias="currentUser", max_length=50) page: int = Field(1, gt=0, lt=2147483647) items_per_page: int = Field(5, gt=-2, lt=2147483647, alias="itemsPerPage") - query_str: Optional[str] = Field(None, alias="q") - filter_spec: Optional[str] = Field(None, alias="filter") + query_str: Optional[str] = Field(None, alias="q", max_length=100) + filter_spec: Optional[str] = Field(None, alias="filter", max_length=500) sort_by: List[str] = Field(default_factory=list, alias="sortBy[]") descending: List[bool] = Field(default_factory=list, alias="descending[]") exclude: List[str] = Field(default_factory=list, alias="exclude[]") diff --git a/src/exceptions.py b/src/exceptions.py index 20443c2..e5aca3d 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -7,7 +7,7 @@ from asyncpg.exceptions import PostgresError from fastapi import FastAPI, HTTPException, Request from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse -from pydantic import BaseModel +from pydantic import BaseModel, Field from slowapi import _rate_limit_exceeded_handler from slowapi.errors import RateLimitExceeded from sqlalchemy.exc import DataError, DBAPIError, IntegrityError, SQLAlchemyError @@ -16,15 +16,15 @@ from src.enums import ResponseStatus class ErrorDetail(BaseModel): - field: Optional[str] = None - message: str - code: Optional[str] = None + field: Optional[str] = Field(None, max_length=100) + message: str = Field(..., max_length=255) + code: Optional[str] = Field(None, max_length=50) params: Optional[Dict[str, Any]] = None class ErrorResponse(BaseModel): data: Optional[Any] = None - message: str + message: str = Field(..., max_length=255) status: ResponseStatus = ResponseStatus.ERROR errors: Optional[List[ErrorDetail]] = None diff --git a/src/models.py b/src/models.py index c7fe804..54f9196 100644 --- a/src/models.py +++ b/src/models.py @@ -108,5 +108,5 @@ T = TypeVar("T") class StandardResponse(BaseModel, Generic[T]): data: Optional[T] = None - message: str = "Success" + message: str = Field("Success", max_length=255) status: ResponseStatus = ResponseStatus.SUCCESS diff --git a/tests/test_validation.py b/tests/test_validation.py new file mode 100644 index 0000000..e69de29