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")
def test_is_async(func, expected): assert is_async(func) == expected
def test_is_async_with_types(types, expected): assert is_async(lambda:..., types) == expected
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, )
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