# pylint: disable=locally-disabled, unused-import
# ruff: noqa: F401
import re
from typing import Any, Dict, Generic, Iterator, List, TypeVar, Union
from pydantic import (
BaseModel as DTO,
)
from pydantic import (
Extra as DTOExtra,
)
from pydantic import (
Field as DTOField,
)
from pydantic import (
Json as DTOJson,
)
from pydantic import (
PrivateAttr,
confloat,
conint,
constr,
validator,
)
from pydantic import (
ValidationError as DTOValidationError,
)
# A GenericDTO is a kind of DTO: isinstance(g, DTO) == True
from pydantic.generics import GenericModel as GenericDTO
from .dto_schema import (
cross_examples,
dto_schema_viz,
print_example,
print_tree,
)
[docs]class DTOPretty:
"""
A Mixin class to add pretty print to DTO
"""
def _repr_pretty_(self, p, cycle): # pylint:disable=invalid-name
class_name = self.__class__.__name__
if cycle:
p.text(f'{class_name}(...)')
else:
with p.group(4, f'{class_name}(', ')'):
for k in self.__fields__: # type: ignore #pylint:disable=no-member
v = getattr(self, k)
p.break_()
p.pretty(k)
p.text(':')
p.pretty(v)
[docs]def fixstr(fixed_length):
return constr(min_length=fixed_length,
max_length=fixed_length)
[docs]class HexStr(str):
"""
Hex string DTO field
"""
@classmethod
def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
field_schema.update(type='string', format='hex-string')
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, hex_str: str) -> str:
r_hex = r'(0x)?[0-9a-fA-F]+'
m = re.fullmatch(r_hex, hex_str)
if not m:
raise ValueError('Invalid hex string: {hex_str}')
return hex_str
DTOCLS = TypeVar('DTOCLS')
[docs]class IterableListGenericDTO(GenericDTO, Generic[DTOCLS]):
_iterator: str
def __iter__(self) -> Iterator[DTOCLS]:
return getattr(self, self._iterator).__iter__()
def __getitem__(self, key) -> DTOCLS:
return getattr(self, self._iterator).__getitem__(key)
[docs] def append(self, obj):
return getattr(self, self._iterator).append(obj)
[docs] def extend(self, obj):
return getattr(self, self._iterator).extend(obj)
[docs]class IntDTO(int):
"""
A DTO that can be used as an integer output (or input) to a model.
When used as a top-level DTO it is serialized as a dict with a
``value`` field ``{"value": 12345}``, otherwise it is serialized
as a number.
It can be used in python code as a normal integer.
"""
@classmethod
def schema(cls):
return {'title': cls.__name__,
'description': 'DTO for an integer value.',
'type': 'object',
'properties': {
'value': {
'title': 'Value',
'description': 'An integer',
'type': 'integer'}
},
'required': ['value']}
@classmethod
def __get_validators__(cls):
yield cls.validate
# A pydantic validator so instances can be deserialized
# from an int or a dict.
@classmethod
def validate(cls, i):
if isinstance(i, int):
return cls(i)
if isinstance(i, dict):
return cls(**i)
raise TypeError(f'{cls.__name__} must be deserialized with an int or dict')
def __new__(cls, value: int, **_kwargs):
return super().__new__(cls, value)
def dict(self):
return {"value": self}
[docs]class FloatDTO(float):
"""
A DTO that can be used as an float output (or input) to a model.
When used as a top-level DTO it is serialized as a dict with a
``value`` field ``{"value": 123.45}``, otherwise it is serialized
as a number.
It can be used in python code as a normal float.
"""
@classmethod
def schema(cls):
return {'title': cls.__name__,
'description': 'DTO for a float value.',
'type': 'object',
'properties': {
'value': {
'title': 'Value',
'description': 'A floating-point number',
'type': 'number'}
},
'required': ['value']}
@classmethod
def __get_validators__(cls):
yield cls.validate
# A pydantic validator so instances can be deserialized
# from an float or a dict.
@classmethod
def validate(cls, i):
if isinstance(i, float):
return cls(i)
if isinstance(i, dict):
return cls(**i)
raise TypeError(f'{cls.__name__} must be deserialized with an float or dict')
def __new__(cls, value: float, **_kwargs):
return super().__new__(cls, value)
def dict(self):
return {"value": self}
[docs]class StrDTO(str):
"""
A DTO that can be used as an string output (or input) to a model.
When used as a top-level DTO it is serialized as a dict with a
``value`` field ``{"value": "foobar"}``, otherwise it is serialized
as a string.
It can be used in python code as a normal string.
"""
@classmethod
def schema(cls):
return {'title': cls.__name__,
'description': 'DTO for a string value.',
'type': 'object',
'properties': {
'value': {
'title': 'Value',
'description': 'A string',
'type': 'string'}
},
'required': ['value']}
@classmethod
def __get_validators__(cls):
yield cls.validate
# A pydantic validator so instances can be deserialized
# from an str or a dict.
@classmethod
def validate(cls, i):
if isinstance(i, str):
return cls(i)
if isinstance(i, dict):
return cls(**i)
raise TypeError(f'{cls.__name__} must be deserialized with a str or dict')
def __new__(cls, value: str, **_kwargs):
return super().__new__(cls, value)
def dict(self):
return {"value": self}
DTOType = Union[DTO, IntDTO, StrDTO, FloatDTO]
DTOTypesTuple = (DTO, IntDTO, StrDTO, FloatDTO)
"""
A tuple containing the DTO types superclasses.
This can be used when checking if an instance is a DTOType
subclass: ```isinstance(obj, DTOTypesTuple)```
"""