Source code for credmark.dto

# 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 EmptyInput(DTO): """ The model does not require any input. This is an empty object. """ class Config: schema_extra: dict = { 'examples': [{}] }
[docs]class EmptyInputSkipTest(EmptyInput): class Config: schema_extra = {'skip_test': True}
[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)``` """