"""Module for `pydantic`-related utilities."""fromtypingimportAny,Callable,Type,TypeVar,castimportpydanticfrompackaging.versionimportVersionfromtyping_extensionsimportParamSpec# Currently unused.# def exclusive_args(# values: Dict,# arg1: str,# arg2: str,# arg1_friendly_name: Optional[str] = None,# arg2_friendly_name: Optional[str] = None,# ) -> None:# arg1_value = values.get(arg1, None)# arg2_value = values.get(arg2, None)# arg1_name = arg1_friendly_name if arg1_friendly_name else f"`{arg1}`"# arg2_name = arg2_friendly_name if arg2_friendly_name else f"`{arg2}`"# if arg1_value is not None and arg2_value is not None:# raise ValueError(f"Must provide either {arg1_name} or {arg2_name} but not both")
[docs]defis_pydantic_dataclass(cls:Type)->bool:"""A helper function to check if a class is a `pydantic` dataclass. Args: cls (Type): Class. Returns: bool: Whether the class is a `pydantic` dataclass. """ifVersion(pydantic.__version__)<Version("2.0.0"):# pragma: no coverreturnhasattr(cls,"__dataclass__")else:returnany([xforxindir(cls)if"pydantic"inx])
PYDANTIC_DATACLASS_WORKAROUND_DICT=dict()
[docs]defmake_pydantic_dataclass(builtin_dataclass:Type)->Type:"""Workaround for a `pydantic` edge case issue when calling ``pydantic.dataclass(<builtin_dataclass>)`` more than once where ``builtin_dataclass`` has a default factory filed after a keyword parameter. E.g. the following would normally fail, this works around the issue. .. code-block:: python from typing import List import dataclasses import pydantic @dataclasses.dataclass class MyDataclass: a: str = "string" b: List[int] = dataclasses.field(default_factory=lambda: [1, 2, 3]) pydantic.dataclasses.dataclass(MyDataclass) # OK. pydantic.dataclasses.dataclass(MyDataclass) # TypeError. Args: builtin_dataclass (Type): Python builtin dataclass. Returns: Type: ``builtin_dataclass`` safely converted to pydantic dataclass. """name=f"{builtin_dataclass.__module__}.{builtin_dataclass.__name__}"ifnamenotinPYDANTIC_DATACLASS_WORKAROUND_DICT:pydantic_dataclass:Any=pydantic.dataclasses.dataclass(builtin_dataclass)PYDANTIC_DATACLASS_WORKAROUND_DICT[name]=pydantic_dataclasselse:pydantic_dataclass=PYDANTIC_DATACLASS_WORKAROUND_DICT[name]returnpydantic_dataclass
P=ParamSpec("P")T=TypeVar("T")
[docs]defvalidate_arguments(*args:Any,**kwargs:Any)->Callable[[Callable[P,T]],Callable[P,T]]:"""Uses the ``Callable[P, T]`` approach to type the pydantic ``validate_arguments`` decorator. Helps `mypy` to correctly understand typing of functions that are decorated by this. See: - https://stackoverflow.com/a/74080156 - https://docs.python.org/3/library/typing.html#typing.ParamSpec Args: *args (Any): Positional arguments to pass to ``pydantic.validate_arguments``. **kwargs (Any): Keyword arguments to pass to ``pydantic.validate_arguments``. Returns: Callable[[Callable[P, T]], Callable[P, T]]: The updated ``pydantic.validate_arguments`` decorator. """returncast(Callable[P,T],# type: ignore [valid-type]pydantic.validate_arguments(*args,**kwargs),# type: ignore [operator])