from os import environ
from typing import Any
from pydantic import (
BaseModel,
ConfigDict,
SerializerFunctionWrapHandler,
model_serializer,
)
from pydantic_core import PydanticUndefined
[docs]
class PdmObject(BaseModel):
"""Shared Base Class for all PDM Entities."""
# https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict
model_config = ConfigDict(
extra=environ.get("EXTRA_FIELDS", "ignore"), # Can we prefer 'forbid' here?
# Validate assignments and defaults to help keep ourselves honest:
validate_assignment=True,
validate_default=True,
)
def _is_default(self, key: str) -> bool:
field_info = self.model_fields[key]
if field_info.default_factory is not None:
default = field_info.default_factory()
elif field_info.default is not PydanticUndefined:
default = field_info.default
else:
default = PydanticUndefined
return getattr(self, key) == default
@staticmethod
def _is_empty(value: Any) -> bool:
return value in (None, [], {})
def _exclude_default_nulls_and_empty(
self, dumped: dict[str, Any]
) -> dict[str, Any]:
"""To avoid cluttering JSON output, we want to omit any None, [], {} values
that are present by default, but preserve any 'empty' values that were deliberately
set by callers."""
filtered = {
key: val
for key, val in dumped.items()
if not (self._is_empty(val) and self._is_default(key))
}
return filtered
@model_serializer(mode="wrap")
def _serialize(
self, default_serializer: SerializerFunctionWrapHandler
) -> dict[str, Any]:
dumped = default_serializer(self)
without_nulls = self._exclude_default_nulls_and_empty(dumped)
return without_nulls