80 lines
2.9 KiB
Python
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))
|
|
)
|