tokencrawler/.venv/lib/python3.9/site-packages/apischema/conversions/utils.py
2022-03-17 22:16:30 +01:00

80 lines
2.9 KiB
Python

from inspect import Parameter, signature
from typing import Any, Callable, Dict, Generic, Optional, Tuple, Type, cast
from apischema.types import AnyType
from apischema.typing import (
get_type_hints,
is_annotated,
is_literal,
is_new_type,
is_type,
is_union,
)
from apischema.utils import get_origin_or_type
Converter = Callable[[Any], Any]
def converter_types(
converter: Converter,
source: Optional[AnyType] = None,
target: Optional[AnyType] = None,
namespace: Dict[str, Any] = None,
) -> Tuple[AnyType, AnyType]:
try:
# in pre 3.9, Generic __new__ perturb signature of types
if (
isinstance(converter, type)
and converter.__new__ is Generic.__new__ is not object.__new__
and converter.__init__ is not object.__init__ # type: ignore
):
parameters = list(signature(converter.__init__).parameters.values())[1:] # type: ignore
else:
parameters = list(signature(converter).parameters.values())
except ValueError: # builtin types
if target is None and is_type(converter):
target = cast(Type[Any], converter)
if source is None:
raise TypeError("Converter source is unknown") from None
else:
if not parameters:
raise TypeError("converter must have at least one parameter")
first_param, *other_params = parameters
for p in other_params:
if p.default is Parameter.empty and p.kind not in (
Parameter.VAR_POSITIONAL,
Parameter.VAR_KEYWORD,
):
raise TypeError(
"converter must have at most one parameter without default"
)
if source is not None and target is not None:
return source, target
types = get_type_hints(converter, None, namespace, include_extras=True)
if not types and is_type(converter):
types = get_type_hints(
converter.__new__, None, namespace, include_extras=True
) or get_type_hints(
converter.__init__, None, namespace, include_extras=True # type: ignore
)
if source is None:
try:
source = types.pop(first_param.name)
except KeyError:
raise TypeError("converter source is unknown") from None
if target is None:
if is_type(converter):
target = cast(Type, converter)
else:
try:
target = types.pop("return")
except KeyError:
raise TypeError("converter target is unknown") from None
return source, target
def is_convertible(tp: AnyType) -> bool:
origin = get_origin_or_type(tp)
return is_new_type(tp) or (
is_type(origin) and not (is_literal(tp) or is_annotated(tp) or is_union(origin))
)