Source code for ska_sdp_piper.piper.piper_base_model

from copy import deepcopy

from pydantic import BaseModel, ConfigDict

from .piper_undefined import PIPER_UNDEFINED, PiperUndefined

__all__ = ("PIPER_MODEL_CONFIG", "PiperBaseModel")


PIPER_MODEL_CONFIG = ConfigDict(
    strict=False,
    extra="forbid",
    # To prevent serialisation issues in YAML config
    # we don't allow arbitrary types
    arbitrary_types_allowed=False,
    validate_assignment=True,
    validate_default=True,
)


[docs] class PiperBaseModel(BaseModel): """ A base model class that enforces strict validation rules on all subclasses. This model extends Pydantic's BaseModel with custom initialization logic to ensure that all subclass fields have default values and properly handle undefined field markers. Validation Checks: - Ensures all fields in subclasses have default values assigned - Fields without defaults must be explicitly annotated with PIPER_UNDEFINED marker - Prevents fields from having both a default value AND PIPER_UNDEFINED annotation - Raises TypeError if any required fields (without defaults) are defined without PIPER_UNDEFINED annotation - Automatically wraps field annotations with serialize/validate methods when PIPER_UNDEFINED marker is detected in metadata All subclasses must conform to these constraints to be properly instantiated. """ model_config = PIPER_MODEL_CONFIG def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) annotations = getattr(cls, "__annotations__", {}) updated_annotations = deepcopy(annotations) for p_name, p_annot in annotations.items(): metadata = getattr(p_annot, "__metadata__", []) if PIPER_UNDEFINED in metadata: assert p_name not in cls.__dict__, ( f"Argument '{p_name}' of model '{cls.__name__}' " "has a default value set, and it " f"is also annotated with {PIPER_UNDEFINED}. This is " "not allowed as it may lead to undesired behavior." ) updated_annotations[p_name] = PiperUndefined.annotate(p_annot) setattr(cls, "__annotations__", updated_annotations) @classmethod def __pydantic_init_subclass__(cls, **kwargs): super().__pydantic_init_subclass__(**kwargs) required_fields = [ name for name, field in cls.model_fields.items() if field.is_required() ] if required_fields: raise TypeError( f"Model '{cls.__name__}' " f"has following fields without defaults: {required_fields}. " "All fields must have a default value. If a field cannot have " f"default value, annotate with {PIPER_UNDEFINED}. " "Please refer piper documentation." )