Date: 2024-12-07 Topic: Refactoring data models for better code reuse
Today I refactored the health food compliance system's data models, migrating from BaseResponse to BaseJudgement as the foundation class.
The original structure had too much duplication. Each compliance check repeated the same fields. The new structure uses inheritance:
# Before: Duplicate fields in each model
class DiseaseFunctionCheck(BaseModel):
is_compliant: bool
reason: str
reference: str
disease_claims: List[str]
class NonPharmaceuticalCheck(BaseModel):
is_compliant: bool
reason: str
reference: str
statement_found: bool# After: Shared base class
class BaseJudgement(BaseModel):
is_or_not: bool = Field(..., description="Compliance status")
reason: str = Field(..., description="Reasoning")
reference: str = Field(..., description="Regulatory reference")
class DiseaseFunctionCheck(BaseJudgement):
disease_claims: List[str] = Field(default_factory=list)
class NonPharmaceuticalCheck(BaseJudgement):
statement_found: boolCustom validation logic with decorators:
class DiseaseFunctionCheck(BaseJudgement):
disease_claims: List[str] = Field(default_factory=list)
@validator('is_or_not')
def validate_consistency(cls, v, values):
# Can't be compliant if disease claims exist
if 'disease_claims' in values and values['disease_claims'] and v:
raise ValueError("Inconsistent: disease claims found but marked compliant")
return vThe final result combines all checks:
class HealthFoodAnalysisResult(BaseModel):
disease_check: DiseaseFunctionCheck
declaration_check: MedicineDeclarationCheck
identification_check: IdentificationCheck
overall_compliance: bool
@validator('overall_compliance', always=True)
def calculate_overall(cls, v, values):
return all([
values.get('disease_check', {}).is_or_not,
values.get('declaration_check', {}).is_or_not,
values.get('identification_check', {}).is_or_not
])FastAPI
├── Starlette (ASGI framework)
├── Pydantic (Data validation)
└── Python Type Hints (Type system)
This stack provides automatic request validation, response serialization, and API documentation.
The refactoring was worthwhile. The new structure reduced code by about 30% and made the relationships between models clearer.
The key insight: inheritance is for "is-a" relationships, composition for "has-a". Each compliance check "is a" judgment (inherits BaseJudgement), while the final result "has" multiple checks (composition).
Pydantic's validators are powerful but can be confusing. The values parameter only contains fields that come before the validated field in the class definition. I had to reorder fields to make validators work correctly.
- Pydantic v2 changes (model_validator, field_validator)
- Python dataclasses vs Pydantic
- OpenAPI schema generation
- Generic types in Pydantic