Beispiel #1
0
 def register(func: Callable, owner: Type, alias2: str):
     alias2 = alias or alias2
     _, *parameters = resolver_parameters(func, check_first=owner is None)
     error_handler2 = error_handler
     if error_handler2 is None:
         error_handler2 = none_error_handler
     elif error_handler2 is Undefined:
         error_handler2 = None
     resolver = Resolver(
         func,
         conversion,
         schema,
         error_handler2,
         parameters,
         parameters_metadata or {},
     )
     _resolvers[owner][alias2] = resolver
     if serialized:
         if is_async(func):
             raise TypeError("Async resolver cannot be used as a serialized method")
         try:
             register_serialized(
                 alias=alias2,
                 conversion=conversion,
                 schema=schema,
                 error_handler=error_handler,
                 owner=owner,
             )(func)
         except Exception:
             raise TypeError("Resolver cannot be used as a serialized method")
Beispiel #2
0
def test_is_async(func, expected):
    assert is_async(func) == expected
Beispiel #3
0
def test_is_async_with_types(types, expected):
    assert is_async(lambda:..., types) == expected
Beispiel #4
0
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if not hasattr(cls, "mutate"):
            return
        if not isinstance(cls.__dict__["mutate"], (classmethod, staticmethod)):
            raise TypeError(
                f"{cls.__name__}.mutate must be a classmethod/staticmethod")
        mutate = getattr(cls, "mutate")
        type_name(f"{cls.__name__}Payload")(cls)
        types = get_type_hints(mutate,
                               localns={cls.__name__: cls},
                               include_extras=True)
        async_mutate = is_async(mutate, types)
        fields: List[Tuple[str, AnyType, Field]] = []
        cmi_param = None
        for param_name, param in signature(mutate).parameters.items():
            if param.kind is Parameter.POSITIONAL_ONLY:
                raise TypeError("Positional only parameters are not supported")
            if param.kind in {
                    Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY
            }:
                if param_name not in types:
                    raise TypeError("Mutation parameters must be typed")
                field_type = types[param_name]
                field_ = MISSING if param.default is Parameter.empty else param.default
                if is_union_of(field_type, ClientMutationId):
                    cmi_param = param_name
                    if cls._client_mutation_id is False:
                        if field_ is MISSING:
                            raise TypeError(
                                "Cannot have a ClientMutationId parameter"
                                " when _client_mutation_id = False")
                        continue
                    elif cls._client_mutation_id is True:
                        field_ = MISSING
                    field_ = field(default=field_,
                                   metadata=alias(CLIENT_MUTATION_ID))
                fields.append((param_name, field_type, field_))
        field_names = [name for (name, _, _) in fields]
        if cmi_param is None and cls._client_mutation_id is not False:
            fields.append((
                CLIENT_MUTATION_ID,
                ClientMutationId
                if cls._client_mutation_id else Optional[ClientMutationId],
                MISSING if cls._client_mutation_id else None,
            ))
            cmi_param = CLIENT_MUTATION_ID
        input_cls = make_dataclass(f"{cls.__name__}Input", fields)

        def wrapper(input):
            return mutate(
                **{name: getattr(input, name)
                   for name in field_names})

        wrapper.__annotations__["input"] = input_cls
        wrapper.__annotations__[
            "return"] = Awaitable[cls] if async_mutate else cls
        if cls._client_mutation_id is not False:
            cls.__annotations__[
                CLIENT_MUTATION_ID] = input_cls.__annotations__[cmi_param]
            setattr(cls, CLIENT_MUTATION_ID, field(init=False))
            wrapped = wrapper

            if async_mutate:

                async def wrapper(input):
                    result = await wrapped(input)
                    setattr(result, CLIENT_MUTATION_ID,
                            getattr(input, cmi_param))
                    return result

            else:

                def wrapper(input):
                    result = wrapped(input)
                    setattr(result, CLIENT_MUTATION_ID,
                            getattr(input, cmi_param))
                    return result

            wrapper = wraps(wrapped)(wrapper)

        cls._mutation = Mutation_(
            function=wrapper,
            alias=camel_to_snake(cls.__name__),
            schema=cls._schema,
            error_handler=cls._error_handler,
        )
Beispiel #5
0
def resolver_resolve(
    resolver: Resolver,
    types: Mapping[str, AnyType],
    aliaser: Aliaser,
    default_deserialization: DefaultConversion,
    default_serialization: DefaultConversion,
    serialized: bool = True,
) -> Callable:
    # graphql deserialization will give Enum objects instead of strings
    def handle_enum(tp: AnyType) -> Optional[AnyConversion]:
        if is_type(tp) and issubclass(tp, Enum):
            return Conversion(identity, source=Any, target=tp)
        return default_deserialization(tp)

    parameters, info_parameter = [], None
    for param in resolver.parameters:
        param_type = types[param.name]
        if is_union_of(param_type, graphql.GraphQLResolveInfo):
            info_parameter = param.name
        else:
            param_field = ObjectField(
                param.name,
                param_type,
                param.default is Parameter.empty,
                resolver.parameters_metadata.get(param.name, empty_dict),
                param.default,
            )
            deserializer = deserialization_method(
                param_type,
                additional_properties=False,
                aliaser=aliaser,
                coerce=False,
                conversion=param_field.deserialization,
                default_conversion=handle_enum,
                fall_back_on_default=False,
                schema=param_field.schema,
            )
            opt_param = is_union_of(param_type, NoneType) or param.default is None
            parameters.append(
                (
                    aliaser(param_field.alias),
                    param.name,
                    deserializer,
                    opt_param,
                    param_field.required,
                )
            )
    func, error_handler = resolver.func, resolver.error_handler
    method_factory = partial_serialization_method_factory(
        aliaser, resolver.conversion, default_serialization
    )

    serialize_result: Callable[[Any], Any]
    if not serialized:
        serialize_result = lambda res: res
    elif is_async(resolver.func):
        serialize_result = as_async(method_factory(types["return"]))
    else:
        serialize_result = method_factory(types["return"])
    serialize_error: Optional[Callable[[Any], Any]]
    if error_handler is None:
        serialize_error = None
    elif is_async(error_handler):
        serialize_error = as_async(method_factory(resolver.error_type()))
    else:
        serialize_error = method_factory(resolver.error_type())

    def resolve(__self, __info, **kwargs):
        values = {}
        errors: Dict[str, ValidationError] = {}
        for alias, param_name, deserializer, opt_param, required in parameters:
            if alias in kwargs:
                # It is possible for the parameter to be non-optional in Python
                # type hints but optional in the generated schema. In this case
                # we should ignore it.
                # See: https://github.com/wyfo/apischema/pull/130#issuecomment-845497392
                if not opt_param and kwargs[alias] is None:
                    assert not required
                    continue
                try:
                    values[param_name] = deserializer(kwargs[alias])
                except ValidationError as err:
                    errors[aliaser(param_name)] = err
            elif opt_param and required:
                values[param_name] = None

        if errors:
            raise ValueError(ValidationError(children=errors).errors)
        if info_parameter:
            values[info_parameter] = __info
        try:
            return serialize_result(func(__self, **values))
        except Exception as error:
            if error_handler is None:
                raise
            assert serialize_error is not None
            return serialize_error(error_handler(error, __self, __info, **kwargs))

    return resolve