def self_deserialization_wrapper(cls: Type) -> Type: wrapper = new_class( f"{cls.__name__}SelfDeserializer", (cls[cls.__parameters__] if has_type_vars(cls) else cls, ), exec_body=lambda ns: ns.update({ "__new__": lambda _, *args, **kwargs: cls(*args, **kwargs) }), ) return type_name(None)(wrapper)
def as_names(cls: EnumCls, aliaser: Callable[[str], str] = lambda s: s) -> EnumCls: # Enum requires to call namespace __setitem__ def exec_body(namespace: dict): for elt in cls: # type: ignore namespace[elt.name] = aliaser(elt.name) if not issubclass(cls, Enum): raise TypeError("as_names must be called with Enum subclass") name_cls = type_name(None)( new_class(cls.__name__, (str, Enum), exec_body=exec_body) ) deserializer(Conversion(partial(getattr, cls), source=name_cls, target=cls)) serializer( Conversion(lambda obj: getattr(name_cls, obj.name), source=cls, target=name_cls) ) return cls
def object_deserialization( func: Callable[..., T], *input_class_modifiers: Callable[[type], Any], parameters_metadata: Mapping[str, Mapping] = None, ) -> Any: fields = parameters_as_fields(func, parameters_metadata) types = get_type_hints(func, include_extras=True) if "return" not in types: raise TypeError("Object deserialization must be typed") return_type = types["return"] bases = () if getattr(return_type, "__parameters__", ()): bases = (Generic[return_type.__parameters__], ) # type: ignore elif func.__name__ != "<lambda>": input_class_modifiers = ( type_name(to_pascal_case(func.__name__)), *input_class_modifiers, ) def __init__(self, **kwargs): self.kwargs = kwargs input_cls = new_class( to_pascal_case(func.__name__), bases, exec_body=lambda ns: ns.update({"__init__": __init__}), ) for modifier in input_class_modifiers: modifier(input_cls) set_object_fields(input_cls, fields) if any(f.additional_properties for f in fields): kwargs_param = next(f.name for f in fields if f.additional_properties) def wrapper(input): kwargs = input.kwargs.copy() kwargs.update(kwargs.pop(kwargs_param)) return func(**kwargs) else: def wrapper(input): return func(**input.kwargs) wrapper.__annotations__["input"] = input_cls wrapper.__annotations__["return"] = return_type return wrapper
def object_serialization( cls: Type[T], fields_and_methods: Union[Iterable[Any], Callable[[], Iterable[Any]]], *output_class_modifiers: Callable[[type], Any], ) -> Callable[[T], Any]: generic, bases = cls, () if getattr(cls, "__parameters__", ()): generic = cls[cls.__parameters__] # type: ignore bases = Generic[cls.__parameters__] # type: ignore elif (callable(fields_and_methods) and fields_and_methods.__name__ != "<lambda>" and not getattr(cls, "__parameters__", ())): output_class_modifiers = ( type_name(to_pascal_case(fields_and_methods.__name__)), *output_class_modifiers, ) def __init__(self, obj): _, new_init = _fields_and_init(cls, fields_and_methods) new_init.__annotations__ = {"obj": generic} output_cls.__init__ = new_init new_init(self, obj) __init__.__annotations__ = {"obj": generic} output_cls = new_class( f"{cls.__name__}Serialization", bases, exec_body=lambda ns: ns.update({"__init__": __init__}), ) for modifier in output_class_modifiers: modifier(output_cls) set_object_fields(output_cls, lambda: _fields_and_init(cls, fields_and_methods)[0]) return output_cls
def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) type_name(graphql=connection_name)(cls)
def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) type_name(graphql=edge_name)(cls)
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, )
TypeVar, ) from graphql.pyutils import camel_to_snake from apischema.aliases import alias from apischema.graphql.schema import Mutation as Mutation_ from apischema.schemas import Schema from apischema.serialization.serialized_methods import ErrorHandler from apischema.type_names import type_name from apischema.types import AnyType, Undefined from apischema.typing import get_type_hints from apischema.utils import is_async, is_union_of, wrap_generic_init_subclass ClientMutationId = NewType("ClientMutationId", str) type_name(None)(ClientMutationId) CLIENT_MUTATION_ID = "client_mutation_id" M = TypeVar("M", bound="Mutation") class Mutation: _error_handler: ErrorHandler = Undefined _schema: Optional[Schema] = None _client_mutation_id: Optional[bool] = None _mutation: Mutation_ # set in __init_subclass__ # Mutate is not defined to prevent Mypy warning about signature of superclass mutate: Callable @wrap_generic_init_subclass def __init_subclass__(cls, **kwargs):