Source code for ampel.base.AmpelBaseModel
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# File: Ampel-interface/ampel/base/AmpelBaseModel.py
# License: BSD-3-Clause
# Author: valery brinnel <firstname.lastname@gmail.com>
# Date: 30.09.2018
# Last Modified Date: 05.01.2022
# Last Modified By: valery brinnel <firstname.lastname@gmail.com>
import warnings
from types import UnionType
from typing import TYPE_CHECKING, Any, TypeAlias, Union, get_args, get_origin
from pydantic import BaseModel
# NB: ModelMetaClass squirrels away generic args in its own
# __pydantic_model_args__ attribute, so we can't use typing.get_args() here
from pydantic._internal._generics import get_args as _internal_get_args
from pydantic._internal._generics import get_origin as _internal_get_origin
if TYPE_CHECKING:
IncEx: TypeAlias = 'set[int] | set[str] | dict[int, Any] | dict[str, Any] | None'
NoneType = type(None)
def safe_issubclass(cls: Any, class_or_tuple: type | tuple[type, ...]) -> bool:
""" non-throwing issubclass """
try:
return issubclass(cls, class_or_tuple)
except TypeError:
return False
[docs]
class AmpelBaseModel(BaseModel):
""" Raises validation errors if extra fields are present """
model_config = {
"arbitrary_types_allowed": True,
"populate_by_name": True,
"validate_default": True,
"extra": "forbid"
}
@classmethod
def __init_subclass__(cls, *args, **kwargs) -> None:
for k, v in cls.__annotations__.items():
# add implicit None default to unions containing None
if (
k not in cls.__dict__
and get_origin(v) in (UnionType, Union)
and NoneType in get_args(v)
):
setattr(cls, k, None)
# add generic args to defaults if missing
elif (
k in cls.__dict__
and safe_issubclass(v, AmpelBaseModel)
and v.get_model_origin() is type(cls.__dict__[k])
and v.get_model_args() and not cls.__dict__[k].get_model_args()
):
warnings.warn(
DeprecationWarning(
f"field {k} declared as {v}, but default has type {type(cls.__dict__[k])}"
" Adding generic args to default, but this will be an error in the future."
),
stacklevel=1
)
setattr(cls, k, v.model_validate(cls.__dict__[k].model_dump()))
super().__init_subclass__(*args, **kwargs)
@classmethod
def get_model_args(cls) -> tuple[type, ...]:
return _internal_get_args(cls)
@classmethod
def get_model_origin(cls) -> None | type:
return _internal_get_origin(cls)
@classmethod
def get_model_keys(cls) -> set[str]:
return set(cls.model_fields.keys())
# keep our own implementation of the (deprecated) BaseModel.dict() for
# future compatibility
def dict(
self,
*,
include: "IncEx" = None,
exclude: "IncEx" = None,
by_alias: bool = False,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
) -> dict[str, Any]:
return self.model_dump(
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none
)